aboutsummaryrefslogtreecommitdiffhomepage
path: root/inc/3rdparty/libraries/tcpdf/tcpdf.php
diff options
context:
space:
mode:
Diffstat (limited to 'inc/3rdparty/libraries/tcpdf/tcpdf.php')
-rw-r--r--inc/3rdparty/libraries/tcpdf/tcpdf.php24579
1 files changed, 24579 insertions, 0 deletions
diff --git a/inc/3rdparty/libraries/tcpdf/tcpdf.php b/inc/3rdparty/libraries/tcpdf/tcpdf.php
new file mode 100644
index 00000000..70c1747a
--- /dev/null
+++ b/inc/3rdparty/libraries/tcpdf/tcpdf.php
@@ -0,0 +1,24579 @@
1<?php
2//============================================================+
3// File name : tcpdf.php
4// Version : 6.0.093
5// Begin : 2002-08-03
6// Last Update : 2014-09-02
7// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
8// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
9// -------------------------------------------------------------------
10// Copyright (C) 2002-2014 Nicola Asuni - Tecnick.com LTD
11//
12// This file is part of TCPDF software library.
13//
14// TCPDF is free software: you can ioredistribute it and/or modify it
15// under the terms of the GNU Lesser General Public License as
16// published by the Free Software Foundation, either version 3 of the
17// License, or (at your option) any later version.
18//
19// TCPDF is distributed in the hope that it will be useful, but
20// WITHOUT ANY WARRANTY; without even the implied warranty of
21// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22// See the GNU Lesser General Public License for more details.
23//
24// You should have received a copy of the License
25// along with TCPDF. If not, see
26// <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
27//
28// See LICENSE.TXT file for more information.
29// -------------------------------------------------------------------
30//
31// Description :
32// This is a PHP class for generating PDF documents without requiring external extensions.
33//
34// NOTE:
35// This class was originally derived in 2002 from the Public
36// Domain FPDF class by Olivier Plathey (http://www.fpdf.org),
37// but now is almost entirely rewritten and contains thousands of
38// new lines of code and hundreds new features.
39//
40// Main features:
41// * no external libraries are required for the basic functions;
42// * all standard page formats, custom page formats, custom margins and units of measure;
43// * UTF-8 Unicode and Right-To-Left languages;
44// * TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;
45// * font subsetting;
46// * methods to publish some XHTML + CSS code, Javascript and Forms;
47// * images, graphic (geometric figures) and transformation methods;
48// * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)
49// * 1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based 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, Datamatrix, QR-Code, PDF417;
50// * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;
51// * automatic page header and footer management;
52// * document encryption up to 256 bit and digital signature certifications;
53// * transactions to UNDO commands;
54// * PDF annotations, including links, text and file attachments;
55// * text rendering modes (fill, stroke and clipping);
56// * multiple columns mode;
57// * no-write page regions;
58// * bookmarks, named destinations and table of content;
59// * text hyphenation;
60// * text stretching and spacing (tracking);
61// * automatic page break, line break and text alignments including justification;
62// * automatic page numbering and page groups;
63// * move and delete pages;
64// * page compression (requires php-zlib extension);
65// * XOBject Templates;
66// * Layers and object visibility.
67// * PDF/A-1b support
68//============================================================+
69
70/**
71 * @file
72 * This is a PHP class for generating PDF documents without requiring external extensions.<br>
73 * TCPDF project (http://www.tcpdf.org) was originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
74 * <h3>TCPDF main features are:</h3>
75 * <ul>
76 * <li>no external libraries are required for the basic functions;</li>
77 * <li>all standard page formats, custom page formats, custom margins and units of measure;</li>
78 * <li>UTF-8 Unicode and Right-To-Left languages;</li>
79 * <li>TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;</li>
80 * <li>font subsetting;</li>
81 * <li>methods to publish some XHTML + CSS code, Javascript and Forms;</li>
82 * <li>images, graphic (geometric figures) and transformation methods;
83 * <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)</li>
84 * <li>1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based 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, Datamatrix, QR-Code, PDF417;</li>
85 * <li>JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;</li>
86 * <li>automatic page header and footer management;</li>
87 * <li>document encryption up to 256 bit and digital signature certifications;</li>
88 * <li>transactions to UNDO commands;</li>
89 * <li>PDF annotations, including links, text and file attachments;</li>
90 * <li>text rendering modes (fill, stroke and clipping);</li>
91 * <li>multiple columns mode;</li>
92 * <li>no-write page regions;</li>
93 * <li>bookmarks, named destinations and table of content;</li>
94 * <li>text hyphenation;</li>
95 * <li>text stretching and spacing (tracking);</li>
96 * <li>automatic page break, line break and text alignments including justification;</li>
97 * <li>automatic page numbering and page groups;</li>
98 * <li>move and delete pages;</li>
99 * <li>page compression (requires php-zlib extension);</li>
100 * <li>XOBject Templates;</li>
101 * <li>Layers and object visibility;</li>
102 * <li>PDF/A-1b support.</li>
103 * </ul>
104 * Tools to encode your unicode fonts are on fonts/utils directory.</p>
105 * @package com.tecnick.tcpdf
106 * @author Nicola Asuni
107 * @version 6.0.093
108 */
109
110// TCPDF configuration
111require_once(dirname(__FILE__).'/tcpdf_autoconfig.php');
112// TCPDF static font methods and data
113require_once(dirname(__FILE__).'/include/tcpdf_font_data.php');
114// TCPDF static font methods and data
115require_once(dirname(__FILE__).'/include/tcpdf_fonts.php');
116// TCPDF static color methods and data
117require_once(dirname(__FILE__).'/include/tcpdf_colors.php');
118// TCPDF static image methods and data
119require_once(dirname(__FILE__).'/include/tcpdf_images.php');
120// TCPDF static methods and data
121require_once(dirname(__FILE__).'/include/tcpdf_static.php');
122
123// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
124
125/**
126 * @class TCPDF
127 * PHP class for generating PDF documents without requiring external extensions.
128 * TCPDF project (http://www.tcpdf.org) has been originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
129 * @package com.tecnick.tcpdf
130 * @brief PHP class for generating PDF documents without requiring external extensions.
131 * @version 6.0.093
132 * @author Nicola Asuni - info@tecnick.com
133 */
134class TCPDF {
135
136 // Protected properties
137
138 /**
139 * Current page number.
140 * @protected
141 */
142 protected $page;
143
144 /**
145 * Current object number.
146 * @protected
147 */
148 protected $n;
149
150 /**
151 * Array of object offsets.
152 * @protected
153 */
154 protected $offsets = array();
155
156 /**
157 * Array of object IDs for each page.
158 * @protected
159 */
160 protected $pageobjects = array();
161
162 /**
163 * Buffer holding in-memory PDF.
164 * @protected
165 */
166 protected $buffer;
167
168 /**
169 * Array containing pages.
170 * @protected
171 */
172 protected $pages = array();
173
174 /**
175 * Current document state.
176 * @protected
177 */
178 protected $state;
179
180 /**
181 * Compression flag.
182 * @protected
183 */
184 protected $compress;
185
186 /**
187 * Current page orientation (P = Portrait, L = Landscape).
188 * @protected
189 */
190 protected $CurOrientation;
191
192 /**
193 * Page dimensions.
194 * @protected
195 */
196 protected $pagedim = array();
197
198 /**
199 * Scale factor (number of points in user unit).
200 * @protected
201 */
202 protected $k;
203
204 /**
205 * Width of page format in points.
206 * @protected
207 */
208 protected $fwPt;
209
210 /**
211 * Height of page format in points.
212 * @protected
213 */
214 protected $fhPt;
215
216 /**
217 * Current width of page in points.
218 * @protected
219 */
220 protected $wPt;
221
222 /**
223 * Current height of page in points.
224 * @protected
225 */
226 protected $hPt;
227
228 /**
229 * Current width of page in user unit.
230 * @protected
231 */
232 protected $w;
233
234 /**
235 * Current height of page in user unit.
236 * @protected
237 */
238 protected $h;
239
240 /**
241 * Left margin.
242 * @protected
243 */
244 protected $lMargin;
245
246 /**
247 * Right margin.
248 * @protected
249 */
250 protected $rMargin;
251
252 /**
253 * Cell left margin (used by regions).
254 * @protected
255 */
256 protected $clMargin;
257
258 /**
259 * Cell right margin (used by regions).
260 * @protected
261 */
262 protected $crMargin;
263
264 /**
265 * Top margin.
266 * @protected
267 */
268 protected $tMargin;
269
270 /**
271 * Page break margin.
272 * @protected
273 */
274 protected $bMargin;
275
276 /**
277 * Array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
278 * @since 5.9.000 (2010-10-03)
279 * @protected
280 */
281 protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
282
283 /**
284 * Array of cell margins ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
285 * @since 5.9.000 (2010-10-04)
286 * @protected
287 */
288 protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
289
290 /**
291 * Current horizontal position in user unit for cell positioning.
292 * @protected
293 */
294 protected $x;
295
296 /**
297 * Current vertical position in user unit for cell positioning.
298 * @protected
299 */
300 protected $y;
301
302 /**
303 * Height of last cell printed.
304 * @protected
305 */
306 protected $lasth;
307
308 /**
309 * Line width in user unit.
310 * @protected
311 */
312 protected $LineWidth;
313
314 /**
315 * Array of standard font names.
316 * @protected
317 */
318 protected $CoreFonts;
319
320 /**
321 * Array of used fonts.
322 * @protected
323 */
324 protected $fonts = array();
325
326 /**
327 * Array of font files.
328 * @protected
329 */
330 protected $FontFiles = array();
331
332 /**
333 * Array of encoding differences.
334 * @protected
335 */
336 protected $diffs = array();
337
338 /**
339 * Array of used images.
340 * @protected
341 */
342 protected $images = array();
343
344 /**
345 * Array of cached files.
346 * @protected
347 */
348 protected $cached_files = array();
349
350 /**
351 * Array of Annotations in pages.
352 * @protected
353 */
354 protected $PageAnnots = array();
355
356 /**
357 * Array of internal links.
358 * @protected
359 */
360 protected $links = array();
361
362 /**
363 * Current font family.
364 * @protected
365 */
366 protected $FontFamily;
367
368 /**
369 * Current font style.
370 * @protected
371 */
372 protected $FontStyle;
373
374 /**
375 * Current font ascent (distance between font top and baseline).
376 * @protected
377 * @since 2.8.000 (2007-03-29)
378 */
379 protected $FontAscent;
380
381 /**
382 * Current font descent (distance between font bottom and baseline).
383 * @protected
384 * @since 2.8.000 (2007-03-29)
385 */
386 protected $FontDescent;
387
388 /**
389 * Underlining flag.
390 * @protected
391 */
392 protected $underline;
393
394 /**
395 * Overlining flag.
396 * @protected
397 */
398 protected $overline;
399
400 /**
401 * Current font info.
402 * @protected
403 */
404 protected $CurrentFont;
405
406 /**
407 * Current font size in points.
408 * @protected
409 */
410 protected $FontSizePt;
411
412 /**
413 * Current font size in user unit.
414 * @protected
415 */
416 protected $FontSize;
417
418 /**
419 * Commands for drawing color.
420 * @protected
421 */
422 protected $DrawColor;
423
424 /**
425 * Commands for filling color.
426 * @protected
427 */
428 protected $FillColor;
429
430 /**
431 * Commands for text color.
432 * @protected
433 */
434 protected $TextColor;
435
436 /**
437 * Indicates whether fill and text colors are different.
438 * @protected
439 */
440 protected $ColorFlag;
441
442 /**
443 * Automatic page breaking.
444 * @protected
445 */
446 protected $AutoPageBreak;
447
448 /**
449 * Threshold used to trigger page breaks.
450 * @protected
451 */
452 protected $PageBreakTrigger;
453
454 /**
455 * Flag set when processing page header.
456 * @protected
457 */
458 protected $InHeader = false;
459
460 /**
461 * Flag set when processing page footer.
462 * @protected
463 */
464 protected $InFooter = false;
465
466 /**
467 * Zoom display mode.
468 * @protected
469 */
470 protected $ZoomMode;
471
472 /**
473 * Layout display mode.
474 * @protected
475 */
476 protected $LayoutMode;
477
478 /**
479 * If true set the document information dictionary in Unicode.
480 * @protected
481 */
482 protected $docinfounicode = true;
483
484 /**
485 * Document title.
486 * @protected
487 */
488 protected $title = '';
489
490 /**
491 * Document subject.
492 * @protected
493 */
494 protected $subject = '';
495
496 /**
497 * Document author.
498 * @protected
499 */
500 protected $author = '';
501
502 /**
503 * Document keywords.
504 * @protected
505 */
506 protected $keywords = '';
507
508 /**
509 * Document creator.
510 * @protected
511 */
512 protected $creator = '';
513
514 /**
515 * Starting page number.
516 * @protected
517 */
518 protected $starting_page_number = 1;
519
520 /**
521 * The right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image.
522 * @since 2002-07-31
523 * @author Nicola Asuni
524 * @protected
525 */
526 protected $img_rb_x;
527
528 /**
529 * The right-bottom corner Y coordinate of last inserted image.
530 * @since 2002-07-31
531 * @author Nicola Asuni
532 * @protected
533 */
534 protected $img_rb_y;
535
536 /**
537 * Adjusting factor to convert pixels to user units.
538 * @since 2004-06-14
539 * @author Nicola Asuni
540 * @protected
541 */
542 protected $imgscale = 1;
543
544 /**
545 * Boolean flag set to true when the input text is unicode (require unicode fonts).
546 * @since 2005-01-02
547 * @author Nicola Asuni
548 * @protected
549 */
550 protected $isunicode = false;
551
552 /**
553 * PDF version.
554 * @since 1.5.3
555 * @protected
556 */
557 protected $PDFVersion = '1.7';
558
559 /**
560 * ID of the stored default header template (-1 = not set).
561 * @protected
562 */
563 protected $header_xobjid = false;
564
565 /**
566 * If true reset the Header Xobject template at each page
567 * @protected
568 */
569 protected $header_xobj_autoreset = false;
570
571 /**
572 * Minimum distance between header and top page margin.
573 * @protected
574 */
575 protected $header_margin;
576
577 /**
578 * Minimum distance between footer and bottom page margin.
579 * @protected
580 */
581 protected $footer_margin;
582
583 /**
584 * Original left margin value.
585 * @protected
586 * @since 1.53.0.TC013
587 */
588 protected $original_lMargin;
589
590 /**
591 * Original right margin value.
592 * @protected
593 * @since 1.53.0.TC013
594 */
595 protected $original_rMargin;
596
597 /**
598 * Default font used on page header.
599 * @protected
600 */
601 protected $header_font;
602
603 /**
604 * Default font used on page footer.
605 * @protected
606 */
607 protected $footer_font;
608
609 /**
610 * Language templates.
611 * @protected
612 */
613 protected $l;
614
615 /**
616 * Barcode to print on page footer (only if set).
617 * @protected
618 */
619 protected $barcode = false;
620
621 /**
622 * Boolean flag to print/hide page header.
623 * @protected
624 */
625 protected $print_header = true;
626
627 /**
628 * Boolean flag to print/hide page footer.
629 * @protected
630 */
631 protected $print_footer = true;
632
633 /**
634 * Header image logo.
635 * @protected
636 */
637 protected $header_logo = '';
638
639 /**
640 * Width of header image logo in user units.
641 * @protected
642 */
643 protected $header_logo_width = 30;
644
645 /**
646 * Title to be printed on default page header.
647 * @protected
648 */
649 protected $header_title = '';
650
651 /**
652 * String to pring on page header after title.
653 * @protected
654 */
655 protected $header_string = '';
656
657 /**
658 * Color for header text (RGB array).
659 * @since 5.9.174 (2012-07-25)
660 * @protected
661 */
662 protected $header_text_color = array(0,0,0);
663
664 /**
665 * Color for header line (RGB array).
666 * @since 5.9.174 (2012-07-25)
667 * @protected
668 */
669 protected $header_line_color = array(0,0,0);
670
671 /**
672 * Color for footer text (RGB array).
673 * @since 5.9.174 (2012-07-25)
674 * @protected
675 */
676 protected $footer_text_color = array(0,0,0);
677
678 /**
679 * Color for footer line (RGB array).
680 * @since 5.9.174 (2012-07-25)
681 * @protected
682 */
683 protected $footer_line_color = array(0,0,0);
684
685 /**
686 * Text shadow data array.
687 * @since 5.9.174 (2012-07-25)
688 * @protected
689 */
690 protected $txtshadow = array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal');
691
692 /**
693 * Default number of columns for html table.
694 * @protected
695 */
696 protected $default_table_columns = 4;
697
698 // variables for html parser
699
700 /**
701 * HTML PARSER: array to store current link and rendering styles.
702 * @protected
703 */
704 protected $HREF = array();
705
706 /**
707 * List of available fonts on filesystem.
708 * @protected
709 */
710 protected $fontlist = array();
711
712 /**
713 * Current foreground color.
714 * @protected
715 */
716 protected $fgcolor;
717
718 /**
719 * HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
720 * @protected
721 */
722 protected $listordered = array();
723
724 /**
725 * HTML PARSER: array count list items on nested lists.
726 * @protected
727 */
728 protected $listcount = array();
729
730 /**
731 * HTML PARSER: current list nesting level.
732 * @protected
733 */
734 protected $listnum = 0;
735
736 /**
737 * HTML PARSER: indent amount for lists.
738 * @protected
739 */
740 protected $listindent = 0;
741
742 /**
743 * HTML PARSER: current list indententation level.
744 * @protected
745 */
746 protected $listindentlevel = 0;
747
748 /**
749 * Current background color.
750 * @protected
751 */
752 protected $bgcolor;
753
754 /**
755 * Temporary font size in points.
756 * @protected
757 */
758 protected $tempfontsize = 10;
759
760 /**
761 * Spacer string for LI tags.
762 * @protected
763 */
764 protected $lispacer = '';
765
766 /**
767 * Default encoding.
768 * @protected
769 * @since 1.53.0.TC010
770 */
771 protected $encoding = 'UTF-8';
772
773 /**
774 * PHP internal encoding.
775 * @protected
776 * @since 1.53.0.TC016
777 */
778 protected $internal_encoding;
779
780 /**
781 * Boolean flag to indicate if the document language is Right-To-Left.
782 * @protected
783 * @since 2.0.000
784 */
785 protected $rtl = false;
786
787 /**
788 * Boolean flag used to force RTL or LTR string direction.
789 * @protected
790 * @since 2.0.000
791 */
792 protected $tmprtl = false;
793
794 // --- Variables used for document encryption:
795
796 /**
797 * IBoolean flag indicating whether document is protected.
798 * @protected
799 * @since 2.0.000 (2008-01-02)
800 */
801 protected $encrypted;
802
803 /**
804 * Array containing encryption settings.
805 * @protected
806 * @since 5.0.005 (2010-05-11)
807 */
808 protected $encryptdata = array();
809
810 /**
811 * Last RC4 key encrypted (cached for optimisation).
812 * @protected
813 * @since 2.0.000 (2008-01-02)
814 */
815 protected $last_enc_key;
816
817 /**
818 * Last RC4 computed key.
819 * @protected
820 * @since 2.0.000 (2008-01-02)
821 */
822 protected $last_enc_key_c;
823
824 /**
825 * File ID (used on document trailer).
826 * @protected
827 * @since 5.0.005 (2010-05-12)
828 */
829 protected $file_id;
830
831 // --- bookmark ---
832
833 /**
834 * Outlines for bookmark.
835 * @protected
836 * @since 2.1.002 (2008-02-12)
837 */
838 protected $outlines = array();
839
840 /**
841 * Outline root for bookmark.
842 * @protected
843 * @since 2.1.002 (2008-02-12)
844 */
845 protected $OutlineRoot;
846
847 // --- javascript and form ---
848
849 /**
850 * Javascript code.
851 * @protected
852 * @since 2.1.002 (2008-02-12)
853 */
854 protected $javascript = '';
855
856 /**
857 * Javascript counter.
858 * @protected
859 * @since 2.1.002 (2008-02-12)
860 */
861 protected $n_js;
862
863 /**
864 * line through state
865 * @protected
866 * @since 2.8.000 (2008-03-19)
867 */
868 protected $linethrough;
869
870 /**
871 * Array with additional document-wide usage rights for the document.
872 * @protected
873 * @since 5.8.014 (2010-08-23)
874 */
875 protected $ur = array();
876
877 /**
878 * DPI (Dot Per Inch) Document Resolution (do not change).
879 * @protected
880 * @since 3.0.000 (2008-03-27)
881 */
882 protected $dpi = 72;
883
884 /**
885 * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
886 * @protected
887 * @since 3.0.000 (2008-03-27)
888 */
889 protected $newpagegroup = array();
890
891 /**
892 * Array that contains the number of pages in each page group.
893 * @protected
894 * @since 3.0.000 (2008-03-27)
895 */
896 protected $pagegroups = array();
897
898 /**
899 * Current page group number.
900 * @protected
901 * @since 3.0.000 (2008-03-27)
902 */
903 protected $currpagegroup = 0;
904
905 /**
906 * Array of transparency objects and parameters.
907 * @protected
908 * @since 3.0.000 (2008-03-27)
909 */
910 protected $extgstates;
911
912 /**
913 * Set the default JPEG compression quality (1-100).
914 * @protected
915 * @since 3.0.000 (2008-03-27)
916 */
917 protected $jpeg_quality;
918
919 /**
920 * Default cell height ratio.
921 * @protected
922 * @since 3.0.014 (2008-05-23)
923 */
924 protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
925
926 /**
927 * PDF viewer preferences.
928 * @protected
929 * @since 3.1.000 (2008-06-09)
930 */
931 protected $viewer_preferences;
932
933 /**
934 * A name object specifying how the document should be displayed when opened.
935 * @protected
936 * @since 3.1.000 (2008-06-09)
937 */
938 protected $PageMode;
939
940 /**
941 * Array for storing gradient information.
942 * @protected
943 * @since 3.1.000 (2008-06-09)
944 */
945 protected $gradients = array();
946
947 /**
948 * Array used to store positions inside the pages buffer (keys are the page numbers).
949 * @protected
950 * @since 3.2.000 (2008-06-26)
951 */
952 protected $intmrk = array();
953
954 /**
955 * Array used to store positions inside the pages buffer (keys are the page numbers).
956 * @protected
957 * @since 5.7.000 (2010-08-03)
958 */
959 protected $bordermrk = array();
960
961 /**
962 * Array used to store page positions to track empty pages (keys are the page numbers).
963 * @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 (keys are the page numbers).
970 * @protected
971 * @since 4.6.021 (2009-07-20)
972 */
973 protected $cntmrk = array();
974
975 /**
976 * Array used to store footer positions of each page.
977 * @protected
978 * @since 3.2.000 (2008-07-01)
979 */
980 protected $footerpos = array();
981
982 /**
983 * Array used to store footer length of each page.
984 * @protected
985 * @since 4.0.014 (2008-07-29)
986 */
987 protected $footerlen = array();
988
989 /**
990 * Boolean flag to indicate if a new line is created.
991 * @protected
992 * @since 3.2.000 (2008-07-01)
993 */
994 protected $newline = true;
995
996 /**
997 * End position of the latest inserted line.
998 * @protected
999 * @since 3.2.000 (2008-07-01)
1000 */
1001 protected $endlinex = 0;
1002
1003 /**
1004 * PDF string for width value of the last line.
1005 * @protected
1006 * @since 4.0.006 (2008-07-16)
1007 */
1008 protected $linestyleWidth = '';
1009
1010 /**
1011 * PDF string for CAP value of the last line.
1012 * @protected
1013 * @since 4.0.006 (2008-07-16)
1014 */
1015 protected $linestyleCap = '0 J';
1016
1017 /**
1018 * PDF string for join value of the last line.
1019 * @protected
1020 * @since 4.0.006 (2008-07-16)
1021 */
1022 protected $linestyleJoin = '0 j';
1023
1024 /**
1025 * PDF string for dash value of the last line.
1026 * @protected
1027 * @since 4.0.006 (2008-07-16)
1028 */
1029 protected $linestyleDash = '[] 0 d';
1030
1031 /**
1032 * Boolean flag to indicate if marked-content sequence is open.
1033 * @protected
1034 * @since 4.0.013 (2008-07-28)
1035 */
1036 protected $openMarkedContent = false;
1037
1038 /**
1039 * Count the latest inserted vertical spaces on HTML.
1040 * @protected
1041 * @since 4.0.021 (2008-08-24)
1042 */
1043 protected $htmlvspace = 0;
1044
1045 /**
1046 * Array of Spot colors.
1047 * @protected
1048 * @since 4.0.024 (2008-09-12)
1049 */
1050 protected $spot_colors = array();
1051
1052 /**
1053 * Symbol used for HTML unordered list items.
1054 * @protected
1055 * @since 4.0.028 (2008-09-26)
1056 */
1057 protected $lisymbol = '';
1058
1059 /**
1060 * String used to mark the beginning and end of EPS image blocks.
1061 * @protected
1062 * @since 4.1.000 (2008-10-18)
1063 */
1064 protected $epsmarker = 'x#!#EPS#!#x';
1065
1066 /**
1067 * Array of transformation matrix.
1068 * @protected
1069 * @since 4.2.000 (2008-10-29)
1070 */
1071 protected $transfmatrix = array();
1072
1073 /**
1074 * Current key for transformation matrix.
1075 * @protected
1076 * @since 4.8.005 (2009-09-17)
1077 */
1078 protected $transfmatrix_key = 0;
1079
1080 /**
1081 * Booklet mode for double-sided pages.
1082 * @protected
1083 * @since 4.2.000 (2008-10-29)
1084 */
1085 protected $booklet = false;
1086
1087 /**
1088 * Epsilon value used for float calculations.
1089 * @protected
1090 * @since 4.2.000 (2008-10-29)
1091 */
1092 protected $feps = 0.005;
1093
1094 /**
1095 * Array used for custom vertical spaces for HTML tags.
1096 * @protected
1097 * @since 4.2.001 (2008-10-30)
1098 */
1099 protected $tagvspaces = array();
1100
1101 /**
1102 * HTML PARSER: custom indent amount for lists. Negative value means disabled.
1103 * @protected
1104 * @since 4.2.007 (2008-11-12)
1105 */
1106 protected $customlistindent = -1;
1107
1108 /**
1109 * Boolean flag to indicate if the border of the cell sides that cross the page should be removed.
1110 * @protected
1111 * @since 4.2.010 (2008-11-14)
1112 */
1113 protected $opencell = true;
1114
1115 /**
1116 * Array of files to embedd.
1117 * @protected
1118 * @since 4.4.000 (2008-12-07)
1119 */
1120 protected $embeddedfiles = array();
1121
1122 /**
1123 * Boolean flag to indicate if we are inside a PRE tag.
1124 * @protected
1125 * @since 4.4.001 (2008-12-08)
1126 */
1127 protected $premode = false;
1128
1129 /**
1130 * Array used to store positions of graphics transformation blocks inside the page buffer.
1131 * keys are the page numbers
1132 * @protected
1133 * @since 4.4.002 (2008-12-09)
1134 */
1135 protected $transfmrk = array();
1136
1137 /**
1138 * Default color for html links.
1139 * @protected
1140 * @since 4.4.003 (2008-12-09)
1141 */
1142 protected $htmlLinkColorArray = array(0, 0, 255);
1143
1144 /**
1145 * Default font style to add to html links.
1146 * @protected
1147 * @since 4.4.003 (2008-12-09)
1148 */
1149 protected $htmlLinkFontStyle = 'U';
1150
1151 /**
1152 * Counts the number of pages.
1153 * @protected
1154 * @since 4.5.000 (2008-12-31)
1155 */
1156 protected $numpages = 0;
1157
1158 /**
1159 * Array containing page lengths in bytes.
1160 * @protected
1161 * @since 4.5.000 (2008-12-31)
1162 */
1163 protected $pagelen = array();
1164
1165 /**
1166 * Counts the number of pages.
1167 * @protected
1168 * @since 4.5.000 (2008-12-31)
1169 */
1170 protected $numimages = 0;
1171
1172 /**
1173 * Store the image keys.
1174 * @protected
1175 * @since 4.5.000 (2008-12-31)
1176 */
1177 protected $imagekeys = array();
1178
1179 /**
1180 * Length of the buffer in bytes.
1181 * @protected
1182 * @since 4.5.000 (2008-12-31)
1183 */
1184 protected $bufferlen = 0;
1185
1186 /**
1187 * If true enables disk caching.
1188 * @protected
1189 * @since 4.5.000 (2008-12-31)
1190 */
1191 protected $diskcache = false;
1192
1193 /**
1194 * Counts the number of fonts.
1195 * @protected
1196 * @since 4.5.000 (2009-01-02)
1197 */
1198 protected $numfonts = 0;
1199
1200 /**
1201 * Store the font keys.
1202 * @protected
1203 * @since 4.5.000 (2009-01-02)
1204 */
1205 protected $fontkeys = array();
1206
1207 /**
1208 * Store the font object IDs.
1209 * @protected
1210 * @since 4.8.001 (2009-09-09)
1211 */
1212 protected $font_obj_ids = array();
1213
1214 /**
1215 * Store the fage status (true when opened, false when closed).
1216 * @protected
1217 * @since 4.5.000 (2009-01-02)
1218 */
1219 protected $pageopen = array();
1220
1221 /**
1222 * Default monospace font.
1223 * @protected
1224 * @since 4.5.025 (2009-03-10)
1225 */
1226 protected $default_monospaced_font = 'courier';
1227
1228 /**
1229 * Cloned copy of the current class object.
1230 * @protected
1231 * @since 4.5.029 (2009-03-19)
1232 */
1233 protected $objcopy;
1234
1235 /**
1236 * Array used to store the lengths of cache files.
1237 * @protected
1238 * @since 4.5.029 (2009-03-19)
1239 */
1240 protected $cache_file_length = array();
1241
1242 /**
1243 * Table header content to be repeated on each new page.
1244 * @protected
1245 * @since 4.5.030 (2009-03-20)
1246 */
1247 protected $thead = '';
1248
1249 /**
1250 * Margins used for table header.
1251 * @protected
1252 * @since 4.5.030 (2009-03-20)
1253 */
1254 protected $theadMargins = array();
1255
1256 /**
1257 * Boolean flag to enable document digital signature.
1258 * @protected
1259 * @since 4.6.005 (2009-04-24)
1260 */
1261 protected $sign = false;
1262
1263 /**
1264 * Digital signature data.
1265 * @protected
1266 * @since 4.6.005 (2009-04-24)
1267 */
1268 protected $signature_data = array();
1269
1270 /**
1271 * Digital signature max length.
1272 * @protected
1273 * @since 4.6.005 (2009-04-24)
1274 */
1275 protected $signature_max_length = 11742;
1276
1277 /**
1278 * Data for digital signature appearance.
1279 * @protected
1280 * @since 5.3.011 (2010-06-16)
1281 */
1282 protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1283
1284 /**
1285 * Array of empty digital signature appearances.
1286 * @protected
1287 * @since 5.9.101 (2011-07-06)
1288 */
1289 protected $empty_signature_appearance = array();
1290
1291 /**
1292 * Boolean flag to enable document timestamping with TSA.
1293 * @protected
1294 * @since 6.0.085 (2014-06-19)
1295 */
1296 protected $tsa_timestamp = false;
1297
1298 /**
1299 * Timestamping data.
1300 * @protected
1301 * @since 6.0.085 (2014-06-19)
1302 */
1303 protected $tsa_data = array();
1304
1305 /**
1306 * Regular expression used to find blank characters (required for word-wrapping).
1307 * @protected
1308 * @since 4.6.006 (2009-04-28)
1309 */
1310 protected $re_spaces = '/[^\S\xa0]/';
1311
1312 /**
1313 * Array of $re_spaces parts.
1314 * @protected
1315 * @since 5.5.011 (2010-07-09)
1316 */
1317 protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1318
1319 /**
1320 * Digital signature object ID.
1321 * @protected
1322 * @since 4.6.022 (2009-06-23)
1323 */
1324 protected $sig_obj_id = 0;
1325
1326 /**
1327 * ID of page objects.
1328 * @protected
1329 * @since 4.7.000 (2009-08-29)
1330 */
1331 protected $page_obj_id = array();
1332
1333 /**
1334 * List of form annotations IDs.
1335 * @protected
1336 * @since 4.8.000 (2009-09-07)
1337 */
1338 protected $form_obj_id = array();
1339
1340 /**
1341 * 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.
1342 * @protected
1343 * @since 4.8.000 (2009-09-07)
1344 */
1345 protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1346
1347 /**
1348 * Javascript objects array.
1349 * @protected
1350 * @since 4.8.000 (2009-09-07)
1351 */
1352 protected $js_objects = array();
1353
1354 /**
1355 * Current form action (used during XHTML rendering).
1356 * @protected
1357 * @since 4.8.000 (2009-09-07)
1358 */
1359 protected $form_action = '';
1360
1361 /**
1362 * Current form encryption type (used during XHTML rendering).
1363 * @protected
1364 * @since 4.8.000 (2009-09-07)
1365 */
1366 protected $form_enctype = 'application/x-www-form-urlencoded';
1367
1368 /**
1369 * Current method to submit forms.
1370 * @protected
1371 * @since 4.8.000 (2009-09-07)
1372 */
1373 protected $form_mode = 'post';
1374
1375 /**
1376 * List of fonts used on form fields (fontname => fontkey).
1377 * @protected
1378 * @since 4.8.001 (2009-09-09)
1379 */
1380 protected $annotation_fonts = array();
1381
1382 /**
1383 * List of radio buttons parent objects.
1384 * @protected
1385 * @since 4.8.001 (2009-09-09)
1386 */
1387 protected $radiobutton_groups = array();
1388
1389 /**
1390 * List of radio group objects IDs.
1391 * @protected
1392 * @since 4.8.001 (2009-09-09)
1393 */
1394 protected $radio_groups = array();
1395
1396 /**
1397 * Text indentation value (used for text-indent CSS attribute).
1398 * @protected
1399 * @since 4.8.006 (2009-09-23)
1400 */
1401 protected $textindent = 0;
1402
1403 /**
1404 * Store page number when startTransaction() is called.
1405 * @protected
1406 * @since 4.8.006 (2009-09-23)
1407 */
1408 protected $start_transaction_page = 0;
1409
1410 /**
1411 * Store Y position when startTransaction() is called.
1412 * @protected
1413 * @since 4.9.001 (2010-03-28)
1414 */
1415 protected $start_transaction_y = 0;
1416
1417 /**
1418 * True when we are printing the thead section on a new page.
1419 * @protected
1420 * @since 4.8.027 (2010-01-25)
1421 */
1422 protected $inthead = false;
1423
1424 /**
1425 * Array of column measures (width, space, starting Y position).
1426 * @protected
1427 * @since 4.9.001 (2010-03-28)
1428 */
1429 protected $columns = array();
1430
1431 /**
1432 * Number of colums.
1433 * @protected
1434 * @since 4.9.001 (2010-03-28)
1435 */
1436 protected $num_columns = 1;
1437
1438 /**
1439 * Current column number.
1440 * @protected
1441 * @since 4.9.001 (2010-03-28)
1442 */
1443 protected $current_column = 0;
1444
1445 /**
1446 * Starting page for columns.
1447 * @protected
1448 * @since 4.9.001 (2010-03-28)
1449 */
1450 protected $column_start_page = 0;
1451
1452 /**
1453 * Maximum page and column selected.
1454 * @protected
1455 * @since 5.8.000 (2010-08-11)
1456 */
1457 protected $maxselcol = array('page' => 0, 'column' => 0);
1458
1459 /**
1460 * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
1461 * @protected
1462 * @since 5.8.000 (2010-08-11)
1463 */
1464 protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1465
1466 /**
1467 * 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.
1468 * @protected
1469 * @since 4.9.008 (2010-04-03)
1470 */
1471 protected $textrendermode = 0;
1472
1473 /**
1474 * Text stroke width in doc units.
1475 * @protected
1476 * @since 4.9.008 (2010-04-03)
1477 */
1478 protected $textstrokewidth = 0;
1479
1480 /**
1481 * Current stroke color.
1482 * @protected
1483 * @since 4.9.008 (2010-04-03)
1484 */
1485 protected $strokecolor;
1486
1487 /**
1488 * Default unit of measure for document.
1489 * @protected
1490 * @since 5.0.000 (2010-04-22)
1491 */
1492 protected $pdfunit = 'mm';
1493
1494 /**
1495 * Boolean flag true when we are on TOC (Table Of Content) page.
1496 * @protected
1497 */
1498 protected $tocpage = false;
1499
1500 /**
1501 * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1502 * @protected
1503 * @since 5.0.000 (2010-04-26)
1504 */
1505 protected $rasterize_vector_images = false;
1506
1507 /**
1508 * Boolean flag: if true enables font subsetting by default.
1509 * @protected
1510 * @since 5.3.002 (2010-06-07)
1511 */
1512 protected $font_subsetting = true;
1513
1514 /**
1515 * Array of default graphic settings.
1516 * @protected
1517 * @since 5.5.008 (2010-07-02)
1518 */
1519 protected $default_graphic_vars = array();
1520
1521 /**
1522 * Array of XObjects.
1523 * @protected
1524 * @since 5.8.014 (2010-08-23)
1525 */
1526 protected $xobjects = array();
1527
1528 /**
1529 * Boolean value true when we are inside an XObject.
1530 * @protected
1531 * @since 5.8.017 (2010-08-24)
1532 */
1533 protected $inxobj = false;
1534
1535 /**
1536 * Current XObject ID.
1537 * @protected
1538 * @since 5.8.017 (2010-08-24)
1539 */
1540 protected $xobjid = '';
1541
1542 /**
1543 * Percentage of character stretching.
1544 * @protected
1545 * @since 5.9.000 (2010-09-29)
1546 */
1547 protected $font_stretching = 100;
1548
1549 /**
1550 * Increases or decreases the space between characters in a text by the specified amount (tracking).
1551 * @protected
1552 * @since 5.9.000 (2010-09-29)
1553 */
1554 protected $font_spacing = 0;
1555
1556 /**
1557 * Array of no-write regions.
1558 * ('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)
1559 * @protected
1560 * @since 5.9.003 (2010-10-14)
1561 */
1562 protected $page_regions = array();
1563
1564 /**
1565 * Boolean value true when page region check is active.
1566 * @protected
1567 */
1568 protected $check_page_regions = true;
1569
1570 /**
1571 * Array of PDF layers data.
1572 * @protected
1573 * @since 5.9.102 (2011-07-13)
1574 */
1575 protected $pdflayers = array();
1576
1577 /**
1578 * A dictionary of names and corresponding destinations (Dests key on document Catalog).
1579 * @protected
1580 * @since 5.9.097 (2011-06-23)
1581 */
1582 protected $dests = array();
1583
1584 /**
1585 * Object ID for Named Destinations
1586 * @protected
1587 * @since 5.9.097 (2011-06-23)
1588 */
1589 protected $n_dests;
1590
1591 /**
1592 * Embedded Files Names
1593 * @protected
1594 * @since 5.9.204 (2013-01-23)
1595 */
1596 protected $efnames = array();
1597
1598 /**
1599 * Directory used for the last SVG image.
1600 * @protected
1601 * @since 5.0.000 (2010-05-05)
1602 */
1603 protected $svgdir = '';
1604
1605 /**
1606 * Deafult unit of measure for SVG.
1607 * @protected
1608 * @since 5.0.000 (2010-05-02)
1609 */
1610 protected $svgunit = 'px';
1611
1612 /**
1613 * Array of SVG gradients.
1614 * @protected
1615 * @since 5.0.000 (2010-05-02)
1616 */
1617 protected $svggradients = array();
1618
1619 /**
1620 * ID of last SVG gradient.
1621 * @protected
1622 * @since 5.0.000 (2010-05-02)
1623 */
1624 protected $svggradientid = 0;
1625
1626 /**
1627 * Boolean value true when in SVG defs group.
1628 * @protected
1629 * @since 5.0.000 (2010-05-02)
1630 */
1631 protected $svgdefsmode = false;
1632
1633 /**
1634 * Array of SVG defs.
1635 * @protected
1636 * @since 5.0.000 (2010-05-02)
1637 */
1638 protected $svgdefs = array();
1639
1640 /**
1641 * Boolean value true when in SVG clipPath tag.
1642 * @protected
1643 * @since 5.0.000 (2010-04-26)
1644 */
1645 protected $svgclipmode = false;
1646
1647 /**
1648 * Array of SVG clipPath commands.
1649 * @protected
1650 * @since 5.0.000 (2010-05-02)
1651 */
1652 protected $svgclippaths = array();
1653
1654 /**
1655 * Array of SVG clipPath tranformation matrix.
1656 * @protected
1657 * @since 5.8.022 (2010-08-31)
1658 */
1659 protected $svgcliptm = array();
1660
1661 /**
1662 * ID of last SVG clipPath.
1663 * @protected
1664 * @since 5.0.000 (2010-05-02)
1665 */
1666 protected $svgclipid = 0;
1667
1668 /**
1669 * SVG text.
1670 * @protected
1671 * @since 5.0.000 (2010-05-02)
1672 */
1673 protected $svgtext = '';
1674
1675 /**
1676 * SVG text properties.
1677 * @protected
1678 * @since 5.8.013 (2010-08-23)
1679 */
1680 protected $svgtextmode = array();
1681
1682 /**
1683 * Array of SVG properties.
1684 * @protected
1685 * @since 5.0.000 (2010-05-02)
1686 */
1687 protected $svgstyles = array(array(
1688 'alignment-baseline' => 'auto',
1689 'baseline-shift' => 'baseline',
1690 'clip' => 'auto',
1691 'clip-path' => 'none',
1692 'clip-rule' => 'nonzero',
1693 'color' => 'black',
1694 'color-interpolation' => 'sRGB',
1695 'color-interpolation-filters' => 'linearRGB',
1696 'color-profile' => 'auto',
1697 'color-rendering' => 'auto',
1698 'cursor' => 'auto',
1699 'direction' => 'ltr',
1700 'display' => 'inline',
1701 'dominant-baseline' => 'auto',
1702 'enable-background' => 'accumulate',
1703 'fill' => 'black',
1704 'fill-opacity' => 1,
1705 'fill-rule' => 'nonzero',
1706 'filter' => 'none',
1707 'flood-color' => 'black',
1708 'flood-opacity' => 1,
1709 'font' => '',
1710 'font-family' => 'helvetica',
1711 'font-size' => 'medium',
1712 'font-size-adjust' => 'none',
1713 'font-stretch' => 'normal',
1714 'font-style' => 'normal',
1715 'font-variant' => 'normal',
1716 'font-weight' => 'normal',
1717 'glyph-orientation-horizontal' => '0deg',
1718 'glyph-orientation-vertical' => 'auto',
1719 'image-rendering' => 'auto',
1720 'kerning' => 'auto',
1721 'letter-spacing' => 'normal',
1722 'lighting-color' => 'white',
1723 'marker' => '',
1724 'marker-end' => 'none',
1725 'marker-mid' => 'none',
1726 'marker-start' => 'none',
1727 'mask' => 'none',
1728 'opacity' => 1,
1729 'overflow' => 'auto',
1730 'pointer-events' => 'visiblePainted',
1731 'shape-rendering' => 'auto',
1732 'stop-color' => 'black',
1733 'stop-opacity' => 1,
1734 'stroke' => 'none',
1735 'stroke-dasharray' => 'none',
1736 'stroke-dashoffset' => 0,
1737 'stroke-linecap' => 'butt',
1738 'stroke-linejoin' => 'miter',
1739 'stroke-miterlimit' => 4,
1740 'stroke-opacity' => 1,
1741 'stroke-width' => 1,
1742 'text-anchor' => 'start',
1743 'text-decoration' => 'none',
1744 'text-rendering' => 'auto',
1745 'unicode-bidi' => 'normal',
1746 'visibility' => 'visible',
1747 'word-spacing' => 'normal',
1748 'writing-mode' => 'lr-tb',
1749 'text-color' => 'black',
1750 'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1751 ));
1752
1753 /**
1754 * If true force sRGB color profile for all document.
1755 * @protected
1756 * @since 5.9.121 (2011-09-28)
1757 */
1758 protected $force_srgb = false;
1759
1760 /**
1761 * If true set the document to PDF/A mode.
1762 * @protected
1763 * @since 5.9.121 (2011-09-27)
1764 */
1765 protected $pdfa_mode = false;
1766
1767 /**
1768 * Document creation date-time
1769 * @protected
1770 * @since 5.9.152 (2012-03-22)
1771 */
1772 protected $doc_creation_timestamp;
1773
1774 /**
1775 * Document modification date-time
1776 * @protected
1777 * @since 5.9.152 (2012-03-22)
1778 */
1779 protected $doc_modification_timestamp;
1780
1781 /**
1782 * Custom XMP data.
1783 * @protected
1784 * @since 5.9.128 (2011-10-06)
1785 */
1786 protected $custom_xmp = '';
1787
1788 /**
1789 * Overprint mode array.
1790 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1791 * @protected
1792 * @since 5.9.152 (2012-03-23)
1793 */
1794 protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1795
1796 /**
1797 * Alpha mode array.
1798 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1799 * @protected
1800 * @since 5.9.152 (2012-03-23)
1801 */
1802 protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1803
1804 /**
1805 * Define the page boundaries boxes to be set on document.
1806 * @protected
1807 * @since 5.9.152 (2012-03-23)
1808 */
1809 protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1810
1811 /**
1812 * If true print TCPDF meta link.
1813 * @protected
1814 * @since 5.9.152 (2012-03-23)
1815 */
1816 protected $tcpdflink = true;
1817
1818 /**
1819 * Cache array for computed GD gamma values.
1820 * @protected
1821 * @since 5.9.1632 (2012-06-05)
1822 */
1823 protected $gdgammacache = array();
1824
1825 //------------------------------------------------------------
1826 // METHODS
1827 //------------------------------------------------------------
1828
1829 /**
1830 * This is the class constructor.
1831 * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1832 *
1833 * IMPORTANT: Please note that this method sets the mb_internal_encoding to ASCII, so if you are using the mbstring module functions with TCPDF you need to correctly set/unset the mb_internal_encoding when needed.
1834 *
1835 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
1836 * @param $unit (string) User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
1837 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
1838 * @param $unicode (boolean) TRUE means that the input text is unicode (default = true)
1839 * @param $encoding (string) Charset encoding (used only when converting back html entities); default is UTF-8.
1840 * @param $diskcache (boolean) If TRUE reduce the RAM memory usage by caching temporary data on filesystem (slower).
1841 * @param $pdfa (boolean) If TRUE set the document to PDF/A mode.
1842 * @public
1843 * @see getPageSizeFromFormat(), setPageFormat()
1844 */
1845 public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
1846 /* Set internal character encoding to ASCII */
1847 if (function_exists('mb_internal_encoding') AND mb_internal_encoding()) {
1848 $this->internal_encoding = mb_internal_encoding();
1849 mb_internal_encoding('ASCII');
1850 }
1851 // set file ID for trailer
1852 $serformat = (is_array($format) ? json_encode($format) : $format);
1853 $this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
1854 $this->font_obj_ids = array();
1855 $this->page_obj_id = array();
1856 $this->form_obj_id = array();
1857 // set pdf/a mode
1858 $this->pdfa_mode = $pdfa;
1859 $this->force_srgb = false;
1860 // set disk caching
1861 $this->diskcache = $diskcache ? true : false;
1862 // set language direction
1863 $this->rtl = false;
1864 $this->tmprtl = false;
1865 // some checks
1866 $this->_dochecks();
1867 // initialization of properties
1868 $this->isunicode = $unicode;
1869 $this->page = 0;
1870 $this->transfmrk[0] = array();
1871 $this->pagedim = array();
1872 $this->n = 2;
1873 $this->buffer = '';
1874 $this->pages = array();
1875 $this->state = 0;
1876 $this->fonts = array();
1877 $this->FontFiles = array();
1878 $this->diffs = array();
1879 $this->images = array();
1880 $this->links = array();
1881 $this->gradients = array();
1882 $this->InFooter = false;
1883 $this->lasth = 0;
1884 $this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
1885 $this->FontStyle = '';
1886 $this->FontSizePt = 12;
1887 $this->underline = false;
1888 $this->overline = false;
1889 $this->linethrough = false;
1890 $this->DrawColor = '0 G';
1891 $this->FillColor = '0 g';
1892 $this->TextColor = '0 g';
1893 $this->ColorFlag = false;
1894 $this->pdflayers = array();
1895 // encryption values
1896 $this->encrypted = false;
1897 $this->last_enc_key = '';
1898 // standard Unicode fonts
1899 $this->CoreFonts = array(
1900 'courier'=>'Courier',
1901 'courierB'=>'Courier-Bold',
1902 'courierI'=>'Courier-Oblique',
1903 'courierBI'=>'Courier-BoldOblique',
1904 'helvetica'=>'Helvetica',
1905 'helveticaB'=>'Helvetica-Bold',
1906 'helveticaI'=>'Helvetica-Oblique',
1907 'helveticaBI'=>'Helvetica-BoldOblique',
1908 'times'=>'Times-Roman',
1909 'timesB'=>'Times-Bold',
1910 'timesI'=>'Times-Italic',
1911 'timesBI'=>'Times-BoldItalic',
1912 'symbol'=>'Symbol',
1913 'zapfdingbats'=>'ZapfDingbats'
1914 );
1915 // set scale factor
1916 $this->setPageUnit($unit);
1917 // set page format and orientation
1918 $this->setPageFormat($format, $orientation);
1919 // page margins (1 cm)
1920 $margin = 28.35 / $this->k;
1921 $this->SetMargins($margin, $margin);
1922 $this->clMargin = $this->lMargin;
1923 $this->crMargin = $this->rMargin;
1924 // internal cell padding
1925 $cpadding = $margin / 10;
1926 $this->setCellPaddings($cpadding, 0, $cpadding, 0);
1927 // cell margins
1928 $this->setCellMargins(0, 0, 0, 0);
1929 // line width (0.2 mm)
1930 $this->LineWidth = 0.57 / $this->k;
1931 $this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
1932 $this->linestyleCap = '0 J';
1933 $this->linestyleJoin = '0 j';
1934 $this->linestyleDash = '[] 0 d';
1935 // automatic page break
1936 $this->SetAutoPageBreak(true, (2 * $margin));
1937 // full width display mode
1938 $this->SetDisplayMode('fullwidth');
1939 // compression
1940 $this->SetCompression();
1941 // set default PDF version number
1942 $this->setPDFVersion();
1943 $this->tcpdflink = true;
1944 $this->encoding = $encoding;
1945 $this->HREF = array();
1946 $this->getFontsList();
1947 $this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
1948 $this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
1949 $this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
1950 $this->extgstates = array();
1951 $this->setTextShadow();
1952 // signature
1953 $this->sign = false;
1954 $this->tsa_timestamp = false;
1955 $this->tsa_data = array();
1956 $this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
1957 $this->empty_signature_appearance = array();
1958 // user's rights
1959 $this->ur['enabled'] = false;
1960 $this->ur['document'] = '/FullSave';
1961 $this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
1962 $this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
1963 $this->ur['signature'] = '/Modify';
1964 $this->ur['ef'] = '/Create/Delete/Modify/Import';
1965 $this->ur['formex'] = '';
1966 // set default JPEG quality
1967 $this->jpeg_quality = 75;
1968 // initialize some settings
1969 TCPDF_FONTS::utf8Bidi(array(''), '', false, $this->isunicode, $this->CurrentFont);
1970 // set default font
1971 $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
1972 $this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1973 $this->setFooterFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1974 // check if PCRE Unicode support is enabled
1975 if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
1976 // PCRE unicode support is turned ON
1977 // \s : any whitespace character
1978 // \p{Z} : any separator
1979 // \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
1980 // \xa0 : Unicode Character 'NO-BREAK SPACE' (U+00A0)
1981 //$this->setSpacesRE('/(?!\xa0)[\s\p{Z}\p{Lo}]/u');
1982 $this->setSpacesRE('/(?!\xa0)[\s\p{Z}]/u');
1983 } else {
1984 // PCRE unicode support is turned OFF
1985 $this->setSpacesRE('/[^\S\xa0]/');
1986 }
1987 $this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1988 // set document creation and modification timestamp
1989 $this->doc_creation_timestamp = time();
1990 $this->doc_modification_timestamp = $this->doc_creation_timestamp;
1991 // get default graphic vars
1992 $this->default_graphic_vars = $this->getGraphicVars();
1993 $this->header_xobj_autoreset = false;
1994 $this->custom_xmp = '';
1995 }
1996
1997 /**
1998 * Default destructor.
1999 * @public
2000 * @since 1.53.0.TC016
2001 */
2002 public function __destruct() {
2003 // restore internal encoding
2004 if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) {
2005 mb_internal_encoding($this->internal_encoding);
2006 }
2007 // unset all class variables
2008 $this->_destroy(true);
2009 }
2010
2011 /**
2012 * Set the units of measure for the document.
2013 * @param $unit (string) User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
2014 * @public
2015 * @since 3.0.015 (2008-06-06)
2016 */
2017 public function setPageUnit($unit) {
2018 $unit = strtolower($unit);
2019 //Set scale factor
2020 switch ($unit) {
2021 // points
2022 case 'px':
2023 case 'pt': {
2024 $this->k = 1;
2025 break;
2026 }
2027 // millimeters
2028 case 'mm': {
2029 $this->k = $this->dpi / 25.4;
2030 break;
2031 }
2032 // centimeters
2033 case 'cm': {
2034 $this->k = $this->dpi / 2.54;
2035 break;
2036 }
2037 // inches
2038 case 'in': {
2039 $this->k = $this->dpi;
2040 break;
2041 }
2042 // unsupported unit
2043 default : {
2044 $this->Error('Incorrect unit: '.$unit);
2045 break;
2046 }
2047 }
2048 $this->pdfunit = $unit;
2049 if (isset($this->CurOrientation)) {
2050 $this->setPageOrientation($this->CurOrientation);
2051 }
2052 }
2053
2054 /**
2055 * Change the format of the current page
2056 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() documentation or an array of two numbers (width, height) or an array containing the following measures and options:<ul>
2057 * <li>['format'] = page format name (one of the above);</li>
2058 * <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>
2059 * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2060 * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2061 * <li>['MediaBox']['llx'] : lower-left x coordinate</li>
2062 * <li>['MediaBox']['lly'] : lower-left y coordinate</li>
2063 * <li>['MediaBox']['urx'] : upper-right x coordinate</li>
2064 * <li>['MediaBox']['ury'] : upper-right y coordinate</li>
2065 * <li>['CropBox'] : the visible region of default user space:</li>
2066 * <li>['CropBox']['llx'] : lower-left x coordinate</li>
2067 * <li>['CropBox']['lly'] : lower-left y coordinate</li>
2068 * <li>['CropBox']['urx'] : upper-right x coordinate</li>
2069 * <li>['CropBox']['ury'] : upper-right y coordinate</li>
2070 * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2071 * <li>['BleedBox']['llx'] : lower-left x coordinate</li>
2072 * <li>['BleedBox']['lly'] : lower-left y coordinate</li>
2073 * <li>['BleedBox']['urx'] : upper-right x coordinate</li>
2074 * <li>['BleedBox']['ury'] : upper-right y coordinate</li>
2075 * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2076 * <li>['TrimBox']['llx'] : lower-left x coordinate</li>
2077 * <li>['TrimBox']['lly'] : lower-left y coordinate</li>
2078 * <li>['TrimBox']['urx'] : upper-right x coordinate</li>
2079 * <li>['TrimBox']['ury'] : upper-right y coordinate</li>
2080 * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2081 * <li>['ArtBox']['llx'] : lower-left x coordinate</li>
2082 * <li>['ArtBox']['lly'] : lower-left y coordinate</li>
2083 * <li>['ArtBox']['urx'] : upper-right x coordinate</li>
2084 * <li>['ArtBox']['ury'] : upper-right y coordinate</li>
2085 * <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>
2086 * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2087 * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2088 * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2089 * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2090 * <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>
2091 * <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>
2092 * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2093 * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2094 * <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>
2095 * <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>
2096 * <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>
2097 * <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>
2098 * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2099 * </ul>
2100 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul>
2101 * <li>P or Portrait (default)</li>
2102 * <li>L or Landscape</li>
2103 * <li>'' (empty string) for automatic orientation</li>
2104 * </ul>
2105 * @protected
2106 * @since 3.0.015 (2008-06-06)
2107 * @see getPageSizeFromFormat()
2108 */
2109 protected function setPageFormat($format, $orientation='P') {
2110 if (!empty($format) AND isset($this->pagedim[$this->page])) {
2111 // remove inherited values
2112 unset($this->pagedim[$this->page]);
2113 }
2114 if (is_string($format)) {
2115 // get page measures from format name
2116 $pf = TCPDF_STATIC::getPageSizeFromFormat($format);
2117 $this->fwPt = $pf[0];
2118 $this->fhPt = $pf[1];
2119 } else {
2120 // the boundaries of the physical medium on which the page shall be displayed or printed
2121 if (isset($format['MediaBox'])) {
2122 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', $format['MediaBox']['llx'], $format['MediaBox']['lly'], $format['MediaBox']['urx'], $format['MediaBox']['ury'], false, $this->k, $this->pagedim);
2123 $this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2124 $this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2125 } else {
2126 if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2127 $pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2128 } else {
2129 if (!isset($format['format'])) {
2130 // default value
2131 $format['format'] = 'A4';
2132 }
2133 $pf = TCPDF_STATIC::getPageSizeFromFormat($format['format']);
2134 }
2135 $this->fwPt = $pf[0];
2136 $this->fhPt = $pf[1];
2137 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2138 }
2139 // the visible region of default user space
2140 if (isset($format['CropBox'])) {
2141 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'CropBox', $format['CropBox']['llx'], $format['CropBox']['lly'], $format['CropBox']['urx'], $format['CropBox']['ury'], false, $this->k, $this->pagedim);
2142 }
2143 // the region to which the contents of the page shall be clipped when output in a production environment
2144 if (isset($format['BleedBox'])) {
2145 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'BleedBox', $format['BleedBox']['llx'], $format['BleedBox']['lly'], $format['BleedBox']['urx'], $format['BleedBox']['ury'], false, $this->k, $this->pagedim);
2146 }
2147 // the intended dimensions of the finished page after trimming
2148 if (isset($format['TrimBox'])) {
2149 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'TrimBox', $format['TrimBox']['llx'], $format['TrimBox']['lly'], $format['TrimBox']['urx'], $format['TrimBox']['ury'], false, $this->k, $this->pagedim);
2150 }
2151 // the page's meaningful content (including potential white space)
2152 if (isset($format['ArtBox'])) {
2153 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'ArtBox', $format['ArtBox']['llx'], $format['ArtBox']['lly'], $format['ArtBox']['urx'], $format['ArtBox']['ury'], false, $this->k, $this->pagedim);
2154 }
2155 // specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2156 if (isset($format['BoxColorInfo'])) {
2157 $this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2158 }
2159 if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2160 // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2161 $this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2162 }
2163 if (isset($format['PZ'])) {
2164 // The page's preferred zoom (magnification) factor
2165 $this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2166 }
2167 if (isset($format['trans'])) {
2168 // The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2169 if (isset($format['trans']['Dur'])) {
2170 // The page's display duration
2171 $this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2172 }
2173 $stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2174 if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2175 // The transition style that shall be used when moving to this page from another during a presentation
2176 $this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2177 $valid_effect = array('Split', 'Blinds');
2178 $valid_vals = array('H', 'V');
2179 if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2180 $this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2181 }
2182 $valid_effect = array('Split', 'Box', 'Fly');
2183 $valid_vals = array('I', 'O');
2184 if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2185 $this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2186 }
2187 $valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2188 if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2189 if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2190 OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2191 OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2192 $this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2193 }
2194 }
2195 if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2196 $this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2197 }
2198 if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2199 $this->pagedim[$this->page]['trans']['B'] = 'true';
2200 }
2201 } else {
2202 $this->pagedim[$this->page]['trans']['S'] = 'R';
2203 }
2204 if (isset($format['trans']['D'])) {
2205 // The duration of the transition effect, in seconds
2206 $this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2207 } else {
2208 $this->pagedim[$this->page]['trans']['D'] = 1;
2209 }
2210 }
2211 }
2212 $this->setPageOrientation($orientation);
2213 }
2214
2215 /**
2216 * Set page orientation.
2217 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
2218 * @param $autopagebreak (boolean) Boolean indicating if auto-page-break mode should be on or off.
2219 * @param $bottommargin (float) bottom margin of the page.
2220 * @public
2221 * @since 3.0.015 (2008-06-06)
2222 */
2223 public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
2224 if (!isset($this->pagedim[$this->page]['MediaBox'])) {
2225 // the boundaries of the physical medium on which the page shall be displayed or printed
2226 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2227 }
2228 if (!isset($this->pagedim[$this->page]['CropBox'])) {
2229 // the visible region of default user space
2230 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'CropBox', $this->pagedim[$this->page]['MediaBox']['llx'], $this->pagedim[$this->page]['MediaBox']['lly'], $this->pagedim[$this->page]['MediaBox']['urx'], $this->pagedim[$this->page]['MediaBox']['ury'], true, $this->k, $this->pagedim);
2231 }
2232 if (!isset($this->pagedim[$this->page]['BleedBox'])) {
2233 // the region to which the contents of the page shall be clipped when output in a production environment
2234 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'BleedBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
2235 }
2236 if (!isset($this->pagedim[$this->page]['TrimBox'])) {
2237 // the intended dimensions of the finished page after trimming
2238 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'TrimBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
2239 }
2240 if (!isset($this->pagedim[$this->page]['ArtBox'])) {
2241 // the page's meaningful content (including potential white space)
2242 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'ArtBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
2243 }
2244 if (!isset($this->pagedim[$this->page]['Rotate'])) {
2245 // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2246 $this->pagedim[$this->page]['Rotate'] = 0;
2247 }
2248 if (!isset($this->pagedim[$this->page]['PZ'])) {
2249 // The page's preferred zoom (magnification) factor
2250 $this->pagedim[$this->page]['PZ'] = 1;
2251 }
2252 if ($this->fwPt > $this->fhPt) {
2253 // landscape
2254 $default_orientation = 'L';
2255 } else {
2256 // portrait
2257 $default_orientation = 'P';
2258 }
2259 $valid_orientations = array('P', 'L');
2260 if (empty($orientation)) {
2261 $orientation = $default_orientation;
2262 } else {
2263 $orientation = strtoupper($orientation[0]);
2264 }
2265 if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2266 $this->CurOrientation = $orientation;
2267 $this->wPt = $this->fhPt;
2268 $this->hPt = $this->fwPt;
2269 } else {
2270 $this->CurOrientation = $default_orientation;
2271 $this->wPt = $this->fwPt;
2272 $this->hPt = $this->fhPt;
2273 }
2274 if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
2275 // swap X and Y coordinates (change page orientation)
2276 $this->pagedim = TCPDF_STATIC::swapPageBoxCoordinates($this->page, $this->pagedim);
2277 }
2278 $this->w = ($this->wPt / $this->k);
2279 $this->h = ($this->hPt / $this->k);
2280 if (TCPDF_STATIC::empty_string($autopagebreak)) {
2281 if (isset($this->AutoPageBreak)) {
2282 $autopagebreak = $this->AutoPageBreak;
2283 } else {
2284 $autopagebreak = true;
2285 }
2286 }
2287 if (TCPDF_STATIC::empty_string($bottommargin)) {
2288 if (isset($this->bMargin)) {
2289 $bottommargin = $this->bMargin;
2290 } else {
2291 // default value = 2 cm
2292 $bottommargin = 2 * 28.35 / $this->k;
2293 }
2294 }
2295 $this->SetAutoPageBreak($autopagebreak, $bottommargin);
2296 // store page dimensions
2297 $this->pagedim[$this->page]['w'] = $this->wPt;
2298 $this->pagedim[$this->page]['h'] = $this->hPt;
2299 $this->pagedim[$this->page]['wk'] = $this->w;
2300 $this->pagedim[$this->page]['hk'] = $this->h;
2301 $this->pagedim[$this->page]['tm'] = $this->tMargin;
2302 $this->pagedim[$this->page]['bm'] = $bottommargin;
2303 $this->pagedim[$this->page]['lm'] = $this->lMargin;
2304 $this->pagedim[$this->page]['rm'] = $this->rMargin;
2305 $this->pagedim[$this->page]['pb'] = $autopagebreak;
2306 $this->pagedim[$this->page]['or'] = $this->CurOrientation;
2307 $this->pagedim[$this->page]['olm'] = $this->original_lMargin;
2308 $this->pagedim[$this->page]['orm'] = $this->original_rMargin;
2309 }
2310
2311 /**
2312 * Set regular expression to detect withespaces or word separators.
2313 * The pattern delimiter must be the forward-slash character "/".
2314 * Some example patterns are:
2315 * <pre>
2316 * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
2317 * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
2318 * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
2319 * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
2320 * \s : any whitespace character
2321 * \p{Z} : any separator
2322 * \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2323 * \xa0 : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2324 * </pre>
2325 * @param $re (string) regular expression (leave empty for default).
2326 * @public
2327 * @since 4.6.016 (2009-06-15)
2328 */
2329 public function setSpacesRE($re='/[^\S\xa0]/') {
2330 $this->re_spaces = $re;
2331 $re_parts = explode('/', $re);
2332 // get pattern parts
2333 $this->re_space = array();
2334 if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2335 $this->re_space['p'] = $re_parts[1];
2336 } else {
2337 $this->re_space['p'] = '[\s]';
2338 }
2339 // set pattern modifiers
2340 if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2341 $this->re_space['m'] = $re_parts[2];
2342 } else {
2343 $this->re_space['m'] = '';
2344 }
2345 }
2346
2347 /**
2348 * Enable or disable Right-To-Left language mode
2349 * @param $enable (Boolean) if true enable Right-To-Left language mode.
2350 * @param $resetx (Boolean) if true reset the X position on direction change.
2351 * @public
2352 * @since 2.0.000 (2008-01-03)
2353 */
2354 public function setRTL($enable, $resetx=true) {
2355 $enable = $enable ? true : false;
2356 $resetx = ($resetx AND ($enable != $this->rtl));
2357 $this->rtl = $enable;
2358 $this->tmprtl = false;
2359 if ($resetx) {
2360 $this->Ln(0);
2361 }
2362 }
2363
2364 /**
2365 * Return the RTL status
2366 * @return boolean
2367 * @public
2368 * @since 4.0.012 (2008-07-24)
2369 */
2370 public function getRTL() {
2371 return $this->rtl;
2372 }
2373
2374 /**
2375 * Force temporary RTL language direction
2376 * @param $mode (mixed) can be false, 'L' for LTR or 'R' for RTL
2377 * @public
2378 * @since 2.1.000 (2008-01-09)
2379 */
2380 public function setTempRTL($mode) {
2381 $newmode = false;
2382 switch (strtoupper($mode)) {
2383 case 'LTR':
2384 case 'L': {
2385 if ($this->rtl) {
2386 $newmode = 'L';
2387 }
2388 break;
2389 }
2390 case 'RTL':
2391 case 'R': {
2392 if (!$this->rtl) {
2393 $newmode = 'R';
2394 }
2395 break;
2396 }
2397 case false:
2398 default: {
2399 $newmode = false;
2400 break;
2401 }
2402 }
2403 $this->tmprtl = $newmode;
2404 }
2405
2406 /**
2407 * Return the current temporary RTL status
2408 * @return boolean
2409 * @public
2410 * @since 4.8.014 (2009-11-04)
2411 */
2412 public function isRTLTextDir() {
2413 return ($this->rtl OR ($this->tmprtl == 'R'));
2414 }
2415
2416 /**
2417 * Set the last cell height.
2418 * @param $h (float) cell height.
2419 * @author Nicola Asuni
2420 * @public
2421 * @since 1.53.0.TC034
2422 */
2423 public function setLastH($h) {
2424 $this->lasth = $h;
2425 }
2426
2427 /**
2428 * Return the cell height
2429 * @param $fontsize (int) Font size in internal units
2430 * @param $padding (boolean) If true add cell padding
2431 * @public
2432 */
2433 public function getCellHeight($fontsize, $padding=TRUE) {
2434 $height = ($fontsize * $this->cell_height_ratio);
2435 if ($padding) {
2436 $height += ($this->cell_padding['T'] + $this->cell_padding['B']);
2437 }
2438 return round($height, 6);
2439 }
2440
2441 /**
2442 * Reset the last cell height.
2443 * @public
2444 * @since 5.9.000 (2010-10-03)
2445 */
2446 public function resetLastH() {
2447 $this->lasth = $this->getCellHeight($this->FontSize);
2448 }
2449
2450 /**
2451 * Get the last cell height.
2452 * @return last cell height
2453 * @public
2454 * @since 4.0.017 (2008-08-05)
2455 */
2456 public function getLastH() {
2457 return $this->lasth;
2458 }
2459
2460 /**
2461 * Set the adjusting factor to convert pixels to user units.
2462 * @param $scale (float) adjusting factor to convert pixels to user units.
2463 * @author Nicola Asuni
2464 * @public
2465 * @since 1.5.2
2466 */
2467 public function setImageScale($scale) {
2468 $this->imgscale = $scale;
2469 }
2470
2471 /**
2472 * Returns the adjusting factor to convert pixels to user units.
2473 * @return float adjusting factor to convert pixels to user units.
2474 * @author Nicola Asuni
2475 * @public
2476 * @since 1.5.2
2477 */
2478 public function getImageScale() {
2479 return $this->imgscale;
2480 }
2481
2482 /**
2483 * Returns an array of page dimensions:
2484 * <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>
2485 * @param $pagenum (int) page number (empty = current page)
2486 * @return array of page dimensions.
2487 * @author Nicola Asuni
2488 * @public
2489 * @since 4.5.027 (2009-03-16)
2490 */
2491 public function getPageDimensions($pagenum='') {
2492 if (empty($pagenum)) {
2493 $pagenum = $this->page;
2494 }
2495 return $this->pagedim[$pagenum];
2496 }
2497
2498 /**
2499 * Returns the page width in units.
2500 * @param $pagenum (int) page number (empty = current page)
2501 * @return int page width.
2502 * @author Nicola Asuni
2503 * @public
2504 * @since 1.5.2
2505 * @see getPageDimensions()
2506 */
2507 public function getPageWidth($pagenum='') {
2508 if (empty($pagenum)) {
2509 return $this->w;
2510 }
2511 return $this->pagedim[$pagenum]['w'];
2512 }
2513
2514 /**
2515 * Returns the page height in units.
2516 * @param $pagenum (int) page number (empty = current page)
2517 * @return int page height.
2518 * @author Nicola Asuni
2519 * @public
2520 * @since 1.5.2
2521 * @see getPageDimensions()
2522 */
2523 public function getPageHeight($pagenum='') {
2524 if (empty($pagenum)) {
2525 return $this->h;
2526 }
2527 return $this->pagedim[$pagenum]['h'];
2528 }
2529
2530 /**
2531 * Returns the page break margin.
2532 * @param $pagenum (int) page number (empty = current page)
2533 * @return int page break margin.
2534 * @author Nicola Asuni
2535 * @public
2536 * @since 1.5.2
2537 * @see getPageDimensions()
2538 */
2539 public function getBreakMargin($pagenum='') {
2540 if (empty($pagenum)) {
2541 return $this->bMargin;
2542 }
2543 return $this->pagedim[$pagenum]['bm'];
2544 }
2545
2546 /**
2547 * Returns the scale factor (number of points in user unit).
2548 * @return int scale factor.
2549 * @author Nicola Asuni
2550 * @public
2551 * @since 1.5.2
2552 */
2553 public function getScaleFactor() {
2554 return $this->k;
2555 }
2556
2557 /**
2558 * Defines the left, top and right margins.
2559 * @param $left (float) Left margin.
2560 * @param $top (float) Top margin.
2561 * @param $right (float) Right margin. Default value is the left one.
2562 * @param $keepmargins (boolean) if true overwrites the default page margins
2563 * @public
2564 * @since 1.0
2565 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
2566 */
2567 public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
2568 //Set left, top and right margins
2569 $this->lMargin = $left;
2570 $this->tMargin = $top;
2571 if ($right == -1) {
2572 $right = $left;
2573 }
2574 $this->rMargin = $right;
2575 if ($keepmargins) {
2576 // overwrite original values
2577 $this->original_lMargin = $this->lMargin;
2578 $this->original_rMargin = $this->rMargin;
2579 }
2580 }
2581
2582 /**
2583 * 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.
2584 * @param $margin (float) The margin.
2585 * @public
2586 * @since 1.4
2587 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2588 */
2589 public function SetLeftMargin($margin) {
2590 //Set left margin
2591 $this->lMargin = $margin;
2592 if (($this->page > 0) AND ($this->x < $margin)) {
2593 $this->x = $margin;
2594 }
2595 }
2596
2597 /**
2598 * Defines the top margin. The method can be called before creating the first page.
2599 * @param $margin (float) The margin.
2600 * @public
2601 * @since 1.5
2602 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2603 */
2604 public function SetTopMargin($margin) {
2605 //Set top margin
2606 $this->tMargin = $margin;
2607 if (($this->page > 0) AND ($this->y < $margin)) {
2608 $this->y = $margin;
2609 }
2610 }
2611
2612 /**
2613 * Defines the right margin. The method can be called before creating the first page.
2614 * @param $margin (float) The margin.
2615 * @public
2616 * @since 1.5
2617 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
2618 */
2619 public function SetRightMargin($margin) {
2620 $this->rMargin = $margin;
2621 if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
2622 $this->x = $this->w - $margin;
2623 }
2624 }
2625
2626 /**
2627 * Set the same internal Cell padding for top, right, bottom, left-
2628 * @param $pad (float) internal padding.
2629 * @public
2630 * @since 2.1.000 (2008-01-09)
2631 * @see getCellPaddings(), setCellPaddings()
2632 */
2633 public function SetCellPadding($pad) {
2634 if ($pad >= 0) {
2635 $this->cell_padding['L'] = $pad;
2636 $this->cell_padding['T'] = $pad;
2637 $this->cell_padding['R'] = $pad;
2638 $this->cell_padding['B'] = $pad;
2639 }
2640 }
2641
2642 /**
2643 * Set the internal Cell paddings.
2644 * @param $left (float) left padding
2645 * @param $top (float) top padding
2646 * @param $right (float) right padding
2647 * @param $bottom (float) bottom padding
2648 * @public
2649 * @since 5.9.000 (2010-10-03)
2650 * @see getCellPaddings(), SetCellPadding()
2651 */
2652 public function setCellPaddings($left='', $top='', $right='', $bottom='') {
2653 if (($left !== '') AND ($left >= 0)) {
2654 $this->cell_padding['L'] = $left;
2655 }
2656 if (($top !== '') AND ($top >= 0)) {
2657 $this->cell_padding['T'] = $top;
2658 }
2659 if (($right !== '') AND ($right >= 0)) {
2660 $this->cell_padding['R'] = $right;
2661 }
2662 if (($bottom !== '') AND ($bottom >= 0)) {
2663 $this->cell_padding['B'] = $bottom;
2664 }
2665 }
2666
2667 /**
2668 * Get the internal Cell padding array.
2669 * @return array of padding values
2670 * @public
2671 * @since 5.9.000 (2010-10-03)
2672 * @see setCellPaddings(), SetCellPadding()
2673 */
2674 public function getCellPaddings() {
2675 return $this->cell_padding;
2676 }
2677
2678 /**
2679 * Set the internal Cell margins.
2680 * @param $left (float) left margin
2681 * @param $top (float) top margin
2682 * @param $right (float) right margin
2683 * @param $bottom (float) bottom margin
2684 * @public
2685 * @since 5.9.000 (2010-10-03)
2686 * @see getCellMargins()
2687 */
2688 public function setCellMargins($left='', $top='', $right='', $bottom='') {
2689 if (($left !== '') AND ($left >= 0)) {
2690 $this->cell_margin['L'] = $left;
2691 }
2692 if (($top !== '') AND ($top >= 0)) {
2693 $this->cell_margin['T'] = $top;
2694 }
2695 if (($right !== '') AND ($right >= 0)) {
2696 $this->cell_margin['R'] = $right;
2697 }
2698 if (($bottom !== '') AND ($bottom >= 0)) {
2699 $this->cell_margin['B'] = $bottom;
2700 }
2701 }
2702
2703 /**
2704 * Get the internal Cell margin array.
2705 * @return array of margin values
2706 * @public
2707 * @since 5.9.000 (2010-10-03)
2708 * @see setCellMargins()
2709 */
2710 public function getCellMargins() {
2711 return $this->cell_margin;
2712 }
2713
2714 /**
2715 * Adjust the internal Cell padding array to take account of the line width.
2716 * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
2717 * @return array of adjustments
2718 * @public
2719 * @since 5.9.000 (2010-10-03)
2720 */
2721 protected function adjustCellPadding($brd=0) {
2722 if (empty($brd)) {
2723 return;
2724 }
2725 if (is_string($brd)) {
2726 // convert string to array
2727 $slen = strlen($brd);
2728 $newbrd = array();
2729 for ($i = 0; $i < $slen; ++$i) {
2730 $newbrd[$brd[$i]] = true;
2731 }
2732 $brd = $newbrd;
2733 } elseif (($brd === 1) OR ($brd === true) OR (is_numeric($brd) AND (intval($brd) > 0))) {
2734 $brd = array('LRTB' => true);
2735 }
2736 if (!is_array($brd)) {
2737 return;
2738 }
2739 // store current cell padding
2740 $cp = $this->cell_padding;
2741 // select border mode
2742 if (isset($brd['mode'])) {
2743 $mode = $brd['mode'];
2744 unset($brd['mode']);
2745 } else {
2746 $mode = 'normal';
2747 }
2748 // process borders
2749 foreach ($brd as $border => $style) {
2750 $line_width = $this->LineWidth;
2751 if (is_array($style) AND isset($style['width'])) {
2752 // get border width
2753 $line_width = $style['width'];
2754 }
2755 $adj = 0; // line width inside the cell
2756 switch ($mode) {
2757 case 'ext': {
2758 $adj = 0;
2759 break;
2760 }
2761 case 'int': {
2762 $adj = $line_width;
2763 break;
2764 }
2765 case 'normal':
2766 default: {
2767 $adj = ($line_width / 2);
2768 break;
2769 }
2770 }
2771 // correct internal cell padding if required to avoid overlap between text and lines
2772 if ((strpos($border,'T') !== false) AND ($this->cell_padding['T'] < $adj)) {
2773 $this->cell_padding['T'] = $adj;
2774 }
2775 if ((strpos($border,'R') !== false) AND ($this->cell_padding['R'] < $adj)) {
2776 $this->cell_padding['R'] = $adj;
2777 }
2778 if ((strpos($border,'B') !== false) AND ($this->cell_padding['B'] < $adj)) {
2779 $this->cell_padding['B'] = $adj;
2780 }
2781 if ((strpos($border,'L') !== false) AND ($this->cell_padding['L'] < $adj)) {
2782 $this->cell_padding['L'] = $adj;
2783 }
2784 }
2785 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']));
2786 }
2787
2788 /**
2789 * 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.
2790 * @param $auto (boolean) Boolean indicating if mode should be on or off.
2791 * @param $margin (float) Distance from the bottom of the page.
2792 * @public
2793 * @since 1.0
2794 * @see Cell(), MultiCell(), AcceptPageBreak()
2795 */
2796 public function SetAutoPageBreak($auto, $margin=0) {
2797 $this->AutoPageBreak = $auto ? true : false;
2798 $this->bMargin = $margin;
2799 $this->PageBreakTrigger = $this->h - $margin;
2800 }
2801
2802 /**
2803 * Return the auto-page-break mode (true or false).
2804 * @return boolean auto-page-break mode
2805 * @public
2806 * @since 5.9.088
2807 */
2808 public function getAutoPageBreak() {
2809 return $this->AutoPageBreak;
2810 }
2811
2812 /**
2813 * Defines the way the document is to be displayed by the viewer.
2814 * @param $zoom (mixed) The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. <ul><li>fullpage: displays the entire page on screen </li><li>fullwidth: uses maximum width of window</li><li>real: uses real size (equivalent to 100% zoom)</li><li>default: uses viewer default mode</li></ul>
2815 * @param $layout (string) The page layout. Possible values are:<ul><li>SinglePage Display one page at a time</li><li>OneColumn Display the pages in one column</li><li>TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left</li><li>TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right</li><li>TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left</li><li>TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right</li></ul>
2816 * @param $mode (string) A name object specifying how the document should be displayed when opened:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible</li><li>UseOC (PDF 1.5) Optional content group panel visible</li><li>UseAttachments (PDF 1.6) Attachments panel visible</li></ul>
2817 * @public
2818 * @since 1.2
2819 */
2820 public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
2821 if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
2822 $this->ZoomMode = $zoom;
2823 } else {
2824 $this->Error('Incorrect zoom display mode: '.$zoom);
2825 }
2826 $this->LayoutMode = TCPDF_STATIC::getPageLayoutMode($layout);
2827 $this->PageMode = TCPDF_STATIC::getPageMode($mode);
2828 }
2829
2830 /**
2831 * 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.
2832 * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
2833 * @param $compress (boolean) Boolean indicating if compression must be enabled.
2834 * @public
2835 * @since 1.4
2836 */
2837 public function SetCompression($compress=true) {
2838 if (function_exists('gzcompress')) {
2839 $this->compress = $compress ? true : false;
2840 } else {
2841 $this->compress = false;
2842 }
2843 }
2844
2845 /**
2846 * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
2847 * @param $mode (boolean) If true force sRGB output intent.
2848 * @public
2849 * @since 5.9.121 (2011-09-28)
2850 */
2851 public function setSRGBmode($mode=false) {
2852 $this->force_srgb = $mode ? true : false;
2853 }
2854
2855 /**
2856 * Turn on/off Unicode mode for document information dictionary (meta tags).
2857 * This has effect only when unicode mode is set to false.
2858 * @param $unicode (boolean) if true set the meta information in Unicode
2859 * @since 5.9.027 (2010-12-01)
2860 * @public
2861 */
2862 public function SetDocInfoUnicode($unicode=true) {
2863 $this->docinfounicode = $unicode ? true : false;
2864 }
2865
2866 /**
2867 * Defines the title of the document.
2868 * @param $title (string) The title.
2869 * @public
2870 * @since 1.2
2871 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
2872 */
2873 public function SetTitle($title) {
2874 $this->title = $title;
2875 }
2876
2877 /**
2878 * Defines the subject of the document.
2879 * @param $subject (string) The subject.
2880 * @public
2881 * @since 1.2
2882 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
2883 */
2884 public function SetSubject($subject) {
2885 $this->subject = $subject;
2886 }
2887
2888 /**
2889 * Defines the author of the document.
2890 * @param $author (string) The name of the author.
2891 * @public
2892 * @since 1.2
2893 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
2894 */
2895 public function SetAuthor($author) {
2896 $this->author = $author;
2897 }
2898
2899 /**
2900 * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
2901 * @param $keywords (string) The list of keywords.
2902 * @public
2903 * @since 1.2
2904 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
2905 */
2906 public function SetKeywords($keywords) {
2907 $this->keywords = $keywords;
2908 }
2909
2910 /**
2911 * Defines the creator of the document. This is typically the name of the application that generates the PDF.
2912 * @param $creator (string) The name of the creator.
2913 * @public
2914 * @since 1.2
2915 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
2916 */
2917 public function SetCreator($creator) {
2918 $this->creator = $creator;
2919 }
2920
2921 /**
2922 * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
2923 * @param $msg (string) The error message
2924 * @public
2925 * @since 1.0
2926 */
2927 public function Error($msg) {
2928 // unset all class variables
2929 $this->_destroy(true);
2930 throw new Exception('TCPDF ERROR: '.$msg);
2931 /*
2932
2933 I had problems with the constants for some reason, so here we force
2934
2935 $this->_destroy(true);
2936 if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
2937 die('<strong>TCPDF ERROR: </strong>'.$msg);
2938 } else {
2939 throw new Exception('TCPDF ERROR: '.$msg);
2940 }*/
2941 }
2942
2943 /**
2944 * This method begins the generation of the PDF document.
2945 * It is not necessary to call it explicitly because AddPage() does it automatically.
2946 * Note: no page is created by this method
2947 * @public
2948 * @since 1.0
2949 * @see AddPage(), Close()
2950 */
2951 public function Open() {
2952 $this->state = 1;
2953 }
2954
2955 /**
2956 * Terminates the PDF document.
2957 * It is not necessary to call this method explicitly because Output() does it automatically.
2958 * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
2959 * @public
2960 * @since 1.0
2961 * @see Open(), Output()
2962 */
2963 public function Close() {
2964 if ($this->state == 3) {
2965 return;
2966 }
2967 if ($this->page == 0) {
2968 $this->AddPage();
2969 }
2970 $this->endLayer();
2971 if ($this->tcpdflink) {
2972 // save current graphic settings
2973 $gvars = $this->getGraphicVars();
2974 $this->setEqualColumns();
2975 $this->lastpage(true);
2976 $this->SetAutoPageBreak(false);
2977 $this->x = 0;
2978 $this->y = $this->h - (1 / $this->k);
2979 $this->lMargin = 0;
2980 $this->_outSaveGraphicsState();
2981 $font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
2982 $this->SetFont($font, '', 1);
2983 $this->setTextRenderingMode(0, false, false);
2984 $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";
2985 $lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
2986 $this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
2987 $this->_outRestoreGraphicsState();
2988 // restore graphic settings
2989 $this->setGraphicVars($gvars);
2990 }
2991 // close page
2992 $this->endPage();
2993 // close document
2994 $this->_enddoc();
2995 // unset all class variables (except critical ones)
2996 $this->_destroy(false);
2997 }
2998
2999 /**
3000 * Move pointer at the specified document page and update page dimensions.
3001 * @param $pnum (int) page number (1 ... numpages)
3002 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
3003 * @public
3004 * @since 2.1.000 (2008-01-07)
3005 * @see getPage(), lastpage(), getNumPages()
3006 */
3007 public function setPage($pnum, $resetmargins=false) {
3008 if (($pnum == $this->page) AND ($this->state == 2)) {
3009 return;
3010 }
3011 if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3012 $this->state = 2;
3013 // save current graphic settings
3014 //$gvars = $this->getGraphicVars();
3015 $oldpage = $this->page;
3016 $this->page = $pnum;
3017 $this->wPt = $this->pagedim[$this->page]['w'];
3018 $this->hPt = $this->pagedim[$this->page]['h'];
3019 $this->w = $this->pagedim[$this->page]['wk'];
3020 $this->h = $this->pagedim[$this->page]['hk'];
3021 $this->tMargin = $this->pagedim[$this->page]['tm'];
3022 $this->bMargin = $this->pagedim[$this->page]['bm'];
3023 $this->original_lMargin = $this->pagedim[$this->page]['olm'];
3024 $this->original_rMargin = $this->pagedim[$this->page]['orm'];
3025 $this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3026 $this->CurOrientation = $this->pagedim[$this->page]['or'];
3027 $this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3028 // restore graphic settings
3029 //$this->setGraphicVars($gvars);
3030 if ($resetmargins) {
3031 $this->lMargin = $this->pagedim[$this->page]['olm'];
3032 $this->rMargin = $this->pagedim[$this->page]['orm'];
3033 $this->SetY($this->tMargin);
3034 } else {
3035 // account for booklet mode
3036 if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3037 $deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3038 $this->lMargin += $deltam;
3039 $this->rMargin -= $deltam;
3040 }
3041 }
3042 } else {
3043 $this->Error('Wrong page number on setPage() function: '.$pnum);
3044 }
3045 }
3046
3047 /**
3048 * Reset pointer to the last document page.
3049 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
3050 * @public
3051 * @since 2.0.000 (2008-01-04)
3052 * @see setPage(), getPage(), getNumPages()
3053 */
3054 public function lastPage($resetmargins=false) {
3055 $this->setPage($this->getNumPages(), $resetmargins);
3056 }
3057
3058 /**
3059 * Get current document page number.
3060 * @return int page number
3061 * @public
3062 * @since 2.1.000 (2008-01-07)
3063 * @see setPage(), lastpage(), getNumPages()
3064 */
3065 public function getPage() {
3066 return $this->page;
3067 }
3068
3069 /**
3070 * Get the total number of insered pages.
3071 * @return int number of pages
3072 * @public
3073 * @since 2.1.000 (2008-01-07)
3074 * @see setPage(), getPage(), lastpage()
3075 */
3076 public function getNumPages() {
3077 return $this->numpages;
3078 }
3079
3080 /**
3081 * Adds a new TOC (Table Of Content) page to the document.
3082 * @param $orientation (string) page orientation.
3083 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3084 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3085 * @public
3086 * @since 5.0.001 (2010-05-06)
3087 * @see AddPage(), startPage(), endPage(), endTOCPage()
3088 */
3089 public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3090 $this->AddPage($orientation, $format, $keepmargins, true);
3091 }
3092
3093 /**
3094 * Terminate the current TOC (Table Of Content) page
3095 * @public
3096 * @since 5.0.001 (2010-05-06)
3097 * @see AddPage(), startPage(), endPage(), addTOCPage()
3098 */
3099 public function endTOCPage() {
3100 $this->endPage(true);
3101 }
3102
3103 /**
3104 * 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).
3105 * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3106 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3107 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3108 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3109 * @param $tocpage (boolean) if true set the tocpage state to true (the added page will be used to display Table Of Content).
3110 * @public
3111 * @since 1.0
3112 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3113 */
3114 public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3115 if ($this->inxobj) {
3116 // we are inside an XObject template
3117 return;
3118 }
3119 if (!isset($this->original_lMargin) OR $keepmargins) {
3120 $this->original_lMargin = $this->lMargin;
3121 }
3122 if (!isset($this->original_rMargin) OR $keepmargins) {
3123 $this->original_rMargin = $this->rMargin;
3124 }
3125 // terminate previous page
3126 $this->endPage();
3127 // start new page
3128 $this->startPage($orientation, $format, $tocpage);
3129 }
3130
3131 /**
3132 * Terminate the current page
3133 * @param $tocpage (boolean) if true set the tocpage state to false (end the page used to display Table Of Content).
3134 * @public
3135 * @since 4.2.010 (2008-11-14)
3136 * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3137 */
3138 public function endPage($tocpage=false) {
3139 // check if page is already closed
3140 if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3141 return;
3142 }
3143 // print page footer
3144 $this->setFooter();
3145 // close page
3146 $this->_endpage();
3147 // mark page as closed
3148 $this->pageopen[$this->page] = false;
3149 if ($tocpage) {
3150 $this->tocpage = false;
3151 }
3152 }
3153
3154 /**
3155 * Starts a new page to the document. The page must be closed using the endPage() function.
3156 * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3157 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3158 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3159 * @param $tocpage (boolean) if true the page is designated to contain the Table-Of-Content.
3160 * @since 4.2.010 (2008-11-14)
3161 * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3162 * @public
3163 */
3164 public function startPage($orientation='', $format='', $tocpage=false) {
3165 if ($tocpage) {
3166 $this->tocpage = true;
3167 }
3168 // move page numbers of documents to be attached
3169 if ($this->tocpage) {
3170 // move reference to unexistent pages (used for page attachments)
3171 // adjust outlines
3172 $tmpoutlines = $this->outlines;
3173 foreach ($tmpoutlines as $key => $outline) {
3174 if (!$outline['f'] AND ($outline['p'] > $this->numpages)) {
3175 $this->outlines[$key]['p'] = ($outline['p'] + 1);
3176 }
3177 }
3178 // adjust dests
3179 $tmpdests = $this->dests;
3180 foreach ($tmpdests as $key => $dest) {
3181 if (!$dest['f'] AND ($dest['p'] > $this->numpages)) {
3182 $this->dests[$key]['p'] = ($dest['p'] + 1);
3183 }
3184 }
3185 // adjust links
3186 $tmplinks = $this->links;
3187 foreach ($tmplinks as $key => $link) {
3188 if (!$link['f'] AND ($link['p'] > $this->numpages)) {
3189 $this->links[$key]['p'] = ($link['p'] + 1);
3190 }
3191 }
3192 }
3193 if ($this->numpages > $this->page) {
3194 // this page has been already added
3195 $this->setPage($this->page + 1);
3196 $this->SetY($this->tMargin);
3197 return;
3198 }
3199 // start a new page
3200 if ($this->state == 0) {
3201 $this->Open();
3202 }
3203 ++$this->numpages;
3204 $this->swapMargins($this->booklet);
3205 // save current graphic settings
3206 $gvars = $this->getGraphicVars();
3207 // start new page
3208 $this->_beginpage($orientation, $format);
3209 // mark page as open
3210 $this->pageopen[$this->page] = true;
3211 // restore graphic settings
3212 $this->setGraphicVars($gvars);
3213 // mark this point
3214 $this->setPageMark();
3215 // print page header
3216 $this->setHeader();
3217 // restore graphic settings
3218 $this->setGraphicVars($gvars);
3219 // mark this point
3220 $this->setPageMark();
3221 // print table header (if any)
3222 $this->setTableHeader();
3223 // set mark for empty page check
3224 $this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
3225 }
3226
3227 /**
3228 * Set start-writing mark on current page stream used to put borders and fills.
3229 * Borders and fills are always created after content and inserted on the position marked by this method.
3230 * This function must be called after calling Image() function for a background image.
3231 * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
3232 * @public
3233 * @since 4.0.016 (2008-07-30)
3234 */
3235 public function setPageMark() {
3236 $this->intmrk[$this->page] = $this->pagelen[$this->page];
3237 $this->bordermrk[$this->page] = $this->intmrk[$this->page];
3238 $this->setContentMark();
3239 }
3240
3241 /**
3242 * Set start-writing mark on selected page.
3243 * Borders and fills are always created after content and inserted on the position marked by this method.
3244 * @param $page (int) page number (default is the current page)
3245 * @protected
3246 * @since 4.6.021 (2009-07-20)
3247 */
3248 protected function setContentMark($page=0) {
3249 if ($page <= 0) {
3250 $page = $this->page;
3251 }
3252 if (isset($this->footerlen[$page])) {
3253 $this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3254 } else {
3255 $this->cntmrk[$page] = $this->pagelen[$page];
3256 }
3257 }
3258
3259 /**
3260 * Set header data.
3261 * @param $ln (string) header image logo
3262 * @param $lw (string) header image logo width in mm
3263 * @param $ht (string) string to print as title on document header
3264 * @param $hs (string) string to print on document header
3265 * @param $tc (array) RGB array color for text.
3266 * @param $lc (array) RGB array color for line.
3267 * @public
3268 */
3269 public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
3270 $this->header_logo = $ln;
3271 $this->header_logo_width = $lw;
3272 $this->header_title = $ht;
3273 $this->header_string = $hs;
3274 $this->header_text_color = $tc;
3275 $this->header_line_color = $lc;
3276 }
3277
3278 /**
3279 * Set footer data.
3280 * @param $tc (array) RGB array color for text.
3281 * @param $lc (array) RGB array color for line.
3282 * @public
3283 */
3284 public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
3285 $this->footer_text_color = $tc;
3286 $this->footer_line_color = $lc;
3287 }
3288
3289 /**
3290 * Returns header data:
3291 * <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>
3292 * @return array()
3293 * @public
3294 * @since 4.0.012 (2008-07-24)
3295 */
3296 public function getHeaderData() {
3297 $ret = array();
3298 $ret['logo'] = $this->header_logo;
3299 $ret['logo_width'] = $this->header_logo_width;
3300 $ret['title'] = $this->header_title;
3301 $ret['string'] = $this->header_string;
3302 $ret['text_color'] = $this->header_text_color;
3303 $ret['line_color'] = $this->header_line_color;
3304 return $ret;
3305 }
3306
3307 /**
3308 * Set header margin.
3309 * (minimum distance between header and top page margin)
3310 * @param $hm (int) distance in user units
3311 * @public
3312 */
3313 public function setHeaderMargin($hm=10) {
3314 $this->header_margin = $hm;
3315 }
3316
3317 /**
3318 * Returns header margin in user units.
3319 * @return float
3320 * @since 4.0.012 (2008-07-24)
3321 * @public
3322 */
3323 public function getHeaderMargin() {
3324 return $this->header_margin;
3325 }
3326
3327 /**
3328 * Set footer margin.
3329 * (minimum distance between footer and bottom page margin)
3330 * @param $fm (int) distance in user units
3331 * @public
3332 */
3333 public function setFooterMargin($fm=10) {
3334 $this->footer_margin = $fm;
3335 }
3336
3337 /**
3338 * Returns footer margin in user units.
3339 * @return float
3340 * @since 4.0.012 (2008-07-24)
3341 * @public
3342 */
3343 public function getFooterMargin() {
3344 return $this->footer_margin;
3345 }
3346 /**
3347 * Set a flag to print page header.
3348 * @param $val (boolean) set to true to print the page header (default), false otherwise.
3349 * @public
3350 */
3351 public function setPrintHeader($val=true) {
3352 $this->print_header = $val ? true : false;
3353 }
3354
3355 /**
3356 * Set a flag to print page footer.
3357 * @param $val (boolean) set to true to print the page footer (default), false otherwise.
3358 * @public
3359 */
3360 public function setPrintFooter($val=true) {
3361 $this->print_footer = $val ? true : false;
3362 }
3363
3364 /**
3365 * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
3366 * @return float
3367 * @public
3368 */
3369 public function getImageRBX() {
3370 return $this->img_rb_x;
3371 }
3372
3373 /**
3374 * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
3375 * @return float
3376 * @public
3377 */
3378 public function getImageRBY() {
3379 return $this->img_rb_y;
3380 }
3381
3382 /**
3383 * Reset the xobject template used by Header() method.
3384 * @public
3385 */
3386 public function resetHeaderTemplate() {
3387 $this->header_xobjid = false;
3388 }
3389
3390 /**
3391 * Set a flag to automatically reset the xobject template used by Header() method at each page.
3392 * @param $val (boolean) set to true to reset Header xobject template at each page, false otherwise.
3393 * @public
3394 */
3395 public function setHeaderTemplateAutoreset($val=true) {
3396 $this->header_xobj_autoreset = $val ? true : false;
3397 }
3398
3399 /**
3400 * This method is used to render the page header.
3401 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3402 * @public
3403 */
3404 public function Header() {
3405 if ($this->header_xobjid === false) {
3406 // start a new XObject Template
3407 $this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
3408 $headerfont = $this->getHeaderFont();
3409 $headerdata = $this->getHeaderData();
3410 $this->y = $this->header_margin;
3411 if ($this->rtl) {
3412 $this->x = $this->w - $this->original_rMargin;
3413 } else {
3414 $this->x = $this->original_lMargin;
3415 }
3416 if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
3417 $imgtype = TCPDF_IMAGES::getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
3418 if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
3419 $this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3420 } elseif ($imgtype == 'svg') {
3421 $this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3422 } else {
3423 $this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3424 }
3425 $imgy = $this->getImageRBY();
3426 } else {
3427 $imgy = $this->y;
3428 }
3429 $cell_height = $this->getCellHeight($headerfont[2] / $this->k);
3430 // set starting margin for text data cell
3431 if ($this->getRTL()) {
3432 $header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
3433 } else {
3434 $header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
3435 }
3436 $cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
3437 $this->SetTextColorArray($this->header_text_color);
3438 // header title
3439 $this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
3440 $this->SetX($header_x);
3441 $this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
3442 // header string
3443 $this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
3444 $this->SetX($header_x);
3445 $this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
3446 // print an ending header line
3447 $this->SetLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
3448 $this->SetY((2.835 / $this->k) + max($imgy, $this->y));
3449 if ($this->rtl) {
3450 $this->SetX($this->original_rMargin);
3451 } else {
3452 $this->SetX($this->original_lMargin);
3453 }
3454 $this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
3455 $this->endTemplate();
3456 }
3457 // print header template
3458 $x = 0;
3459 $dx = 0;
3460 if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
3461 // adjust margins for booklet mode
3462 $dx = ($this->original_lMargin - $this->original_rMargin);
3463 }
3464 if ($this->rtl) {
3465 $x = $this->w + $dx;
3466 } else {
3467 $x = 0 + $dx;
3468 }
3469 $this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
3470 if ($this->header_xobj_autoreset) {
3471 // reset header xobject template at each page
3472 $this->header_xobjid = false;
3473 }
3474 }
3475
3476 /**
3477 * This method is used to render the page footer.
3478 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3479 * @public
3480 */
3481 public function Footer() {
3482 $cur_y = $this->y;
3483 $this->SetTextColorArray($this->footer_text_color);
3484 //set style for cell border
3485 $line_width = (0.85 / $this->k);
3486 $this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
3487 //print document barcode
3488 $barcode = $this->getBarcode();
3489 if (!empty($barcode)) {
3490 $this->Ln($line_width);
3491 $barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
3492 $style = array(
3493 'position' => $this->rtl?'R':'L',
3494 'align' => $this->rtl?'R':'L',
3495 'stretch' => false,
3496 'fitwidth' => true,
3497 'cellfitalign' => '',
3498 'border' => false,
3499 'padding' => 0,
3500 'fgcolor' => array(0,0,0),
3501 'bgcolor' => false,
3502 'text' => false
3503 );
3504 $this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
3505 }
3506 $w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
3507 if (empty($this->pagegroups)) {
3508 $pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
3509 } else {
3510 $pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
3511 }
3512 $this->SetY($cur_y);
3513 //Print page number
3514 if ($this->getRTL()) {
3515 $this->SetX($this->original_rMargin);
3516 $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
3517 } else {
3518 $this->SetX($this->original_lMargin);
3519 $this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
3520 }
3521 }
3522
3523 /**
3524 * This method is used to render the page header.
3525 * @protected
3526 * @since 4.0.012 (2008-07-24)
3527 */
3528 protected function setHeader() {
3529 if (!$this->print_header OR ($this->state != 2)) {
3530 return;
3531 }
3532 $this->InHeader = true;
3533 $this->setGraphicVars($this->default_graphic_vars);
3534 $temp_thead = $this->thead;
3535 $temp_theadMargins = $this->theadMargins;
3536 $lasth = $this->lasth;
3537 $newline = $this->newline;
3538 $this->_outSaveGraphicsState();
3539 $this->rMargin = $this->original_rMargin;
3540 $this->lMargin = $this->original_lMargin;
3541 $this->SetCellPadding(0);
3542 //set current position
3543 if ($this->rtl) {
3544 $this->SetXY($this->original_rMargin, $this->header_margin);
3545 } else {
3546 $this->SetXY($this->original_lMargin, $this->header_margin);
3547 }
3548 $this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
3549 $this->Header();
3550 //restore position
3551 if ($this->rtl) {
3552 $this->SetXY($this->original_rMargin, $this->tMargin);
3553 } else {
3554 $this->SetXY($this->original_lMargin, $this->tMargin);
3555 }
3556 $this->_outRestoreGraphicsState();
3557 $this->lasth = $lasth;
3558 $this->thead = $temp_thead;
3559 $this->theadMargins = $temp_theadMargins;
3560 $this->newline = $newline;
3561 $this->InHeader = false;
3562 }
3563
3564 /**
3565 * This method is used to render the page footer.
3566 * @protected
3567 * @since 4.0.012 (2008-07-24)
3568 */
3569 protected function setFooter() {
3570 if ($this->state != 2) {
3571 return;
3572 }
3573 $this->InFooter = true;
3574 // save current graphic settings
3575 $gvars = $this->getGraphicVars();
3576 // mark this point
3577 $this->footerpos[$this->page] = $this->pagelen[$this->page];
3578 $this->_out("\n");
3579 if ($this->print_footer) {
3580 $this->setGraphicVars($this->default_graphic_vars);
3581 $this->current_column = 0;
3582 $this->num_columns = 1;
3583 $temp_thead = $this->thead;
3584 $temp_theadMargins = $this->theadMargins;
3585 $lasth = $this->lasth;
3586 $this->_outSaveGraphicsState();
3587 $this->rMargin = $this->original_rMargin;
3588 $this->lMargin = $this->original_lMargin;
3589 $this->SetCellPadding(0);
3590 //set current position
3591 $footer_y = $this->h - $this->footer_margin;
3592 if ($this->rtl) {
3593 $this->SetXY($this->original_rMargin, $footer_y);
3594 } else {
3595 $this->SetXY($this->original_lMargin, $footer_y);
3596 }
3597 $this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
3598 $this->Footer();
3599 //restore position
3600 if ($this->rtl) {
3601 $this->SetXY($this->original_rMargin, $this->tMargin);
3602 } else {
3603 $this->SetXY($this->original_lMargin, $this->tMargin);
3604 }
3605 $this->_outRestoreGraphicsState();
3606 $this->lasth = $lasth;
3607 $this->thead = $temp_thead;
3608 $this->theadMargins = $temp_theadMargins;
3609 }
3610 // restore graphic settings
3611 $this->setGraphicVars($gvars);
3612 $this->current_column = $gvars['current_column'];
3613 $this->num_columns = $gvars['num_columns'];
3614 // calculate footer length
3615 $this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
3616 $this->InFooter = false;
3617 }
3618
3619 /**
3620 * Check if we are on the page body (excluding page header and footer).
3621 * @return true if we are not in page header nor in page footer, false otherwise.
3622 * @protected
3623 * @since 5.9.091 (2011-06-15)
3624 */
3625 protected function inPageBody() {
3626 return (($this->InHeader === false) AND ($this->InFooter === false));
3627 }
3628
3629 /**
3630 * This method is used to render the table header on new page (if any).
3631 * @protected
3632 * @since 4.5.030 (2009-03-25)
3633 */
3634 protected function setTableHeader() {
3635 if ($this->num_columns > 1) {
3636 // multi column mode
3637 return;
3638 }
3639 if (isset($this->theadMargins['top'])) {
3640 // restore the original top-margin
3641 $this->tMargin = $this->theadMargins['top'];
3642 $this->pagedim[$this->page]['tm'] = $this->tMargin;
3643 $this->y = $this->tMargin;
3644 }
3645 if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
3646 // set margins
3647 $prev_lMargin = $this->lMargin;
3648 $prev_rMargin = $this->rMargin;
3649 $prev_cell_padding = $this->cell_padding;
3650 $this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
3651 $this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
3652 $this->cell_padding = $this->theadMargins['cell_padding'];
3653 if ($this->rtl) {
3654 $this->x = $this->w - $this->rMargin;
3655 } else {
3656 $this->x = $this->lMargin;
3657 }
3658 // account for special "cell" mode
3659 if ($this->theadMargins['cell']) {
3660 if ($this->rtl) {
3661 $this->x -= $this->cell_padding['R'];
3662 } else {
3663 $this->x += $this->cell_padding['L'];
3664 }
3665 }
3666 $gvars = $this->getGraphicVars();
3667 if (!empty($this->theadMargins['gvars'])) {
3668 // set the correct graphic style
3669 $this->setGraphicVars($this->theadMargins['gvars']);
3670 $this->rMargin = $gvars['rMargin'];
3671 $this->lMargin = $gvars['lMargin'];
3672 }
3673 // print table header
3674 $this->writeHTML($this->thead, false, false, false, false, '');
3675 $this->setGraphicVars($gvars);
3676 // set new top margin to skip the table headers
3677 if (!isset($this->theadMargins['top'])) {
3678 $this->theadMargins['top'] = $this->tMargin;
3679 }
3680 // store end of header position
3681 if (!isset($this->columns[0]['th'])) {
3682 $this->columns[0]['th'] = array();
3683 }
3684 $this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
3685 $this->tMargin = $this->y;
3686 $this->pagedim[$this->page]['tm'] = $this->tMargin;
3687 $this->lasth = 0;
3688 $this->lMargin = $prev_lMargin;
3689 $this->rMargin = $prev_rMargin;
3690 $this->cell_padding = $prev_cell_padding;
3691 }
3692 }
3693
3694 /**
3695 * Returns the current page number.
3696 * @return int page number
3697 * @public
3698 * @since 1.0
3699 * @see getAliasNbPages()
3700 */
3701 public function PageNo() {
3702 return $this->page;
3703 }
3704
3705 /**
3706 * Returns the array of spot colors.
3707 * @return (array) Spot colors array.
3708 * @public
3709 * @since 6.0.038 (2013-09-30)
3710 */
3711 public function getAllSpotColors() {
3712 return $this->spot_colors;
3713 }
3714
3715 /**
3716 * Defines a new spot color.
3717 * It can be expressed in RGB components or gray scale.
3718 * The method can be called before the first page is created and the value is retained from page to page.
3719 * @param $name (string) Full name of the spot color.
3720 * @param $c (float) Cyan color for CMYK. Value between 0 and 100.
3721 * @param $m (float) Magenta color for CMYK. Value between 0 and 100.
3722 * @param $y (float) Yellow color for CMYK. Value between 0 and 100.
3723 * @param $k (float) Key (Black) color for CMYK. Value between 0 and 100.
3724 * @public
3725 * @since 4.0.024 (2008-09-12)
3726 * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3727 */
3728 public function AddSpotColor($name, $c, $m, $y, $k) {
3729 if (!isset($this->spot_colors[$name])) {
3730 $i = (1 + count($this->spot_colors));
3731 $this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
3732 }
3733 }
3734
3735 /**
3736 * Set the spot color for the specified type ('draw', 'fill', 'text').
3737 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3738 * @param $name (string) Name of the spot color.
3739 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3740 * @return (string) PDF color command.
3741 * @public
3742 * @since 5.9.125 (2011-10-03)
3743 */
3744 public function setSpotColor($type, $name, $tint=100) {
3745 $spotcolor = TCPDF_COLORS::getSpotColor($name, $this->spot_colors);
3746 if ($spotcolor === false) {
3747 $this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
3748 }
3749 $tint = (max(0, min(100, $tint)) / 100);
3750 $pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
3751 switch ($type) {
3752 case 'draw': {
3753 $pdfcolor .= sprintf('CS %F SCN', $tint);
3754 $this->DrawColor = $pdfcolor;
3755 $this->strokecolor = $spotcolor;
3756 break;
3757 }
3758 case 'fill': {
3759 $pdfcolor .= sprintf('cs %F scn', $tint);
3760 $this->FillColor = $pdfcolor;
3761 $this->bgcolor = $spotcolor;
3762 break;
3763 }
3764 case 'text': {
3765 $pdfcolor .= sprintf('cs %F scn', $tint);
3766 $this->TextColor = $pdfcolor;
3767 $this->fgcolor = $spotcolor;
3768 break;
3769 }
3770 }
3771 $this->ColorFlag = ($this->FillColor != $this->TextColor);
3772 if ($this->state == 2) {
3773 $this->_out($pdfcolor);
3774 }
3775 if ($this->inxobj) {
3776 // we are inside an XObject template
3777 $this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
3778 }
3779 return $pdfcolor;
3780 }
3781
3782 /**
3783 * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
3784 * @param $name (string) Name of the spot color.
3785 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3786 * @public
3787 * @since 4.0.024 (2008-09-12)
3788 * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3789 */
3790 public function SetDrawSpotColor($name, $tint=100) {
3791 $this->setSpotColor('draw', $name, $tint);
3792 }
3793
3794 /**
3795 * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
3796 * @param $name (string) Name of the spot color.
3797 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3798 * @public
3799 * @since 4.0.024 (2008-09-12)
3800 * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
3801 */
3802 public function SetFillSpotColor($name, $tint=100) {
3803 $this->setSpotColor('fill', $name, $tint);
3804 }
3805
3806 /**
3807 * Defines the spot color used for text.
3808 * @param $name (string) Name of the spot color.
3809 * @param $tint (int) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3810 * @public
3811 * @since 4.0.024 (2008-09-12)
3812 * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
3813 */
3814 public function SetTextSpotColor($name, $tint=100) {
3815 $this->setSpotColor('text', $name, $tint);
3816 }
3817
3818 /**
3819 * Set the color array for the specified type ('draw', 'fill', 'text').
3820 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3821 * The method can be called before the first page is created and the value is retained from page to page.
3822 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3823 * @param $color (array) Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
3824 * @param $ret (boolean) If true do not send the PDF command.
3825 * @return (string) The PDF command or empty string.
3826 * @public
3827 * @since 3.1.000 (2008-06-11)
3828 */
3829 public function setColorArray($type, $color, $ret=false) {
3830 if (is_array($color)) {
3831 $color = array_values($color);
3832 // component: grey, RGB red or CMYK cyan
3833 $c = isset($color[0]) ? $color[0] : -1;
3834 // component: RGB green or CMYK magenta
3835 $m = isset($color[1]) ? $color[1] : -1;
3836 // component: RGB blue or CMYK yellow
3837 $y = isset($color[2]) ? $color[2] : -1;
3838 // component: CMYK black
3839 $k = isset($color[3]) ? $color[3] : -1;
3840 // color name
3841 $name = isset($color[4]) ? $color[4] : '';
3842 if ($c >= 0) {
3843 return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
3844 }
3845 }
3846 return '';
3847 }
3848
3849 /**
3850 * Defines the color used for all drawing operations (lines, rectangles and cell borders).
3851 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3852 * The method can be called before the first page is created and the value is retained from page to page.
3853 * @param $color (array) Array of colors (1, 3 or 4 values).
3854 * @param $ret (boolean) If true do not send the PDF command.
3855 * @return string the PDF command
3856 * @public
3857 * @since 3.1.000 (2008-06-11)
3858 * @see SetDrawColor()
3859 */
3860 public function SetDrawColorArray($color, $ret=false) {
3861 return $this->setColorArray('draw', $color, $ret);
3862 }
3863
3864 /**
3865 * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
3866 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3867 * The method can be called before the first page is created and the value is retained from page to page.
3868 * @param $color (array) Array of colors (1, 3 or 4 values).
3869 * @param $ret (boolean) If true do not send the PDF command.
3870 * @public
3871 * @since 3.1.000 (2008-6-11)
3872 * @see SetFillColor()
3873 */
3874 public function SetFillColorArray($color, $ret=false) {
3875 return $this->setColorArray('fill', $color, $ret);
3876 }
3877
3878 /**
3879 * Defines the color used for text. It can be expressed in RGB components or gray scale.
3880 * The method can be called before the first page is created and the value is retained from page to page.
3881 * @param $color (array) Array of colors (1, 3 or 4 values).
3882 * @param $ret (boolean) If true do not send the PDF command.
3883 * @public
3884 * @since 3.1.000 (2008-6-11)
3885 * @see SetFillColor()
3886 */
3887 public function SetTextColorArray($color, $ret=false) {
3888 return $this->setColorArray('text', $color, $ret);
3889 }
3890
3891 /**
3892 * Defines the color used by the specified type ('draw', 'fill', 'text').
3893 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3894 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3895 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3896 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3897 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3898 * @param $ret (boolean) If true do not send the command.
3899 * @param $name (string) spot color name (if any)
3900 * @return (string) The PDF command or empty string.
3901 * @public
3902 * @since 5.9.125 (2011-10-03)
3903 */
3904 public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3905 // set default values
3906 if (!is_numeric($col1)) {
3907 $col1 = 0;
3908 }
3909 if (!is_numeric($col2)) {
3910 $col2 = -1;
3911 }
3912 if (!is_numeric($col3)) {
3913 $col3 = -1;
3914 }
3915 if (!is_numeric($col4)) {
3916 $col4 = -1;
3917 }
3918 // set color by case
3919 $suffix = '';
3920 if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
3921 // Grey scale
3922 $col1 = max(0, min(255, $col1));
3923 $intcolor = array('G' => $col1);
3924 $pdfcolor = sprintf('%F ', ($col1 / 255));
3925 $suffix = 'g';
3926 } elseif ($col4 == -1) {
3927 // RGB
3928 $col1 = max(0, min(255, $col1));
3929 $col2 = max(0, min(255, $col2));
3930 $col3 = max(0, min(255, $col3));
3931 $intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
3932 $pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
3933 $suffix = 'rg';
3934 } else {
3935 $col1 = max(0, min(100, $col1));
3936 $col2 = max(0, min(100, $col2));
3937 $col3 = max(0, min(100, $col3));
3938 $col4 = max(0, min(100, $col4));
3939 if (empty($name)) {
3940 // CMYK
3941 $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
3942 $pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
3943 $suffix = 'k';
3944 } else {
3945 // SPOT COLOR
3946 $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
3947 $this->AddSpotColor($name, $col1, $col2, $col3, $col4);
3948 $pdfcolor = $this->setSpotColor($type, $name, 100);
3949 }
3950 }
3951 switch ($type) {
3952 case 'draw': {
3953 $pdfcolor .= strtoupper($suffix);
3954 $this->DrawColor = $pdfcolor;
3955 $this->strokecolor = $intcolor;
3956 break;
3957 }
3958 case 'fill': {
3959 $pdfcolor .= $suffix;
3960 $this->FillColor = $pdfcolor;
3961 $this->bgcolor = $intcolor;
3962 break;
3963 }
3964 case 'text': {
3965 $pdfcolor .= $suffix;
3966 $this->TextColor = $pdfcolor;
3967 $this->fgcolor = $intcolor;
3968 break;
3969 }
3970 }
3971 $this->ColorFlag = ($this->FillColor != $this->TextColor);
3972 if (($type != 'text') AND ($this->state == 2)) {
3973 if (!$ret) {
3974 $this->_out($pdfcolor);
3975 }
3976 return $pdfcolor;
3977 }
3978 return '';
3979 }
3980
3981 /**
3982 * 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.
3983 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3984 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3985 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3986 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3987 * @param $ret (boolean) If true do not send the command.
3988 * @param $name (string) spot color name (if any)
3989 * @return string the PDF command
3990 * @public
3991 * @since 1.3
3992 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
3993 */
3994 public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3995 return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
3996 }
3997
3998 /**
3999 * 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.
4000 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4001 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4002 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4003 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4004 * @param $ret (boolean) If true do not send the command.
4005 * @param $name (string) Spot color name (if any).
4006 * @return (string) The PDF command.
4007 * @public
4008 * @since 1.3
4009 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
4010 */
4011 public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4012 return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
4013 }
4014
4015 /**
4016 * 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.
4017 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4018 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4019 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4020 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4021 * @param $ret (boolean) If true do not send the command.
4022 * @param $name (string) Spot color name (if any).
4023 * @return (string) Empty string.
4024 * @public
4025 * @since 1.3
4026 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4027 */
4028 public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4029 return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4030 }
4031
4032 /**
4033 * Returns the length of a string in user unit. A font must be selected.<br>
4034 * @param $s (string) The string whose length is to be computed
4035 * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4036 * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line-through</li><li>O: overline</li></ul> or any combination. The default value is regular.
4037 * @param $fontsize (float) Font size in points. The default value is the current size.
4038 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4039 * @return mixed int total string length or array of characted widths
4040 * @author Nicola Asuni
4041 * @public
4042 * @since 1.2
4043 */
4044 public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4045 return $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont), $s, $this->tmprtl, $this->isunicode, $this->CurrentFont), $fontname, $fontstyle, $fontsize, $getarray);
4046 }
4047
4048 /**
4049 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4050 * @param $sa (string) The array of chars whose total length is to be computed
4051 * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4052 * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular.
4053 * @param $fontsize (float) Font size in points. The default value is the current size.
4054 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4055 * @return mixed int total string length or array of characted widths
4056 * @author Nicola Asuni
4057 * @public
4058 * @since 2.4.000 (2008-03-06)
4059 */
4060 public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4061 // store current values
4062 if (!TCPDF_STATIC::empty_string($fontname)) {
4063 $prev_FontFamily = $this->FontFamily;
4064 $prev_FontStyle = $this->FontStyle;
4065 $prev_FontSizePt = $this->FontSizePt;
4066 $this->SetFont($fontname, $fontstyle, $fontsize, '', 'default', false);
4067 }
4068 // convert UTF-8 array to Latin1 if required
4069 if ($this->isunicode AND (!$this->isUnicodeFont())) {
4070 $sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
4071 }
4072 $w = 0; // total width
4073 $wa = array(); // array of characters widths
4074 foreach ($sa as $ck => $char) {
4075 // character width
4076 $cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4077 $wa[] = $cw;
4078 $w += $cw;
4079 }
4080 // restore previous values
4081 if (!TCPDF_STATIC::empty_string($fontname)) {
4082 $this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4083 }
4084 if ($getarray) {
4085 return $wa;
4086 }
4087 return $w;
4088 }
4089
4090 /**
4091 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
4092 * @param $char (int) The char code whose length is to be returned
4093 * @param $notlast (boolean) If false ignore the font-spacing.
4094 * @return float char width
4095 * @author Nicola Asuni
4096 * @public
4097 * @since 2.4.000 (2008-03-06)
4098 */
4099 public function GetCharWidth($char, $notlast=true) {
4100 // get raw width
4101 $chw = $this->getRawCharWidth($char);
4102 if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
4103 // increase/decrease font spacing
4104 $chw += $this->font_spacing;
4105 }
4106 if ($this->font_stretching != 100) {
4107 // fixed stretching mode
4108 $chw *= ($this->font_stretching / 100);
4109 }
4110 return $chw;
4111 }
4112
4113 /**
4114 * Returns the length of the char in user unit for the current font.
4115 * @param $char (int) The char code whose length is to be returned
4116 * @return float char width
4117 * @author Nicola Asuni
4118 * @public
4119 * @since 5.9.000 (2010-09-28)
4120 */
4121 public function getRawCharWidth($char) {
4122 if ($char == 173) {
4123 // SHY character will not be printed
4124 return (0);
4125 }
4126 if (isset($this->CurrentFont['cw'][$char])) {
4127 $w = $this->CurrentFont['cw'][$char];
4128 } elseif (isset($this->CurrentFont['dw'])) {
4129 // default width
4130 $w = $this->CurrentFont['dw'];
4131 } elseif (isset($this->CurrentFont['cw'][32])) {
4132 // default width
4133 $w = $this->CurrentFont['cw'][32];
4134 } else {
4135 $w = 600;
4136 }
4137 return $this->getAbsFontMeasure($w);
4138 }
4139
4140 /**
4141 * Returns the numbero of characters in a string.
4142 * @param $s (string) The input string.
4143 * @return int number of characters
4144 * @public
4145 * @since 2.0.0001 (2008-01-07)
4146 */
4147 public function GetNumChars($s) {
4148 if ($this->isUnicodeFont()) {
4149 return count(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont));
4150 }
4151 return strlen($s);
4152 }
4153
4154 /**
4155 * Fill the list of available fonts ($this->fontlist).
4156 * @protected
4157 * @since 4.0.013 (2008-07-28)
4158 */
4159 protected function getFontsList() {
4160 if (($fontsdir = opendir(TCPDF_FONTS::_getfontpath())) !== false) {
4161 while (($file = readdir($fontsdir)) !== false) {
4162 if (substr($file, -4) == '.php') {
4163 array_push($this->fontlist, strtolower(basename($file, '.php')));
4164 }
4165 }
4166 closedir($fontsdir);
4167 }
4168 }
4169
4170 /**
4171 * Returns the unicode caracter specified by the value
4172 * @param $c (int) UTF-8 value
4173 * @return Returns the specified character.
4174 * @since 2.3.000 (2008-03-05)
4175 * @public
4176 * @deprecated
4177 */
4178 public function unichr($c) {
4179 return TCPDF_FONTS::unichr($c, $this->isunicode);
4180 }
4181
4182 /**
4183 * Convert and add the selected TrueType or Type1 font to the fonts folder (that must be writeable).
4184 * @param $fontfile (string) Font file (full path).
4185 * @param $fonttype (string) Font type. Leave empty for autodetect mode. Valid values are: TrueTypeUnicode, TrueType, Type1, CID0JP = CID-0 Japanese, CID0KR = CID-0 Korean, CID0CS = CID-0 Chinese Simplified, CID0CT = CID-0 Chinese Traditional.
4186 * @param $enc (string) Name of the encoding table to use. Leave empty for default mode. Omit this parameter for TrueType Unicode and symbolic fonts like Symbol or ZapfDingBats.
4187 * @param $flags (int) Unsigned 32-bit integer containing flags specifying various characteristics of the font (PDF32000:2008 - 9.8.2 Font Descriptor Flags): +1 for fixed font; +4 for symbol or +32 for non-symbol; +64 for italic. Fixed and Italic mode are generally autodetected so you have to set it to 32 = non-symbolic font (default) or 4 = symbolic font.
4188 * @param $outpath (string) Output path for generated font files (must be writeable by the web server). Leave empty for default font folder.
4189 * @param $platid (int) Platform ID for CMAP table to extract (when building a Unicode font for Windows this value should be 3, for Macintosh should be 1).
4190 * @param $encid (int) Encoding ID for CMAP table to extract (when building a Unicode font for Windows this value should be 1, for Macintosh should be 0). When Platform ID is 3, legal values for Encoding ID are: 0=Symbol, 1=Unicode, 2=ShiftJIS, 3=PRC, 4=Big5, 5=Wansung, 6=Johab, 7=Reserved, 8=Reserved, 9=Reserved, 10=UCS-4.
4191 * @param $addcbbox (boolean) If true includes the character bounding box information on the php font file.
4192 * @return (string) TCPDF font name.
4193 * @author Nicola Asuni
4194 * @since 5.9.123 (2010-09-30)
4195 * @public
4196 * @deprecated
4197 */
4198 public function addTTFfont($fontfile, $fonttype='', $enc='', $flags=32, $outpath='', $platid=3, $encid=1, $addcbbox=false) {
4199 return TCPDF_FONTS::addTTFfont($fontfile, $fonttype, $enc, $flags, $outpath, $platid, $encid, $addcbbox);
4200 }
4201
4202 /**
4203 * Imports a TrueType, Type1, core, or CID0 font and makes it available.
4204 * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
4205 * 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.
4206 * @param $family (string) Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
4207 * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular (default)</li><li>B: bold</li><li>I: italic</li><li>BI or IB: bold italic</li></ul>
4208 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4209 * @return array containing the font data, or false in case of error.
4210 * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
4211 * @public
4212 * @since 1.5
4213 * @see SetFont(), setFontSubsetting()
4214 */
4215 public function AddFont($family, $style='', $fontfile='', $subset='default') {
4216 if ($subset === 'default') {
4217 $subset = $this->font_subsetting;
4218 }
4219 if ($this->pdfa_mode) {
4220 $subset = false;
4221 }
4222 if (TCPDF_STATIC::empty_string($family)) {
4223 if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
4224 $family = $this->FontFamily;
4225 } else {
4226 $this->Error('Empty font family');
4227 }
4228 }
4229 // move embedded styles on $style
4230 if (substr($family, -1) == 'I') {
4231 $style .= 'I';
4232 $family = substr($family, 0, -1);
4233 }
4234 if (substr($family, -1) == 'B') {
4235 $style .= 'B';
4236 $family = substr($family, 0, -1);
4237 }
4238 // normalize family name
4239 $family = strtolower($family);
4240 if ((!$this->isunicode) AND ($family == 'arial')) {
4241 $family = 'helvetica';
4242 }
4243 if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4244 $style = '';
4245 }
4246 if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
4247 // all fonts must be embedded
4248 $family = 'pdfa'.$family;
4249 }
4250 $tempstyle = strtoupper($style);
4251 $style = '';
4252 // underline
4253 if (strpos($tempstyle, 'U') !== false) {
4254 $this->underline = true;
4255 } else {
4256 $this->underline = false;
4257 }
4258 // line-through (deleted)
4259 if (strpos($tempstyle, 'D') !== false) {
4260 $this->linethrough = true;
4261 } else {
4262 $this->linethrough = false;
4263 }
4264 // overline
4265 if (strpos($tempstyle, 'O') !== false) {
4266 $this->overline = true;
4267 } else {
4268 $this->overline = false;
4269 }
4270 // bold
4271 if (strpos($tempstyle, 'B') !== false) {
4272 $style .= 'B';
4273 }
4274 // oblique
4275 if (strpos($tempstyle, 'I') !== false) {
4276 $style .= 'I';
4277 }
4278 $bistyle = $style;
4279 $fontkey = $family.$style;
4280 $font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
4281 $fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4282 // check if the font has been already added
4283 $fb = $this->getFontBuffer($fontkey);
4284 if ($fb !== false) {
4285 if ($this->inxobj) {
4286 // we are inside an XObject template
4287 $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
4288 }
4289 return $fontdata;
4290 }
4291 // get specified font directory (if any)
4292 $fontdir = false;
4293 if (!TCPDF_STATIC::empty_string($fontfile)) {
4294 $fontdir = dirname($fontfile);
4295 if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
4296 $fontdir = '';
4297 } else {
4298 $fontdir .= '/';
4299 }
4300 }
4301 // true when the font style variation is missing
4302 $missing_style = false;
4303 // search and include font file
4304 if (TCPDF_STATIC::empty_string($fontfile) OR (!@file_exists($fontfile))) {
4305 // build a standard filenames for specified font
4306 $tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
4307 $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4308 if (TCPDF_STATIC::empty_string($fontfile)) {
4309 $missing_style = true;
4310 // try to remove the style part
4311 $tmp_fontfile = str_replace(' ', '', $family).'.php';
4312 $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4313 }
4314 }
4315 // include font file
4316 if (!TCPDF_STATIC::empty_string($fontfile) AND (@file_exists($fontfile))) {
4317 include($fontfile);
4318 } else {
4319 $this->Error('Could not include font definition file: '.$family.'');
4320 }
4321 // check font parameters
4322 if ((!isset($type)) OR (!isset($cw))) {
4323 $this->Error('The font definition file has a bad format: '.$fontfile.'');
4324 }
4325 // SET default parameters
4326 if (!isset($file) OR TCPDF_STATIC::empty_string($file)) {
4327 $file = '';
4328 }
4329 if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
4330 $enc = '';
4331 }
4332 if (!isset($cidinfo) OR TCPDF_STATIC::empty_string($cidinfo)) {
4333 $cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
4334 $cidinfo['uni2cid'] = array();
4335 }
4336 if (!isset($ctg) OR TCPDF_STATIC::empty_string($ctg)) {
4337 $ctg = '';
4338 }
4339 if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
4340 $desc = array();
4341 }
4342 if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
4343 $up = -100;
4344 }
4345 if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
4346 $ut = 50;
4347 }
4348 if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
4349 $cw = array();
4350 }
4351 if (!isset($dw) OR TCPDF_STATIC::empty_string($dw)) {
4352 // set default width
4353 if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4354 $dw = $desc['MissingWidth'];
4355 } elseif (isset($cw[32])) {
4356 $dw = $cw[32];
4357 } else {
4358 $dw = 600;
4359 }
4360 }
4361 ++$this->numfonts;
4362 if ($type == 'core') {
4363 $name = $this->CoreFonts[$fontkey];
4364 $subset = false;
4365 } elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4366 $subset = false;
4367 } elseif ($type == 'TrueTypeUnicode') {
4368 $enc = 'Identity-H';
4369 } elseif ($type == 'cidfont0') {
4370 if ($this->pdfa_mode) {
4371 $this->Error('All fonts must be embedded in PDF/A mode!');
4372 }
4373 } else {
4374 $this->Error('Unknow font type: '.$type.'');
4375 }
4376 // set name if unset
4377 if (!isset($name) OR empty($name)) {
4378 $name = $fontkey;
4379 }
4380 // create artificial font style variations if missing (only works with non-embedded fonts)
4381 if (($type != 'core') AND $missing_style) {
4382 // style variations
4383 $styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4384 $name .= $styles[$bistyle];
4385 // artificial bold
4386 if (strpos($bistyle, 'B') !== false) {
4387 if (isset($desc['StemV'])) {
4388 // from normal to bold
4389 $desc['StemV'] = round($desc['StemV'] * 1.75);
4390 } else {
4391 // bold
4392 $desc['StemV'] = 123;
4393 }
4394 }
4395 // artificial italic
4396 if (strpos($bistyle, 'I') !== false) {
4397 if (isset($desc['ItalicAngle'])) {
4398 $desc['ItalicAngle'] -= 11;
4399 } else {
4400 $desc['ItalicAngle'] = -11;
4401 }
4402 if (isset($desc['Flags'])) {
4403 $desc['Flags'] |= 64; //bit 7
4404 } else {
4405 $desc['Flags'] = 64;
4406 }
4407 }
4408 }
4409 // check if the array of characters bounding boxes is defined
4410 if (!isset($cbbox)) {
4411 $cbbox = array();
4412 }
4413 // initialize subsetchars
4414 $subsetchars = array_fill(0, 255, true);
4415 $this->setFontBuffer($fontkey, array('fontkey' => $fontkey, 'i' => $this->numfonts, 'type' => $type, 'name' => $name, 'desc' => $desc, 'up' => $up, 'ut' => $ut, 'cw' => $cw, 'cbbox' => $cbbox, 'dw' => $dw, 'enc' => $enc, 'cidinfo' => $cidinfo, 'file' => $file, 'ctg' => $ctg, 'subset' => $subset, 'subsetchars' => $subsetchars));
4416 if ($this->inxobj) {
4417 // we are inside an XObject template
4418 $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
4419 }
4420 if (isset($diff) AND (!empty($diff))) {
4421 //Search existing encodings
4422 $d = 0;
4423 $nb = count($this->diffs);
4424 for ($i=1; $i <= $nb; ++$i) {
4425 if ($this->diffs[$i] == $diff) {
4426 $d = $i;
4427 break;
4428 }
4429 }
4430 if ($d == 0) {
4431 $d = $nb + 1;
4432 $this->diffs[$d] = $diff;
4433 }
4434 $this->setFontSubBuffer($fontkey, 'diff', $d);
4435 }
4436 if (!TCPDF_STATIC::empty_string($file)) {
4437 if (!isset($this->FontFiles[$file])) {
4438 if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4439 $this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4440 } elseif ($type != 'core') {
4441 $this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4442 }
4443 } else {
4444 // update fontkeys that are sharing this font file
4445 $this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
4446 if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
4447 $this->FontFiles[$file]['fontkeys'][] = $fontkey;
4448 }
4449 }
4450 }
4451 return $fontdata;
4452 }
4453
4454 /**
4455 * Sets the font used to print character strings.
4456 * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
4457 * The method can be called before the first page is created and the font is retained from page to page.
4458 * If you just wish to change the current font size, it is simpler to call SetFontSize().
4459 * 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 />
4460 * @param $family (string) Family font. It can be either a name defined by AddFont() or one of the standard Type1 families (case insensitive):<ul><li>times (Times-Roman)</li><li>timesb (Times-Bold)</li><li>timesi (Times-Italic)</li><li>timesbi (Times-BoldItalic)</li><li>helvetica (Helvetica)</li><li>helveticab (Helvetica-Bold)</li><li>helveticai (Helvetica-Oblique)</li><li>helveticabi (Helvetica-BoldOblique)</li><li>courier (Courier)</li><li>courierb (Courier-Bold)</li><li>courieri (Courier-Oblique)</li><li>courierbi (Courier-BoldOblique)</li><li>symbol (Symbol)</li><li>zapfdingbats (ZapfDingbats)</li></ul> It is also possible to pass an empty string. In that case, the current family is retained.
4461 * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats basic fonts or other fonts when not defined.
4462 * @param $size (float) Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12
4463 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4464 * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
4465 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4466 * @author Nicola Asuni
4467 * @public
4468 * @since 1.0
4469 * @see AddFont(), SetFontSize()
4470 */
4471 public function SetFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
4472 //Select a font; size given in points
4473 if ($size === null) {
4474 $size = $this->FontSizePt;
4475 }
4476 if ($size < 0) {
4477 $size = 0;
4478 }
4479 // try to add font (if not already added)
4480 $fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4481 $this->FontFamily = $fontdata['family'];
4482 $this->FontStyle = $fontdata['style'];
4483 if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
4484 // save subset chars of the previous font
4485 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
4486 }
4487 $this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
4488 $this->SetFontSize($size, $out);
4489 }
4490
4491 /**
4492 * Defines the size of the current font.
4493 * @param $size (float) The font size in points.
4494 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4495 * @public
4496 * @since 1.0
4497 * @see SetFont()
4498 */
4499 public function SetFontSize($size, $out=true) {
4500 // font size in points
4501 $this->FontSizePt = $size;
4502 // font size in user units
4503 $this->FontSize = $size / $this->k;
4504 // calculate some font metrics
4505 if (isset($this->CurrentFont['desc']['FontBBox'])) {
4506 $bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4507 $font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4508 } else {
4509 $font_height = $size * 1.219;
4510 }
4511 if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
4512 $font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
4513 }
4514 if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
4515 $font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
4516 }
4517 if (!isset($font_ascent) AND !isset($font_descent)) {
4518 // core font
4519 $font_ascent = 0.76 * $font_height;
4520 $font_descent = $font_height - $font_ascent;
4521 } elseif (!isset($font_descent)) {
4522 $font_descent = $font_height - $font_ascent;
4523 } elseif (!isset($font_ascent)) {
4524 $font_ascent = $font_height - $font_descent;
4525 }
4526 $this->FontAscent = ($font_ascent / $this->k);
4527 $this->FontDescent = ($font_descent / $this->k);
4528 if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
4529 $this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4530 }
4531 }
4532
4533 /**
4534 * Returns the bounding box of the current font in user units.
4535 * @return array
4536 * @public
4537 * @since 5.9.152 (2012-03-23)
4538 */
4539 public function getFontBBox() {
4540 $fbbox = array();
4541 if (isset($this->CurrentFont['desc']['FontBBox'])) {
4542 $tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4543 $fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
4544 } else {
4545 // Find max width
4546 if (isset($this->CurrentFont['desc']['MaxWidth'])) {
4547 $maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
4548 } else {
4549 $maxw = 0;
4550 if (isset($this->CurrentFont['desc']['MissingWidth'])) {
4551 $maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
4552 }
4553 if (isset($this->CurrentFont['desc']['AvgWidth'])) {
4554 $maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
4555 }
4556 if (isset($this->CurrentFont['dw'])) {
4557 $maxw = max($maxw, $this->CurrentFont['dw']);
4558 }
4559 foreach ($this->CurrentFont['cw'] as $char => $w) {
4560 $maxw = max($maxw, $w);
4561 }
4562 if ($maxw == 0) {
4563 $maxw = 600;
4564 }
4565 $maxw = $this->getAbsFontMeasure($maxw);
4566 }
4567 $fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
4568 }
4569 return $fbbox;
4570 }
4571
4572 /**
4573 * Convert a relative font measure into absolute value.
4574 * @param $s (int) Font measure.
4575 * @return float Absolute measure.
4576 * @since 5.9.186 (2012-09-13)
4577 */
4578 public function getAbsFontMeasure($s) {
4579 return ($s * $this->FontSize / 1000);
4580 }
4581
4582 /**
4583 * Returns the glyph bounding box of the specified character in the current font in user units.
4584 * @param $char (int) Input character code.
4585 * @return mixed array(xMin, yMin, xMax, yMax) or FALSE if not defined.
4586 * @since 5.9.186 (2012-09-13)
4587 */
4588 public function getCharBBox($char) {
4589 if (isset($this->CurrentFont['cbbox'][$char])) {
4590 return array_map(array($this,'getAbsFontMeasure'), $this->CurrentFont['cbbox'][intval($char)]);
4591 }
4592 return false;
4593 }
4594
4595 /**
4596 * Return the font descent value
4597 * @param $font (string) font name
4598 * @param $style (string) font style
4599 * @param $size (float) The size (in points)
4600 * @return int font descent
4601 * @public
4602 * @author Nicola Asuni
4603 * @since 4.9.003 (2010-03-30)
4604 */
4605 public function getFontDescent($font, $style='', $size=0) {
4606 $fontdata = $this->AddFont($font, $style);
4607 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4608 if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
4609 $descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
4610 } else {
4611 $descent = (1.219 * 0.24 * $size);
4612 }
4613 return ($descent / $this->k);
4614 }
4615
4616 /**
4617 * Return the font ascent value.
4618 * @param $font (string) font name
4619 * @param $style (string) font style
4620 * @param $size (float) The size (in points)
4621 * @return int font ascent
4622 * @public
4623 * @author Nicola Asuni
4624 * @since 4.9.003 (2010-03-30)
4625 */
4626 public function getFontAscent($font, $style='', $size=0) {
4627 $fontdata = $this->AddFont($font, $style);
4628 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4629 if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
4630 $ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
4631 } else {
4632 $ascent = 1.219 * 0.76 * $size;
4633 }
4634 return ($ascent / $this->k);
4635 }
4636
4637 /**
4638 * Return true in the character is present in the specified font.
4639 * @param $char (mixed) Character to check (integer value or string)
4640 * @param $font (string) Font name (family name).
4641 * @param $style (string) Font style.
4642 * @return (boolean) true if the char is defined, false otherwise.
4643 * @public
4644 * @since 5.9.153 (2012-03-28)
4645 */
4646 public function isCharDefined($char, $font='', $style='') {
4647 if (is_string($char)) {
4648 // get character code
4649 $char = TCPDF_FONTS::UTF8StringToArray($char, $this->isunicode, $this->CurrentFont);
4650 $char = $char[0];
4651 }
4652 if (TCPDF_STATIC::empty_string($font)) {
4653 if (TCPDF_STATIC::empty_string($style)) {
4654 return (isset($this->CurrentFont['cw'][intval($char)]));
4655 }
4656 $font = $this->FontFamily;
4657 }
4658 $fontdata = $this->AddFont($font, $style);
4659 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4660 return (isset($fontinfo['cw'][intval($char)]));
4661 }
4662
4663 /**
4664 * Replace missing font characters on selected font with specified substitutions.
4665 * @param $text (string) Text to process.
4666 * @param $font (string) Font name (family name).
4667 * @param $style (string) Font style.
4668 * @param $subs (array) Array of possible character substitutions. The key is the character to check (integer value) and the value is a single intege value or an array of possible substitutes.
4669 * @return (string) Processed text.
4670 * @public
4671 * @since 5.9.153 (2012-03-28)
4672 */
4673 public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
4674 if (empty($subs)) {
4675 return $text;
4676 }
4677 if (TCPDF_STATIC::empty_string($font)) {
4678 $font = $this->FontFamily;
4679 }
4680 $fontdata = $this->AddFont($font, $style);
4681 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4682 $uniarr = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
4683 foreach ($uniarr as $k => $chr) {
4684 if (!isset($fontinfo['cw'][$chr])) {
4685 // this character is missing on the selected font
4686 if (isset($subs[$chr])) {
4687 // we have available substitutions
4688 if (is_array($subs[$chr])) {
4689 foreach($subs[$chr] as $s) {
4690 if (isset($fontinfo['cw'][$s])) {
4691 $uniarr[$k] = $s;
4692 break;
4693 }
4694 }
4695 } elseif (isset($fontinfo['cw'][$subs[$chr]])) {
4696 $uniarr[$k] = $subs[$chr];
4697 }
4698 }
4699 }
4700 }
4701 return TCPDF_FONTS::UniArrSubString(TCPDF_FONTS::UTF8ArrayToUniArray($uniarr, $this->isunicode));
4702 }
4703
4704 /**
4705 * Defines the default monospaced font.
4706 * @param $font (string) Font name.
4707 * @public
4708 * @since 4.5.025
4709 */
4710 public function SetDefaultMonospacedFont($font) {
4711 $this->default_monospaced_font = $font;
4712 }
4713
4714 /**
4715 * 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 />
4716 * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
4717 * @public
4718 * @since 1.5
4719 * @see Cell(), Write(), Image(), Link(), SetLink()
4720 */
4721 public function AddLink() {
4722 // create a new internal link
4723 $n = count($this->links) + 1;
4724 $this->links[$n] = array('p' => 0, 'y' => 0, 'f' => false);
4725 return $n;
4726 }
4727
4728 /**
4729 * Defines the page and position a link points to.
4730 * @param $link (int) The link identifier returned by AddLink()
4731 * @param $y (float) Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
4732 * @param $page (int) Number of target page; -1 indicates the current page (default value). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
4733 * @public
4734 * @since 1.5
4735 * @see AddLink()
4736 */
4737 public function SetLink($link, $y=0, $page=-1) {
4738 $fixed = false;
4739 if (!empty($page) AND ($page[0] == '*')) {
4740 $page = intval(substr($page, 1));
4741 // this page number will not be changed when moving/add/deleting pages
4742 $fixed = true;
4743 }
4744 if ($page < 0) {
4745 $page = $this->page;
4746 }
4747 if ($y == -1) {
4748 $y = $this->y;
4749 }
4750 $this->links[$link] = array('p' => $page, 'y' => $y, 'f' => $fixed);
4751 }
4752
4753 /**
4754 * Puts a link on a rectangular area of the page.
4755 * 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.
4756 * @param $x (float) Abscissa of the upper-left corner of the rectangle
4757 * @param $y (float) Ordinate of the upper-left corner of the rectangle
4758 * @param $w (float) Width of the rectangle
4759 * @param $h (float) Height of the rectangle
4760 * @param $link (mixed) URL or identifier returned by AddLink()
4761 * @param $spaces (int) number of spaces on the text to link
4762 * @public
4763 * @since 1.5
4764 * @see AddLink(), Annotation(), Cell(), Write(), Image()
4765 */
4766 public function Link($x, $y, $w, $h, $link, $spaces=0) {
4767 $this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
4768 }
4769
4770 /**
4771 * Puts a markup annotation on a rectangular area of the page.
4772 * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
4773 * @param $x (float) Abscissa of the upper-left corner of the rectangle
4774 * @param $y (float) Ordinate of the upper-left corner of the rectangle
4775 * @param $w (float) Width of the rectangle
4776 * @param $h (float) Height of the rectangle
4777 * @param $text (string) annotation text or alternate content
4778 * @param $opt (array) array of options (see section 8.4 of PDF reference 1.7).
4779 * @param $spaces (int) number of spaces on the text to link
4780 * @public
4781 * @since 4.0.018 (2008-08-06)
4782 */
4783 public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
4784 if ($this->inxobj) {
4785 // store parameters for later use on template
4786 $this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
4787 return;
4788 }
4789 if ($x === '') {
4790 $x = $this->x;
4791 }
4792 if ($y === '') {
4793 $y = $this->y;
4794 }
4795 // check page for no-write regions and adapt page margins if necessary
4796 list($x, $y) = $this->checkPageRegions($h, $x, $y);
4797 // recalculate coordinates to account for graphic transformations
4798 if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
4799 for ($i=$this->transfmatrix_key; $i > 0; --$i) {
4800 $maxid = count($this->transfmatrix[$i]) - 1;
4801 for ($j=$maxid; $j >= 0; --$j) {
4802 $ctm = $this->transfmatrix[$i][$j];
4803 if (isset($ctm['a'])) {
4804 $x = $x * $this->k;
4805 $y = ($this->h - $y) * $this->k;
4806 $w = $w * $this->k;
4807 $h = $h * $this->k;
4808 // top left
4809 $xt = $x;
4810 $yt = $y;
4811 $x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4812 $y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4813 // top right
4814 $xt = $x + $w;
4815 $yt = $y;
4816 $x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4817 $y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4818 // bottom left
4819 $xt = $x;
4820 $yt = $y - $h;
4821 $x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4822 $y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4823 // bottom right
4824 $xt = $x + $w;
4825 $yt = $y - $h;
4826 $x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4827 $y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4828 // new coordinates (rectangle area)
4829 $x = min($x1, $x2, $x3, $x4);
4830 $y = max($y1, $y2, $y3, $y4);
4831 $w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
4832 $h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
4833 $x = $x / $this->k;
4834 $y = $this->h - ($y / $this->k);
4835 }
4836 }
4837 }
4838 }
4839 if ($this->page <= 0) {
4840 $page = 1;
4841 } else {
4842 $page = $this->page;
4843 }
4844 if (!isset($this->PageAnnots[$page])) {
4845 $this->PageAnnots[$page] = array();
4846 }
4847 $this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
4848 if (!$this->pdfa_mode) {
4849 if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
4850 AND (@file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
4851 AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
4852 $this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
4853 }
4854 }
4855 // Add widgets annotation's icons
4856 if (isset($opt['mk']['i']) AND @file_exists($opt['mk']['i'])) {
4857 $this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
4858 }
4859 if (isset($opt['mk']['ri']) AND @file_exists($opt['mk']['ri'])) {
4860 $this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4861 }
4862 if (isset($opt['mk']['ix']) AND @file_exists($opt['mk']['ix'])) {
4863 $this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4864 }
4865 }
4866
4867 /**
4868 * Embedd the attached files.
4869 * @since 4.4.000 (2008-12-07)
4870 * @protected
4871 * @see Annotation()
4872 */
4873 protected function _putEmbeddedFiles() {
4874 if ($this->pdfa_mode) {
4875 // embedded files are not allowed in PDF/A mode
4876 return;
4877 }
4878 reset($this->embeddedfiles);
4879 foreach ($this->embeddedfiles as $filename => $filedata) {
4880 $data = TCPDF_STATIC::fileGetContents($filedata['file']);
4881 if ($data !== FALSE) {
4882 $rawsize = strlen($data);
4883 if ($rawsize > 0) {
4884 // update name tree
4885 $this->efnames[$filename] = $filedata['f'].' 0 R';
4886 // embedded file specification object
4887 $out = $this->_getobj($filedata['f'])."\n";
4888 $out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']).' /EF <</F '.$filedata['n'].' 0 R>> >>';
4889 $out .= "\n".'endobj';
4890 $this->_out($out);
4891 // embedded file object
4892 $filter = '';
4893 if ($this->compress) {
4894 $data = gzcompress($data);
4895 $filter = ' /Filter /FlateDecode';
4896 }
4897 $stream = $this->_getrawstream($data, $filedata['n']);
4898 $out = $this->_getobj($filedata['n'])."\n";
4899 $out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
4900 $out .= ' stream'."\n".$stream."\n".'endstream';
4901 $out .= "\n".'endobj';
4902 $this->_out($out);
4903 }
4904 }
4905 }
4906 }
4907
4908 /**
4909 * Prints a text cell at the specified position.
4910 * This method allows to place a string precisely on the page.
4911 * @param $x (float) Abscissa of the cell origin
4912 * @param $y (float) Ordinate of the cell origin
4913 * @param $txt (string) String to print
4914 * @param $fstroke (int) outline size in user units (false = disable)
4915 * @param $fclip (boolean) if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
4916 * @param $ffill (boolean) if true fills the text
4917 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
4918 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
4919 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
4920 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
4921 * @param $link (mixed) URL or identifier returned by AddLink().
4922 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
4923 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
4924 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li><li>B : cell bottom</li></ul>
4925 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
4926 * @param $rtloff (boolean) if true uses the page top-left corner as origin of axis for $x and $y initial position.
4927 * @public
4928 * @since 1.0
4929 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
4930 */
4931 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) {
4932 $textrendermode = $this->textrendermode;
4933 $textstrokewidth = $this->textstrokewidth;
4934 $this->setTextRenderingMode($fstroke, $ffill, $fclip);
4935 $this->SetXY($x, $y, $rtloff);
4936 $this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
4937 // restore previous rendering mode
4938 $this->textrendermode = $textrendermode;
4939 $this->textstrokewidth = $textstrokewidth;
4940 }
4941
4942 /**
4943 * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
4944 * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
4945 * This method is called automatically and should not be called directly by the application.
4946 * @return boolean
4947 * @public
4948 * @since 1.4
4949 * @see SetAutoPageBreak()
4950 */
4951 public function AcceptPageBreak() {
4952 if ($this->num_columns > 1) {
4953 // multi column mode
4954 if ($this->current_column < ($this->num_columns - 1)) {
4955 // go to next column
4956 $this->selectColumn($this->current_column + 1);
4957 } elseif ($this->AutoPageBreak) {
4958 // add a new page
4959 $this->AddPage();
4960 // set first column
4961 $this->selectColumn(0);
4962 }
4963 // avoid page breaking from checkPageBreak()
4964 return false;
4965 }
4966 return $this->AutoPageBreak;
4967 }
4968
4969 /**
4970 * Add page if needed.
4971 * @param $h (float) Cell height. Default value: 0.
4972 * @param $y (mixed) starting y position, leave empty for current position.
4973 * @param $addpage (boolean) if true add a page, otherwise only return the true/false state
4974 * @return boolean true in case of page break, false otherwise.
4975 * @since 3.2.000 (2008-07-01)
4976 * @protected
4977 */
4978 protected function checkPageBreak($h=0, $y='', $addpage=true) {
4979 if (TCPDF_STATIC::empty_string($y)) {
4980 $y = $this->y;
4981 }
4982 $current_page = $this->page;
4983 if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
4984 if ($addpage) {
4985 //Automatic page break
4986 $x = $this->x;
4987 $this->AddPage($this->CurOrientation);
4988 $this->y = $this->tMargin;
4989 $oldpage = $this->page - 1;
4990 if ($this->rtl) {
4991 if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
4992 $this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
4993 } else {
4994 $this->x = $x;
4995 }
4996 } else {
4997 if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
4998 $this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
4999 } else {
5000 $this->x = $x;
5001 }
5002 }
5003 }
5004 return true;
5005 }
5006 if ($current_page != $this->page) {
5007 // account for columns mode
5008 return true;
5009 }
5010 return false;
5011 }
5012
5013 /**
5014 * 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 />
5015 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5016 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
5017 * @param $h (float) Cell height. Default value: 0.
5018 * @param $txt (string) String to print. Default value: empty string.
5019 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5020 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul> Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5021 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5022 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5023 * @param $link (mixed) URL or identifier returned by AddLink().
5024 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5025 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5026 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5027 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
5028 * @public
5029 * @since 1.0
5030 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
5031 */
5032 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') {
5033 $prev_cell_margin = $this->cell_margin;
5034 $prev_cell_padding = $this->cell_padding;
5035 $this->adjustCellPadding($border);
5036 if (!$ignore_min_height) {
5037 $min_cell_height = $this->getCellHeight($this->FontSize);
5038 if ($h < $min_cell_height) {
5039 $h = $min_cell_height;
5040 }
5041 }
5042 $this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5043 // apply text shadow if enabled
5044 if ($this->txtshadow['enabled']) {
5045 // save data
5046 $x = $this->x;
5047 $y = $this->y;
5048 $bc = $this->bgcolor;
5049 $fc = $this->fgcolor;
5050 $sc = $this->strokecolor;
5051 $alpha = $this->alpha;
5052 // print shadow
5053 $this->x += $this->txtshadow['depth_w'];
5054 $this->y += $this->txtshadow['depth_h'];
5055 $this->SetFillColorArray($this->txtshadow['color']);
5056 $this->SetTextColorArray($this->txtshadow['color']);
5057 $this->SetDrawColorArray($this->txtshadow['color']);
5058 if ($this->txtshadow['opacity'] != $alpha['CA']) {
5059 $this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
5060 }
5061 if ($this->state == 2) {
5062 $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5063 }
5064 //restore data
5065 $this->x = $x;
5066 $this->y = $y;
5067 $this->SetFillColorArray($bc);
5068 $this->SetTextColorArray($fc);
5069 $this->SetDrawColorArray($sc);
5070 if ($this->txtshadow['opacity'] != $alpha['CA']) {
5071 $this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
5072 }
5073 }
5074 if ($this->state == 2) {
5075 $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5076 }
5077 $this->cell_padding = $prev_cell_padding;
5078 $this->cell_margin = $prev_cell_margin;
5079 }
5080
5081 /**
5082 * 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 />
5083 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5084 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
5085 * @param $h (float) Cell height. Default value: 0.
5086 * @param $txt (string) String to print. Default value: empty string.
5087 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5088 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5089 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5090 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5091 * @param $link (mixed) URL or identifier returned by AddLink().
5092 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5093 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5094 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5095 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>M : middle</li><li>B : bottom</li></ul>
5096 * @return string containing cell code
5097 * @protected
5098 * @since 1.0
5099 * @see Cell()
5100 */
5101 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') {
5102 // replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
5103 $txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
5104 $prev_cell_margin = $this->cell_margin;
5105 $prev_cell_padding = $this->cell_padding;
5106 $txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
5107 $rs = ''; //string to be returned
5108 $this->adjustCellPadding($border);
5109 if (!$ignore_min_height) {
5110 $min_cell_height = $this->getCellHeight($this->FontSize);
5111 if ($h < $min_cell_height) {
5112 $h = $min_cell_height;
5113 }
5114 }
5115 $k = $this->k;
5116 // check page for no-write regions and adapt page margins if necessary
5117 list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
5118 if ($this->rtl) {
5119 $x = $this->x - $this->cell_margin['R'];
5120 } else {
5121 $x = $this->x + $this->cell_margin['L'];
5122 }
5123 $y = $this->y + $this->cell_margin['T'];
5124 $prev_font_stretching = $this->font_stretching;
5125 $prev_font_spacing = $this->font_spacing;
5126 // cell vertical alignment
5127 switch ($calign) {
5128 case 'A': {
5129 // font top
5130 switch ($valign) {
5131 case 'T': {
5132 // top
5133 $y -= $this->cell_padding['T'];
5134 break;
5135 }
5136 case 'B': {
5137 // bottom
5138 $y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5139 break;
5140 }
5141 default:
5142 case 'C':
5143 case 'M': {
5144 // center
5145 $y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5146 break;
5147 }
5148 }
5149 break;
5150 }
5151 case 'L': {
5152 // font baseline
5153 switch ($valign) {
5154 case 'T': {
5155 // top
5156 $y -= ($this->cell_padding['T'] + $this->FontAscent);
5157 break;
5158 }
5159 case 'B': {
5160 // bottom
5161 $y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5162 break;
5163 }
5164 default:
5165 case 'C':
5166 case 'M': {
5167 // center
5168 $y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5169 break;
5170 }
5171 }
5172 break;
5173 }
5174 case 'D': {
5175 // font bottom
5176 switch ($valign) {
5177 case 'T': {
5178 // top
5179 $y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5180 break;
5181 }
5182 case 'B': {
5183 // bottom
5184 $y -= ($h - $this->cell_padding['B']);
5185 break;
5186 }
5187 default:
5188 case 'C':
5189 case 'M': {
5190 // center
5191 $y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5192 break;
5193 }
5194 }
5195 break;
5196 }
5197 case 'B': {
5198 // cell bottom
5199 $y -= $h;
5200 break;
5201 }
5202 case 'C':
5203 case 'M': {
5204 // cell center
5205 $y -= ($h / 2);
5206 break;
5207 }
5208 default:
5209 case 'T': {
5210 // cell top
5211 break;
5212 }
5213 }
5214 // text vertical alignment
5215 switch ($valign) {
5216 case 'T': {
5217 // top
5218 $yt = $y + $this->cell_padding['T'];
5219 break;
5220 }
5221 case 'B': {
5222 // bottom
5223 $yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5224 break;
5225 }
5226 default:
5227 case 'C':
5228 case 'M': {
5229 // center
5230 $yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5231 break;
5232 }
5233 }
5234 $basefonty = $yt + $this->FontAscent;
5235 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5236 if ($this->rtl) {
5237 $w = $x - $this->lMargin;
5238 } else {
5239 $w = $this->w - $this->rMargin - $x;
5240 }
5241 }
5242 $s = '';
5243 // fill and borders
5244 if (is_string($border) AND (strlen($border) == 4)) {
5245 // full border
5246 $border = 1;
5247 }
5248 if ($fill OR ($border == 1)) {
5249 if ($fill) {
5250 $op = ($border == 1) ? 'B' : 'f';
5251 } else {
5252 $op = 'S';
5253 }
5254 if ($this->rtl) {
5255 $xk = (($x - $w) * $k);
5256 } else {
5257 $xk = ($x * $k);
5258 }
5259 $s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5260 }
5261 // draw borders
5262 $s .= $this->getCellBorder($x, $y, $w, $h, $border);
5263 if ($txt != '') {
5264 $txt2 = $txt;
5265 if ($this->isunicode) {
5266 if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
5267 $txt2 = TCPDF_FONTS::UTF8ToLatin1($txt2, $this->isunicode, $this->CurrentFont);
5268 } else {
5269 $unicode = TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont); // array of UTF-8 unicode values
5270 $unicode = TCPDF_FONTS::utf8Bidi($unicode, '', $this->tmprtl, $this->isunicode, $this->CurrentFont);
5271 // replace thai chars (if any)
5272 if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
5273 // number of chars
5274 $numchars = count($unicode);
5275 // po pla, for far, for fan
5276 $longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
5277 // do chada, to patak
5278 $lowtail = array(0x0e0e, 0x0e0f);
5279 // mai hun arkad, sara i, sara ii, sara ue, sara uee
5280 $upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
5281 // mai ek, mai tho, mai tri, mai chattawa, karan
5282 $tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
5283 // sara u, sara uu, pinthu
5284 $lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
5285 $output = array();
5286 for ($i = 0; $i < $numchars; $i++) {
5287 if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
5288 $ch0 = $unicode[$i];
5289 $ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
5290 $ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
5291 $chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
5292 if (in_array($ch0, $tonemark)) {
5293 if ($chn == 0x0e33) {
5294 // sara um
5295 if (in_array($ch1, $longtail)) {
5296 // tonemark at upper left
5297 $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5298 } else {
5299 // tonemark at upper right (normal position)
5300 $output[] = $ch0;
5301 }
5302 } elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
5303 // tonemark at lower left
5304 $output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
5305 } elseif (in_array($ch1, $upvowel)) {
5306 if (in_array($ch2, $longtail)) {
5307 // tonemark at upper left
5308 $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5309 } else {
5310 // tonemark at upper right (normal position)
5311 $output[] = $ch0;
5312 }
5313 } else {
5314 // tonemark at lower right
5315 $output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
5316 }
5317 } elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
5318 // add lower left nikhahit and sara aa
5319 if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
5320 $output[] = 0xf711;
5321 $this->CurrentFont['subsetchars'][0xf711] = true;
5322 $output[] = 0x0e32;
5323 $this->CurrentFont['subsetchars'][0x0e32] = true;
5324 } else {
5325 $output[] = $ch0;
5326 }
5327 } elseif (in_array($ch1, $longtail)) {
5328 if ($ch0 == 0x0e31) {
5329 // lower left mai hun arkad
5330 $output[] = $this->replaceChar($ch0, 0xf710);
5331 } elseif (in_array($ch0, $upvowel)) {
5332 // lower left
5333 $output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
5334 } elseif ($ch0 == 0x0e47) {
5335 // lower left mai tai koo
5336 $output[] = $this->replaceChar($ch0, 0xf712);
5337 } else {
5338 // normal character
5339 $output[] = $ch0;
5340 }
5341 } elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
5342 // lower vowel
5343 $output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
5344 } elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
5345 // yo ying without lower part
5346 $output[] = $this->replaceChar($ch0, 0xf70f);
5347 } elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
5348 // tho santan without lower part
5349 $output[] = $this->replaceChar($ch0, 0xf700);
5350 } else {
5351 $output[] = $ch0;
5352 }
5353 } else {
5354 // non-thai character
5355 $output[] = $unicode[$i];
5356 }
5357 }
5358 $unicode = $output;
5359 // update font subsetchars
5360 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
5361 } // end of K_THAI_TOPCHARS
5362 $txt2 = TCPDF_FONTS::arrUTF8ToUTF16BE($unicode, false);
5363 }
5364 }
5365 $txt2 = TCPDF_STATIC::_escape($txt2);
5366 // get current text width (considering general font stretching and spacing)
5367 $txwidth = $this->GetStringWidth($txt);
5368 $width = $txwidth;
5369 // check for stretch mode
5370 if ($stretch > 0) {
5371 // calculate ratio between cell width and text width
5372 if ($width <= 0) {
5373 $ratio = 1;
5374 } else {
5375 $ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
5376 }
5377 // check if stretching is required
5378 if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
5379 // the text will be stretched to fit cell width
5380 if ($stretch > 2) {
5381 // set new character spacing
5382 $this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
5383 } else {
5384 // set new horizontal stretching
5385 $this->font_stretching *= $ratio;
5386 }
5387 // recalculate text width (the text fills the entire cell)
5388 $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5389 // reset alignment
5390 $align = '';
5391 }
5392 }
5393 if ($this->font_stretching != 100) {
5394 // apply font stretching
5395 $rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
5396 }
5397 if ($this->font_spacing != 0) {
5398 // increase/decrease font spacing
5399 $rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
5400 }
5401 if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5402 $s .= 'q '.$this->TextColor.' ';
5403 }
5404 // rendering mode
5405 $s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
5406 // count number of spaces
5407 $ns = substr_count($txt, chr(32));
5408 // Justification
5409 $spacewidth = 0;
5410 if (($align == 'J') AND ($ns > 0)) {
5411 if ($this->isUnicodeFont()) {
5412 // get string width without spaces
5413 $width = $this->GetStringWidth(str_replace(' ', '', $txt));
5414 // calculate average space width
5415 $spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / ($this->FontSize?$this->FontSize:1);
5416 if ($this->font_stretching != 100) {
5417 // word spacing is affected by stretching
5418 $spacewidth /= ($this->font_stretching / 100);
5419 }
5420 // set word position to be used with TJ operator
5421 $txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
5422 $unicode_justification = true;
5423 } else {
5424 // get string width
5425 $width = $txwidth;
5426 // new space width
5427 $spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
5428 if ($this->font_stretching != 100) {
5429 // word spacing (Tw) is affected by stretching
5430 $spacewidth /= ($this->font_stretching / 100);
5431 }
5432 // set word spacing
5433 $rs .= sprintf('BT %F Tw ET ', $spacewidth);
5434 }
5435 $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5436 }
5437 // replace carriage return characters
5438 $txt2 = str_replace("\r", ' ', $txt2);
5439 switch ($align) {
5440 case 'C': {
5441 $dx = ($w - $width) / 2;
5442 break;
5443 }
5444 case 'R': {
5445 if ($this->rtl) {
5446 $dx = $this->cell_padding['R'];
5447 } else {
5448 $dx = $w - $width - $this->cell_padding['R'];
5449 }
5450 break;
5451 }
5452 case 'L': {
5453 if ($this->rtl) {
5454 $dx = $w - $width - $this->cell_padding['L'];
5455 } else {
5456 $dx = $this->cell_padding['L'];
5457 }
5458 break;
5459 }
5460 case 'J':
5461 default: {
5462 if ($this->rtl) {
5463 $dx = $this->cell_padding['R'];
5464 } else {
5465 $dx = $this->cell_padding['L'];
5466 }
5467 break;
5468 }
5469 }
5470 if ($this->rtl) {
5471 $xdx = $x - $dx - $width;
5472 } else {
5473 $xdx = $x + $dx;
5474 }
5475 $xdk = $xdx * $k;
5476 // print text
5477 $s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
5478 if (isset($uniblock)) {
5479 // print overlapping characters as separate string
5480 $xshift = 0; // horizontal shift
5481 $ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
5482 $spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
5483 foreach ($uniblock as $uk => $uniarr) {
5484 if (($uk % 2) == 0) {
5485 // x space to skip
5486 if ($spacewidth != 0) {
5487 // justification shift
5488 $xshift += (count(array_keys($uniarr, 32)) * $spw);
5489 }
5490 $xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5491 } else {
5492 // character to print
5493 $topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
5494 $topchr = TCPDF_STATIC::_escape($topchr);
5495 $s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
5496 }
5497 }
5498 }
5499 if ($this->underline) {
5500 $s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5501 }
5502 if ($this->linethrough) {
5503 $s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5504 }
5505 if ($this->overline) {
5506 $s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5507 }
5508 if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5509 $s .= ' Q';
5510 }
5511 if ($link) {
5512 $this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5513 }
5514 }
5515 // output cell
5516 if ($s) {
5517 // output cell
5518 $rs .= $s;
5519 if ($this->font_spacing != 0) {
5520 // reset font spacing mode
5521 $rs .= ' BT 0 Tc ET';
5522 }
5523 if ($this->font_stretching != 100) {
5524 // reset font stretching mode
5525 $rs .= ' BT 100 Tz ET';
5526 }
5527 }
5528 // reset word spacing
5529 if (!$this->isUnicodeFont() AND ($align == 'J')) {
5530 $rs .= ' BT 0 Tw ET';
5531 }
5532 // reset stretching and spacing
5533 $this->font_stretching = $prev_font_stretching;
5534 $this->font_spacing = $prev_font_spacing;
5535 $this->lasth = $h;
5536 if ($ln > 0) {
5537 //Go to the beginning of the next line
5538 $this->y = $y + $h + $this->cell_margin['B'];
5539 if ($ln == 1) {
5540 if ($this->rtl) {
5541 $this->x = $this->w - $this->rMargin;
5542 } else {
5543 $this->x = $this->lMargin;
5544 }
5545 }
5546 } else {
5547 // go left or right by case
5548 if ($this->rtl) {
5549 $this->x = $x - $w - $this->cell_margin['L'];
5550 } else {
5551 $this->x = $x + $w + $this->cell_margin['R'];
5552 }
5553 }
5554 $gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5555 $rs = $gstyles.$rs;
5556 $this->cell_padding = $prev_cell_padding;
5557 $this->cell_margin = $prev_cell_margin;
5558 return $rs;
5559 }
5560
5561 /**
5562 * Replace a char if is defined on the current font.
5563 * @param $oldchar (int) Integer code (unicode) of the character to replace.
5564 * @param $newchar (int) Integer code (unicode) of the new character.
5565 * @return int the replaced char or the old char in case the new char i not defined
5566 * @protected
5567 * @since 5.9.167 (2012-06-22)
5568 */
5569 protected function replaceChar($oldchar, $newchar) {
5570 if ($this->isCharDefined($newchar)) {
5571 // add the new char on the subset list
5572 $this->CurrentFont['subsetchars'][$newchar] = true;
5573 // return the new character
5574 return $newchar;
5575 }
5576 // return the old char
5577 return $oldchar;
5578 }
5579
5580 /**
5581 * Returns the code to draw the cell border
5582 * @param $x (float) X coordinate.
5583 * @param $y (float) Y coordinate.
5584 * @param $w (float) Cell width.
5585 * @param $h (float) Cell height.
5586 * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5587 * @return string containing cell border code
5588 * @protected
5589 * @see SetLineStyle()
5590 * @since 5.7.000 (2010-08-02)
5591 */
5592 protected function getCellBorder($x, $y, $w, $h, $brd) {
5593 $s = ''; // string to be returned
5594 if (empty($brd)) {
5595 return $s;
5596 }
5597 if ($brd == 1) {
5598 $brd = array('LRTB' => true);
5599 }
5600 // calculate coordinates for border
5601 $k = $this->k;
5602 if ($this->rtl) {
5603 $xeL = ($x - $w) * $k;
5604 $xeR = $x * $k;
5605 } else {
5606 $xeL = $x * $k;
5607 $xeR = ($x + $w) * $k;
5608 }
5609 $yeL = (($this->h - ($y + $h)) * $k);
5610 $yeT = (($this->h - $y) * $k);
5611 $xeT = $xeL;
5612 $xeB = $xeR;
5613 $yeR = $yeT;
5614 $yeB = $yeL;
5615 if (is_string($brd)) {
5616 // convert string to array
5617 $slen = strlen($brd);
5618 $newbrd = array();
5619 for ($i = 0; $i < $slen; ++$i) {
5620 $newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
5621 }
5622 $brd = $newbrd;
5623 }
5624 if (isset($brd['mode'])) {
5625 $mode = $brd['mode'];
5626 unset($brd['mode']);
5627 } else {
5628 $mode = 'normal';
5629 }
5630 foreach ($brd as $border => $style) {
5631 if (is_array($style) AND !empty($style)) {
5632 // apply border style
5633 $prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
5634 $s .= $this->SetLineStyle($style, true)."\n";
5635 }
5636 switch ($mode) {
5637 case 'ext': {
5638 $off = (($this->LineWidth / 2) * $k);
5639 $xL = $xeL - $off;
5640 $xR = $xeR + $off;
5641 $yT = $yeT + $off;
5642 $yL = $yeL - $off;
5643 $xT = $xL;
5644 $xB = $xR;
5645 $yR = $yT;
5646 $yB = $yL;
5647 $w += $this->LineWidth;
5648 $h += $this->LineWidth;
5649 break;
5650 }
5651 case 'int': {
5652 $off = ($this->LineWidth / 2) * $k;
5653 $xL = $xeL + $off;
5654 $xR = $xeR - $off;
5655 $yT = $yeT - $off;
5656 $yL = $yeL + $off;
5657 $xT = $xL;
5658 $xB = $xR;
5659 $yR = $yT;
5660 $yB = $yL;
5661 $w -= $this->LineWidth;
5662 $h -= $this->LineWidth;
5663 break;
5664 }
5665 case 'normal':
5666 default: {
5667 $xL = $xeL;
5668 $xT = $xeT;
5669 $xB = $xeB;
5670 $xR = $xeR;
5671 $yL = $yeL;
5672 $yT = $yeT;
5673 $yB = $yeB;
5674 $yR = $yeR;
5675 break;
5676 }
5677 }
5678 // draw borders by case
5679 if (strlen($border) == 4) {
5680 $s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5681 } elseif (strlen($border) == 3) {
5682 if (strpos($border,'B') === false) { // LTR
5683 $s .= sprintf('%F %F m ', $xL, $yL);
5684 $s .= sprintf('%F %F l ', $xT, $yT);
5685 $s .= sprintf('%F %F l ', $xR, $yR);
5686 $s .= sprintf('%F %F l ', $xB, $yB);
5687 $s .= 'S ';
5688 } elseif (strpos($border,'L') === false) { // TRB
5689 $s .= sprintf('%F %F m ', $xT, $yT);
5690 $s .= sprintf('%F %F l ', $xR, $yR);
5691 $s .= sprintf('%F %F l ', $xB, $yB);
5692 $s .= sprintf('%F %F l ', $xL, $yL);
5693 $s .= 'S ';
5694 } elseif (strpos($border,'T') === false) { // RBL
5695 $s .= sprintf('%F %F m ', $xR, $yR);
5696 $s .= sprintf('%F %F l ', $xB, $yB);
5697 $s .= sprintf('%F %F l ', $xL, $yL);
5698 $s .= sprintf('%F %F l ', $xT, $yT);
5699 $s .= 'S ';
5700 } elseif (strpos($border,'R') === false) { // BLT
5701 $s .= sprintf('%F %F m ', $xB, $yB);
5702 $s .= sprintf('%F %F l ', $xL, $yL);
5703 $s .= sprintf('%F %F l ', $xT, $yT);
5704 $s .= sprintf('%F %F l ', $xR, $yR);
5705 $s .= 'S ';
5706 }
5707 } elseif (strlen($border) == 2) {
5708 if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5709 $s .= sprintf('%F %F m ', $xL, $yL);
5710 $s .= sprintf('%F %F l ', $xT, $yT);
5711 $s .= sprintf('%F %F l ', $xR, $yR);
5712 $s .= 'S ';
5713 } elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5714 $s .= sprintf('%F %F m ', $xT, $yT);
5715 $s .= sprintf('%F %F l ', $xR, $yR);
5716 $s .= sprintf('%F %F l ', $xB, $yB);
5717 $s .= 'S ';
5718 } elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5719 $s .= sprintf('%F %F m ', $xR, $yR);
5720 $s .= sprintf('%F %F l ', $xB, $yB);
5721 $s .= sprintf('%F %F l ', $xL, $yL);
5722 $s .= 'S ';
5723 } elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5724 $s .= sprintf('%F %F m ', $xB, $yB);
5725 $s .= sprintf('%F %F l ', $xL, $yL);
5726 $s .= sprintf('%F %F l ', $xT, $yT);
5727 $s .= 'S ';
5728 } elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5729 $s .= sprintf('%F %F m ', $xL, $yL);
5730 $s .= sprintf('%F %F l ', $xT, $yT);
5731 $s .= 'S ';
5732 $s .= sprintf('%F %F m ', $xR, $yR);
5733 $s .= sprintf('%F %F l ', $xB, $yB);
5734 $s .= 'S ';
5735 } elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5736 $s .= sprintf('%F %F m ', $xT, $yT);
5737 $s .= sprintf('%F %F l ', $xR, $yR);
5738 $s .= 'S ';
5739 $s .= sprintf('%F %F m ', $xB, $yB);
5740 $s .= sprintf('%F %F l ', $xL, $yL);
5741 $s .= 'S ';
5742 }
5743 } else { // strlen($border) == 1
5744 if (strpos($border,'L') !== false) { // L
5745 $s .= sprintf('%F %F m ', $xL, $yL);
5746 $s .= sprintf('%F %F l ', $xT, $yT);
5747 $s .= 'S ';
5748 } elseif (strpos($border,'T') !== false) { // T
5749 $s .= sprintf('%F %F m ', $xT, $yT);
5750 $s .= sprintf('%F %F l ', $xR, $yR);
5751 $s .= 'S ';
5752 } elseif (strpos($border,'R') !== false) { // R
5753 $s .= sprintf('%F %F m ', $xR, $yR);
5754 $s .= sprintf('%F %F l ', $xB, $yB);
5755 $s .= 'S ';
5756 } elseif (strpos($border,'B') !== false) { // B
5757 $s .= sprintf('%F %F m ', $xB, $yB);
5758 $s .= sprintf('%F %F l ', $xL, $yL);
5759 $s .= 'S ';
5760 }
5761 }
5762 if (is_array($style) AND !empty($style)) {
5763 // reset border style to previous value
5764 $s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
5765 }
5766 }
5767 return $s;
5768 }
5769
5770 /**
5771 * This method allows printing text with line breaks.
5772 * 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 />
5773 * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
5774 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
5775 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
5776 * @param $txt (string) String to print
5777 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5778 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align</li><li>C: center</li><li>R: right align</li><li>J: justification (default value when $ishtml=false)</li></ul>
5779 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5780 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line [DEFAULT]</li><li>2: below</li></ul>
5781 * @param $x (float) x position in user units
5782 * @param $y (float) y position in user units
5783 * @param $reseth (boolean) if true reset the last cell height (default true).
5784 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5785 * @param $ishtml (boolean) INTERNAL USE ONLY -- set to true if $txt is HTML content (default = false). Never set this parameter to true, use instead writeHTMLCell() or writeHTML() methods.
5786 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
5787 * @param $maxh (float) maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. This feature works only when $ishtml=false.
5788 * @param $valign (string) Vertical alignment of text (requires $maxh = $h > 0). Possible values are:<ul><li>T: TOP</li><li>M: middle</li><li>B: bottom</li></ul>. This feature works only when $ishtml=false and the cell must fit in a single page.
5789 * @param $fitcell (boolean) if true attempt to fit all the text within the cell by reducing the font size (do not work in HTML mode). $maxh must be greater than 0 and wqual to $h.
5790 * @return int Return the number of cells or 1 for html mode.
5791 * @public
5792 * @since 1.3
5793 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
5794 */
5795 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) {
5796 $prev_cell_margin = $this->cell_margin;
5797 $prev_cell_padding = $this->cell_padding;
5798 // adjust internal padding
5799 $this->adjustCellPadding($border);
5800 $mc_padding = $this->cell_padding;
5801 $mc_margin = $this->cell_margin;
5802 $this->cell_padding['T'] = 0;
5803 $this->cell_padding['B'] = 0;
5804 $this->setCellMargins(0, 0, 0, 0);
5805 if (TCPDF_STATIC::empty_string($this->lasth) OR $reseth) {
5806 // reset row height
5807 $this->resetLastH();
5808 }
5809 if (!TCPDF_STATIC::empty_string($y)) {
5810 $this->SetY($y);
5811 } else {
5812 $y = $this->GetY();
5813 }
5814 $resth = 0;
5815 if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
5816 // spit cell in more pages/columns
5817 $newh = ($this->PageBreakTrigger - $y);
5818 $resth = ($h - $newh); // cell to be printed on the next page/column
5819 $h = $newh;
5820 }
5821 // get current page number
5822 $startpage = $this->page;
5823 // get current column
5824 $startcolumn = $this->current_column;
5825 if (!TCPDF_STATIC::empty_string($x)) {
5826 $this->SetX($x);
5827 } else {
5828 $x = $this->GetX();
5829 }
5830 // check page for no-write regions and adapt page margins if necessary
5831 list($x, $y) = $this->checkPageRegions(0, $x, $y);
5832 // apply margins
5833 $oy = $y + $mc_margin['T'];
5834 if ($this->rtl) {
5835 $ox = ($this->w - $x - $mc_margin['R']);
5836 } else {
5837 $ox = ($x + $mc_margin['L']);
5838 }
5839 $this->x = $ox;
5840 $this->y = $oy;
5841 // set width
5842 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5843 if ($this->rtl) {
5844 $w = ($this->x - $this->lMargin - $mc_margin['L']);
5845 } else {
5846 $w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
5847 }
5848 }
5849 // store original margin values
5850 $lMargin = $this->lMargin;
5851 $rMargin = $this->rMargin;
5852 if ($this->rtl) {
5853 $this->rMargin = ($this->w - $this->x);
5854 $this->lMargin = ($this->x - $w);
5855 } else {
5856 $this->lMargin = ($this->x);
5857 $this->rMargin = ($this->w - $this->x - $w);
5858 }
5859 $this->clMargin = $this->lMargin;
5860 $this->crMargin = $this->rMargin;
5861 if ($autopadding) {
5862 // add top padding
5863 $this->y += $mc_padding['T'];
5864 }
5865 if ($ishtml) { // ******* Write HTML text
5866 $this->writeHTML($txt, true, false, $reseth, true, $align);
5867 $nl = 1;
5868 } else { // ******* Write simple text
5869 $prev_FontSizePt = $this->FontSizePt;
5870 if ($fitcell) {
5871 // ajust height values
5872 $tobottom = ($this->h - $this->y - $this->bMargin - $this->cell_padding['T'] - $this->cell_padding['B']);
5873 $h = $maxh = max(min($h, $tobottom), min($maxh, $tobottom));
5874 }
5875 // vertical alignment
5876 if ($maxh > 0) {
5877 // get text height
5878 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5879 if ($fitcell AND ($text_height > $maxh) AND ($this->FontSizePt > 1)) {
5880 // try to reduce font size to fit text on cell (use a quick search algorithm)
5881 $fmin = 1;
5882 $fmax = $this->FontSizePt;
5883 $diff_epsilon = (1 / $this->k); // one point (min resolution)
5884 $maxit = (2 * min(100, max(10, intval($fmax)))); // max number of iterations
5885 while ($maxit >= 0) {
5886 $fmid = (($fmax + $fmin) / 2);
5887 $this->SetFontSize($fmid, false);
5888 $this->resetLastH();
5889 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5890 $diff = ($maxh - $text_height);
5891 if ($diff >= 0) {
5892 if ($diff <= $diff_epsilon) {
5893 break;
5894 }
5895 $fmin = $fmid;
5896 } else {
5897 $fmax = $fmid;
5898 }
5899 --$maxit;
5900 }
5901 if ($maxit < 0) {
5902 // premature exit, we get the minimum font value to fit the cell
5903 $this->SetFontSize($fmin);
5904 $this->resetLastH();
5905 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5906 } else {
5907 $this->SetFontSize($fmid);
5908 $this->resetLastH();
5909 }
5910 }
5911 if ($text_height < $maxh) {
5912 if ($valign == 'M') {
5913 // text vertically centered
5914 $this->y += (($maxh - $text_height) / 2);
5915 } elseif ($valign == 'B') {
5916 // text vertically aligned on bottom
5917 $this->y += ($maxh - $text_height);
5918 }
5919 }
5920 }
5921 $nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
5922 if ($fitcell) {
5923 // restore font size
5924 $this->SetFontSize($prev_FontSizePt);
5925 }
5926 }
5927 if ($autopadding) {
5928 // add bottom padding
5929 $this->y += $mc_padding['B'];
5930 }
5931 // Get end-of-text Y position
5932 $currentY = $this->y;
5933 // get latest page number
5934 $endpage = $this->page;
5935 if ($resth > 0) {
5936 $skip = ($endpage - $startpage);
5937 $tmpresth = $resth;
5938 while ($tmpresth > 0) {
5939 if ($skip <= 0) {
5940 // add a page (or trig AcceptPageBreak() for multicolumn mode)
5941 $this->checkPageBreak($this->PageBreakTrigger + 1);
5942 }
5943 if ($this->num_columns > 1) {
5944 $tmpresth -= ($this->h - $this->y - $this->bMargin);
5945 } else {
5946 $tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
5947 }
5948 --$skip;
5949 }
5950 $currentY = $this->y;
5951 $endpage = $this->page;
5952 }
5953 // get latest column
5954 $endcolumn = $this->current_column;
5955 if ($this->num_columns == 0) {
5956 $this->num_columns = 1;
5957 }
5958 // disable page regions check
5959 $check_page_regions = $this->check_page_regions;
5960 $this->check_page_regions = false;
5961 // get border modes
5962 $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
5963 $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
5964 $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
5965 // design borders around HTML cells.
5966 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
5967 $ccode = '';
5968 $this->setPage($page);
5969 if ($this->num_columns < 2) {
5970 // single-column mode
5971 $this->SetX($x);
5972 $this->y = $this->tMargin;
5973 }
5974 // account for margin changes
5975 if ($page > $startpage) {
5976 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
5977 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
5978 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
5979 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
5980 }
5981 }
5982 if ($startpage == $endpage) {
5983 // single page
5984 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
5985 $this->selectColumn($column);
5986 if ($this->rtl) {
5987 $this->x -= $mc_margin['R'];
5988 } else {
5989 $this->x += $mc_margin['L'];
5990 }
5991 if ($startcolumn == $endcolumn) { // single column
5992 $cborder = $border;
5993 $h = max($h, ($currentY - $oy));
5994 $this->y = $oy;
5995 } elseif ($column == $startcolumn) { // first column
5996 $cborder = $border_start;
5997 $this->y = $oy;
5998 $h = $this->h - $this->y - $this->bMargin;
5999 } elseif ($column == $endcolumn) { // end column
6000 $cborder = $border_end;
6001 $h = $currentY - $this->y;
6002 if ($resth > $h) {
6003 $h = $resth;
6004 }
6005 } else { // middle column
6006 $cborder = $border_middle;
6007 $h = $this->h - $this->y - $this->bMargin;
6008 $resth -= $h;
6009 }
6010 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6011 } // end for each column
6012 } elseif ($page == $startpage) { // first page
6013 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
6014 $this->selectColumn($column);
6015 if ($this->rtl) {
6016 $this->x -= $mc_margin['R'];
6017 } else {
6018 $this->x += $mc_margin['L'];
6019 }
6020 if ($column == $startcolumn) { // first column
6021 $cborder = $border_start;
6022 $this->y = $oy;
6023 $h = $this->h - $this->y - $this->bMargin;
6024 } else { // middle column
6025 $cborder = $border_middle;
6026 $h = $this->h - $this->y - $this->bMargin;
6027 $resth -= $h;
6028 }
6029 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6030 } // end for each column
6031 } elseif ($page == $endpage) { // last page
6032 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
6033 $this->selectColumn($column);
6034 if ($this->rtl) {
6035 $this->x -= $mc_margin['R'];
6036 } else {
6037 $this->x += $mc_margin['L'];
6038 }
6039 if ($column == $endcolumn) {
6040 // end column
6041 $cborder = $border_end;
6042 $h = $currentY - $this->y;
6043 if ($resth > $h) {
6044 $h = $resth;
6045 }
6046 } else {
6047 // middle column
6048 $cborder = $border_middle;
6049 $h = $this->h - $this->y - $this->bMargin;
6050 $resth -= $h;
6051 }
6052 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6053 } // end for each column
6054 } else { // middle page
6055 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6056 $this->selectColumn($column);
6057 if ($this->rtl) {
6058 $this->x -= $mc_margin['R'];
6059 } else {
6060 $this->x += $mc_margin['L'];
6061 }
6062 $cborder = $border_middle;
6063 $h = $this->h - $this->y - $this->bMargin;
6064 $resth -= $h;
6065 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6066 } // end for each column
6067 }
6068 if ($cborder OR $fill) {
6069 $offsetlen = strlen($ccode);
6070 // draw border and fill
6071 if ($this->inxobj) {
6072 // we are inside an XObject template
6073 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6074 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6075 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6076 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
6077 } else {
6078 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
6079 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
6080 }
6081 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6082 $pstart = substr($pagebuff, 0, $pagemark);
6083 $pend = substr($pagebuff, $pagemark);
6084 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6085 } else {
6086 if (end($this->transfmrk[$this->page]) !== false) {
6087 $pagemarkkey = key($this->transfmrk[$this->page]);
6088 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
6089 $this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
6090 } elseif ($this->InFooter) {
6091 $pagemark = $this->footerpos[$this->page];
6092 $this->footerpos[$this->page] += $offsetlen;
6093 } else {
6094 $pagemark = $this->intmrk[$this->page];
6095 $this->intmrk[$this->page] += $offsetlen;
6096 }
6097 $pagebuff = $this->getPageBuffer($this->page);
6098 $pstart = substr($pagebuff, 0, $pagemark);
6099 $pend = substr($pagebuff, $pagemark);
6100 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6101 }
6102 }
6103 } // end for each page
6104 // restore page regions check
6105 $this->check_page_regions = $check_page_regions;
6106 // Get end-of-cell Y position
6107 $currentY = $this->GetY();
6108 // restore previous values
6109 if ($this->num_columns > 1) {
6110 $this->selectColumn();
6111 } else {
6112 // restore original margins
6113 $this->lMargin = $lMargin;
6114 $this->rMargin = $rMargin;
6115 if ($this->page > $startpage) {
6116 // check for margin variations between pages (i.e. booklet mode)
6117 $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
6118 $dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
6119 if (($dl != 0) OR ($dr != 0)) {
6120 $this->lMargin += $dl;
6121 $this->rMargin += $dr;
6122 }
6123 }
6124 }
6125 if ($ln > 0) {
6126 //Go to the beginning of the next line
6127 $this->SetY($currentY + $mc_margin['B']);
6128 if ($ln == 2) {
6129 $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6130 }
6131 } else {
6132 // go left or right by case
6133 $this->setPage($startpage);
6134 $this->y = $y;
6135 $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6136 }
6137 $this->setContentMark();
6138 $this->cell_padding = $prev_cell_padding;
6139 $this->cell_margin = $prev_cell_margin;
6140 $this->clMargin = $this->lMargin;
6141 $this->crMargin = $this->rMargin;
6142 return $nl;
6143 }
6144
6145 /**
6146 * This method return the estimated number of lines for print a simple text string using Multicell() method.
6147 * @param $txt (string) String for calculating his height
6148 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6149 * @param $reseth (boolean) if true reset the last cell height (default false).
6150 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6151 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6152 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6153 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6154 * @author Alexander Escalona Fern\E1ndez, Nicola Asuni
6155 * @public
6156 * @since 4.5.011
6157 */
6158 public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6159 if ($txt === NULL) {
6160 return 0;
6161 }
6162 if ($txt === '') {
6163 // empty string
6164 return 1;
6165 }
6166 // adjust internal padding
6167 $prev_cell_padding = $this->cell_padding;
6168 $prev_lasth = $this->lasth;
6169 if (is_array($cellpadding)) {
6170 $this->cell_padding = $cellpadding;
6171 }
6172 $this->adjustCellPadding($border);
6173 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
6174 if ($this->rtl) {
6175 $w = $this->x - $this->lMargin;
6176 } else {
6177 $w = $this->w - $this->rMargin - $this->x;
6178 }
6179 }
6180 $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6181 if ($reseth) {
6182 // reset row height
6183 $this->resetLastH();
6184 }
6185 $lines = 1;
6186 $sum = 0;
6187 $chars = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont), $txt, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6188 $charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6189 $length = count($chars);
6190 $lastSeparator = -1;
6191 for ($i = 0; $i < $length; ++$i) {
6192 $c = $chars[$i];
6193 $charWidth = $charsWidth[$i];
6194 if (($c != 160)
6195 AND (($c == 173)
6196 OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6197 OR (($c == 45)
6198 AND ($i > 0) AND ($i < ($length - 1))
6199 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i - 1)], $this->isunicode))
6200 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6201 )
6202 )
6203 ) {
6204 $lastSeparator = $i;
6205 }
6206 if ((($sum + $charWidth) > $wmax) OR ($c == 10)) {
6207 ++$lines;
6208 if ($c == 10) {
6209 $lastSeparator = -1;
6210 $sum = 0;
6211 } elseif ($lastSeparator != -1) {
6212 $i = $lastSeparator;
6213 $lastSeparator = -1;
6214 $sum = 0;
6215 } else {
6216 $sum = $charWidth;
6217 }
6218 } else {
6219 $sum += $charWidth;
6220 }
6221 }
6222 if ($chars[($length - 1)] == 10) {
6223 --$lines;
6224 }
6225 $this->cell_padding = $prev_cell_padding;
6226 $this->lasth = $prev_lasth;
6227 return $lines;
6228 }
6229
6230 /**
6231 * This method return the estimated height needed for printing a simple text string using the Multicell() method.
6232 * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6233 * @pre
6234 * // store current object
6235 * $pdf->startTransaction();
6236 * // store starting values
6237 * $start_y = $pdf->GetY();
6238 * $start_page = $pdf->getPage();
6239 * // call your printing functions with your parameters
6240 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6241 * $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);
6242 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6243 * // get the new Y
6244 * $end_y = $pdf->GetY();
6245 * $end_page = $pdf->getPage();
6246 * // calculate height
6247 * $height = 0;
6248 * if ($end_page == $start_page) {
6249 * $height = $end_y - $start_y;
6250 * } else {
6251 * for ($page=$start_page; $page <= $end_page; ++$page) {
6252 * $this->setPage($page);
6253 * if ($page == $start_page) {
6254 * // first page
6255 * $height = $this->h - $start_y - $this->bMargin;
6256 * } elseif ($page == $end_page) {
6257 * // last page
6258 * $height = $end_y - $this->tMargin;
6259 * } else {
6260 * $height = $this->h - $this->tMargin - $this->bMargin;
6261 * }
6262 * }
6263 * }
6264 * // restore previous object
6265 * $pdf = $pdf->rollbackTransaction();
6266 *
6267 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6268 * @param $txt (string) String for calculating his height
6269 * @param $reseth (boolean) if true reset the last cell height (default false).
6270 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6271 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6272 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6273 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6274 * @author Nicola Asuni, Alexander Escalona Fern\E1ndez
6275 * @public
6276 */
6277 public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6278 // adjust internal padding
6279 $prev_cell_padding = $this->cell_padding;
6280 $prev_lasth = $this->lasth;
6281 if (is_array($cellpadding)) {
6282 $this->cell_padding = $cellpadding;
6283 }
6284 $this->adjustCellPadding($border);
6285 $lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6286 $height = $this->getCellHeight(($lines * $this->FontSize), $autopadding);
6287 $this->cell_padding = $prev_cell_padding;
6288 $this->lasth = $prev_lasth;
6289 return $height;
6290 }
6291
6292 /**
6293 * This method prints text from the current position.<br />
6294 * @param $h (float) Line height
6295 * @param $txt (string) String to print
6296 * @param $link (mixed) URL or identifier returned by AddLink()
6297 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
6298 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
6299 * @param $ln (boolean) if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
6300 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
6301 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
6302 * @param $firstblock (boolean) if true the string is the starting of a line.
6303 * @param $maxh (float) maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature.
6304 * @param $wadj (float) first line width will be reduced by this amount (used in HTML mode).
6305 * @param $margin (array) margin array of the parent container
6306 * @return mixed Return the number of cells or the remaining string if $firstline = true.
6307 * @public
6308 * @since 1.5
6309 */
6310 public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
6311 // check page for no-write regions and adapt page margins if necessary
6312 list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
6313 if (strlen($txt) == 0) {
6314 // fix empty text
6315 $txt = ' ';
6316 }
6317 if ($margin === '') {
6318 // set default margins
6319 $margin = $this->cell_margin;
6320 }
6321 // remove carriage returns
6322 $s = str_replace("\r", '', $txt);
6323 // check if string contains arabic text
6324 if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $s)) {
6325 $arabic = true;
6326 } else {
6327 $arabic = false;
6328 }
6329 // check if string contains RTL text
6330 if ($arabic OR ($this->tmprtl == 'R') OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $s)) {
6331 $rtlmode = true;
6332 } else {
6333 $rtlmode = false;
6334 }
6335 // get a char width
6336 $chrwidth = $this->GetCharWidth(46); // dot character
6337 // get array of unicode values
6338 $chars = TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont);
6339 // calculate maximum width for a single character on string
6340 $chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
6341 array_walk($chrw, array($this, 'getRawCharWidth'));
6342 $maxchwidth = max($chrw);
6343 // get array of chars
6344 $uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
6345 // get the number of characters
6346 $nb = count($chars);
6347 // replacement for SHY character (minus symbol)
6348 $shy_replacement = 45;
6349 $shy_replacement_char = TCPDF_FONTS::unichr($shy_replacement, $this->isunicode);
6350 // widht for SHY replacement
6351 $shy_replacement_width = $this->GetCharWidth($shy_replacement);
6352 // page width
6353 $pw = $w = $this->w - $this->lMargin - $this->rMargin;
6354 // calculate remaining line width ($w)
6355 if ($this->rtl) {
6356 $w = $this->x - $this->lMargin;
6357 } else {
6358 $w = $this->w - $this->rMargin - $this->x;
6359 }
6360 // max column width
6361 $wmax = ($w - $wadj);
6362 if (!$firstline) {
6363 $wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
6364 }
6365 if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
6366 // the maximum width character do not fit on column
6367 return '';
6368 }
6369 // minimum row height
6370 $row_height = max($h, $this->getCellHeight($this->FontSize));
6371 // max Y
6372 $maxy = $this->y + $maxh - max($row_height, $h);
6373 $start_page = $this->page;
6374 $i = 0; // character position
6375 $j = 0; // current starting position
6376 $sep = -1; // position of the last blank space
6377 $prevsep = $sep; // previous separator
6378 $shy = false; // true if the last blank is a soft hypen (SHY)
6379 $prevshy = $shy; // previous shy mode
6380 $l = 0; // current string length
6381 $nl = 0; //number of lines
6382 $linebreak = false;
6383 $pc = 0; // previous character
6384 // for each character
6385 while ($i < $nb) {
6386 if (($maxh > 0) AND ($this->y > $maxy) ) {
6387 break;
6388 }
6389 //Get the current character
6390 $c = $chars[$i];
6391 if ($c == 10) { // 10 = "\n" = new line
6392 //Explicit line break
6393 if ($align == 'J') {
6394 if ($this->rtl) {
6395 $talign = 'R';
6396 } else {
6397 $talign = 'L';
6398 }
6399 } else {
6400 $talign = $align;
6401 }
6402 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6403 if ($firstline) {
6404 $startx = $this->x;
6405 $tmparr = array_slice($chars, $j, ($i - $j));
6406 if ($rtlmode) {
6407 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6408 }
6409 $linew = $this->GetArrStringWidth($tmparr);
6410 unset($tmparr);
6411 if ($this->rtl) {
6412 $this->endlinex = $startx - $linew;
6413 } else {
6414 $this->endlinex = $startx + $linew;
6415 }
6416 $w = $linew;
6417 $tmpcellpadding = $this->cell_padding;
6418 if ($maxh == 0) {
6419 $this->SetCellPadding(0);
6420 }
6421 }
6422 if ($firstblock AND $this->isRTLTextDir()) {
6423 $tmpstr = $this->stringRightTrim($tmpstr);
6424 }
6425 // Skip newlines at the begining of a page or column
6426 if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
6427 $this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6428 }
6429 unset($tmpstr);
6430 if ($firstline) {
6431 $this->cell_padding = $tmpcellpadding;
6432 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6433 }
6434 ++$nl;
6435 $j = $i + 1;
6436 $l = 0;
6437 $sep = -1;
6438 $prevsep = $sep;
6439 $shy = false;
6440 // account for margin changes
6441 if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6442 $this->AcceptPageBreak();
6443 if ($this->rtl) {
6444 $this->x -= $margin['R'];
6445 } else {
6446 $this->x += $margin['L'];
6447 }
6448 $this->lMargin += $margin['L'];
6449 $this->rMargin += $margin['R'];
6450 }
6451 $w = $this->getRemainingWidth();
6452 $wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
6453 } else {
6454 // 160 is the non-breaking space.
6455 // 173 is SHY (Soft Hypen).
6456 // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6457 // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6458 // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6459 if (($c != 160)
6460 AND (($c == 173)
6461 OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6462 OR (($c == 45)
6463 AND ($i < ($nb - 1))
6464 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($pc, $this->isunicode))
6465 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6466 )
6467 )
6468 ) {
6469 // update last blank space position
6470 $prevsep = $sep;
6471 $sep = $i;
6472 // check if is a SHY
6473 if (($c == 173) OR ($c == 45)) {
6474 $prevshy = $shy;
6475 $shy = true;
6476 if ($pc == 45) {
6477 $tmp_shy_replacement_width = 0;
6478 $tmp_shy_replacement_char = '';
6479 } else {
6480 $tmp_shy_replacement_width = $shy_replacement_width;
6481 $tmp_shy_replacement_char = $shy_replacement_char;
6482 }
6483 } else {
6484 $shy = false;
6485 }
6486 }
6487 // update string length
6488 if ($this->isUnicodeFont() AND ($arabic)) {
6489 // with bidirectional algorithm some chars may be changed affecting the line length
6490 // *** very slow ***
6491 $l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont));
6492 } else {
6493 $l += $this->GetCharWidth($c);
6494 }
6495 if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) >= $wmax))) {
6496 if (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) {
6497 $sep = $prevsep;
6498 $shy = $prevshy;
6499 }
6500 // we have reached the end of column
6501 if ($sep == -1) {
6502 // check if the line was already started
6503 if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
6504 OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
6505 // print a void cell and go to next line
6506 $this->Cell($w, $h, '', 0, 1);
6507 $linebreak = true;
6508 if ($firstline) {
6509 return (TCPDF_FONTS::UniArrSubString($uchars, $j));
6510 }
6511 } else {
6512 // truncate the word because do not fit on column
6513 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6514 if ($firstline) {
6515 $startx = $this->x;
6516 $tmparr = array_slice($chars, $j, ($i - $j));
6517 if ($rtlmode) {
6518 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6519 }
6520 $linew = $this->GetArrStringWidth($tmparr);
6521 unset($tmparr);
6522 if ($this->rtl) {
6523 $this->endlinex = $startx - $linew;
6524 } else {
6525 $this->endlinex = $startx + $linew;
6526 }
6527 $w = $linew;
6528 $tmpcellpadding = $this->cell_padding;
6529 if ($maxh == 0) {
6530 $this->SetCellPadding(0);
6531 }
6532 }
6533 if ($firstblock AND $this->isRTLTextDir()) {
6534 $tmpstr = $this->stringRightTrim($tmpstr);
6535 }
6536 $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6537 unset($tmpstr);
6538 if ($firstline) {
6539 $this->cell_padding = $tmpcellpadding;
6540 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6541 }
6542 $j = $i;
6543 --$i;
6544 }
6545 } else {
6546 // word wrapping
6547 if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6548 $endspace = 1;
6549 } else {
6550 $endspace = 0;
6551 }
6552 // check the length of the next string
6553 $strrest = TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace));
6554 $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $this->stringTrim($strrest));
6555 if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
6556 // truncate the word because do not fit on a full page width
6557 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6558 if ($firstline) {
6559 $startx = $this->x;
6560 $tmparr = array_slice($chars, $j, ($i - $j));
6561 if ($rtlmode) {
6562 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6563 }
6564 $linew = $this->GetArrStringWidth($tmparr);
6565 unset($tmparr);
6566 if ($this->rtl) {
6567 $this->endlinex = ($startx - $linew);
6568 } else {
6569 $this->endlinex = ($startx + $linew);
6570 }
6571 $w = $linew;
6572 $tmpcellpadding = $this->cell_padding;
6573 if ($maxh == 0) {
6574 $this->SetCellPadding(0);
6575 }
6576 }
6577 if ($firstblock AND $this->isRTLTextDir()) {
6578 $tmpstr = $this->stringRightTrim($tmpstr);
6579 }
6580 $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6581 unset($tmpstr);
6582 if ($firstline) {
6583 $this->cell_padding = $tmpcellpadding;
6584 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6585 }
6586 $j = $i;
6587 --$i;
6588 } else {
6589 // word wrapping
6590 if ($shy) {
6591 // add hypen (minus symbol) at the end of the line
6592 $shy_width = $tmp_shy_replacement_width;
6593 if ($this->rtl) {
6594 $shy_char_left = $tmp_shy_replacement_char;
6595 $shy_char_right = '';
6596 } else {
6597 $shy_char_left = '';
6598 $shy_char_right = $tmp_shy_replacement_char;
6599 }
6600 } else {
6601 $shy_width = 0;
6602 $shy_char_left = '';
6603 $shy_char_right = '';
6604 }
6605 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
6606 if ($firstline) {
6607 $startx = $this->x;
6608 $tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6609 if ($rtlmode) {
6610 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6611 }
6612 $linew = $this->GetArrStringWidth($tmparr);
6613 unset($tmparr);
6614 if ($this->rtl) {
6615 $this->endlinex = $startx - $linew - $shy_width;
6616 } else {
6617 $this->endlinex = $startx + $linew + $shy_width;
6618 }
6619 $w = $linew;
6620 $tmpcellpadding = $this->cell_padding;
6621 if ($maxh == 0) {
6622 $this->SetCellPadding(0);
6623 }
6624 }
6625 // print the line
6626 if ($firstblock AND $this->isRTLTextDir()) {
6627 $tmpstr = $this->stringRightTrim($tmpstr);
6628 }
6629 $this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6630 unset($tmpstr);
6631 if ($firstline) {
6632 if ($chars[$sep] == 45) {
6633 $endspace += 1;
6634 }
6635 // return the remaining text
6636 $this->cell_padding = $tmpcellpadding;
6637 return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
6638 }
6639 $i = $sep;
6640 $sep = -1;
6641 $shy = false;
6642 $j = ($i + 1);
6643 }
6644 }
6645 // account for margin changes
6646 if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6647 $this->AcceptPageBreak();
6648 if ($this->rtl) {
6649 $this->x -= $margin['R'];
6650 } else {
6651 $this->x += $margin['L'];
6652 }
6653 $this->lMargin += $margin['L'];
6654 $this->rMargin += $margin['R'];
6655 }
6656 $w = $this->getRemainingWidth();
6657 $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6658 if ($linebreak) {
6659 $linebreak = false;
6660 } else {
6661 ++$nl;
6662 $l = 0;
6663 }
6664 }
6665 }
6666 // save last character
6667 $pc = $c;
6668 ++$i;
6669 } // end while i < nb
6670 // print last substring (if any)
6671 if ($l > 0) {
6672 switch ($align) {
6673 case 'J':
6674 case 'C': {
6675 $w = $w;
6676 break;
6677 }
6678 case 'L': {
6679 if ($this->rtl) {
6680 $w = $w;
6681 } else {
6682 $w = $l;
6683 }
6684 break;
6685 }
6686 case 'R': {
6687 if ($this->rtl) {
6688 $w = $l;
6689 } else {
6690 $w = $w;
6691 }
6692 break;
6693 }
6694 default: {
6695 $w = $l;
6696 break;
6697 }
6698 }
6699 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
6700 if ($firstline) {
6701 $startx = $this->x;
6702 $tmparr = array_slice($chars, $j, ($nb - $j));
6703 if ($rtlmode) {
6704 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6705 }
6706 $linew = $this->GetArrStringWidth($tmparr);
6707 unset($tmparr);
6708 if ($this->rtl) {
6709 $this->endlinex = $startx - $linew;
6710 } else {
6711 $this->endlinex = $startx + $linew;
6712 }
6713 $w = $linew;
6714 $tmpcellpadding = $this->cell_padding;
6715 if ($maxh == 0) {
6716 $this->SetCellPadding(0);
6717 }
6718 }
6719 if ($firstblock AND $this->isRTLTextDir()) {
6720 $tmpstr = $this->stringRightTrim($tmpstr);
6721 }
6722 $this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6723 unset($tmpstr);
6724 if ($firstline) {
6725 $this->cell_padding = $tmpcellpadding;
6726 return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
6727 }
6728 ++$nl;
6729 }
6730 if ($firstline) {
6731 return '';
6732 }
6733 return $nl;
6734 }
6735
6736 /**
6737 * Returns the remaining width between the current position and margins.
6738 * @return int Return the remaining width
6739 * @protected
6740 */
6741 protected function getRemainingWidth() {
6742 list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
6743 if ($this->rtl) {
6744 return ($this->x - $this->lMargin);
6745 } else {
6746 return ($this->w - $this->rMargin - $this->x);
6747 }
6748 }
6749
6750 /**
6751 * Set the block dimensions accounting for page breaks and page/column fitting
6752 * @param $w (float) width
6753 * @param $h (float) height
6754 * @param $x (float) X coordinate
6755 * @param $y (float) Y coodiante
6756 * @param $fitonpage (boolean) if true the block is resized to not exceed page dimensions.
6757 * @return array($w, $h, $x, $y)
6758 * @protected
6759 * @since 5.5.009 (2010-07-05)
6760 */
6761 protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
6762 if ($w <= 0) {
6763 // set maximum width
6764 $w = ($this->w - $this->lMargin - $this->rMargin);
6765 if ($w <= 0) {
6766 $w = 1;
6767 }
6768 }
6769 if ($h <= 0) {
6770 // set maximum height
6771 $h = ($this->PageBreakTrigger - $this->tMargin);
6772 if ($h <= 0) {
6773 $h = 1;
6774 }
6775 }
6776 // resize the block to be vertically contained on a single page or single column
6777 if ($fitonpage OR $this->AutoPageBreak) {
6778 $ratio_wh = ($w / $h);
6779 if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
6780 $h = $this->PageBreakTrigger - $this->tMargin;
6781 $w = ($h * $ratio_wh);
6782 }
6783 // resize the block to be horizontally contained on a single page or single column
6784 if ($fitonpage) {
6785 $maxw = ($this->w - $this->lMargin - $this->rMargin);
6786 if ($w > $maxw) {
6787 $w = $maxw;
6788 $h = ($w / $ratio_wh);
6789 }
6790 }
6791 }
6792 // Check whether we need a new page or new column first as this does not fit
6793 $prev_x = $this->x;
6794 $prev_y = $this->y;
6795 if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
6796 $y = $this->y;
6797 if ($this->rtl) {
6798 $x += ($prev_x - $this->x);
6799 } else {
6800 $x += ($this->x - $prev_x);
6801 }
6802 $this->newline = true;
6803 }
6804 // resize the block to be contained on the remaining available page or column space
6805 if ($fitonpage) {
6806 $ratio_wh = ($w / $h);
6807 if (($y + $h) > $this->PageBreakTrigger) {
6808 $h = $this->PageBreakTrigger - $y;
6809 $w = ($h * $ratio_wh);
6810 }
6811 if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
6812 $w = $this->w - $this->rMargin - $x;
6813 $h = ($w / $ratio_wh);
6814 } elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
6815 $w = $x - $this->lMargin;
6816 $h = ($w / $ratio_wh);
6817 }
6818 }
6819 return array($w, $h, $x, $y);
6820 }
6821
6822 /**
6823 * Puts an image in the page.
6824 * The upper-left corner must be given.
6825 * The dimensions can be specified in different ways:<ul>
6826 * <li>explicit width and height (expressed in user unit)</li>
6827 * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
6828 * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
6829 * 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;
6830 * The format can be specified explicitly or inferred from the file extension.<br />
6831 * It is possible to put a link on the image.<br />
6832 * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
6833 * @param $file (string) Name of the file containing the image or a '@' character followed by the image data string. To link an image without embedding it on the document, set an asterisk character before the URL (i.e.: '*http://www.example.com/image.jpg').
6834 * @param $x (float) Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
6835 * @param $y (float) Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
6836 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
6837 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
6838 * @param $type (string) Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
6839 * @param $link (mixed) URL or identifier returned by AddLink().
6840 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
6841 * @param $resize (mixed) If true resize (reduce) the image to fit $w and $h (requires GD or ImageMagick library); if false do not resize; if 2 force resize in all cases (upscaling and downscaling).
6842 * @param $dpi (int) dot-per-inch resolution used on resize
6843 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
6844 * @param $ismask (boolean) true if this image is a mask, false otherwise
6845 * @param $imgmask (mixed) image object returned by this function or false
6846 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6847 * @param $fitbox (mixed) If not false scale image dimensions proportionally to fit within the ($w, $h) box. $fitbox can be true or a 2 characters string indicating the image alignment inside the box. The first character indicate the horizontal alignment (L = left, C = center, R = right) the second character indicate the vertical algnment (T = top, M = middle, B = bottom).
6848 * @param $hidden (boolean) If true do not display the image.
6849 * @param $fitonpage (boolean) If true the image is resized to not exceed page dimensions.
6850 * @param $alt (boolean) If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
6851 * @param $altimgs (array) Array of alternate images IDs. Each alternative image must be an array with two values: an integer representing the image ID (the value returned by the Image method) and a boolean value to indicate if the image is the default for printing.
6852 * @return image information
6853 * @public
6854 * @since 1.1
6855 */
6856 public function Image($file, $x='', $y='', $w=0, $h=0, $type='', $link='', $align='', $resize=false, $dpi=300, $palign='', $ismask=false, $imgmask=false, $border=0, $fitbox=false, $hidden=false, $fitonpage=false, $alt=false, $altimgs=array()) {
6857 if ($this->state != 2) {
6858 return;
6859 }
6860 if ($x === '') {
6861 $x = $this->x;
6862 }
6863 if ($y === '') {
6864 $y = $this->y;
6865 }
6866 // check page for no-write regions and adapt page margins if necessary
6867 list($x, $y) = $this->checkPageRegions($h, $x, $y);
6868 $exurl = ''; // external streams
6869 $imsize = FALSE;
6870 // check if we are passing an image as file or string
6871 if ($file[0] === '@') {
6872 // image from string
6873 $imgdata = substr($file, 1);
6874 } else { // image file
6875 if ($file[0] === '*') {
6876 // image as external stream
6877 $file = substr($file, 1);
6878 $exurl = $file;
6879 }
6880 // check if is a local file
6881 if (!@file_exists($file)) {
6882 // try to encode spaces on filename
6883 $tfile = str_replace(' ', '%20', $file);
6884 if (@file_exists($tfile)) {
6885 $file = $tfile;
6886 }
6887 }
6888 if (($imsize = @getimagesize($file)) === FALSE) {
6889 if (in_array($file, $this->imagekeys)) {
6890 // get existing image data
6891 $info = $this->getImageBuffer($file);
6892 $imsize = array($info['w'], $info['h']);
6893 } elseif (strpos($file, '__tcpdf_img') === FALSE) {
6894 $imgdata = TCPDF_STATIC::fileGetContents($file);
6895 }
6896 }
6897 }
6898 if (!empty($imgdata)) {
6899 // copy image to cache
6900 $original_file = $file;
6901 $file = TCPDF_STATIC::getObjFilename('img');
6902 $fp = fopen($file, 'w');
6903 if (!$fp) {
6904 $this->Error('Unable to write file: '.$file);
6905 }
6906 fwrite($fp, $imgdata);
6907 fclose($fp);
6908 unset($imgdata);
6909 $imsize = @getimagesize($file);
6910 if ($imsize === FALSE) {
6911 unlink($file);
6912 $file = $original_file;
6913 } else {
6914 $this->cached_files[] = $file;
6915 }
6916 }
6917 if ($imsize === FALSE) {
6918 if (($w > 0) AND ($h > 0)) {
6919 // get measures from specified data
6920 $pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6921 $ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6922 $imsize = array($pw, $ph);
6923 } else {
6924 $this->Error('[Image] Unable to fetch image: '.$file);
6925 }
6926 }
6927 // file hash
6928 $filehash = md5($this->file_id.$file);
6929 // get original image width and height in pixels
6930 list($pixw, $pixh) = $imsize;
6931 // calculate image width and height on document
6932 if (($w <= 0) AND ($h <= 0)) {
6933 // convert image size to document unit
6934 $w = $this->pixelsToUnits($pixw);
6935 $h = $this->pixelsToUnits($pixh);
6936 } elseif ($w <= 0) {
6937 $w = $h * $pixw / $pixh;
6938 } elseif ($h <= 0) {
6939 $h = $w * $pixh / $pixw;
6940 } elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
6941 if (strlen($fitbox) !== 2) {
6942 // set default alignment
6943 $fitbox = '--';
6944 }
6945 // scale image dimensions proportionally to fit within the ($w, $h) box
6946 if ((($w * $pixh) / ($h * $pixw)) < 1) {
6947 // store current height
6948 $oldh = $h;
6949 // calculate new height
6950 $h = $w * $pixh / $pixw;
6951 // height difference
6952 $hdiff = ($oldh - $h);
6953 // vertical alignment
6954 switch (strtoupper($fitbox[1])) {
6955 case 'T': {
6956 break;
6957 }
6958 case 'M': {
6959 $y += ($hdiff / 2);
6960 break;
6961 }
6962 case 'B': {
6963 $y += $hdiff;
6964 break;
6965 }
6966 }
6967 } else {
6968 // store current width
6969 $oldw = $w;
6970 // calculate new width
6971 $w = $h * $pixw / $pixh;
6972 // width difference
6973 $wdiff = ($oldw - $w);
6974 // horizontal alignment
6975 switch (strtoupper($fitbox[0])) {
6976 case 'L': {
6977 if ($this->rtl) {
6978 $x -= $wdiff;
6979 }
6980 break;
6981 }
6982 case 'C': {
6983 if ($this->rtl) {
6984 $x -= ($wdiff / 2);
6985 } else {
6986 $x += ($wdiff / 2);
6987 }
6988 break;
6989 }
6990 case 'R': {
6991 if (!$this->rtl) {
6992 $x += $wdiff;
6993 }
6994 break;
6995 }
6996 }
6997 }
6998 }
6999 // fit the image on available space
7000 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
7001 // calculate new minimum dimensions in pixels
7002 $neww = round($w * $this->k * $dpi / $this->dpi);
7003 $newh = round($h * $this->k * $dpi / $this->dpi);
7004 // check if resize is necessary (resize is used only to reduce the image)
7005 $newsize = ($neww * $newh);
7006 $pixsize = ($pixw * $pixh);
7007 if (intval($resize) == 2) {
7008 $resize = true;
7009 } elseif ($newsize >= $pixsize) {
7010 $resize = false;
7011 }
7012 // check if image has been already added on document
7013 $newimage = true;
7014 if (in_array($file, $this->imagekeys)) {
7015 $newimage = false;
7016 // get existing image data
7017 $info = $this->getImageBuffer($file);
7018 if (strpos($file, '__tcpdf_imgmask_') === FALSE) {
7019 // check if the newer image is larger
7020 $oldsize = ($info['w'] * $info['h']);
7021 if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7022 $newimage = true;
7023 }
7024 }
7025 } elseif (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_imgmask_') === FALSE)) {
7026 // create temp image file (without alpha channel)
7027 $tempfile_plain = K_PATH_CACHE.'__tcpdf_imgmask_plain_'.$filehash;
7028 // create temp alpha file
7029 $tempfile_alpha = K_PATH_CACHE.'__tcpdf_imgmask_alpha_'.$filehash;
7030 // check for cached images
7031 if (in_array($tempfile_plain, $this->imagekeys)) {
7032 // get existing image data
7033 $info = $this->getImageBuffer($tempfile_plain);
7034 // check if the newer image is larger
7035 $oldsize = ($info['w'] * $info['h']);
7036 if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7037 $newimage = true;
7038 } else {
7039 $newimage = false;
7040 // embed mask image
7041 $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7042 // embed image, masked with previously embedded mask
7043 return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7044 }
7045 }
7046 }
7047 if ($newimage) {
7048 //First use of image, get info
7049 $type = strtolower($type);
7050 if ($type == '') {
7051 $type = TCPDF_IMAGES::getImageFileType($file, $imsize);
7052 } elseif ($type == 'jpg') {
7053 $type = 'jpeg';
7054 }
7055 $mqr = TCPDF_STATIC::get_mqr();
7056 TCPDF_STATIC::set_mqr(false);
7057 // Specific image handlers (defined on TCPDF_IMAGES CLASS)
7058 $mtd = '_parse'.$type;
7059 // GD image handler function
7060 $gdfunction = 'imagecreatefrom'.$type;
7061 $info = false;
7062 if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
7063 // TCPDF image functions
7064 $info = TCPDF_IMAGES::$mtd($file);
7065 if (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_imgmask_') === FALSE)
7066 AND (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns'])))) {
7067 return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
7068 }
7069 }
7070 if (($info === false) AND function_exists($gdfunction)) {
7071 try {
7072 // GD library
7073 $img = $gdfunction($file);
7074 if ($img !== false) {
7075 if ($resize) {
7076 $imgr = imagecreatetruecolor($neww, $newh);
7077 if (($type == 'gif') OR ($type == 'png')) {
7078 $imgr = TCPDF_IMAGES::setGDImageTransparency($imgr, $img);
7079 }
7080 imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7081 $img = $imgr;
7082 }
7083 if (($type == 'gif') OR ($type == 'png')) {
7084 $info = TCPDF_IMAGES::_toPNG($img);
7085 } else {
7086 $info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality);
7087 }
7088 }
7089 } catch(Exception $e) {
7090 $info = false;
7091 }
7092 }
7093 if (($info === false) AND extension_loaded('imagick')) {
7094 try {
7095 // ImageMagick library
7096 $img = new Imagick();
7097 if ($type == 'svg') {
7098 if ($file[0] === '@') {
7099 // image from string
7100 $svgimg = substr($file, 1);
7101 } else {
7102 // get SVG file content
7103 $svgimg = TCPDF_STATIC::fileGetContents($file);
7104 }
7105 if ($svgimg !== FALSE) {
7106 // get width and height
7107 $regs = array();
7108 if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7109 $svgtag = $regs[1];
7110 $tmp = array();
7111 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7112 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7113 $owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
7114 $svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7115 } else {
7116 $ow = $w;
7117 }
7118 $tmp = array();
7119 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7120 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7121 $ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
7122 $svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7123 } else {
7124 $oh = $h;
7125 }
7126 $tmp = array();
7127 if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7128 $vbw = ($ow * $this->imgscale * $this->k);
7129 $vbh = ($oh * $this->imgscale * $this->k);
7130 $vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
7131 $svgtag = $vbox.$svgtag;
7132 }
7133 $svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7134 }
7135 $img->readImageBlob($svgimg);
7136 }
7137 } else {
7138 $img->readImage($file);
7139 }
7140 if ($resize) {
7141 $img->resizeImage($neww, $newh, 10, 1, false);
7142 }
7143 $img->setCompressionQuality($this->jpeg_quality);
7144 $img->setImageFormat('jpeg');
7145 $tempname = TCPDF_STATIC::getObjFilename('img');
7146 $img->writeImage($tempname);
7147 $info = TCPDF_IMAGES::_parsejpeg($tempname);
7148 unlink($tempname);
7149 $img->destroy();
7150 } catch(Exception $e) {
7151 $info = false;
7152 }
7153 }
7154 if ($info === false) {
7155 // unable to process image
7156 return;
7157 }
7158 TCPDF_STATIC::set_mqr($mqr);
7159 if ($ismask) {
7160 // force grayscale
7161 $info['cs'] = 'DeviceGray';
7162 }
7163 if ($imgmask !== false) {
7164 $info['masked'] = $imgmask;
7165 }
7166 if (!empty($exurl)) {
7167 $info['exurl'] = $exurl;
7168 }
7169 // array of alternative images
7170 $info['altimgs'] = $altimgs;
7171 // add image to document
7172 $info['i'] = $this->setImageBuffer($file, $info);
7173 }
7174 // set alignment
7175 $this->img_rb_y = $y + $h;
7176 // set alignment
7177 if ($this->rtl) {
7178 if ($palign == 'L') {
7179 $ximg = $this->lMargin;
7180 } elseif ($palign == 'C') {
7181 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7182 } elseif ($palign == 'R') {
7183 $ximg = $this->w - $this->rMargin - $w;
7184 } else {
7185 $ximg = $x - $w;
7186 }
7187 $this->img_rb_x = $ximg;
7188 } else {
7189 if ($palign == 'L') {
7190 $ximg = $this->lMargin;
7191 } elseif ($palign == 'C') {
7192 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7193 } elseif ($palign == 'R') {
7194 $ximg = $this->w - $this->rMargin - $w;
7195 } else {
7196 $ximg = $x;
7197 }
7198 $this->img_rb_x = $ximg + $w;
7199 }
7200 if ($ismask OR $hidden) {
7201 // image is not displayed
7202 return $info['i'];
7203 }
7204 $xkimg = $ximg * $this->k;
7205 if (!$alt) {
7206 // only non-alternative immages will be set
7207 $this->_out(sprintf('q %F 0 0 %F %F %F cm /I%u Do Q', ($w * $this->k), ($h * $this->k), $xkimg, (($this->h - ($y + $h)) * $this->k), $info['i']));
7208 }
7209 if (!empty($border)) {
7210 $bx = $this->x;
7211 $by = $this->y;
7212 $this->x = $ximg;
7213 if ($this->rtl) {
7214 $this->x += $w;
7215 }
7216 $this->y = $y;
7217 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7218 $this->x = $bx;
7219 $this->y = $by;
7220 }
7221 if ($link) {
7222 $this->Link($ximg, $y, $w, $h, $link, 0);
7223 }
7224 // set pointer to align the next text/objects
7225 switch($align) {
7226 case 'T': {
7227 $this->y = $y;
7228 $this->x = $this->img_rb_x;
7229 break;
7230 }
7231 case 'M': {
7232 $this->y = $y + round($h/2);
7233 $this->x = $this->img_rb_x;
7234 break;
7235 }
7236 case 'B': {
7237 $this->y = $this->img_rb_y;
7238 $this->x = $this->img_rb_x;
7239 break;
7240 }
7241 case 'N': {
7242 $this->SetY($this->img_rb_y);
7243 break;
7244 }
7245 default:{
7246 break;
7247 }
7248 }
7249 $this->endlinex = $this->img_rb_x;
7250 if ($this->inxobj) {
7251 // we are inside an XObject template
7252 $this->xobjects[$this->xobjid]['images'][] = $info['i'];
7253 }
7254 return $info['i'];
7255 }
7256
7257 /**
7258 * Extract info from a PNG image with alpha channel using the Imagick or GD library.
7259 * @param $file (string) Name of the file containing the image.
7260 * @param $x (float) Abscissa of the upper-left corner.
7261 * @param $y (float) Ordinate of the upper-left corner.
7262 * @param $wpx (float) Original width of the image in pixels.
7263 * @param $hpx (float) original height of the image in pixels.
7264 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7265 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7266 * @param $type (string) Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
7267 * @param $link (mixed) URL or identifier returned by AddLink().
7268 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
7269 * @param $resize (boolean) If true resize (reduce) the image to fit $w and $h (requires GD library).
7270 * @param $dpi (int) dot-per-inch resolution used on resize
7271 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
7272 * @param $filehash (string) File hash used to build unique file names.
7273 * @author Nicola Asuni
7274 * @protected
7275 * @since 4.3.007 (2008-12-04)
7276 * @see Image()
7277 */
7278 protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
7279 // create temp images
7280 if (empty($filehash)) {
7281 $filehash = md5($this->file_id.$file);
7282 }
7283 // create temp image file (without alpha channel)
7284 $tempfile_plain = K_PATH_CACHE.'__tcpdf_imgmask_plain_'.$filehash;
7285 // create temp alpha file
7286 $tempfile_alpha = K_PATH_CACHE.'__tcpdf_imgmask_alpha_'.$filehash;
7287 $parsed = false;
7288 $parse_error = '';
7289 // ImageMagick extension
7290 if (($parsed === false) AND extension_loaded('imagick')) {
7291 try {
7292 // ImageMagick library
7293 $img = new Imagick();
7294 $img->readImage($file);
7295 // clone image object
7296 $imga = TCPDF_STATIC::objclone($img);
7297 // extract alpha channel
7298 if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
7299 $img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
7300 } else {
7301 $img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7302 $img->negateImage(true);
7303 }
7304 $img->setImageFormat('png');
7305 $img->writeImage($tempfile_alpha);
7306 // remove alpha channel
7307 if (method_exists($imga, 'setImageMatte')) {
7308 $imga->setImageMatte(false);
7309 } else {
7310 $imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7311 }
7312 $imga->setImageFormat('png');
7313 $imga->writeImage($tempfile_plain);
7314 $parsed = true;
7315 } catch (Exception $e) {
7316 // Imagemagick fails, try with GD
7317 $parse_error = 'Imagick library error: '.$e->getMessage();
7318 }
7319 }
7320 // GD extension
7321 if (($parsed === false) AND function_exists('imagecreatefrompng')) {
7322 try {
7323 // generate images
7324 $img = imagecreatefrompng($file);
7325 $imgalpha = imagecreate($wpx, $hpx);
7326 // generate gray scale palette (0 -> 255)
7327 for ($c = 0; $c < 256; ++$c) {
7328 ImageColorAllocate($imgalpha, $c, $c, $c);
7329 }
7330 // extract alpha channel
7331 for ($xpx = 0; $xpx < $wpx; ++$xpx) {
7332 for ($ypx = 0; $ypx < $hpx; ++$ypx) {
7333 $color = imagecolorat($img, $xpx, $ypx);
7334 // get and correct gamma color
7335 $alpha = $this->getGDgamma($img, $color);
7336 imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
7337 }
7338 }
7339 imagepng($imgalpha, $tempfile_alpha);
7340 imagedestroy($imgalpha);
7341 // extract image without alpha channel
7342 $imgplain = imagecreatetruecolor($wpx, $hpx);
7343 imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7344 imagepng($imgplain, $tempfile_plain);
7345 imagedestroy($imgplain);
7346 $parsed = true;
7347 } catch (Exception $e) {
7348 // GD fails
7349 $parse_error = 'GD library error: '.$e->getMessage();
7350 }
7351 }
7352 if ($parsed === false) {
7353 if (empty($parse_error)) {
7354 $this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
7355 } else {
7356 $this->Error($parse_error);
7357 }
7358 }
7359 // embed mask image
7360 $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7361 // embed image, masked with previously embedded mask
7362 $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7363 // remove temp files
7364 unlink($tempfile_alpha);
7365 unlink($tempfile_plain);
7366 }
7367
7368 /**
7369 * Get the GD-corrected PNG gamma value from alpha color
7370 * @param $img (int) GD image Resource ID.
7371 * @param $c (int) alpha color
7372 * @protected
7373 * @since 4.3.007 (2008-12-04)
7374 */
7375 protected function getGDgamma($img, $c) {
7376 if (!isset($this->gdgammacache['#'.$c])) {
7377 $colors = imagecolorsforindex($img, $c);
7378 // GD alpha is only 7 bit (0 -> 127)
7379 $this->gdgammacache['#'.$c] = (((127 - $colors['alpha']) / 127) * 255);
7380 // correct gamma
7381 $this->gdgammacache['#'.$c] = (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255);
7382 // store the latest values on cache to improve performances
7383 if (count($this->gdgammacache) > 8) {
7384 // remove one element from the cache array
7385 array_shift($this->gdgammacache);
7386 }
7387 }
7388 return $this->gdgammacache['#'.$c];
7389 }
7390
7391 /**
7392 * Performs a line break.
7393 * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
7394 * @param $h (float) The height of the break. By default, the value equals the height of the last printed cell.
7395 * @param $cell (boolean) if true add the current left (or right o for RTL) padding to the X coordinate
7396 * @public
7397 * @since 1.0
7398 * @see Cell()
7399 */
7400 public function Ln($h='', $cell=false) {
7401 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'])) {
7402 // revove vertical space from the top of the column
7403 return;
7404 }
7405 if ($cell) {
7406 if ($this->rtl) {
7407 $cellpadding = $this->cell_padding['R'];
7408 } else {
7409 $cellpadding = $this->cell_padding['L'];
7410 }
7411 } else {
7412 $cellpadding = 0;
7413 }
7414 if ($this->rtl) {
7415 $this->x = $this->w - $this->rMargin - $cellpadding;
7416 } else {
7417 $this->x = $this->lMargin + $cellpadding;
7418 }
7419 if (is_string($h)) {
7420 $this->y += $this->lasth;
7421 } else {
7422 $this->y += $h;
7423 }
7424 $this->newline = true;
7425 }
7426
7427 /**
7428 * Returns the relative X value of current position.
7429 * The value is relative to the left border for LTR languages and to the right border for RTL languages.
7430 * @return float
7431 * @public
7432 * @since 1.2
7433 * @see SetX(), GetY(), SetY()
7434 */
7435 public function GetX() {
7436 //Get x position
7437 if ($this->rtl) {
7438 return ($this->w - $this->x);
7439 } else {
7440 return $this->x;
7441 }
7442 }
7443
7444 /**
7445 * Returns the absolute X value of current position.
7446 * @return float
7447 * @public
7448 * @since 1.2
7449 * @see SetX(), GetY(), SetY()
7450 */
7451 public function GetAbsX() {
7452 return $this->x;
7453 }
7454
7455 /**
7456 * Returns the ordinate of the current position.
7457 * @return float
7458 * @public
7459 * @since 1.0
7460 * @see SetY(), GetX(), SetX()
7461 */
7462 public function GetY() {
7463 return $this->y;
7464 }
7465
7466 /**
7467 * Defines the abscissa of the current position.
7468 * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
7469 * @param $x (float) The value of the abscissa in user units.
7470 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7471 * @public
7472 * @since 1.2
7473 * @see GetX(), GetY(), SetY(), SetXY()
7474 */
7475 public function SetX($x, $rtloff=false) {
7476 $x = floatval($x);
7477 if (!$rtloff AND $this->rtl) {
7478 if ($x >= 0) {
7479 $this->x = $this->w - $x;
7480 } else {
7481 $this->x = abs($x);
7482 }
7483 } else {
7484 if ($x >= 0) {
7485 $this->x = $x;
7486 } else {
7487 $this->x = $this->w + $x;
7488 }
7489 }
7490 if ($this->x < 0) {
7491 $this->x = 0;
7492 }
7493 if ($this->x > $this->w) {
7494 $this->x = $this->w;
7495 }
7496 }
7497
7498 /**
7499 * Moves the current abscissa back to the left margin and sets the ordinate.
7500 * If the passed value is negative, it is relative to the bottom of the page.
7501 * @param $y (float) The value of the ordinate in user units.
7502 * @param $resetx (bool) if true (default) reset the X position.
7503 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7504 * @public
7505 * @since 1.0
7506 * @see GetX(), GetY(), SetY(), SetXY()
7507 */
7508 public function SetY($y, $resetx=true, $rtloff=false) {
7509 $y = floatval($y);
7510 if ($resetx) {
7511 //reset x
7512 if (!$rtloff AND $this->rtl) {
7513 $this->x = $this->w - $this->rMargin;
7514 } else {
7515 $this->x = $this->lMargin;
7516 }
7517 }
7518 if ($y >= 0) {
7519 $this->y = $y;
7520 } else {
7521 $this->y = $this->h + $y;
7522 }
7523 if ($this->y < 0) {
7524 $this->y = 0;
7525 }
7526 if ($this->y > $this->h) {
7527 $this->y = $this->h;
7528 }
7529 }
7530
7531 /**
7532 * Defines the abscissa and ordinate of the current position.
7533 * If the passed values are negative, they are relative respectively to the right and bottom of the page.
7534 * @param $x (float) The value of the abscissa.
7535 * @param $y (float) The value of the ordinate.
7536 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7537 * @public
7538 * @since 1.2
7539 * @see SetX(), SetY()
7540 */
7541 public function SetXY($x, $y, $rtloff=false) {
7542 $this->SetY($y, false, $rtloff);
7543 $this->SetX($x, $rtloff);
7544 }
7545
7546 /**
7547 * Set the absolute X coordinate of the current pointer.
7548 * @param $x (float) The value of the abscissa in user units.
7549 * @public
7550 * @since 5.9.186 (2012-09-13)
7551 * @see setAbsX(), setAbsY(), SetAbsXY()
7552 */
7553 public function SetAbsX($x) {
7554 $this->x = floatval($x);
7555 }
7556
7557 /**
7558 * Set the absolute Y coordinate of the current pointer.
7559 * @param $y (float) (float) The value of the ordinate in user units.
7560 * @public
7561 * @since 5.9.186 (2012-09-13)
7562 * @see setAbsX(), setAbsY(), SetAbsXY()
7563 */
7564 public function SetAbsY($y) {
7565 $this->y = floatval($y);
7566 }
7567
7568 /**
7569 * Set the absolute X and Y coordinates of the current pointer.
7570 * @param $x (float) The value of the abscissa in user units.
7571 * @param $y (float) (float) The value of the ordinate in user units.
7572 * @public
7573 * @since 5.9.186 (2012-09-13)
7574 * @see setAbsX(), setAbsY(), SetAbsXY()
7575 */
7576 public function SetAbsXY($x, $y) {
7577 $this->SetAbsX($x);
7578 $this->SetAbsY($y);
7579 }
7580
7581 /**
7582 * Send the document to a given destination: string, local file or browser.
7583 * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
7584 * The method first calls Close() if necessary to terminate the document.
7585 * @param $name (string) The name of the file when saved. Note that special characters are removed and blanks characters are replaced with the underscore character.
7586 * @param $dest (string) Destination where to send the document. It can take one of the following values:<ul><li>I: send the file inline to the browser (default). The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.</li><li>D: send to the browser and force a file download with the name given by name.</li><li>F: save to a local server file with the name given by name.</li><li>S: return the document as a string (name is ignored).</li><li>FI: equivalent to F + I option</li><li>FD: equivalent to F + D option</li><li>E: return the document as base64 mime multi-part email attachment (RFC 2045)</li></ul>
7587 * @public
7588 * @since 1.0
7589 * @see Close()
7590 */
7591 public function Output($name='doc.pdf', $dest='I') {
7592 //Output PDF to some destination
7593 //Finish document if necessary
7594 if ($this->state < 3) {
7595 $this->Close();
7596 }
7597 //Normalize parameters
7598 if (is_bool($dest)) {
7599 $dest = $dest ? 'D' : 'F';
7600 }
7601 $dest = strtoupper($dest);
7602 if ($dest[0] != 'F') {
7603 $name = preg_replace('/[\s]+/', '_', $name);
7604 $name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
7605 }
7606 if ($this->sign) {
7607 // *** apply digital signature to the document ***
7608 // get the document content
7609 $pdfdoc = $this->getBuffer();
7610 // remove last newline
7611 $pdfdoc = substr($pdfdoc, 0, -1);
7612 // Remove the original buffer
7613 if (isset($this->diskcache) AND $this->diskcache) {
7614 // remove buffer file from cache
7615 unlink($this->buffer);
7616 }
7617 unset($this->buffer);
7618 // remove filler space
7619 $byterange_string_len = strlen(TCPDF_STATIC::$byterange_string);
7620 // define the ByteRange
7621 $byte_range = array();
7622 $byte_range[0] = 0;
7623 $byte_range[1] = strpos($pdfdoc, TCPDF_STATIC::$byterange_string) + $byterange_string_len + 10;
7624 $byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
7625 $byte_range[3] = strlen($pdfdoc) - $byte_range[2];
7626 $pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
7627 // replace the ByteRange
7628 $byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
7629 $byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
7630 $pdfdoc = str_replace(TCPDF_STATIC::$byterange_string, $byterange, $pdfdoc);
7631 // write the document to a temporary folder
7632 $tempdoc = TCPDF_STATIC::getObjFilename('doc');
7633 $f = fopen($tempdoc, 'wb');
7634 if (!$f) {
7635 $this->Error('Unable to create temporary file: '.$tempdoc);
7636 }
7637 $pdfdoc_length = strlen($pdfdoc);
7638 fwrite($f, $pdfdoc, $pdfdoc_length);
7639 fclose($f);
7640 // get digital signature via openssl library
7641 $tempsign = TCPDF_STATIC::getObjFilename('sig');
7642 if (empty($this->signature_data['extracerts'])) {
7643 openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
7644 } else {
7645 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']);
7646 }
7647 unlink($tempdoc);
7648 // read signature
7649 $signature = file_get_contents($tempsign);
7650 unlink($tempsign);
7651 // extract signature
7652 $signature = substr($signature, $pdfdoc_length);
7653 $signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
7654 $tmparr = explode("\n\n", $signature);
7655 $signature = $tmparr[1];
7656 unset($tmparr);
7657 // decode signature
7658 $signature = base64_decode(trim($signature));
7659 // add TSA timestamp to signature
7660 $signature = $this->applyTSA($signature);
7661 // convert signature to hex
7662 $signature = current(unpack('H*', $signature));
7663 $signature = str_pad($signature, $this->signature_max_length, '0');
7664 // disable disk caching
7665 $this->diskcache = false;
7666 // Add signature to the document
7667 $this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
7668 $this->bufferlen = strlen($this->buffer);
7669 }
7670 switch($dest) {
7671 case 'I': {
7672 // Send PDF to the standard output
7673 if (ob_get_contents()) {
7674 $this->Error('Some data has already been output, can\'t send PDF file');
7675 }
7676 if (php_sapi_name() != 'cli') {
7677 // send output to a browser
7678 header('Content-Type: application/pdf');
7679 if (headers_sent()) {
7680 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7681 }
7682 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7683 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7684 header('Pragma: public');
7685 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7686 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7687 header('Content-Disposition: inline; filename="'.basename($name).'"');
7688 TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7689 } else {
7690 echo $this->getBuffer();
7691 }
7692 break;
7693 }
7694 case 'D': {
7695 // download PDF as file
7696 if (ob_get_contents()) {
7697 $this->Error('Some data has already been output, can\'t send PDF file');
7698 }
7699 header('Content-Description: File Transfer');
7700 if (headers_sent()) {
7701 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7702 }
7703 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7704 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7705 header('Pragma: public');
7706 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7707 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7708 // force download dialog
7709 if (strpos(php_sapi_name(), 'cgi') === false) {
7710 header('Content-Type: application/force-download');
7711 header('Content-Type: application/octet-stream', false);
7712 header('Content-Type: application/download', false);
7713 header('Content-Type: application/pdf', false);
7714 } else {
7715 header('Content-Type: application/pdf');
7716 }
7717 // use the Content-Disposition header to supply a recommended filename
7718 header('Content-Disposition: attachment; filename="'.basename($name).'"');
7719 header('Content-Transfer-Encoding: binary');
7720 TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7721 break;
7722 }
7723 case 'F':
7724 case 'FI':
7725 case 'FD': {
7726 // save PDF to a local file
7727 if ($this->diskcache) {
7728 copy($this->buffer, $name);
7729 } else {
7730 $f = fopen($name, 'wb');
7731 if (!$f) {
7732 $this->Error('Unable to create output file: '.$name);
7733 }
7734 fwrite($f, $this->getBuffer(), $this->bufferlen);
7735 fclose($f);
7736 }
7737 if ($dest == 'FI') {
7738 // send headers to browser
7739 header('Content-Type: application/pdf');
7740 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7741 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7742 header('Pragma: public');
7743 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7744 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7745 header('Content-Disposition: inline; filename="'.basename($name).'"');
7746 TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7747 } elseif ($dest == 'FD') {
7748 // send headers to browser
7749 if (ob_get_contents()) {
7750 $this->Error('Some data has already been output, can\'t send PDF file');
7751 }
7752 header('Content-Description: File Transfer');
7753 if (headers_sent()) {
7754 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7755 }
7756 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7757 header('Pragma: public');
7758 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7759 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7760 // force download dialog
7761 if (strpos(php_sapi_name(), 'cgi') === false) {
7762 header('Content-Type: application/force-download');
7763 header('Content-Type: application/octet-stream', false);
7764 header('Content-Type: application/download', false);
7765 header('Content-Type: application/pdf', false);
7766 } else {
7767 header('Content-Type: application/pdf');
7768 }
7769 // use the Content-Disposition header to supply a recommended filename
7770 header('Content-Disposition: attachment; filename="'.basename($name).'"');
7771 header('Content-Transfer-Encoding: binary');
7772 TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7773 }
7774 break;
7775 }
7776 case 'E': {
7777 // return PDF as base64 mime multi-part email attachment (RFC 2045)
7778 $retval = 'Content-Type: application/pdf;'."\r\n";
7779 $retval .= ' name="'.$name.'"'."\r\n";
7780 $retval .= 'Content-Transfer-Encoding: base64'."\r\n";
7781 $retval .= 'Content-Disposition: attachment;'."\r\n";
7782 $retval .= ' filename="'.$name.'"'."\r\n\r\n";
7783 $retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
7784 return $retval;
7785 }
7786 case 'S': {
7787 // returns PDF as a string
7788 return $this->getBuffer();
7789 }
7790 default: {
7791 $this->Error('Incorrect output destination: '.$dest);
7792 }
7793 }
7794 return '';
7795 }
7796
7797 /**
7798 * Unset all class variables except the following critical variables.
7799 * @param $destroyall (boolean) if true destroys all class variables, otherwise preserves critical variables.
7800 * @param $preserve_objcopy (boolean) if true preserves the objcopy variable
7801 * @public
7802 * @since 4.5.016 (2009-02-24)
7803 */
7804 public function _destroy($destroyall=false, $preserve_objcopy=false) {
7805 if ($destroyall AND isset($this->diskcache) AND $this->diskcache AND (!$preserve_objcopy) AND (!TCPDF_STATIC::empty_string($this->buffer))) {
7806 // remove buffer file from cache
7807 unlink($this->buffer);
7808 }
7809 if ($destroyall AND !empty($this->cached_files)) {
7810 // remove cached files
7811 foreach ($this->cached_files as $cachefile) {
7812 if (is_file($cachefile)) {
7813 unlink($cachefile);
7814 }
7815 }
7816 unset($this->cached_files);
7817 }
7818 $preserve = array(
7819 'internal_encoding',
7820 'state',
7821 'bufferlen',
7822 'buffer',
7823 'diskcache',
7824 'cached_files',
7825 'sign',
7826 'signature_data',
7827 'signature_max_length',
7828 'byterange_string',
7829 'tsa_timestamp',
7830 'tsa_data'
7831 );
7832 foreach (array_keys(get_object_vars($this)) as $val) {
7833 if ($destroyall OR !in_array($val, $preserve)) {
7834 if ((!$preserve_objcopy OR ($val != 'objcopy')) AND isset($this->$val)) {
7835 unset($this->$val);
7836 }
7837 }
7838 }
7839 }
7840
7841 /**
7842 * Check for locale-related bug
7843 * @protected
7844 */
7845 protected function _dochecks() {
7846 //Check for locale-related bug
7847 if (1.1 == 1) {
7848 $this->Error('Don\'t alter the locale before including class file');
7849 }
7850 //Check for decimal separator
7851 if (sprintf('%.1F', 1.0) != '1.0') {
7852 setlocale(LC_NUMERIC, 'C');
7853 }
7854 }
7855
7856 /**
7857 * Return an array containing variations for the basic page number alias.
7858 * @param $a (string) Base alias.
7859 * @return array of page number aliases
7860 * @protected
7861 */
7862 protected function getInternalPageNumberAliases($a= '') {
7863 $alias = array();
7864 // build array of Unicode + ASCII variants (the order is important)
7865 $alias = array('u' => array(), 'a' => array());
7866 $u = '{'.$a.'}';
7867 $alias['u'][] = TCPDF_STATIC::_escape($u);
7868 if ($this->isunicode) {
7869 $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($u, $this->isunicode, $this->CurrentFont));
7870 $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($u, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7871 $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($a, $this->isunicode, $this->CurrentFont));
7872 $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($a, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7873 }
7874 $alias['a'][] = TCPDF_STATIC::_escape($a);
7875 return $alias;
7876 }
7877
7878 /**
7879 * Return an array containing all internal page aliases.
7880 * @return array of page number aliases
7881 * @protected
7882 */
7883 protected function getAllInternalPageNumberAliases() {
7884 $basic_alias = array(TCPDF_STATIC::$alias_tot_pages, TCPDF_STATIC::$alias_num_page, TCPDF_STATIC::$alias_group_tot_pages, TCPDF_STATIC::$alias_group_num_page, TCPDF_STATIC::$alias_right_shift);
7885 $pnalias = array();
7886 foreach($basic_alias as $k => $a) {
7887 $pnalias[$k] = $this->getInternalPageNumberAliases($a);
7888 }
7889 return $pnalias;
7890 }
7891
7892 /**
7893 * Replace right shift page number aliases with spaces to correct right alignment.
7894 * This works perfectly only when using monospaced fonts.
7895 * @param $page (string) Page content.
7896 * @param $aliases (array) Array of page aliases.
7897 * @param $diff (int) initial difference to add.
7898 * @return replaced page content.
7899 * @protected
7900 */
7901 protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
7902 foreach ($aliases as $type => $alias) {
7903 foreach ($alias as $a) {
7904 // find position of compensation factor
7905 $startnum = (strpos($a, ':') + 1);
7906 $a = substr($a, 0, $startnum);
7907 if (($pos = strpos($page, $a)) !== false) {
7908 // end of alias
7909 $endnum = strpos($page, '}', $pos);
7910 // string to be replaced
7911 $aa = substr($page, $pos, ($endnum - $pos + 1));
7912 // get compensation factor
7913 $ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
7914 $ratio = preg_replace('/[^0-9\.]/', '', $ratio);
7915 $ratio = floatval($ratio);
7916 if ($type == 'u') {
7917 $chrdiff = floor(($diff + 12) * $ratio);
7918 $shift = str_repeat(' ', $chrdiff);
7919 $shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
7920 } else {
7921 $chrdiff = floor(($diff + 11) * $ratio);
7922 $shift = str_repeat(' ', $chrdiff);
7923 }
7924 $page = str_replace($aa, $shift, $page);
7925 }
7926 }
7927 }
7928 return $page;
7929 }
7930
7931 /**
7932 * Set page boxes to be included on page descriptions.
7933 * @param $boxes (array) Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
7934 * @protected
7935 */
7936 protected function setPageBoxTypes($boxes) {
7937 $this->page_boxes = array();
7938 foreach ($boxes as $box) {
7939 if (in_array($box, TCPDF_STATIC::$pageboxes)) {
7940 $this->page_boxes[] = $box;
7941 }
7942 }
7943 }
7944
7945 /**
7946 * Output pages (and replace page number aliases).
7947 * @protected
7948 */
7949 protected function _putpages() {
7950 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
7951 // get internal aliases for page numbers
7952 $pnalias = $this->getAllInternalPageNumberAliases();
7953 $num_pages = $this->numpages;
7954 $ptpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $num_pages - 1));
7955 $ptpu = TCPDF_FONTS::UTF8ToUTF16BE($ptpa, false, $this->isunicode, $this->CurrentFont);
7956 $ptp_num_chars = $this->GetNumChars($ptpa);
7957 $pagegroupnum = 0;
7958 $groupnum = 0;
7959 $ptgu = 1;
7960 $ptga = 1;
7961 $ptg_num_chars = 1;
7962 for ($n = 1; $n <= $num_pages; ++$n) {
7963 // get current page
7964 $temppage = $this->getPageBuffer($n);
7965 $pagelen = strlen($temppage);
7966 // set replacements for total pages number
7967 $pnpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $n - 1));
7968 $pnpu = TCPDF_FONTS::UTF8ToUTF16BE($pnpa, false, $this->isunicode, $this->CurrentFont);
7969 $pnp_num_chars = $this->GetNumChars($pnpa);
7970 $pdiff = 0; // difference used for right shift alignment of page numbers
7971 $gdiff = 0; // difference used for right shift alignment of page group numbers
7972 if (!empty($this->pagegroups)) {
7973 if (isset($this->newpagegroup[$n])) {
7974 $pagegroupnum = 0;
7975 ++$groupnum;
7976 $ptga = TCPDF_STATIC::formatPageNumber($this->pagegroups[$groupnum]);
7977 $ptgu = TCPDF_FONTS::UTF8ToUTF16BE($ptga, false, $this->isunicode, $this->CurrentFont);
7978 $ptg_num_chars = $this->GetNumChars($ptga);
7979 }
7980 ++$pagegroupnum;
7981 $pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
7982 $pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
7983 $png_num_chars = $this->GetNumChars($pnga);
7984 // replace page numbers
7985 $replace = array();
7986 $replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
7987 $replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
7988 $replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
7989 $replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
7990 list($temppage, $gdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $gdiff);
7991 }
7992 // replace page numbers
7993 $replace = array();
7994 $replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
7995 $replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
7996 $replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
7997 $replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
7998 list($temppage, $pdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $pdiff);
7999 // replace right shift alias
8000 $temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
8001 // replace EPS marker
8002 $temppage = str_replace($this->epsmarker, '', $temppage);
8003 //Page
8004 $this->page_obj_id[$n] = $this->_newobj();
8005 $out = '<<';
8006 $out .= ' /Type /Page';
8007 $out .= ' /Parent 1 0 R';
8008 if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
8009 $out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
8010 }
8011 $out .= ' /Resources 2 0 R';
8012 foreach ($this->page_boxes as $box) {
8013 $out .= ' /'.$box;
8014 $out .= sprintf(' [%F %F %F %F]', $this->pagedim[$n][$box]['llx'], $this->pagedim[$n][$box]['lly'], $this->pagedim[$n][$box]['urx'], $this->pagedim[$n][$box]['ury']);
8015 }
8016 if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
8017 $out .= ' /BoxColorInfo <<';
8018 foreach ($this->page_boxes as $box) {
8019 if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
8020 $out .= ' /'.$box.' <<';
8021 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
8022 $color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
8023 $out .= ' /C [';
8024 $out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
8025 $out .= ' ]';
8026 }
8027 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
8028 $out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
8029 }
8030 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
8031 $out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
8032 }
8033 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
8034 $dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
8035 $out .= ' /D [';
8036 foreach ($dashes as $dash) {
8037 $out .= sprintf(' %F', ($dash * $this->k));
8038 }
8039 $out .= ' ]';
8040 }
8041 $out .= ' >>';
8042 }
8043 }
8044 $out .= ' >>';
8045 }
8046 $out .= ' /Contents '.($this->n + 1).' 0 R';
8047 $out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
8048 if (!$this->pdfa_mode) {
8049 $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
8050 }
8051 if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
8052 // page transitions
8053 if (isset($this->pagedim[$n]['trans']['Dur'])) {
8054 $out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
8055 }
8056 $out .= ' /Trans <<';
8057 $out .= ' /Type /Trans';
8058 if (isset($this->pagedim[$n]['trans']['S'])) {
8059 $out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
8060 }
8061 if (isset($this->pagedim[$n]['trans']['D'])) {
8062 $out .= ' /D '.$this->pagedim[$n]['trans']['D'];
8063 }
8064 if (isset($this->pagedim[$n]['trans']['Dm'])) {
8065 $out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
8066 }
8067 if (isset($this->pagedim[$n]['trans']['M'])) {
8068 $out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
8069 }
8070 if (isset($this->pagedim[$n]['trans']['Di'])) {
8071 $out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
8072 }
8073 if (isset($this->pagedim[$n]['trans']['SS'])) {
8074 $out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
8075 }
8076 if (isset($this->pagedim[$n]['trans']['B'])) {
8077 $out .= ' /B '.$this->pagedim[$n]['trans']['B'];
8078 }
8079 $out .= ' >>';
8080 }
8081 $out .= $this->_getannotsrefs($n);
8082 $out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8083 $out .= ' >>';
8084 $out .= "\n".'endobj';
8085 $this->_out($out);
8086 //Page content
8087 $p = ($this->compress) ? gzcompress($temppage) : $temppage;
8088 $this->_newobj();
8089 $p = $this->_getrawstream($p);
8090 $this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8091 if ($this->diskcache) {
8092 // remove temporary files
8093 unlink($this->pages[$n]);
8094 }
8095 }
8096 //Pages root
8097 $out = $this->_getobj(1)."\n";
8098 $out .= '<< /Type /Pages /Kids [';
8099 foreach($this->page_obj_id as $page_obj) {
8100 $out .= ' '.$page_obj.' 0 R';
8101 }
8102 $out .= ' ] /Count '.$num_pages.' >>';
8103 $out .= "\n".'endobj';
8104 $this->_out($out);
8105 }
8106
8107 /**
8108 * Output references to page annotations
8109 * @param $n (int) page number
8110 * @protected
8111 * @author Nicola Asuni
8112 * @since 4.7.000 (2008-08-29)
8113 * @deprecated
8114 */
8115 protected function _putannotsrefs($n) {
8116 $this->_out($this->_getannotsrefs($n));
8117 }
8118
8119 /**
8120 * Get references to page annotations.
8121 * @param $n (int) page number
8122 * @return string
8123 * @protected
8124 * @author Nicola Asuni
8125 * @since 5.0.010 (2010-05-17)
8126 */
8127 protected function _getannotsrefs($n) {
8128 if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
8129 return '';
8130 }
8131 $out = ' /Annots [';
8132 if (isset($this->PageAnnots[$n])) {
8133 foreach ($this->PageAnnots[$n] as $key => $val) {
8134 if (!in_array($val['n'], $this->radio_groups)) {
8135 $out .= ' '.$val['n'].' 0 R';
8136 }
8137 }
8138 // add radiobutton groups
8139 if (isset($this->radiobutton_groups[$n])) {
8140 foreach ($this->radiobutton_groups[$n] as $key => $data) {
8141 if (isset($data['n'])) {
8142 $out .= ' '.$data['n'].' 0 R';
8143 }
8144 }
8145 }
8146 }
8147 if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
8148 // set reference for signature object
8149 $out .= ' '.$this->sig_obj_id.' 0 R';
8150 }
8151 if (!empty($this->empty_signature_appearance)) {
8152 foreach ($this->empty_signature_appearance as $esa) {
8153 if ($esa['page'] == $n) {
8154 // set reference for empty signature objects
8155 $out .= ' '.$esa['objid'].' 0 R';
8156 }
8157 }
8158 }
8159 $out .= ' ]';
8160 return $out;
8161 }
8162
8163 /**
8164 * Output annotations objects for all pages.
8165 * !!! THIS METHOD IS NOT YET COMPLETED !!!
8166 * See section 12.5 of PDF 32000_2008 reference.
8167 * @protected
8168 * @author Nicola Asuni
8169 * @since 4.0.018 (2008-08-06)
8170 */
8171 protected function _putannotsobjs() {
8172 // reset object counter
8173 for ($n=1; $n <= $this->numpages; ++$n) {
8174 if (isset($this->PageAnnots[$n])) {
8175 // set page annotations
8176 foreach ($this->PageAnnots[$n] as $key => $pl) {
8177 $annot_obj_id = $this->PageAnnots[$n][$key]['n'];
8178 // create annotation object for grouping radiobuttons
8179 if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
8180 $radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
8181 $annots = '<<';
8182 $annots .= ' /Type /Annot';
8183 $annots .= ' /Subtype /Widget';
8184 $annots .= ' /Rect [0 0 0 0]';
8185 if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
8186 // read only
8187 $annots .= ' /F 68';
8188 $annots .= ' /Ff 49153';
8189 } else {
8190 $annots .= ' /F 4'; // default print for PDF/A
8191 $annots .= ' /Ff 49152';
8192 }
8193 $annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8194 if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8195 $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
8196 }
8197 $annots .= ' /FT /Btn';
8198 $annots .= ' /Kids [';
8199 $defval = '';
8200 foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
8201 if (isset($data['kid'])) {
8202 $annots .= ' '.$data['kid'].' 0 R';
8203 if ($data['def'] !== 'Off') {
8204 $defval = $data['def'];
8205 }
8206 }
8207 }
8208 $annots .= ' ]';
8209 if (!empty($defval)) {
8210 $annots .= ' /V /'.$defval;
8211 }
8212 $annots .= ' >>';
8213 $this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8214 $this->form_obj_id[] = $radio_button_obj_id;
8215 // store object id to be used on Parent entry of Kids
8216 $this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
8217 }
8218 $formfield = false;
8219 $pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
8220 $a = $pl['x'] * $this->k;
8221 $b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
8222 $c = $pl['w'] * $this->k;
8223 $d = $pl['h'] * $this->k;
8224 $rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
8225 // create new annotation object
8226 $annots = '<</Type /Annot';
8227 $annots .= ' /Subtype /'.$pl['opt']['subtype'];
8228 $annots .= ' /Rect ['.$rect.']';
8229 $ft = array('Btn', 'Tx', 'Ch', 'Sig');
8230 if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8231 $annots .= ' /FT /'.$pl['opt']['ft'];
8232 $formfield = true;
8233 }
8234 $annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8235 $annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
8236 $annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8237 $annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
8238 if (isset($pl['opt']['f'])) {
8239 $fval = 0;
8240 if (is_array($pl['opt']['f'])) {
8241 foreach ($pl['opt']['f'] as $f) {
8242 switch (strtolower($f)) {
8243 case 'invisible': {
8244 $fval += 1 << 0;
8245 break;
8246 }
8247 case 'hidden': {
8248 $fval += 1 << 1;
8249 break;
8250 }
8251 case 'print': {
8252 $fval += 1 << 2;
8253 break;
8254 }
8255 case 'nozoom': {
8256 $fval += 1 << 3;
8257 break;
8258 }
8259 case 'norotate': {
8260 $fval += 1 << 4;
8261 break;
8262 }
8263 case 'noview': {
8264 $fval += 1 << 5;
8265 break;
8266 }
8267 case 'readonly': {
8268 $fval += 1 << 6;
8269 break;
8270 }
8271 case 'locked': {
8272 $fval += 1 << 8;
8273 break;
8274 }
8275 case 'togglenoview': {
8276 $fval += 1 << 9;
8277 break;
8278 }
8279 case 'lockedcontents': {
8280 $fval += 1 << 10;
8281 break;
8282 }
8283 default: {
8284 break;
8285 }
8286 }
8287 }
8288 } else {
8289 $fval = intval($pl['opt']['f']);
8290 }
8291 } else {
8292 $fval = 4;
8293 }
8294 if ($this->pdfa_mode) {
8295 // force print flag for PDF/A mode
8296 $fval |= 4;
8297 }
8298 $annots .= ' /F '.intval($fval);
8299 if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8300 $annots .= ' /AS /'.$pl['opt']['as'];
8301 }
8302 if (isset($pl['opt']['ap'])) {
8303 // appearance stream
8304 $annots .= ' /AP <<';
8305 if (is_array($pl['opt']['ap'])) {
8306 foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8307 // $apmode can be: n = normal; r = rollover; d = down;
8308 $annots .= ' /'.strtoupper($apmode);
8309 if (is_array($apdef)) {
8310 $annots .= ' <<';
8311 foreach ($apdef as $apstate => $stream) {
8312 // reference to XObject that define the appearance for this mode-state
8313 $apsobjid = $this->_putAPXObject($c, $d, $stream);
8314 $annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8315 }
8316 $annots .= ' >>';
8317 } else {
8318 // reference to XObject that define the appearance for this mode
8319 $apsobjid = $this->_putAPXObject($c, $d, $apdef);
8320 $annots .= ' '.$apsobjid.' 0 R';
8321 }
8322 }
8323 } else {
8324 $annots .= $pl['opt']['ap'];
8325 }
8326 $annots .= ' >>';
8327 }
8328 if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8329 $annots .= ' /BS <<';
8330 $annots .= ' /Type /Border';
8331 if (isset($pl['opt']['bs']['w'])) {
8332 $annots .= ' /W '.intval($pl['opt']['bs']['w']);
8333 }
8334 $bstyles = array('S', 'D', 'B', 'I', 'U');
8335 if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8336 $annots .= ' /S /'.$pl['opt']['bs']['s'];
8337 }
8338 if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8339 $annots .= ' /D [';
8340 foreach ($pl['opt']['bs']['d'] as $cord) {
8341 $annots .= ' '.intval($cord);
8342 }
8343 $annots .= ']';
8344 }
8345 $annots .= ' >>';
8346 } else {
8347 $annots .= ' /Border [';
8348 if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8349 $annots .= intval($pl['opt']['border'][0]).' ';
8350 $annots .= intval($pl['opt']['border'][1]).' ';
8351 $annots .= intval($pl['opt']['border'][2]);
8352 if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8353 $annots .= ' [';
8354 foreach ($pl['opt']['border'][3] as $dash) {
8355 $annots .= intval($dash).' ';
8356 }
8357 $annots .= ']';
8358 }
8359 } else {
8360 $annots .= '0 0 0';
8361 }
8362 $annots .= ']';
8363 }
8364 if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8365 $annots .= ' /BE <<';
8366 $bstyles = array('S', 'C');
8367 if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
8368 $annots .= ' /S /'.$pl['opt']['bs']['s'];
8369 } else {
8370 $annots .= ' /S /S';
8371 }
8372 if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8373 $annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
8374 }
8375 $annots .= '>>';
8376 }
8377 if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8378 $annots .= ' /C '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['c']);
8379 }
8380 //$annots .= ' /StructParent ';
8381 //$annots .= ' /OC ';
8382 $markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8383 if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8384 // this is a markup type
8385 if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8386 $annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8387 }
8388 //$annots .= ' /Popup ';
8389 if (isset($pl['opt']['ca'])) {
8390 $annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
8391 }
8392 if (isset($pl['opt']['rc'])) {
8393 $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8394 }
8395 $annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
8396 //$annots .= ' /IRT ';
8397 if (isset($pl['opt']['subj'])) {
8398 $annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8399 }
8400 //$annots .= ' /RT ';
8401 //$annots .= ' /IT ';
8402 //$annots .= ' /ExData ';
8403 }
8404 $lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8405 // Annotation types
8406 switch (strtolower($pl['opt']['subtype'])) {
8407 case 'text': {
8408 if (isset($pl['opt']['open'])) {
8409 $annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
8410 }
8411 $iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8412 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8413 $annots .= ' /Name /'.$pl['opt']['name'];
8414 } else {
8415 $annots .= ' /Name /Note';
8416 }
8417 $statemodels = array('Marked', 'Review');
8418 if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
8419 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8420 } else {
8421 $pl['opt']['statemodel'] = 'Marked';
8422 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8423 }
8424 if ($pl['opt']['statemodel'] == 'Marked') {
8425 $states = array('Accepted', 'Unmarked');
8426 } else {
8427 $states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8428 }
8429 if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
8430 $annots .= ' /State /'.$pl['opt']['state'];
8431 } else {
8432 if ($pl['opt']['statemodel'] == 'Marked') {
8433 $annots .= ' /State /Unmarked';
8434 } else {
8435 $annots .= ' /State /None';
8436 }
8437 }
8438 break;
8439 }
8440 case 'link': {
8441 if (is_string($pl['txt'])) {
8442 if ($pl['txt'][0] == '#') {
8443 // internal destination
8444 $annots .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1));
8445 } elseif ($pl['txt'][0] == '%') {
8446 // embedded PDF file
8447 $filename = basename(substr($pl['txt'], 1));
8448 $annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
8449 } elseif ($pl['txt'][0] == '*') {
8450 // embedded generic file
8451 $filename = basename(substr($pl['txt'], 1));
8452 $jsa = 'var D=event.target.doc;var MyData=D.dataObjects;for (var i in MyData) if (MyData[i].path=="'.$filename.'") D.exportDataObject( { cName : MyData[i].name, nLaunch : 2});';
8453 $annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
8454 } else {
8455 $parsedUrl = parse_url($pl['txt']);
8456 if (empty($parsedUrl['scheme']) AND (strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
8457 // relative link to a PDF file
8458 $dest = '[0 /Fit]'; // default page 0
8459 if (!empty($parsedUrl['fragment'])) {
8460 // check for named destination
8461 $tmp = explode('=', $parsedUrl['fragment']);
8462 $dest = '('.((count($tmp) == 2) ? $tmp[1] : $tmp[0]).')';
8463 }
8464 $annots .= ' /A <</S /GoToR /D '.$dest.' /F '.$this->_datastring($this->unhtmlentities($parsedUrl['path']), $annot_obj_id).' /NewWindow true>>';
8465 } else {
8466 // external URI link
8467 $annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8468 }
8469 }
8470 } elseif (isset($this->links[$pl['txt']])) {
8471 // internal link ID
8472 $l = $this->links[$pl['txt']];
8473 if (isset($this->page_obj_id[($l['p'])])) {
8474 $annots .= sprintf(' /Dest [%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($l['p'])], ($this->pagedim[$l['p']]['h'] - ($l['y'] * $this->k)));
8475 }
8476 }
8477 $hmodes = array('N', 'I', 'O', 'P');
8478 if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8479 $annots .= ' /H /'.$pl['opt']['h'];
8480 } else {
8481 $annots .= ' /H /I';
8482 }
8483 //$annots .= ' /PA ';
8484 //$annots .= ' /Quadpoints ';
8485 break;
8486 }
8487 case 'freetext': {
8488 if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8489 $annots .= ' /DA ('.$pl['opt']['da'].')';
8490 }
8491 if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8492 $annots .= ' /Q '.intval($pl['opt']['q']);
8493 }
8494 if (isset($pl['opt']['rc'])) {
8495 $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8496 }
8497 if (isset($pl['opt']['ds'])) {
8498 $annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8499 }
8500 if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8501 $annots .= ' /CL [';
8502 foreach ($pl['opt']['cl'] as $cl) {
8503 $annots .= sprintf('%F ', $cl * $this->k);
8504 }
8505 $annots .= ']';
8506 }
8507 $tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8508 if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8509 $annots .= ' /IT /'.$pl['opt']['it'];
8510 }
8511 if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8512 $l = $pl['opt']['rd'][0] * $this->k;
8513 $r = $pl['opt']['rd'][1] * $this->k;
8514 $t = $pl['opt']['rd'][2] * $this->k;
8515 $b = $pl['opt']['rd'][3] * $this->k;
8516 $annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
8517 }
8518 if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8519 $annots .= ' /LE /'.$pl['opt']['le'];
8520 }
8521 break;
8522 }
8523 case 'line': {
8524 break;
8525 }
8526 case 'square': {
8527 break;
8528 }
8529 case 'circle': {
8530 break;
8531 }
8532 case 'polygon': {
8533 break;
8534 }
8535 case 'polyline': {
8536 break;
8537 }
8538 case 'highlight': {
8539 break;
8540 }
8541 case 'underline': {
8542 break;
8543 }
8544 case 'squiggly': {
8545 break;
8546 }
8547 case 'strikeout': {
8548 break;
8549 }
8550 case 'stamp': {
8551 break;
8552 }
8553 case 'caret': {
8554 break;
8555 }
8556 case 'ink': {
8557 break;
8558 }
8559 case 'popup': {
8560 break;
8561 }
8562 case 'fileattachment': {
8563 if ($this->pdfa_mode) {
8564 // embedded files are not allowed in PDF/A mode
8565 break;
8566 }
8567 if (!isset($pl['opt']['fs'])) {
8568 break;
8569 }
8570 $filename = basename($pl['opt']['fs']);
8571 if (isset($this->embeddedfiles[$filename]['f'])) {
8572 $annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
8573 $iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8574 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8575 $annots .= ' /Name /'.$pl['opt']['name'];
8576 } else {
8577 $annots .= ' /Name /PushPin';
8578 }
8579 // index (zero-based) of the annotation in the Annots array of this page
8580 $this->embeddedfiles[$filename]['a'] = $key;
8581 }
8582 break;
8583 }
8584 case 'sound': {
8585 if (!isset($pl['opt']['fs'])) {
8586 break;
8587 }
8588 $filename = basename($pl['opt']['fs']);
8589 if (isset($this->embeddedfiles[$filename]['f'])) {
8590 // ... TO BE COMPLETED ...
8591 // /R /C /B /E /CO /CP
8592 $annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
8593 $iconsapp = array('Speaker', 'Mic');
8594 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8595 $annots .= ' /Name /'.$pl['opt']['name'];
8596 } else {
8597 $annots .= ' /Name /Speaker';
8598 }
8599 }
8600 break;
8601 }
8602 case 'movie': {
8603 break;
8604 }
8605 case 'widget': {
8606 $hmode = array('N', 'I', 'O', 'P', 'T');
8607 if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8608 $annots .= ' /H /'.$pl['opt']['h'];
8609 }
8610 if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8611 $annots .= ' /MK <<';
8612 if (isset($pl['opt']['mk']['r'])) {
8613 $annots .= ' /R '.$pl['opt']['mk']['r'];
8614 }
8615 if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8616 $annots .= ' /BC '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bc']);
8617 }
8618 if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8619 $annots .= ' /BG '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bg']);
8620 }
8621 if (isset($pl['opt']['mk']['ca'])) {
8622 $annots .= ' /CA '.$pl['opt']['mk']['ca'];
8623 }
8624 if (isset($pl['opt']['mk']['rc'])) {
8625 $annots .= ' /RC '.$pl['opt']['mk']['rc'];
8626 }
8627 if (isset($pl['opt']['mk']['ac'])) {
8628 $annots .= ' /AC '.$pl['opt']['mk']['ac'];
8629 }
8630 if (isset($pl['opt']['mk']['i'])) {
8631 $info = $this->getImageBuffer($pl['opt']['mk']['i']);
8632 if ($info !== false) {
8633 $annots .= ' /I '.$info['n'].' 0 R';
8634 }
8635 }
8636 if (isset($pl['opt']['mk']['ri'])) {
8637 $info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8638 if ($info !== false) {
8639 $annots .= ' /RI '.$info['n'].' 0 R';
8640 }
8641 }
8642 if (isset($pl['opt']['mk']['ix'])) {
8643 $info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8644 if ($info !== false) {
8645 $annots .= ' /IX '.$info['n'].' 0 R';
8646 }
8647 }
8648 if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8649 $annots .= ' /IF <<';
8650 $if_sw = array('A', 'B', 'S', 'N');
8651 if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8652 $annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8653 }
8654 $if_s = array('A', 'P');
8655 if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8656 $annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8657 }
8658 if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8659 $annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8660 }
8661 if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8662 $annots .= ' /FB true';
8663 }
8664 $annots .= '>>';
8665 }
8666 if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8667 $annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8668 }
8669 $annots .= '>>';
8670 } // end MK
8671 // --- Entries for field dictionaries ---
8672 if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8673 // set parent
8674 $annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
8675 }
8676 if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8677 $annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8678 }
8679 if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8680 $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8681 }
8682 if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8683 $annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8684 }
8685 if (isset($pl['opt']['ff'])) {
8686 if (is_array($pl['opt']['ff'])) {
8687 // array of bit settings
8688 $flag = 0;
8689 foreach($pl['opt']['ff'] as $val) {
8690 $flag += 1 << ($val - 1);
8691 }
8692 } else {
8693 $flag = intval($pl['opt']['ff']);
8694 }
8695 $annots .= ' /Ff '.$flag;
8696 }
8697 if (isset($pl['opt']['maxlen'])) {
8698 $annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
8699 }
8700 if (isset($pl['opt']['v'])) {
8701 $annots .= ' /V';
8702 if (is_array($pl['opt']['v'])) {
8703 foreach ($pl['opt']['v'] AS $optval) {
8704 if (is_float($optval)) {
8705 $optval = sprintf('%F', $optval);
8706 }
8707 $annots .= ' '.$optval;
8708 }
8709 } else {
8710 $annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8711 }
8712 }
8713 if (isset($pl['opt']['dv'])) {
8714 $annots .= ' /DV';
8715 if (is_array($pl['opt']['dv'])) {
8716 foreach ($pl['opt']['dv'] AS $optval) {
8717 if (is_float($optval)) {
8718 $optval = sprintf('%F', $optval);
8719 }
8720 $annots .= ' '.$optval;
8721 }
8722 } else {
8723 $annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8724 }
8725 }
8726 if (isset($pl['opt']['rv'])) {
8727 $annots .= ' /RV';
8728 if (is_array($pl['opt']['rv'])) {
8729 foreach ($pl['opt']['rv'] AS $optval) {
8730 if (is_float($optval)) {
8731 $optval = sprintf('%F', $optval);
8732 }
8733 $annots .= ' '.$optval;
8734 }
8735 } else {
8736 $annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
8737 }
8738 }
8739 if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
8740 $annots .= ' /A << '.$pl['opt']['a'].' >>';
8741 }
8742 if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
8743 $annots .= ' /AA << '.$pl['opt']['aa'].' >>';
8744 }
8745 if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8746 $annots .= ' /DA ('.$pl['opt']['da'].')';
8747 }
8748 if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8749 $annots .= ' /Q '.intval($pl['opt']['q']);
8750 }
8751 if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
8752 $annots .= ' /Opt [';
8753 foreach($pl['opt']['opt'] AS $copt) {
8754 if (is_array($copt)) {
8755 $annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
8756 } else {
8757 $annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8758 }
8759 }
8760 $annots .= ']';
8761 }
8762 if (isset($pl['opt']['ti'])) {
8763 $annots .= ' /TI '.intval($pl['opt']['ti']);
8764 }
8765 if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
8766 $annots .= ' /I [';
8767 foreach($pl['opt']['i'] AS $copt) {
8768 $annots .= intval($copt).' ';
8769 }
8770 $annots .= ']';
8771 }
8772 break;
8773 }
8774 case 'screen': {
8775 break;
8776 }
8777 case 'printermark': {
8778 break;
8779 }
8780 case 'trapnet': {
8781 break;
8782 }
8783 case 'watermark': {
8784 break;
8785 }
8786 case '3d': {
8787 break;
8788 }
8789 default: {
8790 break;
8791 }
8792 }
8793 $annots .= '>>';
8794 // create new annotation object
8795 $this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
8796 if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
8797 // store reference of form object
8798 $this->form_obj_id[] = $annot_obj_id;
8799 }
8800 }
8801 }
8802 } // end for each page
8803 }
8804
8805 /**
8806 * Put appearance streams XObject used to define annotation's appearance states.
8807 * @param $w (int) annotation width
8808 * @param $h (int) annotation height
8809 * @param $stream (string) appearance stream
8810 * @return int object ID
8811 * @protected
8812 * @since 4.8.001 (2009-09-09)
8813 */
8814 protected function _putAPXObject($w=0, $h=0, $stream='') {
8815 $stream = trim($stream);
8816 $out = $this->_getobj()."\n";
8817 $this->xobjects['AX'.$this->n] = array('n' => $this->n);
8818 $out .= '<<';
8819 $out .= ' /Type /XObject';
8820 $out .= ' /Subtype /Form';
8821 $out .= ' /FormType 1';
8822 if ($this->compress) {
8823 $stream = gzcompress($stream);
8824 $out .= ' /Filter /FlateDecode';
8825 }
8826 $rect = sprintf('%F %F', $w, $h);
8827 $out .= ' /BBox [0 0 '.$rect.']';
8828 $out .= ' /Matrix [1 0 0 1 0 0]';
8829 $out .= ' /Resources 2 0 R';
8830 $stream = $this->_getrawstream($stream);
8831 $out .= ' /Length '.strlen($stream);
8832 $out .= ' >>';
8833 $out .= ' stream'."\n".$stream."\n".'endstream';
8834 $out .= "\n".'endobj';
8835 $this->_out($out);
8836 return $this->n;
8837 }
8838
8839 /**
8840 * Output fonts.
8841 * @author Nicola Asuni
8842 * @protected
8843 */
8844 protected function _putfonts() {
8845 $nf = $this->n;
8846 foreach ($this->diffs as $diff) {
8847 //Encodings
8848 $this->_newobj();
8849 $this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
8850 }
8851 $mqr = TCPDF_STATIC::get_mqr();
8852 TCPDF_STATIC::set_mqr(false);
8853 foreach ($this->FontFiles as $file => $info) {
8854 // search and get font file to embedd
8855 $fontfile = TCPDF_FONTS::getFontFullPath($file, $info['fontdir']);
8856 if (!TCPDF_STATIC::empty_string($fontfile)) {
8857 $font = file_get_contents($fontfile);
8858 $compressed = (substr($file, -2) == '.z');
8859 if ((!$compressed) AND (isset($info['length2']))) {
8860 $header = (ord($font[0]) == 128);
8861 if ($header) {
8862 // strip first binary header
8863 $font = substr($font, 6);
8864 }
8865 if ($header AND (ord($font[$info['length1']]) == 128)) {
8866 // strip second binary header
8867 $font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
8868 }
8869 } elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
8870 if ($compressed) {
8871 // uncompress font
8872 $font = gzuncompress($font);
8873 }
8874 // merge subset characters
8875 $subsetchars = array(); // used chars
8876 foreach ($info['fontkeys'] as $fontkey) {
8877 $fontinfo = $this->getFontBuffer($fontkey);
8878 $subsetchars += $fontinfo['subsetchars'];
8879 }
8880 // rebuild a font subset
8881 $font = TCPDF_FONTS::_getTrueTypeFontSubset($font, $subsetchars);
8882 // calculate new font length
8883 $info['length1'] = strlen($font);
8884 if ($compressed) {
8885 // recompress font
8886 $font = gzcompress($font);
8887 }
8888 }
8889 $this->_newobj();
8890 $this->FontFiles[$file]['n'] = $this->n;
8891 $stream = $this->_getrawstream($font);
8892 $out = '<< /Length '.strlen($stream);
8893 if ($compressed) {
8894 $out .= ' /Filter /FlateDecode';
8895 }
8896 $out .= ' /Length1 '.$info['length1'];
8897 if (isset($info['length2'])) {
8898 $out .= ' /Length2 '.$info['length2'].' /Length3 0';
8899 }
8900 $out .= ' >>';
8901 $out .= ' stream'."\n".$stream."\n".'endstream';
8902 $out .= "\n".'endobj';
8903 $this->_out($out);
8904 }
8905 }
8906 TCPDF_STATIC::set_mqr($mqr);
8907 foreach ($this->fontkeys as $k) {
8908 //Font objects
8909 $font = $this->getFontBuffer($k);
8910 $type = $font['type'];
8911 $name = $font['name'];
8912 if ($type == 'core') {
8913 // standard core font
8914 $out = $this->_getobj($this->font_obj_ids[$k])."\n";
8915 $out .= '<</Type /Font';
8916 $out .= ' /Subtype /Type1';
8917 $out .= ' /BaseFont /'.$name;
8918 $out .= ' /Name /F'.$font['i'];
8919 if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
8920 $out .= ' /Encoding /WinAnsiEncoding';
8921 }
8922 if ($k == 'helvetica') {
8923 // add default font for annotations
8924 $this->annotation_fonts[$k] = $font['i'];
8925 }
8926 $out .= ' >>';
8927 $out .= "\n".'endobj';
8928 $this->_out($out);
8929 } elseif (($type == 'Type1') OR ($type == 'TrueType')) {
8930 // additional Type1 or TrueType font
8931 $out = $this->_getobj($this->font_obj_ids[$k])."\n";
8932 $out .= '<</Type /Font';
8933 $out .= ' /Subtype /'.$type;
8934 $out .= ' /BaseFont /'.$name;
8935 $out .= ' /Name /F'.$font['i'];
8936 $out .= ' /FirstChar 32 /LastChar 255';
8937 $out .= ' /Widths '.($this->n + 1).' 0 R';
8938 $out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
8939 if ($font['enc']) {
8940 if (isset($font['diff'])) {
8941 $out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
8942 } else {
8943 $out .= ' /Encoding /WinAnsiEncoding';
8944 }
8945 }
8946 $out .= ' >>';
8947 $out .= "\n".'endobj';
8948 $this->_out($out);
8949 // Widths
8950 $this->_newobj();
8951 $s = '[';
8952 for ($i = 32; $i < 256; ++$i) {
8953 if (isset($font['cw'][$i])) {
8954 $s .= $font['cw'][$i].' ';
8955 } else {
8956 $s .= $font['dw'].' ';
8957 }
8958 }
8959 $s .= ']';
8960 $s .= "\n".'endobj';
8961 $this->_out($s);
8962 //Descriptor
8963 $this->_newobj();
8964 $s = '<</Type /FontDescriptor /FontName /'.$name;
8965 foreach ($font['desc'] as $fdk => $fdv) {
8966 if (is_float($fdv)) {
8967 $fdv = sprintf('%F', $fdv);
8968 }
8969 $s .= ' /'.$fdk.' '.$fdv.'';
8970 }
8971 if (!TCPDF_STATIC::empty_string($font['file'])) {
8972 $s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
8973 }
8974 $s .= '>>';
8975 $s .= "\n".'endobj';
8976 $this->_out($s);
8977 } else {
8978 // additional types
8979 $mtd = '_put'.strtolower($type);
8980 if (!method_exists($this, $mtd)) {
8981 $this->Error('Unsupported font type: '.$type);
8982 }
8983 $this->$mtd($font);
8984 }
8985 }
8986 }
8987
8988 /**
8989 * Adds unicode fonts.<br>
8990 * Based on PDF Reference 1.3 (section 5)
8991 * @param $font (array) font data
8992 * @protected
8993 * @author Nicola Asuni
8994 * @since 1.52.0.TC005 (2005-01-05)
8995 */
8996 protected function _puttruetypeunicode($font) {
8997 $fontname = '';
8998 if ($font['subset']) {
8999 // change name for font subsetting
9000 $subtag = sprintf('%06u', $font['i']);
9001 $subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
9002 $fontname .= $subtag.'+';
9003 }
9004 $fontname .= $font['name'];
9005 // Type0 Font
9006 // A composite font composed of other fonts, organized hierarchically
9007 $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9008 $out .= '<< /Type /Font';
9009 $out .= ' /Subtype /Type0';
9010 $out .= ' /BaseFont /'.$fontname;
9011 $out .= ' /Name /F'.$font['i'];
9012 $out .= ' /Encoding /'.$font['enc'];
9013 $out .= ' /ToUnicode '.($this->n + 1).' 0 R';
9014 $out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
9015 $out .= ' >>';
9016 $out .= "\n".'endobj';
9017 $this->_out($out);
9018 // ToUnicode map for Identity-H
9019 $stream = TCPDF_FONT_DATA::$uni_identity_h;
9020 // ToUnicode Object
9021 $this->_newobj();
9022 $stream = ($this->compress) ? gzcompress($stream) : $stream;
9023 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9024 $stream = $this->_getrawstream($stream);
9025 $this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
9026 // CIDFontType2
9027 // A CIDFont whose glyph descriptions are based on TrueType font technology
9028 $oid = $this->_newobj();
9029 $out = '<< /Type /Font';
9030 $out .= ' /Subtype /CIDFontType2';
9031 $out .= ' /BaseFont /'.$fontname;
9032 // A dictionary containing entries that define the character collection of the CIDFont.
9033 $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9034 $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9035 $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9036 $out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
9037 $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9038 $out .= ' /DW '.$font['dw']; // default width
9039 $out .= "\n".TCPDF_FONTS::_putfontwidths($font, 0);
9040 if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9041 $out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
9042 }
9043 $out .= ' >>';
9044 $out .= "\n".'endobj';
9045 $this->_out($out);
9046 // Font descriptor
9047 // A font descriptor describing the CIDFont default metrics other than its glyph widths
9048 $this->_newobj();
9049 $out = '<< /Type /FontDescriptor';
9050 $out .= ' /FontName /'.$fontname;
9051 foreach ($font['desc'] as $key => $value) {
9052 if (is_float($value)) {
9053 $value = sprintf('%F', $value);
9054 }
9055 $out .= ' /'.$key.' '.$value;
9056 }
9057 $fontdir = false;
9058 if (!TCPDF_STATIC::empty_string($font['file'])) {
9059 // A stream containing a TrueType font
9060 $out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
9061 $fontdir = $this->FontFiles[$font['file']]['fontdir'];
9062 }
9063 $out .= ' >>';
9064 $out .= "\n".'endobj';
9065 $this->_out($out);
9066 if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9067 $this->_newobj();
9068 // Embed CIDToGIDMap
9069 // A specification of the mapping from CIDs to glyph indices
9070 // search and get CTG font file to embedd
9071 $ctgfile = strtolower($font['ctg']);
9072 // search and get ctg font file to embedd
9073 $fontfile = TCPDF_FONTS::getFontFullPath($ctgfile, $fontdir);
9074 if (TCPDF_STATIC::empty_string($fontfile)) {
9075 $this->Error('Font file not found: '.$ctgfile);
9076 }
9077 $stream = $this->_getrawstream(file_get_contents($fontfile));
9078 $out = '<< /Length '.strlen($stream).'';
9079 if (substr($fontfile, -2) == '.z') { // check file extension
9080 // Decompresses data encoded using the public-domain
9081 // zlib/deflate compression method, reproducing the
9082 // original text or binary data
9083 $out .= ' /Filter /FlateDecode';
9084 }
9085 $out .= ' >>';
9086 $out .= ' stream'."\n".$stream."\n".'endstream';
9087 $out .= "\n".'endobj';
9088 $this->_out($out);
9089 }
9090 }
9091
9092 /**
9093 * Output CID-0 fonts.
9094 * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
9095 * @param $font (array) font data
9096 * @protected
9097 * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
9098 * @since 3.2.000 (2008-06-23)
9099 */
9100 protected function _putcidfont0($font) {
9101 $cidoffset = 0;
9102 if (!isset($font['cw'][1])) {
9103 $cidoffset = 31;
9104 }
9105 if (isset($font['cidinfo']['uni2cid'])) {
9106 // convert unicode to cid.
9107 $uni2cid = $font['cidinfo']['uni2cid'];
9108 $cw = array();
9109 foreach ($font['cw'] as $uni => $width) {
9110 if (isset($uni2cid[$uni])) {
9111 $cw[($uni2cid[$uni] + $cidoffset)] = $width;
9112 } elseif ($uni < 256) {
9113 $cw[$uni] = $width;
9114 } // else unknown character
9115 }
9116 $font = array_merge($font, array('cw' => $cw));
9117 }
9118 $name = $font['name'];
9119 $enc = $font['enc'];
9120 if ($enc) {
9121 $longname = $name.'-'.$enc;
9122 } else {
9123 $longname = $name;
9124 }
9125 $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9126 $out .= '<</Type /Font';
9127 $out .= ' /Subtype /Type0';
9128 $out .= ' /BaseFont /'.$longname;
9129 $out .= ' /Name /F'.$font['i'];
9130 if ($enc) {
9131 $out .= ' /Encoding /'.$enc;
9132 }
9133 $out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
9134 $out .= ' >>';
9135 $out .= "\n".'endobj';
9136 $this->_out($out);
9137 $oid = $this->_newobj();
9138 $out = '<</Type /Font';
9139 $out .= ' /Subtype /CIDFontType0';
9140 $out .= ' /BaseFont /'.$name;
9141 $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9142 $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9143 $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9144 $out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
9145 $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9146 $out .= ' /DW '.$font['dw'];
9147 $out .= "\n".TCPDF_FONTS::_putfontwidths($font, $cidoffset);
9148 $out .= ' >>';
9149 $out .= "\n".'endobj';
9150 $this->_out($out);
9151 $this->_newobj();
9152 $s = '<</Type /FontDescriptor /FontName /'.$name;
9153 foreach ($font['desc'] as $k => $v) {
9154 if ($k != 'Style') {
9155 if (is_float($v)) {
9156 $v = sprintf('%F', $v);
9157 }
9158 $s .= ' /'.$k.' '.$v.'';
9159 }
9160 }
9161 $s .= '>>';
9162 $s .= "\n".'endobj';
9163 $this->_out($s);
9164 }
9165
9166 /**
9167 * Output images.
9168 * @protected
9169 */
9170 protected function _putimages() {
9171 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9172 foreach ($this->imagekeys as $file) {
9173 $info = $this->getImageBuffer($file);
9174 // set object for alternate images array
9175 if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
9176 $altoid = $this->_newobj();
9177 $out = '[';
9178 foreach ($info['altimgs'] as $altimage) {
9179 if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
9180 $out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
9181 $out .= ' /DefaultForPrinting';
9182 if ($altimage[1] === true) {
9183 $out .= ' true';
9184 } else {
9185 $out .= ' false';
9186 }
9187 $out .= ' >>';
9188 }
9189 }
9190 $out .= ' ]';
9191 $out .= "\n".'endobj';
9192 $this->_out($out);
9193 }
9194 // set image object
9195 $oid = $this->_newobj();
9196 $this->xobjects['I'.$info['i']] = array('n' => $oid);
9197 $this->setImageSubBuffer($file, 'n', $this->n);
9198 $out = '<</Type /XObject';
9199 $out .= ' /Subtype /Image';
9200 $out .= ' /Width '.$info['w'];
9201 $out .= ' /Height '.$info['h'];
9202 if (array_key_exists('masked', $info)) {
9203 $out .= ' /SMask '.($this->n - 1).' 0 R';
9204 }
9205 // set color space
9206 $icc = false;
9207 if (isset($info['icc']) AND ($info['icc'] !== false)) {
9208 // ICC Colour Space
9209 $icc = true;
9210 $out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
9211 } elseif ($info['cs'] == 'Indexed') {
9212 // Indexed Colour Space
9213 $out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
9214 } else {
9215 // Device Colour Space
9216 $out .= ' /ColorSpace /'.$info['cs'];
9217 }
9218 if ($info['cs'] == 'DeviceCMYK') {
9219 $out .= ' /Decode [1 0 1 0 1 0 1 0]';
9220 }
9221 $out .= ' /BitsPerComponent '.$info['bpc'];
9222 if (isset($altoid) AND ($altoid > 0)) {
9223 // reference to alternate images dictionary
9224 $out .= ' /Alternates '.$altoid.' 0 R';
9225 }
9226 if (isset($info['exurl']) AND !empty($info['exurl'])) {
9227 // external stream
9228 $out .= ' /Length 0';
9229 $out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
9230 if (isset($info['f'])) {
9231 $out .= ' /FFilter /'.$info['f'];
9232 }
9233 $out .= ' >>';
9234 $out .= ' stream'."\n".'endstream';
9235 } else {
9236 if (isset($info['f'])) {
9237 $out .= ' /Filter /'.$info['f'];
9238 }
9239 if (isset($info['parms'])) {
9240 $out .= ' '.$info['parms'];
9241 }
9242 if (isset($info['trns']) AND is_array($info['trns'])) {
9243 $trns = '';
9244 $count_info = count($info['trns']);
9245 if ($info['cs'] == 'Indexed') {
9246 $maxval =(pow(2, $info['bpc']) - 1);
9247 for ($i = 0; $i < $count_info; ++$i) {
9248 if (($info['trns'][$i] != 0) AND ($info['trns'][$i] != $maxval)) {
9249 // this is not a binary type mask @TODO: create a SMask
9250 $trns = '';
9251 break;
9252 } elseif (empty($trns) AND ($info['trns'][$i] == 0)) {
9253 // store the first fully transparent value
9254 $trns .= $i.' '.$i.' ';
9255 }
9256 }
9257 } else {
9258 // grayscale or RGB
9259 for ($i = 0; $i < $count_info; ++$i) {
9260 if ($info['trns'][$i] == 0) {
9261 $trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
9262 }
9263 }
9264 }
9265 // Colour Key Masking
9266 if (!empty($trns)) {
9267 $out .= ' /Mask ['.$trns.']';
9268 }
9269 }
9270 $stream = $this->_getrawstream($info['data']);
9271 $out .= ' /Length '.strlen($stream).' >>';
9272 $out .= ' stream'."\n".$stream."\n".'endstream';
9273 }
9274 $out .= "\n".'endobj';
9275 $this->_out($out);
9276 if ($icc) {
9277 // ICC colour profile
9278 $this->_newobj();
9279 $icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
9280 $icc = $this->_getrawstream($icc);
9281 $this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9282 } elseif ($info['cs'] == 'Indexed') {
9283 // colour palette
9284 $this->_newobj();
9285 $pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
9286 $pal = $this->_getrawstream($pal);
9287 $this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
9288 }
9289 }
9290 }
9291
9292 /**
9293 * Output Form XObjects Templates.
9294 * @author Nicola Asuni
9295 * @since 5.8.017 (2010-08-24)
9296 * @protected
9297 * @see startTemplate(), endTemplate(), printTemplate()
9298 */
9299 protected function _putxobjects() {
9300 foreach ($this->xobjects as $key => $data) {
9301 if (isset($data['outdata'])) {
9302 $stream = str_replace($this->epsmarker, '', trim($data['outdata']));
9303 $out = $this->_getobj($data['n'])."\n";
9304 $out .= '<<';
9305 $out .= ' /Type /XObject';
9306 $out .= ' /Subtype /Form';
9307 $out .= ' /FormType 1';
9308 if ($this->compress) {
9309 $stream = gzcompress($stream);
9310 $out .= ' /Filter /FlateDecode';
9311 }
9312 $out .= sprintf(' /BBox [%F %F %F %F]', ($data['x'] * $this->k), (-$data['y'] * $this->k), (($data['w'] + $data['x']) * $this->k), (($data['h'] - $data['y']) * $this->k));
9313 $out .= ' /Matrix [1 0 0 1 0 0]';
9314 $out .= ' /Resources <<';
9315 $out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9316 if (!$this->pdfa_mode) {
9317 // transparency
9318 if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
9319 $out .= ' /ExtGState <<';
9320 foreach ($data['extgstates'] as $k => $extgstate) {
9321 if (isset($this->extgstates[$k]['name'])) {
9322 $out .= ' /'.$this->extgstates[$k]['name'];
9323 } else {
9324 $out .= ' /GS'.$k;
9325 }
9326 $out .= ' '.$this->extgstates[$k]['n'].' 0 R';
9327 }
9328 $out .= ' >>';
9329 }
9330 if (isset($data['gradients']) AND !empty($data['gradients'])) {
9331 $gp = '';
9332 $gs = '';
9333 foreach ($data['gradients'] as $id => $grad) {
9334 // gradient patterns
9335 $gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
9336 // gradient shadings
9337 $gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
9338 }
9339 $out .= ' /Pattern <<'.$gp.' >>';
9340 $out .= ' /Shading <<'.$gs.' >>';
9341 }
9342 }
9343 // spot colors
9344 if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
9345 $out .= ' /ColorSpace <<';
9346 foreach ($data['spot_colors'] as $name => $color) {
9347 $out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
9348 }
9349 $out .= ' >>';
9350 }
9351 // fonts
9352 if (!empty($data['fonts'])) {
9353 $out .= ' /Font <<';
9354 foreach ($data['fonts'] as $fontkey => $fontid) {
9355 $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9356 }
9357 $out .= ' >>';
9358 }
9359 // images or nested xobjects
9360 if (!empty($data['images']) OR !empty($data['xobjects'])) {
9361 $out .= ' /XObject <<';
9362 foreach ($data['images'] as $imgid) {
9363 $out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
9364 }
9365 foreach ($data['xobjects'] as $sub_id => $sub_objid) {
9366 $out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
9367 }
9368 $out .= ' >>';
9369 }
9370 $out .= ' >>'; //end resources
9371 if (isset($data['group']) AND ($data['group'] !== false)) {
9372 // set transparency group
9373 $out .= ' /Group << /Type /Group /S /Transparency';
9374 if (is_array($data['group'])) {
9375 if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
9376 $out .= ' /CS /'.$data['group']['CS'];
9377 }
9378 if (isset($data['group']['I'])) {
9379 $out .= ' /I /'.($data['group']['I']===true?'true':'false');
9380 }
9381 if (isset($data['group']['K'])) {
9382 $out .= ' /K /'.($data['group']['K']===true?'true':'false');
9383 }
9384 }
9385 $out .= ' >>';
9386 }
9387 $stream = $this->_getrawstream($stream, $data['n']);
9388 $out .= ' /Length '.strlen($stream);
9389 $out .= ' >>';
9390 $out .= ' stream'."\n".$stream."\n".'endstream';
9391 $out .= "\n".'endobj';
9392 $this->_out($out);
9393 }
9394 }
9395 }
9396
9397 /**
9398 * Output Spot Colors Resources.
9399 * @protected
9400 * @since 4.0.024 (2008-09-12)
9401 */
9402 protected function _putspotcolors() {
9403 foreach ($this->spot_colors as $name => $color) {
9404 $this->_newobj();
9405 $this->spot_colors[$name]['n'] = $this->n;
9406 $out = '[/Separation /'.str_replace(' ', '#20', $name);
9407 $out .= ' /DeviceCMYK <<';
9408 $out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
9409 $out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
9410 $out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
9411 $out .= "\n".'endobj';
9412 $this->_out($out);
9413 }
9414 }
9415
9416 /**
9417 * Return XObjects Dictionary.
9418 * @return string XObjects dictionary
9419 * @protected
9420 * @since 5.8.014 (2010-08-23)
9421 */
9422 protected function _getxobjectdict() {
9423 $out = '';
9424 foreach ($this->xobjects as $id => $objid) {
9425 $out .= ' /'.$id.' '.$objid['n'].' 0 R';
9426 }
9427 return $out;
9428 }
9429
9430 /**
9431 * Output Resources Dictionary.
9432 * @protected
9433 */
9434 protected function _putresourcedict() {
9435 $out = $this->_getobj(2)."\n";
9436 $out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9437 $out .= ' /Font <<';
9438 foreach ($this->fontkeys as $fontkey) {
9439 $font = $this->getFontBuffer($fontkey);
9440 $out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
9441 }
9442 $out .= ' >>';
9443 $out .= ' /XObject <<';
9444 $out .= $this->_getxobjectdict();
9445 $out .= ' >>';
9446 // layers
9447 if (!empty($this->pdflayers)) {
9448 $out .= ' /Properties <<';
9449 foreach ($this->pdflayers as $layer) {
9450 $out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
9451 }
9452 $out .= ' >>';
9453 }
9454 if (!$this->pdfa_mode) {
9455 // transparency
9456 if (isset($this->extgstates) AND !empty($this->extgstates)) {
9457 $out .= ' /ExtGState <<';
9458 foreach ($this->extgstates as $k => $extgstate) {
9459 if (isset($extgstate['name'])) {
9460 $out .= ' /'.$extgstate['name'];
9461 } else {
9462 $out .= ' /GS'.$k;
9463 }
9464 $out .= ' '.$extgstate['n'].' 0 R';
9465 }
9466 $out .= ' >>';
9467 }
9468 if (isset($this->gradients) AND !empty($this->gradients)) {
9469 $gp = '';
9470 $gs = '';
9471 foreach ($this->gradients as $id => $grad) {
9472 // gradient patterns
9473 $gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
9474 // gradient shadings
9475 $gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
9476 }
9477 $out .= ' /Pattern <<'.$gp.' >>';
9478 $out .= ' /Shading <<'.$gs.' >>';
9479 }
9480 }
9481 // spot colors
9482 if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
9483 $out .= ' /ColorSpace <<';
9484 foreach ($this->spot_colors as $color) {
9485 $out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
9486 }
9487 $out .= ' >>';
9488 }
9489 $out .= ' >>';
9490 $out .= "\n".'endobj';
9491 $this->_out($out);
9492 }
9493
9494 /**
9495 * Output Resources.
9496 * @protected
9497 */
9498 protected function _putresources() {
9499 $this->_putextgstates();
9500 $this->_putocg();
9501 $this->_putfonts();
9502 $this->_putimages();
9503 $this->_putspotcolors();
9504 $this->_putshaders();
9505 $this->_putxobjects();
9506 $this->_putresourcedict();
9507 $this->_putdests();
9508 $this->_putEmbeddedFiles();
9509 $this->_putannotsobjs();
9510 $this->_putjavascript();
9511 $this->_putbookmarks();
9512 $this->_putencryption();
9513 }
9514
9515 /**
9516 * Adds some Metadata information (Document Information Dictionary)
9517 * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
9518 * @return int object id
9519 * @protected
9520 */
9521 protected function _putinfo() {
9522 $oid = $this->_newobj();
9523 $out = '<<';
9524 // store current isunicode value
9525 $prev_isunicode = $this->isunicode;
9526 if ($this->docinfounicode) {
9527 $this->isunicode = true;
9528 }
9529 if (!TCPDF_STATIC::empty_string($this->title)) {
9530 // The document's title.
9531 $out .= ' /Title '.$this->_textstring($this->title, $oid);
9532 }
9533 if (!TCPDF_STATIC::empty_string($this->author)) {
9534 // The name of the person who created the document.
9535 $out .= ' /Author '.$this->_textstring($this->author, $oid);
9536 }
9537 if (!TCPDF_STATIC::empty_string($this->subject)) {
9538 // The subject of the document.
9539 $out .= ' /Subject '.$this->_textstring($this->subject, $oid);
9540 }
9541 if (!TCPDF_STATIC::empty_string($this->keywords)) {
9542 // Keywords associated with the document.
9543 $out .= ' /Keywords '.$this->_textstring($this->keywords, $oid);
9544 }
9545 if (!TCPDF_STATIC::empty_string($this->creator)) {
9546 // 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.
9547 $out .= ' /Creator '.$this->_textstring($this->creator, $oid);
9548 }
9549 // restore previous isunicode value
9550 $this->isunicode = $prev_isunicode;
9551 // default producer
9552 $out .= ' /Producer '.$this->_textstring(TCPDF_STATIC::getTCPDFProducer(), $oid);
9553 // The date and time the document was created, in human-readable form
9554 $out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
9555 // The date and time the document was most recently modified, in human-readable form
9556 $out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
9557 // A name object indicating whether the document has been modified to include trapping information
9558 $out .= ' /Trapped /False';
9559 $out .= ' >>';
9560 $out .= "\n".'endobj';
9561 $this->_out($out);
9562 return $oid;
9563 }
9564
9565 /**
9566 * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
9567 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9568 * @param $xmp (string) Custom XMP data.
9569 * @since 5.9.128 (2011-10-06)
9570 * @public
9571 */
9572 public function setExtraXMP($xmp) {
9573 $this->custom_xmp = $xmp;
9574 }
9575
9576 /**
9577 * Put XMP data object and return ID.
9578 * @return (int) The object ID.
9579 * @since 5.9.121 (2011-09-28)
9580 * @protected
9581 */
9582 protected function _putXMP() {
9583 $oid = $this->_newobj();
9584 // store current isunicode value
9585 $prev_isunicode = $this->isunicode;
9586 $this->isunicode = true;
9587 $prev_encrypted = $this->encrypted;
9588 $this->encrypted = false;
9589 // set XMP data
9590 $xmp = '<?xpacket begin="'.TCPDF_FONTS::unichr(0xfeff, $this->isunicode).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
9591 $xmp .= '<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 4.2.1-c043 52.372728, 2009/01/18-15:08:04">'."\n";
9592 $xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
9593 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
9594 $xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
9595 $xmp .= "\t\t\t".'<dc:title>'."\n";
9596 $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9597 $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->title).'</rdf:li>'."\n";
9598 $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9599 $xmp .= "\t\t\t".'</dc:title>'."\n";
9600 $xmp .= "\t\t\t".'<dc:creator>'."\n";
9601 $xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
9602 $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->author).'</rdf:li>'."\n";
9603 $xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
9604 $xmp .= "\t\t\t".'</dc:creator>'."\n";
9605 $xmp .= "\t\t\t".'<dc:description>'."\n";
9606 $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9607 $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->subject).'</rdf:li>'."\n";
9608 $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9609 $xmp .= "\t\t\t".'</dc:description>'."\n";
9610 $xmp .= "\t\t\t".'<dc:subject>'."\n";
9611 $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9612 $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->keywords).'</rdf:li>'."\n";
9613 $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9614 $xmp .= "\t\t\t".'</dc:subject>'."\n";
9615 $xmp .= "\t\t".'</rdf:Description>'."\n";
9616 // convert doc creation date format
9617 $dcdate = TCPDF_STATIC::getFormattedDate($this->doc_creation_timestamp);
9618 $doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
9619 $doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
9620 $doccreationdate .= '+'.substr($dcdate, 15, 2).':'.substr($dcdate, 18, 2);
9621 $doccreationdate = TCPDF_STATIC::_escapeXML($doccreationdate);
9622 // convert doc modification date format
9623 $dmdate = TCPDF_STATIC::getFormattedDate($this->doc_modification_timestamp);
9624 $docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
9625 $docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
9626 $docmoddate .= '+'.substr($dmdate, 15, 2).':'.substr($dmdate, 18, 2);
9627 $docmoddate = TCPDF_STATIC::_escapeXML($docmoddate);
9628 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
9629 $xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
9630 $xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
9631 $xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
9632 $xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
9633 $xmp .= "\t\t".'</rdf:Description>'."\n";
9634 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
9635 $xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC::_escapeXML($this->keywords).'</pdf:Keywords>'."\n";
9636 $xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC::_escapeXML(TCPDF_STATIC::getTCPDFProducer()).'</pdf:Producer>'."\n";
9637 $xmp .= "\t\t".'</rdf:Description>'."\n";
9638 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
9639 $uuid = 'uuid:'.substr($this->file_id, 0, 8).'-'.substr($this->file_id, 8, 4).'-'.substr($this->file_id, 12, 4).'-'.substr($this->file_id, 16, 4).'-'.substr($this->file_id, 20, 12);
9640 $xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
9641 $xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
9642 $xmp .= "\t\t".'</rdf:Description>'."\n";
9643 if ($this->pdfa_mode) {
9644 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
9645 $xmp .= "\t\t\t".'<pdfaid:part>1</pdfaid:part>'."\n";
9646 $xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
9647 $xmp .= "\t\t".'</rdf:Description>'."\n";
9648 }
9649 // XMP extension schemas
9650 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/" xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#" xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#">'."\n";
9651 $xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
9652 $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9653 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9654 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
9655 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
9656 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
9657 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9658 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9659 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
9660 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
9661 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
9662 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9663 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9664 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9665 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9666 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
9667 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9668 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9669 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9670 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9671 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9672 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9673 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9674 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
9675 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
9676 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
9677 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9678 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9679 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9680 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9681 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
9682 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
9683 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
9684 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9685 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9686 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9687 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
9688 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
9689 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9690 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9691 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9692 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9693 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
9694 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
9695 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9696 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9697 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9698 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9699 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9700 $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9701 $xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
9702 $xmp .= "\t\t".'</rdf:Description>'."\n";
9703 $xmp .= "\t".'</rdf:RDF>'."\n";
9704 $xmp .= $this->custom_xmp;
9705 $xmp .= '</x:xmpmeta>'."\n";
9706 $xmp .= '<?xpacket end="w"?>';
9707 $out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
9708 // restore previous isunicode value
9709 $this->isunicode = $prev_isunicode;
9710 $this->encrypted = $prev_encrypted;
9711 $this->_out($out);
9712 return $oid;
9713 }
9714
9715 /**
9716 * Output Catalog.
9717 * @return int object id
9718 * @protected
9719 */
9720 protected function _putcatalog() {
9721 // put XMP
9722 $xmpobj = $this->_putXMP();
9723 // if required, add standard sRGB_IEC61966-2.1 blackscaled ICC colour profile
9724 if ($this->pdfa_mode OR $this->force_srgb) {
9725 $iccobj = $this->_newobj();
9726 $icc = file_get_contents(dirname(__FILE__).'/include/sRGB.icc');
9727 $filter = '';
9728 if ($this->compress) {
9729 $filter = ' /Filter /FlateDecode';
9730 $icc = gzcompress($icc);
9731 }
9732 $icc = $this->_getrawstream($icc);
9733 $this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9734 }
9735 // start catalog
9736 $oid = $this->_newobj();
9737 $out = '<< /Type /Catalog';
9738 $out .= ' /Version /'.$this->PDFVersion;
9739 //$out .= ' /Extensions <<>>';
9740 $out .= ' /Pages 1 0 R';
9741 //$out .= ' /PageLabels ' //...;
9742 $out .= ' /Names <<';
9743 if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
9744 $out .= ' /JavaScript '.$this->n_js;
9745 }
9746 if (!empty($this->efnames)) {
9747 $out .= ' /EmbeddedFiles <</Names [';
9748 foreach ($this->efnames AS $fn => $fref) {
9749 $out .= ' '.$this->_datastring($fn).' '.$fref;
9750 }
9751 $out .= ' ]>>';
9752 }
9753 $out .= ' >>';
9754 if (!empty($this->dests)) {
9755 $out .= ' /Dests '.($this->n_dests).' 0 R';
9756 }
9757 $out .= $this->_putviewerpreferences();
9758 if (isset($this->LayoutMode) AND (!TCPDF_STATIC::empty_string($this->LayoutMode))) {
9759 $out .= ' /PageLayout /'.$this->LayoutMode;
9760 }
9761 if (isset($this->PageMode) AND (!TCPDF_STATIC::empty_string($this->PageMode))) {
9762 $out .= ' /PageMode /'.$this->PageMode;
9763 }
9764 if (count($this->outlines) > 0) {
9765 $out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
9766 $out .= ' /PageMode /UseOutlines';
9767 }
9768 //$out .= ' /Threads []';
9769 if ($this->ZoomMode == 'fullpage') {
9770 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
9771 } elseif ($this->ZoomMode == 'fullwidth') {
9772 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
9773 } elseif ($this->ZoomMode == 'real') {
9774 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
9775 } elseif (!is_string($this->ZoomMode)) {
9776 $out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
9777 }
9778 //$out .= ' /AA <<>>';
9779 //$out .= ' /URI <<>>';
9780 $out .= ' /Metadata '.$xmpobj.' 0 R';
9781 //$out .= ' /StructTreeRoot <<>>';
9782 //$out .= ' /MarkInfo <<>>';
9783 if (isset($this->l['a_meta_language'])) {
9784 $out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
9785 }
9786 //$out .= ' /SpiderInfo <<>>';
9787 // set OutputIntent to sRGB IEC61966-2.1 if required
9788 if ($this->pdfa_mode OR $this->force_srgb) {
9789 $out .= ' /OutputIntents [<<';
9790 $out .= ' /Type /OutputIntent';
9791 $out .= ' /S /GTS_PDFA1';
9792 $out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9793 $out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9794 $out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
9795 $out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9796 $out .= ' /DestOutputProfile '.$iccobj.' 0 R';
9797 $out .= ' >>]';
9798 }
9799 //$out .= ' /PieceInfo <<>>';
9800 if (!empty($this->pdflayers)) {
9801 $lyrobjs = '';
9802 $lyrobjs_off = '';
9803 $lyrobjs_lock = '';
9804 foreach ($this->pdflayers as $layer) {
9805 $layer_obj_ref = ' '.$layer['objid'].' 0 R';
9806 $lyrobjs .= $layer_obj_ref;
9807 if ($layer['view'] === false) {
9808 $lyrobjs_off .= $layer_obj_ref;
9809 }
9810 if ($layer['lock']) {
9811 $lyrobjs_lock .= $layer_obj_ref;
9812 }
9813 }
9814 $out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
9815 $out .= ' /D <<';
9816 $out .= ' /Name '.$this->_textstring('Layers', $oid);
9817 $out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
9818 $out .= ' /BaseState /ON';
9819 $out .= ' /OFF ['.$lyrobjs_off.']';
9820 $out .= ' /Locked ['.$lyrobjs_lock.']';
9821 $out .= ' /Intent /View';
9822 $out .= ' /AS [';
9823 $out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
9824 $out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
9825 $out .= ' ]';
9826 $out .= ' /Order ['.$lyrobjs.']';
9827 $out .= ' /ListMode /AllPages';
9828 //$out .= ' /RBGroups ['..']';
9829 //$out .= ' /Locked ['..']';
9830 $out .= ' >>';
9831 $out .= ' >>';
9832 }
9833 // AcroForm
9834 if (!empty($this->form_obj_id)
9835 OR ($this->sign AND isset($this->signature_data['cert_type']))
9836 OR !empty($this->empty_signature_appearance)) {
9837 $out .= ' /AcroForm <<';
9838 $objrefs = '';
9839 if ($this->sign AND isset($this->signature_data['cert_type'])) {
9840 // set reference for signature object
9841 $objrefs .= $this->sig_obj_id.' 0 R';
9842 }
9843 if (!empty($this->empty_signature_appearance)) {
9844 foreach ($this->empty_signature_appearance as $esa) {
9845 // set reference for empty signature objects
9846 $objrefs .= ' '.$esa['objid'].' 0 R';
9847 }
9848 }
9849 if (!empty($this->form_obj_id)) {
9850 foreach($this->form_obj_id as $objid) {
9851 $objrefs .= ' '.$objid.' 0 R';
9852 }
9853 }
9854 $out .= ' /Fields ['.$objrefs.']';
9855 // It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
9856 if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
9857 $out .= ' /NeedAppearances false';
9858 }
9859 if ($this->sign AND isset($this->signature_data['cert_type'])) {
9860 if ($this->signature_data['cert_type'] > 0) {
9861 $out .= ' /SigFlags 3';
9862 } else {
9863 $out .= ' /SigFlags 1';
9864 }
9865 }
9866 //$out .= ' /CO ';
9867 if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
9868 $out .= ' /DR <<';
9869 $out .= ' /Font <<';
9870 foreach ($this->annotation_fonts as $fontkey => $fontid) {
9871 $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9872 }
9873 $out .= ' >> >>';
9874 }
9875 $font = $this->getFontBuffer('helvetica');
9876 $out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
9877 $out .= ' /Q '.(($this->rtl)?'2':'0');
9878 //$out .= ' /XFA ';
9879 $out .= ' >>';
9880 // signatures
9881 if ($this->sign AND isset($this->signature_data['cert_type'])
9882 AND (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A'))) {
9883 if ($this->signature_data['cert_type'] > 0) {
9884 $out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
9885 } else {
9886 $out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
9887 }
9888 }
9889 }
9890 //$out .= ' /Legal <<>>';
9891 //$out .= ' /Requirements []';
9892 //$out .= ' /Collection <<>>';
9893 //$out .= ' /NeedsRendering true';
9894 $out .= ' >>';
9895 $out .= "\n".'endobj';
9896 $this->_out($out);
9897 return $oid;
9898 }
9899
9900 /**
9901 * Output viewer preferences.
9902 * @return string for viewer preferences
9903 * @author Nicola asuni
9904 * @since 3.1.000 (2008-06-09)
9905 * @protected
9906 */
9907 protected function _putviewerpreferences() {
9908 $vp = $this->viewer_preferences;
9909 $out = ' /ViewerPreferences <<';
9910 if ($this->rtl) {
9911 $out .= ' /Direction /R2L';
9912 } else {
9913 $out .= ' /Direction /L2R';
9914 }
9915 if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
9916 $out .= ' /HideToolbar true';
9917 }
9918 if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
9919 $out .= ' /HideMenubar true';
9920 }
9921 if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
9922 $out .= ' /HideWindowUI true';
9923 }
9924 if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
9925 $out .= ' /FitWindow true';
9926 }
9927 if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
9928 $out .= ' /CenterWindow true';
9929 }
9930 if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
9931 $out .= ' /DisplayDocTitle true';
9932 }
9933 if (isset($vp['NonFullScreenPageMode'])) {
9934 $out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
9935 }
9936 if (isset($vp['ViewArea'])) {
9937 $out .= ' /ViewArea /'.$vp['ViewArea'];
9938 }
9939 if (isset($vp['ViewClip'])) {
9940 $out .= ' /ViewClip /'.$vp['ViewClip'];
9941 }
9942 if (isset($vp['PrintArea'])) {
9943 $out .= ' /PrintArea /'.$vp['PrintArea'];
9944 }
9945 if (isset($vp['PrintClip'])) {
9946 $out .= ' /PrintClip /'.$vp['PrintClip'];
9947 }
9948 if (isset($vp['PrintScaling'])) {
9949 $out .= ' /PrintScaling /'.$vp['PrintScaling'];
9950 }
9951 if (isset($vp['Duplex']) AND (!TCPDF_STATIC::empty_string($vp['Duplex']))) {
9952 $out .= ' /Duplex /'.$vp['Duplex'];
9953 }
9954 if (isset($vp['PickTrayByPDFSize'])) {
9955 if ($vp['PickTrayByPDFSize']) {
9956 $out .= ' /PickTrayByPDFSize true';
9957 } else {
9958 $out .= ' /PickTrayByPDFSize false';
9959 }
9960 }
9961 if (isset($vp['PrintPageRange'])) {
9962 $PrintPageRangeNum = '';
9963 foreach ($vp['PrintPageRange'] as $k => $v) {
9964 $PrintPageRangeNum .= ' '.($v - 1).'';
9965 }
9966 $out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
9967 }
9968 if (isset($vp['NumCopies'])) {
9969 $out .= ' /NumCopies '.intval($vp['NumCopies']);
9970 }
9971 $out .= ' >>';
9972 return $out;
9973 }
9974
9975 /**
9976 * Output PDF File Header (7.5.2).
9977 * @protected
9978 */
9979 protected function _putheader() {
9980 $this->_out('%PDF-'.$this->PDFVersion);
9981 $this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
9982 }
9983
9984 /**
9985 * Output end of document (EOF).
9986 * @protected
9987 */
9988 protected function _enddoc() {
9989 if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
9990 // save subset chars of the previous font
9991 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
9992 }
9993 $this->state = 1;
9994 $this->_putheader();
9995 $this->_putpages();
9996 $this->_putresources();
9997 // empty signature fields
9998 if (!empty($this->empty_signature_appearance)) {
9999 foreach ($this->empty_signature_appearance as $key => $esa) {
10000 // widget annotation for empty signature
10001 $out = $this->_getobj($esa['objid'])."\n";
10002 $out .= '<< /Type /Annot';
10003 $out .= ' /Subtype /Widget';
10004 $out .= ' /Rect ['.$esa['rect'].']';
10005 $out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
10006 $out .= ' /F 4';
10007 $out .= ' /FT /Sig';
10008 $signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
10009 $out .= ' /T '.$this->_textstring($signame, $esa['objid']);
10010 $out .= ' /Ff 0';
10011 $out .= ' >>';
10012 $out .= "\n".'endobj';
10013 $this->_out($out);
10014 }
10015 }
10016 // Signature
10017 if ($this->sign AND isset($this->signature_data['cert_type'])) {
10018 // widget annotation for signature
10019 $out = $this->_getobj($this->sig_obj_id)."\n";
10020 $out .= '<< /Type /Annot';
10021 $out .= ' /Subtype /Widget';
10022 $out .= ' /Rect ['.$this->signature_appearance['rect'].']';
10023 $out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
10024 $out .= ' /F 4';
10025 $out .= ' /FT /Sig';
10026 $out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
10027 $out .= ' /Ff 0';
10028 $out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
10029 $out .= ' >>';
10030 $out .= "\n".'endobj';
10031 $this->_out($out);
10032 // signature
10033 $this->_putsignature();
10034 }
10035 // Info
10036 $objid_info = $this->_putinfo();
10037 // Catalog
10038 $objid_catalog = $this->_putcatalog();
10039 // Cross-ref
10040 $o = $this->bufferlen;
10041 // XREF section
10042 $this->_out('xref');
10043 $this->_out('0 '.($this->n + 1));
10044 $this->_out('0000000000 65535 f ');
10045 $freegen = ($this->n + 2);
10046 for ($i=1; $i <= $this->n; ++$i) {
10047 if (!isset($this->offsets[$i]) AND ($i > 1)) {
10048 $this->_out(sprintf('0000000000 %05d f ', $freegen));
10049 ++$freegen;
10050 } else {
10051 $this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
10052 }
10053 }
10054 // TRAILER
10055 $out = 'trailer'."\n";
10056 $out .= '<<';
10057 $out .= ' /Size '.($this->n + 1);
10058 $out .= ' /Root '.$objid_catalog.' 0 R';
10059 $out .= ' /Info '.$objid_info.' 0 R';
10060 if ($this->encrypted) {
10061 $out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
10062 }
10063 $out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
10064 $out .= ' >>';
10065 $this->_out($out);
10066 $this->_out('startxref');
10067 $this->_out($o);
10068 $this->_out('%%EOF');
10069 $this->state = 3; // end-of-doc
10070 if ($this->diskcache) {
10071 // remove temporary files used for images
10072 foreach ($this->imagekeys as $key) {
10073 // remove temporary files
10074 unlink($this->images[$key]);
10075 }
10076 foreach ($this->fontkeys as $key) {
10077 // remove temporary files
10078 unlink($this->fonts[$key]);
10079 }
10080 }
10081 }
10082
10083 /**
10084 * Initialize a new page.
10085 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
10086 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
10087 * @protected
10088 * @see getPageSizeFromFormat(), setPageFormat()
10089 */
10090 protected function _beginpage($orientation='', $format='') {
10091 ++$this->page;
10092 $this->pageobjects[$this->page] = array();
10093 $this->setPageBuffer($this->page, '');
10094 // initialize array for graphics tranformation positions inside a page buffer
10095 $this->transfmrk[$this->page] = array();
10096 $this->state = 2;
10097 if (TCPDF_STATIC::empty_string($orientation)) {
10098 if (isset($this->CurOrientation)) {
10099 $orientation = $this->CurOrientation;
10100 } elseif ($this->fwPt > $this->fhPt) {
10101 // landscape
10102 $orientation = 'L';
10103 } else {
10104 // portrait
10105 $orientation = 'P';
10106 }
10107 }
10108 if (TCPDF_STATIC::empty_string($format)) {
10109 $this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10110 $this->setPageOrientation($orientation);
10111 } else {
10112 $this->setPageFormat($format, $orientation);
10113 }
10114 if ($this->rtl) {
10115 $this->x = $this->w - $this->rMargin;
10116 } else {
10117 $this->x = $this->lMargin;
10118 }
10119 $this->y = $this->tMargin;
10120 if (isset($this->newpagegroup[$this->page])) {
10121 // start a new group
10122 $this->currpagegroup = $this->newpagegroup[$this->page];
10123 $this->pagegroups[$this->currpagegroup] = 1;
10124 } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
10125 ++$this->pagegroups[$this->currpagegroup];
10126 }
10127 }
10128
10129 /**
10130 * Mark end of page.
10131 * @protected
10132 */
10133 protected function _endpage() {
10134 $this->setVisibility('all');
10135 $this->state = 1;
10136 }
10137
10138 /**
10139 * Begin a new object and return the object number.
10140 * @return int object number
10141 * @protected
10142 */
10143 protected function _newobj() {
10144 $this->_out($this->_getobj());
10145 return $this->n;
10146 }
10147
10148 /**
10149 * Return the starting object string for the selected object ID.
10150 * @param $objid (int) Object ID (leave empty to get a new ID).
10151 * @return string the starting object string
10152 * @protected
10153 * @since 5.8.009 (2010-08-20)
10154 */
10155 protected function _getobj($objid='') {
10156 if ($objid === '') {
10157 ++$this->n;
10158 $objid = $this->n;
10159 }
10160 $this->offsets[$objid] = $this->bufferlen;
10161 $this->pageobjects[$this->page][] = $objid;
10162 return $objid.' 0 obj';
10163 }
10164
10165 /**
10166 * Underline text.
10167 * @param $x (int) X coordinate
10168 * @param $y (int) Y coordinate
10169 * @param $txt (string) text to underline
10170 * @protected
10171 */
10172 protected function _dounderline($x, $y, $txt) {
10173 $w = $this->GetStringWidth($txt);
10174 return $this->_dounderlinew($x, $y, $w);
10175 }
10176
10177 /**
10178 * Underline for rectangular text area.
10179 * @param $x (int) X coordinate
10180 * @param $y (int) Y coordinate
10181 * @param $w (int) width to underline
10182 * @protected
10183 * @since 4.8.008 (2009-09-29)
10184 */
10185 protected function _dounderlinew($x, $y, $w) {
10186 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10187 return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
10188 }
10189
10190 /**
10191 * Line through text.
10192 * @param $x (int) X coordinate
10193 * @param $y (int) Y coordinate
10194 * @param $txt (string) text to linethrough
10195 * @protected
10196 */
10197 protected function _dolinethrough($x, $y, $txt) {
10198 $w = $this->GetStringWidth($txt);
10199 return $this->_dolinethroughw($x, $y, $w);
10200 }
10201
10202 /**
10203 * Line through for rectangular text area.
10204 * @param $x (int) X coordinate
10205 * @param $y (int) Y coordinate
10206 * @param $w (int) line length (width)
10207 * @protected
10208 * @since 4.9.008 (2009-09-29)
10209 */
10210 protected function _dolinethroughw($x, $y, $w) {
10211 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10212 return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
10213 }
10214
10215 /**
10216 * Overline text.
10217 * @param $x (int) X coordinate
10218 * @param $y (int) Y coordinate
10219 * @param $txt (string) text to overline
10220 * @protected
10221 * @since 4.9.015 (2010-04-19)
10222 */
10223 protected function _dooverline($x, $y, $txt) {
10224 $w = $this->GetStringWidth($txt);
10225 return $this->_dooverlinew($x, $y, $w);
10226 }
10227
10228 /**
10229 * Overline for rectangular text area.
10230 * @param $x (int) X coordinate
10231 * @param $y (int) Y coordinate
10232 * @param $w (int) width to overline
10233 * @protected
10234 * @since 4.9.015 (2010-04-19)
10235 */
10236 protected function _dooverlinew($x, $y, $w) {
10237 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10238 return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
10239
10240 }
10241
10242 /**
10243 * Format a data string for meta information
10244 * @param $s (string) data string to escape.
10245 * @param $n (int) object ID
10246 * @return string escaped string.
10247 * @protected
10248 */
10249 protected function _datastring($s, $n=0) {
10250 if ($n == 0) {
10251 $n = $this->n;
10252 }
10253 $s = $this->_encrypt_data($n, $s);
10254 return '('. TCPDF_STATIC::_escape($s).')';
10255 }
10256
10257 /**
10258 * Set the document creation timestamp
10259 * @param $time (mixed) Document creation timestamp in seconds or date-time string.
10260 * @public
10261 * @since 5.9.152 (2012-03-23)
10262 */
10263 public function setDocCreationTimestamp($time) {
10264 if (is_string($time)) {
10265 $time = TCPDF_STATIC::getTimestamp($time);
10266 }
10267 $this->doc_creation_timestamp = intval($time);
10268 }
10269
10270 /**
10271 * Set the document modification timestamp
10272 * @param $time (mixed) Document modification timestamp in seconds or date-time string.
10273 * @public
10274 * @since 5.9.152 (2012-03-23)
10275 */
10276 public function setDocModificationTimestamp($time) {
10277 if (is_string($time)) {
10278 $time = TCPDF_STATIC::getTimestamp($time);
10279 }
10280 $this->doc_modification_timestamp = intval($time);
10281 }
10282
10283 /**
10284 * Returns document creation timestamp in seconds.
10285 * @return (int) Creation timestamp in seconds.
10286 * @public
10287 * @since 5.9.152 (2012-03-23)
10288 */
10289 public function getDocCreationTimestamp() {
10290 return $this->doc_creation_timestamp;
10291 }
10292
10293 /**
10294 * Returns document modification timestamp in seconds.
10295 * @return (int) Modfication timestamp in seconds.
10296 * @public
10297 * @since 5.9.152 (2012-03-23)
10298 */
10299 public function getDocModificationTimestamp() {
10300 return $this->doc_modification_timestamp;
10301 }
10302
10303 /**
10304 * Returns a formatted date for meta information
10305 * @param $n (int) Object ID.
10306 * @param $timestamp (int) Timestamp to convert.
10307 * @return string escaped date string.
10308 * @protected
10309 * @since 4.6.028 (2009-08-25)
10310 */
10311 protected function _datestring($n=0, $timestamp=0) {
10312 if ((empty($timestamp)) OR ($timestamp < 0)) {
10313 $timestamp = $this->doc_creation_timestamp;
10314 }
10315 return $this->_datastring('D:'.TCPDF_STATIC::getFormattedDate($timestamp), $n);
10316 }
10317
10318 /**
10319 * Format a text string for meta information
10320 * @param $s (string) string to escape.
10321 * @param $n (int) object ID
10322 * @return string escaped string.
10323 * @protected
10324 */
10325 protected function _textstring($s, $n=0) {
10326 if ($this->isunicode) {
10327 //Convert string to UTF-16BE
10328 $s = TCPDF_FONTS::UTF8ToUTF16BE($s, true, $this->isunicode, $this->CurrentFont);
10329 }
10330 return $this->_datastring($s, $n);
10331 }
10332
10333 /**
10334 * THIS METHOD IS DEPRECATED
10335 * Format a text string
10336 * @param $s (string) string to escape.
10337 * @return string escaped string.
10338 * @protected
10339 * @deprecated
10340 */
10341 protected function _escapetext($s) {
10342 if ($this->isunicode) {
10343 if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
10344 $s = TCPDF_FONTS::UTF8ToLatin1($s, $this->isunicode, $this->CurrentFont);
10345 } else {
10346 //Convert string to UTF-16BE and reverse RTL language
10347 $s = TCPDF_FONTS::utf8StrRev($s, false, $this->tmprtl, $this->isunicode, $this->CurrentFont);
10348 }
10349 }
10350 return TCPDF_STATIC::_escape($s);
10351 }
10352
10353 /**
10354 * get raw output stream.
10355 * @param $s (string) string to output.
10356 * @param $n (int) object reference for encryption mode
10357 * @protected
10358 * @author Nicola Asuni
10359 * @since 5.5.000 (2010-06-22)
10360 */
10361 protected function _getrawstream($s, $n=0) {
10362 if ($n <= 0) {
10363 // default to current object
10364 $n = $this->n;
10365 }
10366 return $this->_encrypt_data($n, $s);
10367 }
10368
10369 /**
10370 * Format output stream (DEPRECATED).
10371 * @param $s (string) string to output.
10372 * @param $n (int) object reference for encryption mode
10373 * @protected
10374 * @deprecated
10375 */
10376 protected function _getstream($s, $n=0) {
10377 return 'stream'."\n".$this->_getrawstream($s, $n)."\n".'endstream';
10378 }
10379
10380 /**
10381 * Output a stream (DEPRECATED).
10382 * @param $s (string) string to output.
10383 * @param $n (int) object reference for encryption mode
10384 * @protected
10385 * @deprecated
10386 */
10387 protected function _putstream($s, $n=0) {
10388 $this->_out($this->_getstream($s, $n));
10389 }
10390
10391 /**
10392 * Output a string to the document.
10393 * @param $s (string) string to output.
10394 * @protected
10395 */
10396 protected function _out($s) {
10397 if ($this->state == 2) {
10398 if ($this->inxobj) {
10399 // we are inside an XObject template
10400 $this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
10401 } elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
10402 // puts data before page footer
10403 $pagebuff = $this->getPageBuffer($this->page);
10404 $page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
10405 $footer = substr($pagebuff, -$this->footerlen[$this->page]);
10406 $this->setPageBuffer($this->page, $page.$s."\n".$footer);
10407 // update footer position
10408 $this->footerpos[$this->page] += strlen($s."\n");
10409 } else {
10410 // set page data
10411 $this->setPageBuffer($this->page, $s."\n", true);
10412 }
10413 } elseif ($this->state > 0) {
10414 // set general data
10415 $this->setBuffer($s."\n");
10416 }
10417 }
10418
10419 /**
10420 * Set header font.
10421 * @param $font (array) Array describing the basic font parameters: (family, style, size).
10422 * @public
10423 * @since 1.1
10424 */
10425 public function setHeaderFont($font) {
10426 $this->header_font = $font;
10427 }
10428
10429 /**
10430 * Get header font.
10431 * @return array() Array describing the basic font parameters: (family, style, size).
10432 * @public
10433 * @since 4.0.012 (2008-07-24)
10434 */
10435 public function getHeaderFont() {
10436 return $this->header_font;
10437 }
10438
10439 /**
10440 * Set footer font.
10441 * @param $font (array) Array describing the basic font parameters: (family, style, size).
10442 * @public
10443 * @since 1.1
10444 */
10445 public function setFooterFont($font) {
10446 $this->footer_font = $font;
10447 }
10448
10449 /**
10450 * Get Footer font.
10451 * @return array() Array describing the basic font parameters: (family, style, size).
10452 * @public
10453 * @since 4.0.012 (2008-07-24)
10454 */
10455 public function getFooterFont() {
10456 return $this->footer_font;
10457 }
10458
10459 /**
10460 * Set language array.
10461 * @param $language (array)
10462 * @public
10463 * @since 1.1
10464 */
10465 public function setLanguageArray($language) {
10466 $this->l = $language;
10467 if (isset($this->l['a_meta_dir'])) {
10468 $this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
10469 } else {
10470 $this->rtl = false;
10471 }
10472 }
10473
10474 /**
10475 * Returns the PDF data.
10476 * @public
10477 */
10478 public function getPDFData() {
10479 if ($this->state < 3) {
10480 $this->Close();
10481 }
10482 return $this->buffer;
10483 }
10484
10485 /**
10486 * Output anchor link.
10487 * @param $url (string) link URL or internal link (i.e.: &lt;a href="#23,4.5"&gt;link to page 23 at 4.5 Y position&lt;/a&gt;)
10488 * @param $name (string) link name
10489 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
10490 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
10491 * @param $color (array) array of RGB text color
10492 * @param $style (string) font style (U, D, B, I)
10493 * @param $firstblock (boolean) if true the string is the starting of a line.
10494 * @return the number of cells used or the remaining text if $firstline = true;
10495 * @public
10496 */
10497 public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
10498 if (isset($url[1]) AND ($url[0] == '#')) {
10499 // convert url to internal link
10500 $lnkdata = explode(',', $url);
10501 if (isset($lnkdata[0]) ) {
10502 $page = substr($lnkdata[0], 1);
10503 if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
10504 $lnky = floatval($lnkdata[1]);
10505 } else {
10506 $lnky = 0;
10507 }
10508 $url = $this->AddLink();
10509 $this->SetLink($url, $lnky, $page);
10510 }
10511 }
10512 // store current settings
10513 $prevcolor = $this->fgcolor;
10514 $prevstyle = $this->FontStyle;
10515 if (empty($color)) {
10516 $this->SetTextColorArray($this->htmlLinkColorArray);
10517 } else {
10518 $this->SetTextColorArray($color);
10519 }
10520 if ($style == -1) {
10521 $this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
10522 } else {
10523 $this->SetFont('', $this->FontStyle.$style);
10524 }
10525 $ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
10526 // restore settings
10527 $this->SetFont('', $prevstyle);
10528 $this->SetTextColorArray($prevcolor);
10529 return $ret;
10530 }
10531
10532 /**
10533 * Converts pixels to User's Units.
10534 * @param $px (int) pixels
10535 * @return float value in user's unit
10536 * @public
10537 * @see setImageScale(), getImageScale()
10538 */
10539 public function pixelsToUnits($px) {
10540 return ($px / ($this->imgscale * $this->k));
10541 }
10542
10543 /**
10544 * Reverse function for htmlentities.
10545 * Convert entities in UTF-8.
10546 * @param $text_to_convert (string) Text to convert.
10547 * @return string converted text string
10548 * @public
10549 */
10550 public function unhtmlentities($text_to_convert) {
10551 return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
10552 }
10553
10554 // ENCRYPTION METHODS ----------------------------------
10555
10556 /**
10557 * Compute encryption key depending on object number where the encrypted data is stored.
10558 * This is used for all strings and streams without crypt filter specifier.
10559 * @param $n (int) object number
10560 * @return int object key
10561 * @protected
10562 * @author Nicola Asuni
10563 * @since 2.0.000 (2008-01-02)
10564 */
10565 protected function _objectkey($n) {
10566 $objkey = $this->encryptdata['key'].pack('VXxx', $n);
10567 if ($this->encryptdata['mode'] == 2) { // AES-128
10568 // AES padding
10569 $objkey .= "\x73\x41\x6C\x54"; // sAlT
10570 }
10571 $objkey = substr(TCPDF_STATIC::_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
10572 $objkey = substr($objkey, 0, 16);
10573 return $objkey;
10574 }
10575
10576 /**
10577 * Encrypt the input string.
10578 * @param $n (int) object number
10579 * @param $s (string) data string to encrypt
10580 * @return encrypted string
10581 * @protected
10582 * @author Nicola Asuni
10583 * @since 5.0.005 (2010-05-11)
10584 */
10585 protected function _encrypt_data($n, $s) {
10586 if (!$this->encrypted) {
10587 return $s;
10588 }
10589 switch ($this->encryptdata['mode']) {
10590 case 0: // RC4-40
10591 case 1: { // RC4-128
10592 $s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
10593 break;
10594 }
10595 case 2: { // AES-128
10596 $s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
10597 break;
10598 }
10599 case 3: { // AES-256
10600 $s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
10601 break;
10602 }
10603 }
10604 return $s;
10605 }
10606
10607 /**
10608 * Put encryption on PDF document.
10609 * @protected
10610 * @author Nicola Asuni
10611 * @since 2.0.000 (2008-01-02)
10612 */
10613 protected function _putencryption() {
10614 if (!$this->encrypted) {
10615 return;
10616 }
10617 $this->encryptdata['objid'] = $this->_newobj();
10618 $out = '<<';
10619 if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
10620 $this->encryptdata['Filter'] = 'Standard';
10621 }
10622 $out .= ' /Filter /'.$this->encryptdata['Filter'];
10623 if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
10624 $out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
10625 }
10626 if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
10627 $this->encryptdata['V'] = 1;
10628 }
10629 // V is a code specifying the algorithm to be used in encrypting and decrypting the document
10630 $out .= ' /V '.$this->encryptdata['V'];
10631 if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
10632 // The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
10633 $out .= ' /Length '.$this->encryptdata['Length'];
10634 } else {
10635 $out .= ' /Length 40';
10636 }
10637 if ($this->encryptdata['V'] >= 4) {
10638 if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
10639 $this->encryptdata['StmF'] = 'Identity';
10640 }
10641 if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
10642 // The name of the crypt filter that shall be used when decrypting all strings in the document.
10643 $this->encryptdata['StrF'] = 'Identity';
10644 }
10645 // A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
10646 if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
10647 $out .= ' /CF <<';
10648 $out .= ' /'.$this->encryptdata['StmF'].' <<';
10649 $out .= ' /Type /CryptFilter';
10650 if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
10651 // The method used
10652 $out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
10653 if ($this->encryptdata['pubkey']) {
10654 $out .= ' /Recipients [';
10655 foreach ($this->encryptdata['Recipients'] as $rec) {
10656 $out .= ' <'.$rec.'>';
10657 }
10658 $out .= ' ]';
10659 if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
10660 $out .= ' /EncryptMetadata false';
10661 } else {
10662 $out .= ' /EncryptMetadata true';
10663 }
10664 }
10665 } else {
10666 $out .= ' /CFM /None';
10667 }
10668 if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
10669 // The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
10670 $out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
10671 } else {
10672 $out .= ' /AuthEvent /DocOpen';
10673 }
10674 if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
10675 // The bit length of the encryption key.
10676 $out .= ' /Length '.$this->encryptdata['CF']['Length'];
10677 }
10678 $out .= ' >> >>';
10679 }
10680 // The name of the crypt filter that shall be used by default when decrypting streams.
10681 $out .= ' /StmF /'.$this->encryptdata['StmF'];
10682 // The name of the crypt filter that shall be used when decrypting all strings in the document.
10683 $out .= ' /StrF /'.$this->encryptdata['StrF'];
10684 if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
10685 // The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
10686 $out .= ' /EFF /'.$this->encryptdata[''];
10687 }
10688 }
10689 // Additional encryption dictionary entries for the standard security handler
10690 if ($this->encryptdata['pubkey']) {
10691 if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
10692 $out .= ' /Recipients [';
10693 foreach ($this->encryptdata['Recipients'] as $rec) {
10694 $out .= ' <'.$rec.'>';
10695 }
10696 $out .= ' ]';
10697 }
10698 } else {
10699 $out .= ' /R';
10700 if ($this->encryptdata['V'] == 5) { // AES-256
10701 $out .= ' 5';
10702 $out .= ' /OE ('.TCPDF_STATIC::_escape($this->encryptdata['OE']).')';
10703 $out .= ' /UE ('.TCPDF_STATIC::_escape($this->encryptdata['UE']).')';
10704 $out .= ' /Perms ('.TCPDF_STATIC::_escape($this->encryptdata['perms']).')';
10705 } elseif ($this->encryptdata['V'] == 4) { // AES-128
10706 $out .= ' 4';
10707 } elseif ($this->encryptdata['V'] < 2) { // RC-40
10708 $out .= ' 2';
10709 } else { // RC-128
10710 $out .= ' 3';
10711 }
10712 $out .= ' /O ('.TCPDF_STATIC::_escape($this->encryptdata['O']).')';
10713 $out .= ' /U ('.TCPDF_STATIC::_escape($this->encryptdata['U']).')';
10714 $out .= ' /P '.$this->encryptdata['P'];
10715 if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
10716 $out .= ' /EncryptMetadata false';
10717 } else {
10718 $out .= ' /EncryptMetadata true';
10719 }
10720 }
10721 $out .= ' >>';
10722 $out .= "\n".'endobj';
10723 $this->_out($out);
10724 }
10725
10726 /**
10727 * Compute U value (used for encryption)
10728 * @return string U value
10729 * @protected
10730 * @since 2.0.000 (2008-01-02)
10731 * @author Nicola Asuni
10732 */
10733 protected function _Uvalue() {
10734 if ($this->encryptdata['mode'] == 0) { // RC4-40
10735 return TCPDF_STATIC::_RC4($this->encryptdata['key'], TCPDF_STATIC::$enc_padding, $this->last_enc_key, $this->last_enc_key_c);
10736 } elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
10737 $tmp = TCPDF_STATIC::_md5_16(TCPDF_STATIC::$enc_padding.$this->encryptdata['fileid']);
10738 $enc = TCPDF_STATIC::_RC4($this->encryptdata['key'], $tmp, $this->last_enc_key, $this->last_enc_key_c);
10739 $len = strlen($tmp);
10740 for ($i = 1; $i <= 19; ++$i) {
10741 $ek = '';
10742 for ($j = 0; $j < $len; ++$j) {
10743 $ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
10744 }
10745 $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10746 }
10747 $enc .= str_repeat("\x00", 16);
10748 return substr($enc, 0, 32);
10749 } elseif ($this->encryptdata['mode'] == 3) { // AES-256
10750 $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10751 // User Validation Salt
10752 $this->encryptdata['UVS'] = substr($seed, 0, 8);
10753 // User Key Salt
10754 $this->encryptdata['UKS'] = substr($seed, 8, 16);
10755 return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
10756 }
10757 }
10758
10759 /**
10760 * Compute UE value (used for encryption)
10761 * @return string UE value
10762 * @protected
10763 * @since 5.9.006 (2010-10-19)
10764 * @author Nicola Asuni
10765 */
10766 protected function _UEvalue() {
10767 $hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
10768 $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
10769 return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
10770 }
10771
10772 /**
10773 * Compute O value (used for encryption)
10774 * @return string O value
10775 * @protected
10776 * @since 2.0.000 (2008-01-02)
10777 * @author Nicola Asuni
10778 */
10779 protected function _Ovalue() {
10780 if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
10781 $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['owner_password']);
10782 if ($this->encryptdata['mode'] > 0) {
10783 for ($i = 0; $i < 50; ++$i) {
10784 $tmp = TCPDF_STATIC::_md5_16($tmp);
10785 }
10786 }
10787 $owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
10788 $enc = TCPDF_STATIC::_RC4($owner_key, $this->encryptdata['user_password'], $this->last_enc_key, $this->last_enc_key_c);
10789 if ($this->encryptdata['mode'] > 0) {
10790 $len = strlen($owner_key);
10791 for ($i = 1; $i <= 19; ++$i) {
10792 $ek = '';
10793 for ($j = 0; $j < $len; ++$j) {
10794 $ek .= chr(ord($owner_key[$j]) ^ $i);
10795 }
10796 $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10797 }
10798 }
10799 return $enc;
10800 } elseif ($this->encryptdata['mode'] == 3) { // AES-256
10801 $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10802 // Owner Validation Salt
10803 $this->encryptdata['OVS'] = substr($seed, 0, 8);
10804 // Owner Key Salt
10805 $this->encryptdata['OKS'] = substr($seed, 8, 16);
10806 return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
10807 }
10808 }
10809
10810 /**
10811 * Compute OE value (used for encryption)
10812 * @return string OE value
10813 * @protected
10814 * @since 5.9.006 (2010-10-19)
10815 * @author Nicola Asuni
10816 */
10817 protected function _OEvalue() {
10818 $hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
10819 $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
10820 return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
10821 }
10822
10823 /**
10824 * Convert password for AES-256 encryption mode
10825 * @param $password (string) password
10826 * @return string password
10827 * @protected
10828 * @since 5.9.006 (2010-10-19)
10829 * @author Nicola Asuni
10830 */
10831 protected function _fixAES256Password($password) {
10832 $psw = ''; // password to be returned
10833 $psw_array = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($password, $this->isunicode, $this->CurrentFont), $password, $this->rtl, $this->isunicode, $this->CurrentFont);
10834 foreach ($psw_array as $c) {
10835 $psw .= TCPDF_FONTS::unichr($c, $this->isunicode);
10836 }
10837 return substr($psw, 0, 127);
10838 }
10839
10840 /**
10841 * Compute encryption key
10842 * @protected
10843 * @since 2.0.000 (2008-01-02)
10844 * @author Nicola Asuni
10845 */
10846 protected function _generateencryptionkey() {
10847 $keybytelen = ($this->encryptdata['Length'] / 8);
10848 if (!$this->encryptdata['pubkey']) { // standard mode
10849 if ($this->encryptdata['mode'] == 3) { // AES-256
10850 // generate 256 bit random key
10851 $this->encryptdata['key'] = substr(hash('sha256', TCPDF_STATIC::getRandomSeed(), true), 0, $keybytelen);
10852 // truncate passwords
10853 $this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
10854 $this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
10855 // Compute U value
10856 $this->encryptdata['U'] = $this->_Uvalue();
10857 // Compute UE value
10858 $this->encryptdata['UE'] = $this->_UEvalue();
10859 // Compute O value
10860 $this->encryptdata['O'] = $this->_Ovalue();
10861 // Compute OE value
10862 $this->encryptdata['OE'] = $this->_OEvalue();
10863 // Compute P value
10864 $this->encryptdata['P'] = $this->encryptdata['protection'];
10865 // Computing the encryption dictionary's Perms (permissions) value
10866 $perms = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
10867 $perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
10868 if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
10869 $perms .= 'F';
10870 } else {
10871 $perms .= 'T';
10872 }
10873 $perms .= 'adb'; // bytes 9-11
10874 $perms .= 'nick'; // bytes 12-15
10875 $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB));
10876 $this->encryptdata['perms'] = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->encryptdata['key'], $perms, MCRYPT_MODE_ECB, $iv);
10877 } else { // RC4-40, RC4-128, AES-128
10878 // Pad passwords
10879 $this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10880 $this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10881 // Compute O value
10882 $this->encryptdata['O'] = $this->_Ovalue();
10883 // get default permissions (reverse byte order)
10884 $permissions = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']);
10885 // Compute encryption key
10886 $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
10887 if ($this->encryptdata['mode'] > 0) {
10888 for ($i = 0; $i < 50; ++$i) {
10889 $tmp = TCPDF_STATIC::_md5_16(substr($tmp, 0, $keybytelen));
10890 }
10891 }
10892 $this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
10893 // Compute U value
10894 $this->encryptdata['U'] = $this->_Uvalue();
10895 // Compute P value
10896 $this->encryptdata['P'] = $this->encryptdata['protection'];
10897 }
10898 } else { // Public-Key mode
10899 // random 20-byte seed
10900 $seed = sha1(TCPDF_STATIC::getRandomSeed(), true);
10901 $recipient_bytes = '';
10902 foreach ($this->encryptdata['pubkeys'] as $pubkey) {
10903 // for each public certificate
10904 if (isset($pubkey['p'])) {
10905 $pkprotection = TCPDF_STATIC::getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
10906 } else {
10907 $pkprotection = $this->encryptdata['protection'];
10908 }
10909 // get default permissions (reverse byte order)
10910 $pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
10911 // envelope data
10912 $envelope = $seed.$pkpermissions;
10913 // write the envelope data to a temporary file
10914 $tempkeyfile = TCPDF_STATIC::getObjFilename('key');
10915 $f = fopen($tempkeyfile, 'wb');
10916 if (!$f) {
10917 $this->Error('Unable to create temporary key file: '.$tempkeyfile);
10918 }
10919 $envelope_length = strlen($envelope);
10920 fwrite($f, $envelope, $envelope_length);
10921 fclose($f);
10922 $tempencfile = TCPDF_STATIC::getObjFilename('enc');
10923 if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
10924 $this->Error('Unable to encrypt the file: '.$tempkeyfile);
10925 }
10926 unlink($tempkeyfile);
10927 // read encryption signature
10928 $signature = file_get_contents($tempencfile, false, null, $envelope_length);
10929 unlink($tempencfile);
10930 // extract signature
10931 $signature = substr($signature, strpos($signature, 'Content-Disposition'));
10932 $tmparr = explode("\n\n", $signature);
10933 $signature = trim($tmparr[1]);
10934 unset($tmparr);
10935 // decode signature
10936 $signature = base64_decode($signature);
10937 // convert signature to hex
10938 $hexsignature = current(unpack('H*', $signature));
10939 // store signature on recipients array
10940 $this->encryptdata['Recipients'][] = $hexsignature;
10941 // The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
10942 $recipient_bytes .= $signature;
10943 }
10944 // calculate encryption key
10945 if ($this->encryptdata['mode'] == 3) { // AES-256
10946 $this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
10947 } else { // RC4-40, RC4-128, AES-128
10948 $this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
10949 }
10950 }
10951 }
10952
10953 /**
10954 * Set document protection
10955 * Remark: the protection against modification is for people who have the full Acrobat product.
10956 * 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.
10957 * 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.
10958 * @param $permissions (Array) the set of permissions (specify the ones you want to block):<ul><li>print : Print the document;</li><li>modify : Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';</li><li>copy : Copy or otherwise extract text and graphics from the document;</li><li>annot-forms : Add or modify text annotations, fill in interactive form fields, and, if 'modify' is also set, create or modify interactive form fields (including signature fields);</li><li>fill-forms : Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified;</li><li>extract : Extract text and graphics (in support of accessibility to users with disabilities or for other purposes);</li><li>assemble : Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set;</li><li>print-high : Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.</li><li>owner : (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.</li></ul>
10959 * @param $user_pass (String) user password. Empty by default.
10960 * @param $owner_pass (String) owner password. If not specified, a random value is used.
10961 * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
10962 * @param $pubkeys (String) array of recipients containing public-key certificates ('c') and permissions ('p'). For example: array(array('c' => 'file://../examples/data/cert/tcpdf.crt', 'p' => array('print')))
10963 * @public
10964 * @since 2.0.000 (2008-01-02)
10965 * @author Nicola Asuni
10966 */
10967 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) {
10968 if ($this->pdfa_mode) {
10969 // encryption is not allowed in PDF/A mode
10970 return;
10971 }
10972 $this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
10973 if (($pubkeys !== null) AND (is_array($pubkeys))) {
10974 // public-key mode
10975 $this->encryptdata['pubkeys'] = $pubkeys;
10976 if ($mode == 0) {
10977 // public-Key Security requires at least 128 bit
10978 $mode = 1;
10979 }
10980 if (!function_exists('openssl_pkcs7_encrypt')) {
10981 $this->Error('Public-Key Security requires openssl library.');
10982 }
10983 // Set Public-Key filter (availabe are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
10984 $this->encryptdata['pubkey'] = true;
10985 $this->encryptdata['Filter'] = 'Adobe.PubSec';
10986 $this->encryptdata['StmF'] = 'DefaultCryptFilter';
10987 $this->encryptdata['StrF'] = 'DefaultCryptFilter';
10988 } else {
10989 // standard mode (password mode)
10990 $this->encryptdata['pubkey'] = false;
10991 $this->encryptdata['Filter'] = 'Standard';
10992 $this->encryptdata['StmF'] = 'StdCF';
10993 $this->encryptdata['StrF'] = 'StdCF';
10994 }
10995 if ($mode > 1) { // AES
10996 if (!extension_loaded('mcrypt')) {
10997 $this->Error('AES encryption requires mcrypt library (http://www.php.net/manual/en/mcrypt.requirements.php).');
10998 }
10999 if (mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
11000 $this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
11001 }
11002 if (($mode == 3) AND !function_exists('hash')) {
11003 // the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
11004 $this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
11005 }
11006 }
11007 if ($owner_pass === null) {
11008 $owner_pass = md5(TCPDF_STATIC::getRandomSeed());
11009 }
11010 $this->encryptdata['user_password'] = $user_pass;
11011 $this->encryptdata['owner_password'] = $owner_pass;
11012 $this->encryptdata['mode'] = $mode;
11013 switch ($mode) {
11014 case 0: { // RC4 40 bit
11015 $this->encryptdata['V'] = 1;
11016 $this->encryptdata['Length'] = 40;
11017 $this->encryptdata['CF']['CFM'] = 'V2';
11018 break;
11019 }
11020 case 1: { // RC4 128 bit
11021 $this->encryptdata['V'] = 2;
11022 $this->encryptdata['Length'] = 128;
11023 $this->encryptdata['CF']['CFM'] = 'V2';
11024 if ($this->encryptdata['pubkey']) {
11025 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
11026 $this->encryptdata['Recipients'] = array();
11027 }
11028 break;
11029 }
11030 case 2: { // AES 128 bit
11031 $this->encryptdata['V'] = 4;
11032 $this->encryptdata['Length'] = 128;
11033 $this->encryptdata['CF']['CFM'] = 'AESV2';
11034 $this->encryptdata['CF']['Length'] = 128;
11035 if ($this->encryptdata['pubkey']) {
11036 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
11037 $this->encryptdata['Recipients'] = array();
11038 }
11039 break;
11040 }
11041 case 3: { // AES 256 bit
11042 $this->encryptdata['V'] = 5;
11043 $this->encryptdata['Length'] = 256;
11044 $this->encryptdata['CF']['CFM'] = 'AESV3';
11045 $this->encryptdata['CF']['Length'] = 256;
11046 if ($this->encryptdata['pubkey']) {
11047 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
11048 $this->encryptdata['Recipients'] = array();
11049 }
11050 break;
11051 }
11052 }
11053 $this->encrypted = true;
11054 $this->encryptdata['fileid'] = TCPDF_STATIC::convertHexStringToString($this->file_id);
11055 $this->_generateencryptionkey();
11056 }
11057
11058 // END OF ENCRYPTION FUNCTIONS -------------------------
11059
11060 // START TRANSFORMATIONS SECTION -----------------------
11061
11062 /**
11063 * Starts a 2D tranformation saving current graphic state.
11064 * This function must be called before scaling, mirroring, translation, rotation and skewing.
11065 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11066 * @public
11067 * @since 2.1.000 (2008-01-07)
11068 * @see StartTransform(), StopTransform()
11069 */
11070 public function StartTransform() {
11071 if ($this->state != 2) {
11072 return;
11073 }
11074 $this->_outSaveGraphicsState();
11075 if ($this->inxobj) {
11076 // we are inside an XObject template
11077 $this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
11078 } else {
11079 $this->transfmrk[$this->page][] = $this->pagelen[$this->page];
11080 }
11081 ++$this->transfmatrix_key;
11082 $this->transfmatrix[$this->transfmatrix_key] = array();
11083 }
11084
11085 /**
11086 * Stops a 2D tranformation restoring previous graphic state.
11087 * This function must be called after scaling, mirroring, translation, rotation and skewing.
11088 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11089 * @public
11090 * @since 2.1.000 (2008-01-07)
11091 * @see StartTransform(), StopTransform()
11092 */
11093 public function StopTransform() {
11094 if ($this->state != 2) {
11095 return;
11096 }
11097 $this->_outRestoreGraphicsState();
11098 if (isset($this->transfmatrix[$this->transfmatrix_key])) {
11099 array_pop($this->transfmatrix[$this->transfmatrix_key]);
11100 --$this->transfmatrix_key;
11101 }
11102 if ($this->inxobj) {
11103 // we are inside an XObject template
11104 array_pop($this->xobjects[$this->xobjid]['transfmrk']);
11105 } else {
11106 array_pop($this->transfmrk[$this->page]);
11107 }
11108 }
11109 /**
11110 * Horizontal Scaling.
11111 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
11112 * @param $x (int) abscissa of the scaling center. Default is current x position
11113 * @param $y (int) ordinate of the scaling center. Default is current y position
11114 * @public
11115 * @since 2.1.000 (2008-01-07)
11116 * @see StartTransform(), StopTransform()
11117 */
11118 public function ScaleX($s_x, $x='', $y='') {
11119 $this->Scale($s_x, 100, $x, $y);
11120 }
11121
11122 /**
11123 * Vertical Scaling.
11124 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
11125 * @param $x (int) abscissa of the scaling center. Default is current x position
11126 * @param $y (int) ordinate of the scaling center. Default is current y position
11127 * @public
11128 * @since 2.1.000 (2008-01-07)
11129 * @see StartTransform(), StopTransform()
11130 */
11131 public function ScaleY($s_y, $x='', $y='') {
11132 $this->Scale(100, $s_y, $x, $y);
11133 }
11134
11135 /**
11136 * Vertical and horizontal proportional Scaling.
11137 * @param $s (float) scaling factor for width and height as percent. 0 is not allowed.
11138 * @param $x (int) abscissa of the scaling center. Default is current x position
11139 * @param $y (int) ordinate of the scaling center. Default is current y position
11140 * @public
11141 * @since 2.1.000 (2008-01-07)
11142 * @see StartTransform(), StopTransform()
11143 */
11144 public function ScaleXY($s, $x='', $y='') {
11145 $this->Scale($s, $s, $x, $y);
11146 }
11147
11148 /**
11149 * Vertical and horizontal non-proportional Scaling.
11150 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
11151 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
11152 * @param $x (int) abscissa of the scaling center. Default is current x position
11153 * @param $y (int) ordinate of the scaling center. Default is current y position
11154 * @public
11155 * @since 2.1.000 (2008-01-07)
11156 * @see StartTransform(), StopTransform()
11157 */
11158 public function Scale($s_x, $s_y, $x='', $y='') {
11159 if ($x === '') {
11160 $x = $this->x;
11161 }
11162 if ($y === '') {
11163 $y = $this->y;
11164 }
11165 if (($s_x == 0) OR ($s_y == 0)) {
11166 $this->Error('Please do not use values equal to zero for scaling');
11167 }
11168 $y = ($this->h - $y) * $this->k;
11169 $x *= $this->k;
11170 //calculate elements of transformation matrix
11171 $s_x /= 100;
11172 $s_y /= 100;
11173 $tm = array();
11174 $tm[0] = $s_x;
11175 $tm[1] = 0;
11176 $tm[2] = 0;
11177 $tm[3] = $s_y;
11178 $tm[4] = $x * (1 - $s_x);
11179 $tm[5] = $y * (1 - $s_y);
11180 //scale the coordinate system
11181 $this->Transform($tm);
11182 }
11183
11184 /**
11185 * Horizontal Mirroring.
11186 * @param $x (int) abscissa of the point. Default is current x position
11187 * @public
11188 * @since 2.1.000 (2008-01-07)
11189 * @see StartTransform(), StopTransform()
11190 */
11191 public function MirrorH($x='') {
11192 $this->Scale(-100, 100, $x);
11193 }
11194
11195 /**
11196 * Verical Mirroring.
11197 * @param $y (int) ordinate of the point. Default is current y position
11198 * @public
11199 * @since 2.1.000 (2008-01-07)
11200 * @see StartTransform(), StopTransform()
11201 */
11202 public function MirrorV($y='') {
11203 $this->Scale(100, -100, '', $y);
11204 }
11205
11206 /**
11207 * Point reflection mirroring.
11208 * @param $x (int) abscissa of the point. Default is current x position
11209 * @param $y (int) ordinate of the point. Default is current y position
11210 * @public
11211 * @since 2.1.000 (2008-01-07)
11212 * @see StartTransform(), StopTransform()
11213 */
11214 public function MirrorP($x='',$y='') {
11215 $this->Scale(-100, -100, $x, $y);
11216 }
11217
11218 /**
11219 * Reflection against a straight line through point (x, y) with the gradient angle (angle).
11220 * @param $angle (float) gradient angle of the straight line. Default is 0 (horizontal line).
11221 * @param $x (int) abscissa of the point. Default is current x position
11222 * @param $y (int) ordinate of the point. Default is current y position
11223 * @public
11224 * @since 2.1.000 (2008-01-07)
11225 * @see StartTransform(), StopTransform()
11226 */
11227 public function MirrorL($angle=0, $x='',$y='') {
11228 $this->Scale(-100, 100, $x, $y);
11229 $this->Rotate(-2*($angle-90), $x, $y);
11230 }
11231
11232 /**
11233 * Translate graphic object horizontally.
11234 * @param $t_x (int) movement to the right (or left for RTL)
11235 * @public
11236 * @since 2.1.000 (2008-01-07)
11237 * @see StartTransform(), StopTransform()
11238 */
11239 public function TranslateX($t_x) {
11240 $this->Translate($t_x, 0);
11241 }
11242
11243 /**
11244 * Translate graphic object vertically.
11245 * @param $t_y (int) movement to the bottom
11246 * @public
11247 * @since 2.1.000 (2008-01-07)
11248 * @see StartTransform(), StopTransform()
11249 */
11250 public function TranslateY($t_y) {
11251 $this->Translate(0, $t_y);
11252 }
11253
11254 /**
11255 * Translate graphic object horizontally and vertically.
11256 * @param $t_x (int) movement to the right
11257 * @param $t_y (int) movement to the bottom
11258 * @public
11259 * @since 2.1.000 (2008-01-07)
11260 * @see StartTransform(), StopTransform()
11261 */
11262 public function Translate($t_x, $t_y) {
11263 //calculate elements of transformation matrix
11264 $tm = array();
11265 $tm[0] = 1;
11266 $tm[1] = 0;
11267 $tm[2] = 0;
11268 $tm[3] = 1;
11269 $tm[4] = $t_x * $this->k;
11270 $tm[5] = -$t_y * $this->k;
11271 //translate the coordinate system
11272 $this->Transform($tm);
11273 }
11274
11275 /**
11276 * Rotate object.
11277 * @param $angle (float) angle in degrees for counter-clockwise rotation
11278 * @param $x (int) abscissa of the rotation center. Default is current x position
11279 * @param $y (int) ordinate of the rotation center. Default is current y position
11280 * @public
11281 * @since 2.1.000 (2008-01-07)
11282 * @see StartTransform(), StopTransform()
11283 */
11284 public function Rotate($angle, $x='', $y='') {
11285 if ($x === '') {
11286 $x = $this->x;
11287 }
11288 if ($y === '') {
11289 $y = $this->y;
11290 }
11291 $y = ($this->h - $y) * $this->k;
11292 $x *= $this->k;
11293 //calculate elements of transformation matrix
11294 $tm = array();
11295 $tm[0] = cos(deg2rad($angle));
11296 $tm[1] = sin(deg2rad($angle));
11297 $tm[2] = -$tm[1];
11298 $tm[3] = $tm[0];
11299 $tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
11300 $tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
11301 //rotate the coordinate system around ($x,$y)
11302 $this->Transform($tm);
11303 }
11304
11305 /**
11306 * Skew horizontally.
11307 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11308 * @param $x (int) abscissa of the skewing center. default is current x position
11309 * @param $y (int) ordinate of the skewing center. default is current y position
11310 * @public
11311 * @since 2.1.000 (2008-01-07)
11312 * @see StartTransform(), StopTransform()
11313 */
11314 public function SkewX($angle_x, $x='', $y='') {
11315 $this->Skew($angle_x, 0, $x, $y);
11316 }
11317
11318 /**
11319 * Skew vertically.
11320 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11321 * @param $x (int) abscissa of the skewing center. default is current x position
11322 * @param $y (int) ordinate of the skewing center. default is current y position
11323 * @public
11324 * @since 2.1.000 (2008-01-07)
11325 * @see StartTransform(), StopTransform()
11326 */
11327 public function SkewY($angle_y, $x='', $y='') {
11328 $this->Skew(0, $angle_y, $x, $y);
11329 }
11330
11331 /**
11332 * Skew.
11333 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11334 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11335 * @param $x (int) abscissa of the skewing center. default is current x position
11336 * @param $y (int) ordinate of the skewing center. default is current y position
11337 * @public
11338 * @since 2.1.000 (2008-01-07)
11339 * @see StartTransform(), StopTransform()
11340 */
11341 public function Skew($angle_x, $angle_y, $x='', $y='') {
11342 if ($x === '') {
11343 $x = $this->x;
11344 }
11345 if ($y === '') {
11346 $y = $this->y;
11347 }
11348 if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
11349 $this->Error('Please use values between -90 and +90 degrees for Skewing.');
11350 }
11351 $x *= $this->k;
11352 $y = ($this->h - $y) * $this->k;
11353 //calculate elements of transformation matrix
11354 $tm = array();
11355 $tm[0] = 1;
11356 $tm[1] = tan(deg2rad($angle_y));
11357 $tm[2] = tan(deg2rad($angle_x));
11358 $tm[3] = 1;
11359 $tm[4] = -$tm[2] * $y;
11360 $tm[5] = -$tm[1] * $x;
11361 //skew the coordinate system
11362 $this->Transform($tm);
11363 }
11364
11365 /**
11366 * Apply graphic transformations.
11367 * @param $tm (array) transformation matrix
11368 * @protected
11369 * @since 2.1.000 (2008-01-07)
11370 * @see StartTransform(), StopTransform()
11371 */
11372 protected function Transform($tm) {
11373 if ($this->state != 2) {
11374 return;
11375 }
11376 $this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
11377 // add tranformation matrix
11378 $this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
11379 // update transformation mark
11380 if ($this->inxobj) {
11381 // we are inside an XObject template
11382 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
11383 $key = key($this->xobjects[$this->xobjid]['transfmrk']);
11384 $this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
11385 }
11386 } elseif (end($this->transfmrk[$this->page]) !== false) {
11387 $key = key($this->transfmrk[$this->page]);
11388 $this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
11389 }
11390 }
11391
11392 // END TRANSFORMATIONS SECTION -------------------------
11393
11394 // START GRAPHIC FUNCTIONS SECTION ---------------------
11395 // The following section is based on the code provided by David Hernandez Sanz
11396
11397 /**
11398 * 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.
11399 * @param $width (float) The width.
11400 * @public
11401 * @since 1.0
11402 * @see Line(), Rect(), Cell(), MultiCell()
11403 */
11404 public function SetLineWidth($width) {
11405 //Set line width
11406 $this->LineWidth = $width;
11407 $this->linestyleWidth = sprintf('%F w', ($width * $this->k));
11408 if ($this->state == 2) {
11409 $this->_out($this->linestyleWidth);
11410 }
11411 }
11412
11413 /**
11414 * Returns the current the line width.
11415 * @return int Line width
11416 * @public
11417 * @since 2.1.000 (2008-01-07)
11418 * @see Line(), SetLineWidth()
11419 */
11420 public function GetLineWidth() {
11421 return $this->LineWidth;
11422 }
11423
11424 /**
11425 * Set line style.
11426 * @param $style (array) Line style. Array with keys among the following:
11427 * <ul>
11428 * <li>width (float): Width of the line in user units.</li>
11429 * <li>cap (string): Type of cap to put on the line. Possible values are:
11430 * butt, round, square. The difference between "square" and "butt" is that
11431 * "square" projects a flat end past the end of the line.</li>
11432 * <li>join (string): Type of join. Possible values are: miter, round,
11433 * bevel.</li>
11434 * <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
11435 * series of length values, which are the lengths of the on and off dashes.
11436 * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
11437 * 1 off, 2 on, 1 off, ...</li>
11438 * <li>phase (integer): Modifier on the dash pattern which is used to shift
11439 * the point at which the pattern starts.</li>
11440 * <li>color (array): Draw color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName).</li>
11441 * </ul>
11442 * @param $ret (boolean) if true do not send the command.
11443 * @return string the PDF command
11444 * @public
11445 * @since 2.1.000 (2008-01-08)
11446 */
11447 public function SetLineStyle($style, $ret=false) {
11448 $s = ''; // string to be returned
11449 if (!is_array($style)) {
11450 return;
11451 }
11452 if (isset($style['width'])) {
11453 $this->LineWidth = $style['width'];
11454 $this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
11455 $s .= $this->linestyleWidth.' ';
11456 }
11457 if (isset($style['cap'])) {
11458 $ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
11459 if (isset($ca[$style['cap']])) {
11460 $this->linestyleCap = $ca[$style['cap']].' J';
11461 $s .= $this->linestyleCap.' ';
11462 }
11463 }
11464 if (isset($style['join'])) {
11465 $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
11466 if (isset($ja[$style['join']])) {
11467 $this->linestyleJoin = $ja[$style['join']].' j';
11468 $s .= $this->linestyleJoin.' ';
11469 }
11470 }
11471 if (isset($style['dash'])) {
11472 $dash_string = '';
11473 if ($style['dash']) {
11474 if (preg_match('/^.+,/', $style['dash']) > 0) {
11475 $tab = explode(',', $style['dash']);
11476 } else {
11477 $tab = array($style['dash']);
11478 }
11479 $dash_string = '';
11480 foreach ($tab as $i => $v) {
11481 if ($i) {
11482 $dash_string .= ' ';
11483 }
11484 $dash_string .= sprintf('%F', $v);
11485 }
11486 }
11487 if (!isset($style['phase']) OR !$style['dash']) {
11488 $style['phase'] = 0;
11489 }
11490 $this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
11491 $s .= $this->linestyleDash.' ';
11492 }
11493 if (isset($style['color'])) {
11494 $s .= $this->SetDrawColorArray($style['color'], true).' ';
11495 }
11496 if (!$ret AND ($this->state == 2)) {
11497 $this->_out($s);
11498 }
11499 return $s;
11500 }
11501
11502 /**
11503 * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
11504 * @param $x (float) Abscissa of point.
11505 * @param $y (float) Ordinate of point.
11506 * @protected
11507 * @since 2.1.000 (2008-01-08)
11508 */
11509 protected function _outPoint($x, $y) {
11510 if ($this->state == 2) {
11511 $this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
11512 }
11513 }
11514
11515 /**
11516 * Append a straight line segment from the current point to the point (x, y).
11517 * The new current point shall be (x, y).
11518 * @param $x (float) Abscissa of end point.
11519 * @param $y (float) Ordinate of end point.
11520 * @protected
11521 * @since 2.1.000 (2008-01-08)
11522 */
11523 protected function _outLine($x, $y) {
11524 if ($this->state == 2) {
11525 $this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
11526 }
11527 }
11528
11529 /**
11530 * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
11531 * @param $x (float) Abscissa of upper-left corner.
11532 * @param $y (float) Ordinate of upper-left corner.
11533 * @param $w (float) Width.
11534 * @param $h (float) Height.
11535 * @param $op (string) options
11536 * @protected
11537 * @since 2.1.000 (2008-01-08)
11538 */
11539 protected function _outRect($x, $y, $w, $h, $op) {
11540 if ($this->state == 2) {
11541 $this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k), (($this->h - $y) * $this->k), ($w * $this->k), (-$h * $this->k), $op));
11542 }
11543 }
11544
11545 /**
11546 * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x2, y2) as the Bezier control points.
11547 * The new current point shall be (x3, y3).
11548 * @param $x1 (float) Abscissa of control point 1.
11549 * @param $y1 (float) Ordinate of control point 1.
11550 * @param $x2 (float) Abscissa of control point 2.
11551 * @param $y2 (float) Ordinate of control point 2.
11552 * @param $x3 (float) Abscissa of end point.
11553 * @param $y3 (float) Ordinate of end point.
11554 * @protected
11555 * @since 2.1.000 (2008-01-08)
11556 */
11557 protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
11558 if ($this->state == 2) {
11559 $this->_out(sprintf('%F %F %F %F %F %F c', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11560 }
11561 }
11562
11563 /**
11564 * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using the current point and (x2, y2) as the Bezier control points.
11565 * The new current point shall be (x3, y3).
11566 * @param $x2 (float) Abscissa of control point 2.
11567 * @param $y2 (float) Ordinate of control point 2.
11568 * @param $x3 (float) Abscissa of end point.
11569 * @param $y3 (float) Ordinate of end point.
11570 * @protected
11571 * @since 4.9.019 (2010-04-26)
11572 */
11573 protected function _outCurveV($x2, $y2, $x3, $y3) {
11574 if ($this->state == 2) {
11575 $this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11576 }
11577 }
11578
11579 /**
11580 * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x3, y3) as the Bezier control points.
11581 * The new current point shall be (x3, y3).
11582 * @param $x1 (float) Abscissa of control point 1.
11583 * @param $y1 (float) Ordinate of control point 1.
11584 * @param $x3 (float) Abscissa of end point.
11585 * @param $y3 (float) Ordinate of end point.
11586 * @protected
11587 * @since 2.1.000 (2008-01-08)
11588 */
11589 protected function _outCurveY($x1, $y1, $x3, $y3) {
11590 if ($this->state == 2) {
11591 $this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11592 }
11593 }
11594
11595 /**
11596 * Draws a line between two points.
11597 * @param $x1 (float) Abscissa of first point.
11598 * @param $y1 (float) Ordinate of first point.
11599 * @param $x2 (float) Abscissa of second point.
11600 * @param $y2 (float) Ordinate of second point.
11601 * @param $style (array) Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
11602 * @public
11603 * @since 1.0
11604 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
11605 */
11606 public function Line($x1, $y1, $x2, $y2, $style=array()) {
11607 if ($this->state != 2) {
11608 return;
11609 }
11610 if (is_array($style)) {
11611 $this->SetLineStyle($style);
11612 }
11613 $this->_outPoint($x1, $y1);
11614 $this->_outLine($x2, $y2);
11615 $this->_out('S');
11616 }
11617
11618 /**
11619 * Draws a rectangle.
11620 * @param $x (float) Abscissa of upper-left corner.
11621 * @param $y (float) Ordinate of upper-left corner.
11622 * @param $w (float) Width.
11623 * @param $h (float) Height.
11624 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11625 * @param $border_style (array) Border style of rectangle. Array with keys among the following:
11626 * <ul>
11627 * <li>all: Line style of all borders. Array like for SetLineStyle().</li>
11628 * <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
11629 * </ul>
11630 * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
11631 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11632 * @public
11633 * @since 1.0
11634 * @see SetLineStyle()
11635 */
11636 public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
11637 if ($this->state != 2) {
11638 return;
11639 }
11640 if (empty($style)) {
11641 $style = 'S';
11642 }
11643 if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
11644 // set background color
11645 $this->SetFillColorArray($fill_color);
11646 }
11647 if (!empty($border_style)) {
11648 if (isset($border_style['all']) AND !empty($border_style['all'])) {
11649 //set global style for border
11650 $this->SetLineStyle($border_style['all']);
11651 $border_style = array();
11652 } else {
11653 // remove stroke operator from style
11654 $opnostroke = array('S' => '', 'D' => '', 's' => '', 'd' => '', 'B' => 'F', 'FD' => 'F', 'DF' => 'F', 'B*' => 'F*', 'F*D' => 'F*', 'DF*' => 'F*', 'b' => 'f', 'fd' => 'f', 'df' => 'f', 'b*' => 'f*', 'f*d' => 'f*', 'df*' => 'f*' );
11655 if (isset($opnostroke[$style])) {
11656 $style = $opnostroke[$style];
11657 }
11658 }
11659 }
11660 if (!empty($style)) {
11661 $op = TCPDF_STATIC::getPathPaintOperator($style);
11662 $this->_outRect($x, $y, $w, $h, $op);
11663 }
11664 if (!empty($border_style)) {
11665 $border_style2 = array();
11666 foreach ($border_style as $line => $value) {
11667 $length = strlen($line);
11668 for ($i = 0; $i < $length; ++$i) {
11669 $border_style2[$line[$i]] = $value;
11670 }
11671 }
11672 $border_style = $border_style2;
11673 if (isset($border_style['L']) AND $border_style['L']) {
11674 $this->Line($x, $y, $x, $y + $h, $border_style['L']);
11675 }
11676 if (isset($border_style['T']) AND $border_style['T']) {
11677 $this->Line($x, $y, $x + $w, $y, $border_style['T']);
11678 }
11679 if (isset($border_style['R']) AND $border_style['R']) {
11680 $this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
11681 }
11682 if (isset($border_style['B']) AND $border_style['B']) {
11683 $this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
11684 }
11685 }
11686 }
11687
11688 /**
11689 * Draws a Bezier curve.
11690 * The Bezier curve is a tangent to the line between the control points at
11691 * either end of the curve.
11692 * @param $x0 (float) Abscissa of start point.
11693 * @param $y0 (float) Ordinate of start point.
11694 * @param $x1 (float) Abscissa of control point 1.
11695 * @param $y1 (float) Ordinate of control point 1.
11696 * @param $x2 (float) Abscissa of control point 2.
11697 * @param $y2 (float) Ordinate of control point 2.
11698 * @param $x3 (float) Abscissa of end point.
11699 * @param $y3 (float) Ordinate of end point.
11700 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11701 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11702 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11703 * @public
11704 * @see SetLineStyle()
11705 * @since 2.1.000 (2008-01-08)
11706 */
11707 public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
11708 if ($this->state != 2) {
11709 return;
11710 }
11711 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11712 $this->SetFillColorArray($fill_color);
11713 }
11714 $op = TCPDF_STATIC::getPathPaintOperator($style);
11715 if ($line_style) {
11716 $this->SetLineStyle($line_style);
11717 }
11718 $this->_outPoint($x0, $y0);
11719 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11720 $this->_out($op);
11721 }
11722
11723 /**
11724 * Draws a poly-Bezier curve.
11725 * Each Bezier curve segment is a tangent to the line between the control points at
11726 * either end of the curve.
11727 * @param $x0 (float) Abscissa of start point.
11728 * @param $y0 (float) Ordinate of start point.
11729 * @param $segments (float) An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
11730 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11731 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11732 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11733 * @public
11734 * @see SetLineStyle()
11735 * @since 3.0008 (2008-05-12)
11736 */
11737 public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
11738 if ($this->state != 2) {
11739 return;
11740 }
11741 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11742 $this->SetFillColorArray($fill_color);
11743 }
11744 $op = TCPDF_STATIC::getPathPaintOperator($style);
11745 if ($op == 'f') {
11746 $line_style = array();
11747 }
11748 if ($line_style) {
11749 $this->SetLineStyle($line_style);
11750 }
11751 $this->_outPoint($x0, $y0);
11752 foreach ($segments as $segment) {
11753 list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
11754 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11755 }
11756 $this->_out($op);
11757 }
11758
11759 /**
11760 * Draws an ellipse.
11761 * An ellipse is formed from n Bezier curves.
11762 * @param $x0 (float) Abscissa of center point.
11763 * @param $y0 (float) Ordinate of center point.
11764 * @param $rx (float) Horizontal radius.
11765 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11766 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
11767 * @param $astart: (float) Angle start of draw line. Default value: 0.
11768 * @param $afinish: (float) Angle finish of draw line. Default value: 360.
11769 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11770 * @param $line_style (array) Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
11771 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11772 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11773 * @author Nicola Asuni
11774 * @public
11775 * @since 2.1.000 (2008-01-08)
11776 */
11777 public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11778 if ($this->state != 2) {
11779 return;
11780 }
11781 if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
11782 $ry = $rx;
11783 }
11784 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11785 $this->SetFillColorArray($fill_color);
11786 }
11787 $op = TCPDF_STATIC::getPathPaintOperator($style);
11788 if ($op == 'f') {
11789 $line_style = array();
11790 }
11791 if ($line_style) {
11792 $this->SetLineStyle($line_style);
11793 }
11794 $this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
11795 $this->_out($op);
11796 }
11797
11798 /**
11799 * Append an elliptical arc to the current path.
11800 * An ellipse is formed from n Bezier curves.
11801 * @param $xc (float) Abscissa of center point.
11802 * @param $yc (float) Ordinate of center point.
11803 * @param $rx (float) Horizontal radius.
11804 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11805 * @param $xang: (float) Angle between the X-axis and the major axis of the ellipse. Default value: 0.
11806 * @param $angs: (float) Angle start of draw line. Default value: 0.
11807 * @param $angf: (float) Angle finish of draw line. Default value: 360.
11808 * @param $pie (boolean) if true do not mark the border point (used to draw pie sectors).
11809 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11810 * @param $startpoint (boolean) if true output a starting point.
11811 * @param $ccw (boolean) if true draws in counter-clockwise.
11812 * @param $svg (boolean) if true the angles are in svg mode (already calculated).
11813 * @return array bounding box coordinates (x min, y min, x max, y max)
11814 * @author Nicola Asuni
11815 * @protected
11816 * @since 4.9.019 (2010-04-26)
11817 */
11818 protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
11819 if (($rx <= 0) OR ($ry < 0)) {
11820 return;
11821 }
11822 $k = $this->k;
11823 if ($nc < 2) {
11824 $nc = 2;
11825 }
11826 $xmin = 2147483647;
11827 $ymin = 2147483647;
11828 $xmax = 0;
11829 $ymax = 0;
11830 if ($pie) {
11831 // center of the arc
11832 $this->_outPoint($xc, $yc);
11833 }
11834 $xang = deg2rad((float) $xang);
11835 $angs = deg2rad((float) $angs);
11836 $angf = deg2rad((float) $angf);
11837 if ($svg) {
11838 $as = $angs;
11839 $af = $angf;
11840 } else {
11841 $as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
11842 $af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
11843 }
11844 if ($as < 0) {
11845 $as += (2 * M_PI);
11846 }
11847 if ($af < 0) {
11848 $af += (2 * M_PI);
11849 }
11850 if ($ccw AND ($as > $af)) {
11851 // reverse rotation
11852 $as -= (2 * M_PI);
11853 } elseif (!$ccw AND ($as < $af)) {
11854 // reverse rotation
11855 $af -= (2 * M_PI);
11856 }
11857 $total_angle = ($af - $as);
11858 if ($nc < 2) {
11859 $nc = 2;
11860 }
11861 // total arcs to draw
11862 $nc *= (2 * abs($total_angle) / M_PI);
11863 $nc = round($nc) + 1;
11864 // angle of each arc
11865 $arcang = ($total_angle / $nc);
11866 // center point in PDF coordinates
11867 $x0 = $xc;
11868 $y0 = ($this->h - $yc);
11869 // starting angle
11870 $ang = $as;
11871 $alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
11872 $cos_xang = cos($xang);
11873 $sin_xang = sin($xang);
11874 $cos_ang = cos($ang);
11875 $sin_ang = sin($ang);
11876 // first arc point
11877 $px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11878 $py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11879 // first Bezier control point
11880 $qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11881 $qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11882 if ($pie) {
11883 // line from center to arc starting point
11884 $this->_outLine($px1, $this->h - $py1);
11885 } elseif ($startpoint) {
11886 // arc starting point
11887 $this->_outPoint($px1, $this->h - $py1);
11888 }
11889 // draw arcs
11890 for ($i = 1; $i <= $nc; ++$i) {
11891 // starting angle
11892 $ang = $as + ($i * $arcang);
11893 if ($i == $nc) {
11894 $ang = $af;
11895 }
11896 $cos_ang = cos($ang);
11897 $sin_ang = sin($ang);
11898 // second arc point
11899 $px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11900 $py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11901 // second Bezier control point
11902 $qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11903 $qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11904 // draw arc
11905 $cx1 = ($px1 + $qx1);
11906 $cy1 = ($this->h - ($py1 + $qy1));
11907 $cx2 = ($px2 - $qx2);
11908 $cy2 = ($this->h - ($py2 - $qy2));
11909 $cx3 = $px2;
11910 $cy3 = ($this->h - $py2);
11911 $this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
11912 // get bounding box coordinates
11913 $xmin = min($xmin, $cx1, $cx2, $cx3);
11914 $ymin = min($ymin, $cy1, $cy2, $cy3);
11915 $xmax = max($xmax, $cx1, $cx2, $cx3);
11916 $ymax = max($ymax, $cy1, $cy2, $cy3);
11917 // move to next point
11918 $px1 = $px2;
11919 $py1 = $py2;
11920 $qx1 = $qx2;
11921 $qy1 = $qy2;
11922 }
11923 if ($pie) {
11924 $this->_outLine($xc, $yc);
11925 // get bounding box coordinates
11926 $xmin = min($xmin, $xc);
11927 $ymin = min($ymin, $yc);
11928 $xmax = max($xmax, $xc);
11929 $ymax = max($ymax, $yc);
11930 }
11931 return array($xmin, $ymin, $xmax, $ymax);
11932 }
11933
11934 /**
11935 * Draws a circle.
11936 * A circle is formed from n Bezier curves.
11937 * @param $x0 (float) Abscissa of center point.
11938 * @param $y0 (float) Ordinate of center point.
11939 * @param $r (float) Radius.
11940 * @param $angstr: (float) Angle start of draw line. Default value: 0.
11941 * @param $angend: (float) Angle finish of draw line. Default value: 360.
11942 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11943 * @param $line_style (array) Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
11944 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11945 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of circle.
11946 * @public
11947 * @since 2.1.000 (2008-01-08)
11948 */
11949 public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11950 $this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
11951 }
11952
11953 /**
11954 * Draws a polygonal line
11955 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11956 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11957 * @param $line_style (array) Line style of polygon. Array with keys among the following:
11958 * <ul>
11959 * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11960 * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11961 * </ul>
11962 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11963 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11964 * @since 4.8.003 (2009-09-15)
11965 * @public
11966 */
11967 public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
11968 $this->Polygon($p, $style, $line_style, $fill_color, false);
11969 }
11970
11971 /**
11972 * Draws a polygon.
11973 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11974 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11975 * @param $line_style (array) Line style of polygon. Array with keys among the following:
11976 * <ul>
11977 * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11978 * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11979 * </ul>
11980 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11981 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11982 * @param $closed (boolean) if true the polygon is closes, otherwise will remain open
11983 * @public
11984 * @since 2.1.000 (2008-01-08)
11985 */
11986 public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
11987 if ($this->state != 2) {
11988 return;
11989 }
11990 $nc = count($p); // number of coordinates
11991 $np = $nc / 2; // number of points
11992 if ($closed) {
11993 // close polygon by adding the first 2 points at the end (one line)
11994 for ($i = 0; $i < 4; ++$i) {
11995 $p[$nc + $i] = $p[$i];
11996 }
11997 // copy style for the last added line
11998 if (isset($line_style[0])) {
11999 $line_style[$np] = $line_style[0];
12000 }
12001 $nc += 4;
12002 }
12003 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12004 $this->SetFillColorArray($fill_color);
12005 }
12006 $op = TCPDF_STATIC::getPathPaintOperator($style);
12007 if ($op == 'f') {
12008 $line_style = array();
12009 }
12010 $draw = true;
12011 if ($line_style) {
12012 if (isset($line_style['all'])) {
12013 $this->SetLineStyle($line_style['all']);
12014 } else {
12015 $draw = false;
12016 if ($op == 'B') {
12017 // draw fill
12018 $op = 'f';
12019 $this->_outPoint($p[0], $p[1]);
12020 for ($i = 2; $i < $nc; $i = $i + 2) {
12021 $this->_outLine($p[$i], $p[$i + 1]);
12022 }
12023 $this->_out($op);
12024 }
12025 // draw outline
12026 $this->_outPoint($p[0], $p[1]);
12027 for ($i = 2; $i < $nc; $i = $i + 2) {
12028 $line_num = ($i / 2) - 1;
12029 if (isset($line_style[$line_num])) {
12030 if ($line_style[$line_num] != 0) {
12031 if (is_array($line_style[$line_num])) {
12032 $this->_out('S');
12033 $this->SetLineStyle($line_style[$line_num]);
12034 $this->_outPoint($p[$i - 2], $p[$i - 1]);
12035 $this->_outLine($p[$i], $p[$i + 1]);
12036 $this->_out('S');
12037 $this->_outPoint($p[$i], $p[$i + 1]);
12038 } else {
12039 $this->_outLine($p[$i], $p[$i + 1]);
12040 }
12041 }
12042 } else {
12043 $this->_outLine($p[$i], $p[$i + 1]);
12044 }
12045 }
12046 $this->_out($op);
12047 }
12048 }
12049 if ($draw) {
12050 $this->_outPoint($p[0], $p[1]);
12051 for ($i = 2; $i < $nc; $i = $i + 2) {
12052 $this->_outLine($p[$i], $p[$i + 1]);
12053 }
12054 $this->_out($op);
12055 }
12056 }
12057
12058 /**
12059 * Draws a regular polygon.
12060 * @param $x0 (float) Abscissa of center point.
12061 * @param $y0 (float) Ordinate of center point.
12062 * @param $r: (float) Radius of inscribed circle.
12063 * @param $ns (integer) Number of sides.
12064 * @param $angle (float) Angle oriented (anti-clockwise). Default value: 0.
12065 * @param $draw_circle (boolean) Draw inscribed circle or not. Default value: false.
12066 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12067 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
12068 * <ul>
12069 * <li>all: Line style of all sides. Array like for SetLineStyle().</li>
12070 * <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
12071 * </ul>
12072 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12073 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12074 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
12075 * <ul>
12076 * <li>D or empty string: Draw (default).</li>
12077 * <li>F: Fill.</li>
12078 * <li>DF or FD: Draw and fill.</li>
12079 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12080 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12081 * </ul>
12082 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12083 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12084 * @public
12085 * @since 2.1.000 (2008-01-08)
12086 */
12087 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()) {
12088 if (3 > $ns) {
12089 $ns = 3;
12090 }
12091 if ($draw_circle) {
12092 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12093 }
12094 $p = array();
12095 for ($i = 0; $i < $ns; ++$i) {
12096 $a = $angle + ($i * 360 / $ns);
12097 $a_rad = deg2rad((float) $a);
12098 $p[] = $x0 + ($r * sin($a_rad));
12099 $p[] = $y0 + ($r * cos($a_rad));
12100 }
12101 $this->Polygon($p, $style, $line_style, $fill_color);
12102 }
12103
12104 /**
12105 * Draws a star polygon
12106 * @param $x0 (float) Abscissa of center point.
12107 * @param $y0 (float) Ordinate of center point.
12108 * @param $r (float) Radius of inscribed circle.
12109 * @param $nv (integer) Number of vertices.
12110 * @param $ng (integer) Number of gap (if ($ng % $nv = 1) then is a regular polygon).
12111 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
12112 * @param $draw_circle: (boolean) Draw inscribed circle or not. Default value is false.
12113 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12114 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
12115 * <ul>
12116 * <li>all: Line style of all sides. Array like for
12117 * SetLineStyle().</li>
12118 * <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
12119 * </ul>
12120 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12121 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12122 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
12123 * <ul>
12124 * <li>D or empty string: Draw (default).</li>
12125 * <li>F: Fill.</li>
12126 * <li>DF or FD: Draw and fill.</li>
12127 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12128 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12129 * </ul>
12130 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12131 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12132 * @public
12133 * @since 2.1.000 (2008-01-08)
12134 */
12135 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()) {
12136 if ($nv < 2) {
12137 $nv = 2;
12138 }
12139 if ($draw_circle) {
12140 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12141 }
12142 $p2 = array();
12143 $visited = array();
12144 for ($i = 0; $i < $nv; ++$i) {
12145 $a = $angle + ($i * 360 / $nv);
12146 $a_rad = deg2rad((float) $a);
12147 $p2[] = $x0 + ($r * sin($a_rad));
12148 $p2[] = $y0 + ($r * cos($a_rad));
12149 $visited[] = false;
12150 }
12151 $p = array();
12152 $i = 0;
12153 do {
12154 $p[] = $p2[$i * 2];
12155 $p[] = $p2[($i * 2) + 1];
12156 $visited[$i] = true;
12157 $i += $ng;
12158 $i %= $nv;
12159 } while (!$visited[$i]);
12160 $this->Polygon($p, $style, $line_style, $fill_color);
12161 }
12162
12163 /**
12164 * Draws a rounded rectangle.
12165 * @param $x (float) Abscissa of upper-left corner.
12166 * @param $y (float) Ordinate of upper-left corner.
12167 * @param $w (float) Width.
12168 * @param $h (float) Height.
12169 * @param $r (float) the radius of the circle used to round off the corners of the rectangle.
12170 * @param $round_corner (string) Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
12171 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12172 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12173 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
12174 * @public
12175 * @since 2.1.000 (2008-01-08)
12176 */
12177 public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12178 $this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
12179 }
12180
12181 /**
12182 * Draws a rounded rectangle.
12183 * @param $x (float) Abscissa of upper-left corner.
12184 * @param $y (float) Ordinate of upper-left corner.
12185 * @param $w (float) Width.
12186 * @param $h (float) Height.
12187 * @param $rx (float) the x-axis radius of the ellipse used to round off the corners of the rectangle.
12188 * @param $ry (float) the y-axis radius of the ellipse used to round off the corners of the rectangle.
12189 * @param $round_corner (string) Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
12190 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12191 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12192 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
12193 * @public
12194 * @since 4.9.019 (2010-04-22)
12195 */
12196 public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12197 if ($this->state != 2) {
12198 return;
12199 }
12200 if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
12201 // Not rounded
12202 $this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
12203 return;
12204 }
12205 // Rounded
12206 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12207 $this->SetFillColorArray($fill_color);
12208 }
12209 $op = TCPDF_STATIC::getPathPaintOperator($style);
12210 if ($op == 'f') {
12211 $border_style = array();
12212 }
12213 if ($border_style) {
12214 $this->SetLineStyle($border_style);
12215 }
12216 $MyArc = 4 / 3 * (sqrt(2) - 1);
12217 $this->_outPoint($x + $rx, $y);
12218 $xc = $x + $w - $rx;
12219 $yc = $y + $ry;
12220 $this->_outLine($xc, $y);
12221 if ($round_corner[0]) {
12222 $this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
12223 } else {
12224 $this->_outLine($x + $w, $y);
12225 }
12226 $xc = $x + $w - $rx;
12227 $yc = $y + $h - $ry;
12228 $this->_outLine($x + $w, $yc);
12229 if ($round_corner[1]) {
12230 $this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
12231 } else {
12232 $this->_outLine($x + $w, $y + $h);
12233 }
12234 $xc = $x + $rx;
12235 $yc = $y + $h - $ry;
12236 $this->_outLine($xc, $y + $h);
12237 if ($round_corner[2]) {
12238 $this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
12239 } else {
12240 $this->_outLine($x, $y + $h);
12241 }
12242 $xc = $x + $rx;
12243 $yc = $y + $ry;
12244 $this->_outLine($x, $yc);
12245 if ($round_corner[3]) {
12246 $this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
12247 } else {
12248 $this->_outLine($x, $y);
12249 $this->_outLine($x + $rx, $y);
12250 }
12251 $this->_out($op);
12252 }
12253
12254 /**
12255 * Draws a grahic arrow.
12256 * @param $x0 (float) Abscissa of first point.
12257 * @param $y0 (float) Ordinate of first point.
12258 * @param $x1 (float) Abscissa of second point.
12259 * @param $y1 (float) Ordinate of second point.
12260 * @param $head_style (int) (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
12261 * @param $arm_size (float) length of arrowhead arms
12262 * @param $arm_angle (int) angle between an arm and the shaft
12263 * @author Piotr Galecki, Nicola Asuni, Andy Meier
12264 * @since 4.6.018 (2009-07-10)
12265 */
12266 public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
12267 // getting arrow direction angle
12268 // 0 deg angle is when both arms go along X axis. angle grows clockwise.
12269 $dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
12270 if ($dir_angle < 0) {
12271 $dir_angle += (2 * M_PI);
12272 }
12273 $arm_angle = deg2rad($arm_angle);
12274 $sx1 = $x1;
12275 $sy1 = $y1;
12276 if ($head_style > 0) {
12277 // calculate the stopping point for the arrow shaft
12278 $sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
12279 $sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
12280 }
12281 // main arrow line / shaft
12282 $this->Line($x0, $y0, $sx1, $sy1);
12283 // left arrowhead arm tip
12284 $x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
12285 $y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
12286 // right arrowhead arm tip
12287 $x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
12288 $y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
12289 $mode = 'D';
12290 $style = array();
12291 switch ($head_style) {
12292 case 0: {
12293 // draw only arrowhead arms
12294 $mode = 'D';
12295 $style = array(1, 1, 0);
12296 break;
12297 }
12298 case 1: {
12299 // draw closed arrowhead, but no fill
12300 $mode = 'D';
12301 break;
12302 }
12303 case 2: {
12304 // closed and filled arrowhead
12305 $mode = 'DF';
12306 break;
12307 }
12308 case 3: {
12309 // filled arrowhead
12310 $mode = 'F';
12311 break;
12312 }
12313 }
12314 $this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
12315 }
12316
12317 // END GRAPHIC FUNCTIONS SECTION -----------------------
12318
12319 /**
12320 * Add a Named Destination.
12321 * NOTE: destination names are unique, so only last entry will be saved.
12322 * @param $name (string) Destination name.
12323 * @param $y (float) Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
12324 * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12325 * @param $x (float) X position in user units of the destiantion on the selected page (default = -1 = current position;).
12326 * @return (string) Stripped named destination identifier or false in case of error.
12327 * @public
12328 * @author Christian Deligant, Nicola Asuni
12329 * @since 5.9.097 (2011-06-23)
12330 */
12331 public function setDestination($name, $y=-1, $page='', $x=-1) {
12332 // remove unsupported characters
12333 $name = TCPDF_STATIC::encodeNameObject($name);
12334 if (TCPDF_STATIC::empty_string($name)) {
12335 return false;
12336 }
12337 if ($y == -1) {
12338 $y = $this->GetY();
12339 } elseif ($y < 0) {
12340 $y = 0;
12341 } elseif ($y > $this->h) {
12342 $y = $this->h;
12343 }
12344 if ($x == -1) {
12345 $x = $this->GetX();
12346 } elseif ($x < 0) {
12347 $x = 0;
12348 } elseif ($x > $this->w) {
12349 $x = $this->w;
12350 }
12351 $fixed = false;
12352 if (!empty($page) AND ($page[0] == '*')) {
12353 $page = intval(substr($page, 1));
12354 // this page number will not be changed when moving/add/deleting pages
12355 $fixed = true;
12356 }
12357 if (empty($page)) {
12358 $page = $this->PageNo();
12359 if (empty($page)) {
12360 return;
12361 }
12362 }
12363 $this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
12364 return $name;
12365 }
12366
12367 /**
12368 * Return the Named Destination array.
12369 * @return (array) Named Destination array.
12370 * @public
12371 * @author Nicola Asuni
12372 * @since 5.9.097 (2011-06-23)
12373 */
12374 public function getDestination() {
12375 return $this->dests;
12376 }
12377
12378 /**
12379 * Insert Named Destinations.
12380 * @protected
12381 * @author Johannes G\FCntert, Nicola Asuni
12382 * @since 5.9.098 (2011-06-23)
12383 */
12384 protected function _putdests() {
12385 if (empty($this->dests)) {
12386 return;
12387 }
12388 $this->n_dests = $this->_newobj();
12389 $out = ' <<';
12390 foreach($this->dests as $name => $o) {
12391 $out .= ' /'.$name.' '.sprintf('[%u 0 R /XYZ %F %F null]', $this->page_obj_id[($o['p'])], ($o['x'] * $this->k), ($this->pagedim[$o['p']]['h'] - ($o['y'] * $this->k)));
12392 }
12393 $out .= ' >>';
12394 $out .= "\n".'endobj';
12395 $this->_out($out);
12396 }
12397
12398 /**
12399 * Adds a bookmark - alias for Bookmark().
12400 * @param $txt (string) Bookmark description.
12401 * @param $level (int) Bookmark level (minimum value is 0).
12402 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12403 * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12404 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12405 * @param $color (array) RGB color array (values from 0 to 255).
12406 * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12407 * @param $link (mixed) URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
12408 * @public
12409 */
12410 public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12411 $this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
12412 }
12413
12414 /**
12415 * Adds a bookmark.
12416 * @param $txt (string) Bookmark description.
12417 * @param $level (int) Bookmark level (minimum value is 0).
12418 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12419 * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12420 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12421 * @param $color (array) RGB color array (values from 0 to 255).
12422 * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12423 * @param $link (mixed) URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
12424 * @public
12425 * @since 2.1.002 (2008-02-12)
12426 */
12427 public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12428 if ($level < 0) {
12429 $level = 0;
12430 }
12431 if (isset($this->outlines[0])) {
12432 $lastoutline = end($this->outlines);
12433 $maxlevel = $lastoutline['l'] + 1;
12434 } else {
12435 $maxlevel = 0;
12436 }
12437 if ($level > $maxlevel) {
12438 $level = $maxlevel;
12439 }
12440 if ($y == -1) {
12441 $y = $this->GetY();
12442 } elseif ($y < 0) {
12443 $y = 0;
12444 } elseif ($y > $this->h) {
12445 $y = $this->h;
12446 }
12447 if ($x == -1) {
12448 $x = $this->GetX();
12449 } elseif ($x < 0) {
12450 $x = 0;
12451 } elseif ($x > $this->w) {
12452 $x = $this->w;
12453 }
12454 $fixed = false;
12455 if (!empty($page) AND ($page[0] == '*')) {
12456 $page = intval(substr($page, 1));
12457 // this page number will not be changed when moving/add/deleting pages
12458 $fixed = true;
12459 }
12460 if (empty($page)) {
12461 $page = $this->PageNo();
12462 if (empty($page)) {
12463 return;
12464 }
12465 }
12466 $this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
12467 }
12468
12469 /**
12470 * Sort bookmarks for page and key.
12471 * @protected
12472 * @since 5.9.119 (2011-09-19)
12473 */
12474 protected function sortBookmarks() {
12475 // get sorting columns
12476 $outline_p = array();
12477 $outline_y = array();
12478 foreach ($this->outlines as $key => $row) {
12479 $outline_p[$key] = $row['p'];
12480 $outline_k[$key] = $key;
12481 }
12482 // sort outlines by page and original position
12483 array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
12484 }
12485
12486 /**
12487 * Create a bookmark PDF string.
12488 * @protected
12489 * @author Olivier Plathey, Nicola Asuni
12490 * @since 2.1.002 (2008-02-12)
12491 */
12492 protected function _putbookmarks() {
12493 $nb = count($this->outlines);
12494 if ($nb == 0) {
12495 return;
12496 }
12497 // sort bookmarks
12498 $this->sortBookmarks();
12499 $lru = array();
12500 $level = 0;
12501 foreach ($this->outlines as $i => $o) {
12502 if ($o['l'] > 0) {
12503 $parent = $lru[($o['l'] - 1)];
12504 //Set parent and last pointers
12505 $this->outlines[$i]['parent'] = $parent;
12506 $this->outlines[$parent]['last'] = $i;
12507 if ($o['l'] > $level) {
12508 //Level increasing: set first pointer
12509 $this->outlines[$parent]['first'] = $i;
12510 }
12511 } else {
12512 $this->outlines[$i]['parent'] = $nb;
12513 }
12514 if (($o['l'] <= $level) AND ($i > 0)) {
12515 //Set prev and next pointers
12516 $prev = $lru[$o['l']];
12517 $this->outlines[$prev]['next'] = $i;
12518 $this->outlines[$i]['prev'] = $prev;
12519 }
12520 $lru[$o['l']] = $i;
12521 $level = $o['l'];
12522 }
12523 //Outline items
12524 $n = $this->n + 1;
12525 $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';
12526 foreach ($this->outlines as $i => $o) {
12527 $oid = $this->_newobj();
12528 // covert HTML title to string
12529 $title = preg_replace($nltags, "\n", $o['t']);
12530 $title = preg_replace("/[\r]+/si", '', $title);
12531 $title = preg_replace("/[\n]+/si", "\n", $title);
12532 $title = strip_tags($title);
12533 $title = $this->stringTrim($title);
12534 $out = '<</Title '.$this->_textstring($title, $oid);
12535 $out .= ' /Parent '.($n + $o['parent']).' 0 R';
12536 if (isset($o['prev'])) {
12537 $out .= ' /Prev '.($n + $o['prev']).' 0 R';
12538 }
12539 if (isset($o['next'])) {
12540 $out .= ' /Next '.($n + $o['next']).' 0 R';
12541 }
12542 if (isset($o['first'])) {
12543 $out .= ' /First '.($n + $o['first']).' 0 R';
12544 }
12545 if (isset($o['last'])) {
12546 $out .= ' /Last '.($n + $o['last']).' 0 R';
12547 }
12548 if (isset($o['u']) AND !empty($o['u'])) {
12549 // link
12550 if (is_string($o['u'])) {
12551 if ($o['u'][0] == '#') {
12552 // internal destination
12553 $out .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($o['u'], 1));
12554 } elseif ($o['u'][0] == '%') {
12555 // embedded PDF file
12556 $filename = basename(substr($o['u'], 1));
12557 $out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
12558 } elseif ($o['u'][0] == '*') {
12559 // embedded generic file
12560 $filename = basename(substr($o['u'], 1));
12561 $jsa = 'var D=event.target.doc;var MyData=D.dataObjects;for (var i in MyData) if (MyData[i].path=="'.$filename.'") D.exportDataObject( { cName : MyData[i].name, nLaunch : 2});';
12562 $out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
12563 } else {
12564 // external URI link
12565 $out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
12566 }
12567 } elseif (isset($this->links[$o['u']])) {
12568 // internal link ID
12569 $l = $this->links[$o['u']];
12570 if (isset($this->page_obj_id[($l['p'])])) {
12571 $out .= sprintf(' /Dest [%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($l['p'])], ($this->pagedim[$l['p']]['h'] - ($l['y'] * $this->k)));
12572 }
12573 }
12574 } elseif (isset($this->page_obj_id[($o['p'])])) {
12575 // link to a page
12576 $out .= ' '.sprintf('/Dest [%u 0 R /XYZ %F %F null]', $this->page_obj_id[($o['p'])], ($o['x'] * $this->k), ($this->pagedim[$o['p']]['h'] - ($o['y'] * $this->k)));
12577 }
12578 // set font style
12579 $style = 0;
12580 if (!empty($o['s'])) {
12581 // bold
12582 if (strpos($o['s'], 'B') !== false) {
12583 $style |= 2;
12584 }
12585 // oblique
12586 if (strpos($o['s'], 'I') !== false) {
12587 $style |= 1;
12588 }
12589 }
12590 $out .= sprintf(' /F %d', $style);
12591 // set bookmark color
12592 if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
12593 $color = array_values($o['c']);
12594 $out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
12595 } else {
12596 // black
12597 $out .= ' /C [0.0 0.0 0.0]';
12598 }
12599 $out .= ' /Count 0'; // normally closed item
12600 $out .= ' >>';
12601 $out .= "\n".'endobj';
12602 $this->_out($out);
12603 }
12604 //Outline root
12605 $this->OutlineRoot = $this->_newobj();
12606 $this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
12607 }
12608
12609 // --- JAVASCRIPT ------------------------------------------------------
12610
12611 /**
12612 * Adds a javascript
12613 * @param $script (string) Javascript code
12614 * @public
12615 * @author Johannes G\FCntert, Nicola Asuni
12616 * @since 2.1.002 (2008-02-12)
12617 */
12618 public function IncludeJS($script) {
12619 $this->javascript .= $script;
12620 }
12621
12622 /**
12623 * Adds a javascript object and return object ID
12624 * @param $script (string) Javascript code
12625 * @param $onload (boolean) if true executes this object when opening the document
12626 * @return int internal object ID
12627 * @public
12628 * @author Nicola Asuni
12629 * @since 4.8.000 (2009-09-07)
12630 */
12631 public function addJavascriptObject($script, $onload=false) {
12632 if ($this->pdfa_mode) {
12633 // javascript is not allowed in PDF/A mode
12634 return false;
12635 }
12636 ++$this->n;
12637 $this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
12638 return $this->n;
12639 }
12640
12641 /**
12642 * Create a javascript PDF string.
12643 * @protected
12644 * @author Johannes G\FCntert, Nicola Asuni
12645 * @since 2.1.002 (2008-02-12)
12646 */
12647 protected function _putjavascript() {
12648 if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
12649 return;
12650 }
12651 if (strpos($this->javascript, 'this.addField') > 0) {
12652 if (!$this->ur['enabled']) {
12653 //$this->setUserRights();
12654 }
12655 // the following two lines are used to avoid form fields duplication after saving
12656 // The addField method only works when releasing user rights (UR3)
12657 $jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
12658 $jsb = "getField('tcpdfdocsaved').value='saved';";
12659 $this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
12660 }
12661 // name tree for javascript
12662 $this->n_js = '<< /Names [';
12663 if (!empty($this->javascript)) {
12664 $this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
12665 }
12666 if (!empty($this->js_objects)) {
12667 foreach ($this->js_objects as $key => $val) {
12668 if ($val['onload']) {
12669 $this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
12670 }
12671 }
12672 }
12673 $this->n_js .= ' ] >>';
12674 // default Javascript object
12675 if (!empty($this->javascript)) {
12676 $obj_id = $this->_newobj();
12677 $out = '<< /S /JavaScript';
12678 $out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
12679 $out .= ' >>';
12680 $out .= "\n".'endobj';
12681 $this->_out($out);
12682 }
12683 // additional Javascript objects
12684 if (!empty($this->js_objects)) {
12685 foreach ($this->js_objects as $key => $val) {
12686 $out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
12687 $this->_out($out);
12688 }
12689 }
12690 }
12691
12692 /**
12693 * Adds a javascript form field.
12694 * @param $type (string) field type
12695 * @param $name (string) field name
12696 * @param $x (int) horizontal position
12697 * @param $y (int) vertical position
12698 * @param $w (int) width
12699 * @param $h (int) height
12700 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12701 * @protected
12702 * @author Denis Van Nuffelen, Nicola Asuni
12703 * @since 2.1.002 (2008-02-12)
12704 */
12705 protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
12706 if ($this->rtl) {
12707 $x = $x - $w;
12708 }
12709 // the followind avoid fields duplication after saving the document
12710 $this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
12711 $k = $this->k;
12712 $this->javascript .= sprintf("f".$name."=this.addField('%s','%s',%u,[%F,%F,%F,%F]);", $name, $type, $this->PageNo()-1, $x*$k, ($this->h-$y)*$k+1, ($x+$w)*$k, ($this->h-$y-$h)*$k+1)."\n";
12713 $this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
12714 while (list($key, $val) = each($prop)) {
12715 if (strcmp(substr($key, -5), 'Color') == 0) {
12716 $val = TCPDF_COLORS::_JScolor($val);
12717 } else {
12718 $val = "'".$val."'";
12719 }
12720 $this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
12721 }
12722 if ($this->rtl) {
12723 $this->x -= $w;
12724 } else {
12725 $this->x += $w;
12726 }
12727 $this->javascript .= '}';
12728 }
12729
12730 // --- FORM FIELDS -----------------------------------------------------
12731
12732
12733
12734 /**
12735 * Set default properties for form fields.
12736 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12737 * @public
12738 * @author Nicola Asuni
12739 * @since 4.8.000 (2009-09-06)
12740 */
12741 public function setFormDefaultProp($prop=array()) {
12742 $this->default_form_prop = $prop;
12743 }
12744
12745 /**
12746 * Return the default properties for form fields.
12747 * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12748 * @public
12749 * @author Nicola Asuni
12750 * @since 4.8.000 (2009-09-06)
12751 */
12752 public function getFormDefaultProp() {
12753 return $this->default_form_prop;
12754 }
12755
12756 /**
12757 * Creates a text field
12758 * @param $name (string) field name
12759 * @param $w (float) Width of the rectangle
12760 * @param $h (float) Height of the rectangle
12761 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12762 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12763 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12764 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12765 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12766 * @public
12767 * @author Nicola Asuni
12768 * @since 4.8.000 (2009-09-07)
12769 */
12770 public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12771 if ($x === '') {
12772 $x = $this->x;
12773 }
12774 if ($y === '') {
12775 $y = $this->y;
12776 }
12777 // check page for no-write regions and adapt page margins if necessary
12778 list($x, $y) = $this->checkPageRegions($h, $x, $y);
12779 if ($js) {
12780 $this->_addfield('text', $name, $x, $y, $w, $h, $prop);
12781 return;
12782 }
12783 // get default style
12784 $prop = array_merge($this->getFormDefaultProp(), $prop);
12785 // get annotation data
12786 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12787 // set default appearance stream
12788 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12789 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12790 $popt['da'] = $fontstyle;
12791 // build appearance stream
12792 $popt['ap'] = array();
12793 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12794 $text = '';
12795 if (isset($prop['value']) AND !empty($prop['value'])) {
12796 $text = $prop['value'];
12797 } elseif (isset($opt['v']) AND !empty($opt['v'])) {
12798 $text = $opt['v'];
12799 }
12800 $tmpid = $this->startTemplate($w, $h, false);
12801 $align = '';
12802 if (isset($popt['q'])) {
12803 switch ($popt['q']) {
12804 case 0: {
12805 $align = 'L';
12806 break;
12807 }
12808 case 1: {
12809 $align = 'C';
12810 break;
12811 }
12812 case 2: {
12813 $align = 'R';
12814 break;
12815 }
12816 default: {
12817 $align = '';
12818 break;
12819 }
12820 }
12821 }
12822 $this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12823 $this->endTemplate();
12824 --$this->n;
12825 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12826 unset($this->xobjects[$tmpid]);
12827 $popt['ap']['n'] .= 'Q EMC';
12828 // merge options
12829 $opt = array_merge($popt, $opt);
12830 // remove some conflicting options
12831 unset($opt['bs']);
12832 // set remaining annotation data
12833 $opt['Subtype'] = 'Widget';
12834 $opt['ft'] = 'Tx';
12835 $opt['t'] = $name;
12836 // Additional annotation's parameters (check _putannotsobj() method):
12837 //$opt['f']
12838 //$opt['as']
12839 //$opt['bs']
12840 //$opt['be']
12841 //$opt['c']
12842 //$opt['border']
12843 //$opt['h']
12844 //$opt['mk'];
12845 //$opt['mk']['r']
12846 //$opt['mk']['bc'];
12847 //$opt['mk']['bg'];
12848 unset($opt['mk']['ca']);
12849 unset($opt['mk']['rc']);
12850 unset($opt['mk']['ac']);
12851 unset($opt['mk']['i']);
12852 unset($opt['mk']['ri']);
12853 unset($opt['mk']['ix']);
12854 unset($opt['mk']['if']);
12855 //$opt['mk']['if']['sw'];
12856 //$opt['mk']['if']['s'];
12857 //$opt['mk']['if']['a'];
12858 //$opt['mk']['if']['fb'];
12859 unset($opt['mk']['tp']);
12860 //$opt['tu']
12861 //$opt['tm']
12862 //$opt['ff']
12863 //$opt['v']
12864 //$opt['dv']
12865 //$opt['a']
12866 //$opt['aa']
12867 //$opt['q']
12868 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12869 if ($this->rtl) {
12870 $this->x -= $w;
12871 } else {
12872 $this->x += $w;
12873 }
12874 }
12875
12876 /**
12877 * Creates a RadioButton field.
12878 * @param $name (string) Field name.
12879 * @param $w (int) Width of the radio button.
12880 * @param $prop (array) Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12881 * @param $opt (array) Annotation parameters. Possible values are described on official PDF32000_2008 reference.
12882 * @param $onvalue (string) Value to be returned if selected.
12883 * @param $checked (boolean) Define the initial state.
12884 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12885 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12886 * @param $js (boolean) If true put the field using JavaScript (requires Acrobat Writer to be rendered).
12887 * @public
12888 * @author Nicola Asuni
12889 * @since 4.8.000 (2009-09-07)
12890 */
12891 public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
12892 if ($x === '') {
12893 $x = $this->x;
12894 }
12895 if ($y === '') {
12896 $y = $this->y;
12897 }
12898 // check page for no-write regions and adapt page margins if necessary
12899 list($x, $y) = $this->checkPageRegions($w, $x, $y);
12900 if ($js) {
12901 $this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
12902 return;
12903 }
12904 if (TCPDF_STATIC::empty_string($onvalue)) {
12905 $onvalue = 'On';
12906 }
12907 if ($checked) {
12908 $defval = $onvalue;
12909 } else {
12910 $defval = 'Off';
12911 }
12912 // set font
12913 $font = 'zapfdingbats';
12914 if ($this->pdfa_mode) {
12915 // all fonts must be embedded
12916 $font = 'pdfa'.$font;
12917 }
12918 $this->AddFont($font);
12919 $tmpfont = $this->getFontBuffer($font);
12920 // set data for parent group
12921 if (!isset($this->radiobutton_groups[$this->page])) {
12922 $this->radiobutton_groups[$this->page] = array();
12923 }
12924 if (!isset($this->radiobutton_groups[$this->page][$name])) {
12925 $this->radiobutton_groups[$this->page][$name] = array();
12926 ++$this->n;
12927 $this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
12928 $this->radio_groups[] = $this->n;
12929 }
12930 $kid = ($this->n + 1);
12931 // save object ID to be added on Kids entry on parent object
12932 $this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
12933 // get default style
12934 $prop = array_merge($this->getFormDefaultProp(), $prop);
12935 $prop['NoToggleToOff'] = 'true';
12936 $prop['Radio'] = 'true';
12937 $prop['borderStyle'] = 'inset';
12938 // get annotation data
12939 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12940 // set additional default options
12941 $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
12942 $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
12943 $popt['da'] = $fontstyle;
12944 // build appearance stream
12945 $popt['ap'] = array();
12946 $popt['ap']['n'] = array();
12947 $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
12948 $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
12949 $popt['ap']['n'][$onvalue] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(108).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
12950 $popt['ap']['n']['Off'] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(109).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
12951 if (!isset($popt['mk'])) {
12952 $popt['mk'] = array();
12953 }
12954 $popt['mk']['ca'] = '(l)';
12955 // merge options
12956 $opt = array_merge($popt, $opt);
12957 // set remaining annotation data
12958 $opt['Subtype'] = 'Widget';
12959 $opt['ft'] = 'Btn';
12960 if ($checked) {
12961 $opt['v'] = array('/'.$onvalue);
12962 $opt['as'] = $onvalue;
12963 } else {
12964 $opt['as'] = 'Off';
12965 }
12966 // store readonly flag
12967 if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
12968 $this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
12969 }
12970 $this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
12971 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
12972 if ($this->rtl) {
12973 $this->x -= $w;
12974 } else {
12975 $this->x += $w;
12976 }
12977 }
12978
12979 /**
12980 * Creates a List-box field
12981 * @param $name (string) field name
12982 * @param $w (int) width
12983 * @param $h (int) height
12984 * @param $values (array) array containing the list of values.
12985 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12986 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12987 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12988 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12989 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12990 * @public
12991 * @author Nicola Asuni
12992 * @since 4.8.000 (2009-09-07)
12993 */
12994 public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12995 if ($x === '') {
12996 $x = $this->x;
12997 }
12998 if ($y === '') {
12999 $y = $this->y;
13000 }
13001 // check page for no-write regions and adapt page margins if necessary
13002 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13003 if ($js) {
13004 $this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
13005 $s = '';
13006 foreach ($values as $value) {
13007 if (is_array($value)) {
13008 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13009 } else {
13010 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13011 }
13012 }
13013 $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13014 return;
13015 }
13016 // get default style
13017 $prop = array_merge($this->getFormDefaultProp(), $prop);
13018 // get annotation data
13019 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13020 // set additional default values
13021 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13022 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13023 $popt['da'] = $fontstyle;
13024 // build appearance stream
13025 $popt['ap'] = array();
13026 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13027 $text = '';
13028 foreach($values as $item) {
13029 if (is_array($item)) {
13030 $text .= $item[1]."\n";
13031 } else {
13032 $text .= $item."\n";
13033 }
13034 }
13035 $tmpid = $this->startTemplate($w, $h, false);
13036 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13037 $this->endTemplate();
13038 --$this->n;
13039 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13040 unset($this->xobjects[$tmpid]);
13041 $popt['ap']['n'] .= 'Q EMC';
13042 // merge options
13043 $opt = array_merge($popt, $opt);
13044 // set remaining annotation data
13045 $opt['Subtype'] = 'Widget';
13046 $opt['ft'] = 'Ch';
13047 $opt['t'] = $name;
13048 $opt['opt'] = $values;
13049 unset($opt['mk']['ca']);
13050 unset($opt['mk']['rc']);
13051 unset($opt['mk']['ac']);
13052 unset($opt['mk']['i']);
13053 unset($opt['mk']['ri']);
13054 unset($opt['mk']['ix']);
13055 unset($opt['mk']['if']);
13056 unset($opt['mk']['tp']);
13057 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13058 if ($this->rtl) {
13059 $this->x -= $w;
13060 } else {
13061 $this->x += $w;
13062 }
13063 }
13064
13065 /**
13066 * Creates a Combo-box field
13067 * @param $name (string) field name
13068 * @param $w (int) width
13069 * @param $h (int) height
13070 * @param $values (array) array containing the list of values.
13071 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13072 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13073 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13074 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13075 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13076 * @public
13077 * @author Nicola Asuni
13078 * @since 4.8.000 (2009-09-07)
13079 */
13080 public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
13081 if ($x === '') {
13082 $x = $this->x;
13083 }
13084 if ($y === '') {
13085 $y = $this->y;
13086 }
13087 // check page for no-write regions and adapt page margins if necessary
13088 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13089 if ($js) {
13090 $this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
13091 $s = '';
13092 foreach ($values as $value) {
13093 if (is_array($value)) {
13094 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13095 } else {
13096 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13097 }
13098 }
13099 $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13100 return;
13101 }
13102 // get default style
13103 $prop = array_merge($this->getFormDefaultProp(), $prop);
13104 $prop['Combo'] = true;
13105 // get annotation data
13106 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13107 // set additional default options
13108 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13109 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13110 $popt['da'] = $fontstyle;
13111 // build appearance stream
13112 $popt['ap'] = array();
13113 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13114 $text = '';
13115 foreach($values as $item) {
13116 if (is_array($item)) {
13117 $text .= $item[1]."\n";
13118 } else {
13119 $text .= $item."\n";
13120 }
13121 }
13122 $tmpid = $this->startTemplate($w, $h, false);
13123 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13124 $this->endTemplate();
13125 --$this->n;
13126 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13127 unset($this->xobjects[$tmpid]);
13128 $popt['ap']['n'] .= 'Q EMC';
13129 // merge options
13130 $opt = array_merge($popt, $opt);
13131 // set remaining annotation data
13132 $opt['Subtype'] = 'Widget';
13133 $opt['ft'] = 'Ch';
13134 $opt['t'] = $name;
13135 $opt['opt'] = $values;
13136 unset($opt['mk']['ca']);
13137 unset($opt['mk']['rc']);
13138 unset($opt['mk']['ac']);
13139 unset($opt['mk']['i']);
13140 unset($opt['mk']['ri']);
13141 unset($opt['mk']['ix']);
13142 unset($opt['mk']['if']);
13143 unset($opt['mk']['tp']);
13144 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13145 if ($this->rtl) {
13146 $this->x -= $w;
13147 } else {
13148 $this->x += $w;
13149 }
13150 }
13151
13152 /**
13153 * Creates a CheckBox field
13154 * @param $name (string) field name
13155 * @param $w (int) width
13156 * @param $checked (boolean) define the initial state.
13157 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13158 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13159 * @param $onvalue (string) value to be returned if selected.
13160 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13161 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13162 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13163 * @public
13164 * @author Nicola Asuni
13165 * @since 4.8.000 (2009-09-07)
13166 */
13167 public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
13168 if ($x === '') {
13169 $x = $this->x;
13170 }
13171 if ($y === '') {
13172 $y = $this->y;
13173 }
13174 // check page for no-write regions and adapt page margins if necessary
13175 list($x, $y) = $this->checkPageRegions($w, $x, $y);
13176 if ($js) {
13177 $this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
13178 return;
13179 }
13180 if (!isset($prop['value'])) {
13181 $prop['value'] = array('Yes');
13182 }
13183 // get default style
13184 $prop = array_merge($this->getFormDefaultProp(), $prop);
13185 $prop['borderStyle'] = 'inset';
13186 // get annotation data
13187 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13188 // set additional default options
13189 $font = 'zapfdingbats';
13190 if ($this->pdfa_mode) {
13191 // all fonts must be embedded
13192 $font = 'pdfa'.$font;
13193 }
13194 $this->AddFont($font);
13195 $tmpfont = $this->getFontBuffer($font);
13196 $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
13197 $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
13198 $popt['da'] = $fontstyle;
13199 // build appearance stream
13200 $popt['ap'] = array();
13201 $popt['ap']['n'] = array();
13202 $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
13203 $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
13204 $popt['ap']['n']['Yes'] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(110).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
13205 $popt['ap']['n']['Off'] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(111).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
13206 // merge options
13207 $opt = array_merge($popt, $opt);
13208 // set remaining annotation data
13209 $opt['Subtype'] = 'Widget';
13210 $opt['ft'] = 'Btn';
13211 $opt['t'] = $name;
13212 if (TCPDF_STATIC::empty_string($onvalue)) {
13213 $onvalue = 'Yes';
13214 }
13215 $opt['opt'] = array($onvalue);
13216 if ($checked) {
13217 $opt['v'] = array('/Yes');
13218 $opt['as'] = 'Yes';
13219 } else {
13220 $opt['v'] = array('/Off');
13221 $opt['as'] = 'Off';
13222 }
13223 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13224 if ($this->rtl) {
13225 $this->x -= $w;
13226 } else {
13227 $this->x += $w;
13228 }
13229 }
13230
13231 /**
13232 * Creates a button field
13233 * @param $name (string) field name
13234 * @param $w (int) width
13235 * @param $h (int) height
13236 * @param $caption (string) caption.
13237 * @param $action (mixed) action triggered by pressing the button. Use a string to specify a javascript action. Use an array to specify a form action options as on section 12.7.5 of PDF32000_2008.
13238 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13239 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13240 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13241 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13242 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13243 * @public
13244 * @author Nicola Asuni
13245 * @since 4.8.000 (2009-09-07)
13246 */
13247 public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
13248 if ($x === '') {
13249 $x = $this->x;
13250 }
13251 if ($y === '') {
13252 $y = $this->y;
13253 }
13254 // check page for no-write regions and adapt page margins if necessary
13255 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13256 if ($js) {
13257 $this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
13258 $this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
13259 $this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
13260 $this->javascript .= 'f'.$name.".highlight='push';\n";
13261 $this->javascript .= 'f'.$name.".print=false;\n";
13262 return;
13263 }
13264 // get default style
13265 $prop = array_merge($this->getFormDefaultProp(), $prop);
13266 $prop['Pushbutton'] = 'true';
13267 $prop['highlight'] = 'push';
13268 $prop['display'] = 'display.noPrint';
13269 // get annotation data
13270 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13271 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13272 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13273 $popt['da'] = $fontstyle;
13274 // build appearance stream
13275 $popt['ap'] = array();
13276 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13277 $tmpid = $this->startTemplate($w, $h, false);
13278 $bw = (2 / $this->k); // border width
13279 $border = array(
13280 'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13281 'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
13282 'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13283 'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
13284 $this->SetFillColor(204);
13285 $this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
13286 $this->endTemplate();
13287 --$this->n;
13288 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13289 unset($this->xobjects[$tmpid]);
13290 $popt['ap']['n'] .= 'Q EMC';
13291 // set additional default options
13292 if (!isset($popt['mk'])) {
13293 $popt['mk'] = array();
13294 }
13295 $ann_obj_id = ($this->n + 1);
13296 if (!empty($action) AND !is_array($action)) {
13297 $ann_obj_id = ($this->n + 2);
13298 }
13299 $popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
13300 $popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
13301 $popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
13302 // merge options
13303 $opt = array_merge($popt, $opt);
13304 // set remaining annotation data
13305 $opt['Subtype'] = 'Widget';
13306 $opt['ft'] = 'Btn';
13307 $opt['t'] = $caption;
13308 $opt['v'] = $name;
13309 if (!empty($action)) {
13310 if (is_array($action)) {
13311 // form action options as on section 12.7.5 of PDF32000_2008.
13312 $opt['aa'] = '/D <<';
13313 $bmode = array('SubmitForm', 'ResetForm', 'ImportData');
13314 foreach ($action AS $key => $val) {
13315 if (($key == 'S') AND in_array($val, $bmode)) {
13316 $opt['aa'] .= ' /S /'.$val;
13317 } elseif (($key == 'F') AND (!empty($val))) {
13318 $opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
13319 } elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
13320 $opt['aa'] .= ' /Fields [';
13321 foreach ($val AS $field) {
13322 $opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
13323 }
13324 $opt['aa'] .= ']';
13325 } elseif (($key == 'Flags')) {
13326 $ff = 0;
13327 if (is_array($val)) {
13328 foreach ($val AS $flag) {
13329 switch ($flag) {
13330 case 'Include/Exclude': {
13331 $ff += 1 << 0;
13332 break;
13333 }
13334 case 'IncludeNoValueFields': {
13335 $ff += 1 << 1;
13336 break;
13337 }
13338 case 'ExportFormat': {
13339 $ff += 1 << 2;
13340 break;
13341 }
13342 case 'GetMethod': {
13343 $ff += 1 << 3;
13344 break;
13345 }
13346 case 'SubmitCoordinates': {
13347 $ff += 1 << 4;
13348 break;
13349 }
13350 case 'XFDF': {
13351 $ff += 1 << 5;
13352 break;
13353 }
13354 case 'IncludeAppendSaves': {
13355 $ff += 1 << 6;
13356 break;
13357 }
13358 case 'IncludeAnnotations': {
13359 $ff += 1 << 7;
13360 break;
13361 }
13362 case 'SubmitPDF': {
13363 $ff += 1 << 8;
13364 break;
13365 }
13366 case 'CanonicalFormat': {
13367 $ff += 1 << 9;
13368 break;
13369 }
13370 case 'ExclNonUserAnnots': {
13371 $ff += 1 << 10;
13372 break;
13373 }
13374 case 'ExclFKey': {
13375 $ff += 1 << 11;
13376 break;
13377 }
13378 case 'EmbedForm': {
13379 $ff += 1 << 13;
13380 break;
13381 }
13382 }
13383 }
13384 } else {
13385 $ff = intval($val);
13386 }
13387 $opt['aa'] .= ' /Flags '.$ff;
13388 }
13389 }
13390 $opt['aa'] .= ' >>';
13391 } else {
13392 // Javascript action or raw action command
13393 $js_obj_id = $this->addJavascriptObject($action);
13394 $opt['aa'] = '/D '.$js_obj_id.' 0 R';
13395 }
13396 }
13397 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13398 if ($this->rtl) {
13399 $this->x -= $w;
13400 } else {
13401 $this->x += $w;
13402 }
13403 }
13404
13405 // --- END FORMS FIELDS ------------------------------------------------
13406
13407 /**
13408 * Add certification signature (DocMDP or UR3)
13409 * You can set only one signature type
13410 * @protected
13411 * @author Nicola Asuni
13412 * @since 4.6.008 (2009-05-07)
13413 */
13414 protected function _putsignature() {
13415 if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
13416 return;
13417 }
13418 $sigobjid = ($this->sig_obj_id + 1);
13419 $out = $this->_getobj($sigobjid)."\n";
13420 $out .= '<< /Type /Sig';
13421 $out .= ' /Filter /Adobe.PPKLite';
13422 $out .= ' /SubFilter /adbe.pkcs7.detached';
13423 $out .= ' '.TCPDF_STATIC::$byterange_string;
13424 $out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
13425 if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
13426 $out .= ' /Reference ['; // array of signature reference dictionaries
13427 $out .= ' << /Type /SigRef';
13428 if ($this->signature_data['cert_type'] > 0) {
13429 $out .= ' /TransformMethod /DocMDP';
13430 $out .= ' /TransformParams <<';
13431 $out .= ' /Type /TransformParams';
13432 $out .= ' /P '.$this->signature_data['cert_type'];
13433 $out .= ' /V /1.2';
13434 } else {
13435 $out .= ' /TransformMethod /UR3';
13436 $out .= ' /TransformParams <<';
13437 $out .= ' /Type /TransformParams';
13438 $out .= ' /V /2.2';
13439 if (!TCPDF_STATIC::empty_string($this->ur['document'])) {
13440 $out .= ' /Document['.$this->ur['document'].']';
13441 }
13442 if (!TCPDF_STATIC::empty_string($this->ur['form'])) {
13443 $out .= ' /Form['.$this->ur['form'].']';
13444 }
13445 if (!TCPDF_STATIC::empty_string($this->ur['signature'])) {
13446 $out .= ' /Signature['.$this->ur['signature'].']';
13447 }
13448 if (!TCPDF_STATIC::empty_string($this->ur['annots'])) {
13449 $out .= ' /Annots['.$this->ur['annots'].']';
13450 }
13451 if (!TCPDF_STATIC::empty_string($this->ur['ef'])) {
13452 $out .= ' /EF['.$this->ur['ef'].']';
13453 }
13454 if (!TCPDF_STATIC::empty_string($this->ur['formex'])) {
13455 $out .= ' /FormEX['.$this->ur['formex'].']';
13456 }
13457 }
13458 $out .= ' >>'; // close TransformParams
13459 // optional digest data (values must be calculated and replaced later)
13460 //$out .= ' /Data ********** 0 R';
13461 //$out .= ' /DigestMethod/MD5';
13462 //$out .= ' /DigestLocation[********** 34]';
13463 //$out .= ' /DigestValue<********************************>';
13464 $out .= ' >>';
13465 $out .= ' ]'; // end of reference
13466 }
13467 if (isset($this->signature_data['info']['Name']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Name'])) {
13468 $out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
13469 }
13470 if (isset($this->signature_data['info']['Location']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Location'])) {
13471 $out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
13472 }
13473 if (isset($this->signature_data['info']['Reason']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Reason'])) {
13474 $out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
13475 }
13476 if (isset($this->signature_data['info']['ContactInfo']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['ContactInfo'])) {
13477 $out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
13478 }
13479 $out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
13480 $out .= ' >>';
13481 $out .= "\n".'endobj';
13482 $this->_out($out);
13483 }
13484
13485 /**
13486 * Set User's Rights for PDF Reader
13487 * WARNING: This is experimental and currently do not work.
13488 * Check the PDF Reference 8.7.1 Transform Methods,
13489 * Table 8.105 Entries in the UR transform parameters dictionary
13490 * @param $enable (boolean) if true enable user's rights on PDF reader
13491 * @param $document (string) Names specifying additional document-wide usage rights for the document. The only defined value is "/FullSave", which permits a user to save the document along with modified form and/or annotation data.
13492 * @param $annots (string) Names specifying additional annotation-related usage rights for the document. Valid names in PDF 1.5 and later are /Create/Delete/Modify/Copy/Import/Export, which permit the user to perform the named operation on annotations.
13493 * @param $form (string) Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
13494 * @param $signature (string) Names specifying additional signature-related usage rights for the document. The only defined value is /Modify, which permits a user to apply a digital signature to an existing signature form field or clear a signed signature form field.
13495 * @param $ef (string) Names specifying additional usage rights for named embedded files in the document. Valid names are /Create/Delete/Modify/Import, which permit the user to perform the named operation on named embedded files
13496 Names specifying additional embedded-files-related usage rights for the document.
13497 * @param $formex (string) Names specifying additional form-field-related usage rights. The only valid name is BarcodePlaintext, which permits text form field data to be encoded as a plaintext two-dimensional barcode.
13498 * @public
13499 * @author Nicola Asuni
13500 * @since 2.9.000 (2008-03-26)
13501 */
13502 public function setUserRights(
13503 $enable=true,
13504 $document='/FullSave',
13505 $annots='/Create/Delete/Modify/Copy/Import/Export',
13506 $form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
13507 $signature='/Modify',
13508 $ef='/Create/Delete/Modify/Import',
13509 $formex='') {
13510 $this->ur['enabled'] = $enable;
13511 $this->ur['document'] = $document;
13512 $this->ur['annots'] = $annots;
13513 $this->ur['form'] = $form;
13514 $this->ur['signature'] = $signature;
13515 $this->ur['ef'] = $ef;
13516 $this->ur['formex'] = $formex;
13517 if (!$this->sign) {
13518 $this->setSignature('', '', '', '', 0, array());
13519 }
13520 }
13521
13522 /**
13523 * Enable document signature (requires the OpenSSL Library).
13524 * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
13525 * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13526 * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13527 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
13528 * @param $signing_cert (mixed) signing certificate (string or filename prefixed with 'file://')
13529 * @param $private_key (mixed) private key (string or filename prefixed with 'file://')
13530 * @param $private_key_password (string) password
13531 * @param $extracerts (string) specifies the name of a file containing a bunch of extra certificates to include in the signature which can for example be used to help the recipient to verify the certificate that you used.
13532 * @param $cert_type (int) The access permissions granted for this document. Valid values shall be: 1 = No changes to the document shall be permitted; any change to the document shall invalidate the signature; 2 = Permitted changes shall be filling in forms, instantiating page templates, and signing; other changes shall invalidate the signature; 3 = Permitted changes shall be the same as for 2, as well as annotation creation, deletion, and modification; other changes shall invalidate the signature.
13533 * @param $info (array) array of option information: Name, Location, Reason, ContactInfo.
13534 * @param $approval (string) Enable approval signature eg. for PDF incremental update
13535 * @public
13536 * @author Nicola Asuni
13537 * @since 4.6.005 (2009-04-24)
13538 */
13539 public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array(), $approval='') {
13540 // to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13541 // to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13542 // to convert pfx certificate to pem: openssl
13543 // OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
13544 $this->sign = true;
13545 ++$this->n;
13546 $this->sig_obj_id = $this->n; // signature widget
13547 ++$this->n; // signature object ($this->sig_obj_id + 1)
13548 $this->signature_data = array();
13549 if (strlen($signing_cert) == 0) {
13550 $this->Error('Please provide a certificate file and password!');
13551 }
13552 if (strlen($private_key) == 0) {
13553 $private_key = $signing_cert;
13554 }
13555 $this->signature_data['signcert'] = $signing_cert;
13556 $this->signature_data['privkey'] = $private_key;
13557 $this->signature_data['password'] = $private_key_password;
13558 $this->signature_data['extracerts'] = $extracerts;
13559 $this->signature_data['cert_type'] = $cert_type;
13560 $this->signature_data['info'] = $info;
13561 $this->signature_data['approval'] = $approval;
13562 }
13563
13564 /**
13565 * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
13566 * @param $x (float) Abscissa of the upper-left corner.
13567 * @param $y (float) Ordinate of the upper-left corner.
13568 * @param $w (float) Width of the signature area.
13569 * @param $h (float) Height of the signature area.
13570 * @param $page (int) option page number (if < 0 the current page is used).
13571 * @param $name (string) Name of the signature.
13572 * @public
13573 * @author Nicola Asuni
13574 * @since 5.3.011 (2010-06-17)
13575 */
13576 public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13577 $this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13578 }
13579
13580 /**
13581 * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
13582 * @param $x (float) Abscissa of the upper-left corner.
13583 * @param $y (float) Ordinate of the upper-left corner.
13584 * @param $w (float) Width of the signature area.
13585 * @param $h (float) Height of the signature area.
13586 * @param $page (int) option page number (if < 0 the current page is used).
13587 * @param $name (string) Name of the signature.
13588 * @public
13589 * @author Nicola Asuni
13590 * @since 5.9.101 (2011-07-06)
13591 */
13592 public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13593 ++$this->n;
13594 $this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13595 }
13596
13597 /**
13598 * Get the array that defines the signature appearance (page and rectangle coordinates).
13599 * @param $x (float) Abscissa of the upper-left corner.
13600 * @param $y (float) Ordinate of the upper-left corner.
13601 * @param $w (float) Width of the signature area.
13602 * @param $h (float) Height of the signature area.
13603 * @param $page (int) option page number (if < 0 the current page is used).
13604 * @param $name (string) Name of the signature.
13605 * @return (array) Array defining page and rectangle coordinates of signature appearance.
13606 * @protected
13607 * @author Nicola Asuni
13608 * @since 5.9.101 (2011-07-06)
13609 */
13610 protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13611 $sigapp = array();
13612 if (($page < 1) OR ($page > $this->numpages)) {
13613 $sigapp['page'] = $this->page;
13614 } else {
13615 $sigapp['page'] = intval($page);
13616 }
13617 if (empty($name)) {
13618 $sigapp['name'] = 'Signature';
13619 } else {
13620 $sigapp['name'] = $name;
13621 }
13622 $a = $x * $this->k;
13623 $b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
13624 $c = $w * $this->k;
13625 $d = $h * $this->k;
13626 $sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
13627 return $sigapp;
13628 }
13629
13630 /**
13631 * Enable document timestamping (requires the OpenSSL Library).
13632 * The trusted timestamping improve document security that means that no one should be able to change the document once it has been recorded.
13633 * Use with digital signature only!
13634 * @param $tsa_host (string) Time Stamping Authority (TSA) server (prefixed with 'https://')
13635 * @param $tsa_username (string) Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional)
13636 * @param $tsa_password (string) Specifies the password for TSA authorization (optional)
13637 * @param $tsa_cert (string) Specifies the location of TSA certificate for authorization (optional for cURL)
13638 * @public
13639 * @author Richard Stockinger
13640 * @since 6.0.090 (2014-06-16)
13641 */
13642 public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') {
13643 $this->tsa_data = array();
13644 if (!function_exists('curl_init')) {
13645 $this->Error('Please enable cURL PHP extension!');
13646 }
13647 if (strlen($tsa_host) == 0) {
13648 $this->Error('Please specify the host of Time Stamping Authority (TSA)!');
13649 }
13650 $this->tsa_data['tsa_host'] = $tsa_host;
13651 if (is_file($tsa_username)) {
13652 $this->tsa_data['tsa_auth'] = $tsa_username;
13653 } else {
13654 $this->tsa_data['tsa_username'] = $tsa_username;
13655 }
13656 $this->tsa_data['tsa_password'] = $tsa_password;
13657 $this->tsa_data['tsa_cert'] = $tsa_cert;
13658 $this->tsa_timestamp = true;
13659 }
13660
13661 /**
13662 * NOT YET IMPLEMENTED
13663 * Request TSA for a timestamp
13664 * @param $signature (string) Digital signature as binary string
13665 * @return (string) Timestamped digital signature
13666 * @protected
13667 * @author Richard Stockinger
13668 * @since 6.0.090 (2014-06-16)
13669 */
13670 protected function applyTSA($signature) {
13671 if (!$this->tsa_timestamp) {
13672 return $signature;
13673 }
13674 //@TODO: implement this feature
13675 return $signature;
13676 }
13677
13678 /**
13679 * Create a new page group.
13680 * NOTE: call this function before calling AddPage()
13681 * @param $page (int) starting group page (leave empty for next page).
13682 * @public
13683 * @since 3.0.000 (2008-03-27)
13684 */
13685 public function startPageGroup($page='') {
13686 if (empty($page)) {
13687 $page = $this->page + 1;
13688 }
13689 $this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
13690 }
13691
13692 /**
13693 * Set the starting page number.
13694 * @param $num (int) Starting page number.
13695 * @since 5.9.093 (2011-06-16)
13696 * @public
13697 */
13698 public function setStartingPageNumber($num=1) {
13699 $this->starting_page_number = max(0, intval($num));
13700 }
13701
13702 /**
13703 * Returns the string alias used right align page numbers.
13704 * If the current font is unicode type, the returned string wil contain an additional open curly brace.
13705 * @return string
13706 * @since 5.9.099 (2011-06-27)
13707 * @public
13708 */
13709 public function getAliasRightShift() {
13710 // calculate aproximatively the ratio between widths of aliases and replacements.
13711 $ref = '{'.TCPDF_STATIC::$alias_right_shift.'}{'.TCPDF_STATIC::$alias_tot_pages.'}{'.TCPDF_STATIC::$alias_num_page.'}';
13712 $rep = str_repeat(' ', $this->GetNumChars($ref));
13713 $wrep = $this->GetStringWidth($rep);
13714 if ($wrep > 0) {
13715 $wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
13716 } else {
13717 $wdiff = 1;
13718 }
13719 $sdiff = sprintf('%F', $wdiff);
13720 $alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
13721 if ($this->isUnicodeFont()) {
13722 $alias = '{'.$alias;
13723 }
13724 return $alias;
13725 }
13726
13727 /**
13728 * Returns the string alias used for the total number of pages.
13729 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13730 * This alias will be replaced by the total number of pages in the document.
13731 * @return string
13732 * @since 4.0.018 (2008-08-08)
13733 * @public
13734 */
13735 public function getAliasNbPages() {
13736 if ($this->isUnicodeFont()) {
13737 return '{'.TCPDF_STATIC::$alias_tot_pages.'}';
13738 }
13739 return TCPDF_STATIC::$alias_tot_pages;
13740 }
13741
13742 /**
13743 * Returns the string alias used for the page number.
13744 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13745 * This alias will be replaced by the page number.
13746 * @return string
13747 * @since 4.5.000 (2009-01-02)
13748 * @public
13749 */
13750 public function getAliasNumPage() {
13751 if ($this->isUnicodeFont()) {
13752 return '{'.TCPDF_STATIC::$alias_num_page.'}';
13753 }
13754 return TCPDF_STATIC::$alias_num_page;
13755 }
13756
13757 /**
13758 * Return the alias for the total number of pages in the current page group.
13759 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13760 * This alias will be replaced by the total number of pages in this group.
13761 * @return alias of the current page group
13762 * @public
13763 * @since 3.0.000 (2008-03-27)
13764 */
13765 public function getPageGroupAlias() {
13766 if ($this->isUnicodeFont()) {
13767 return '{'.TCPDF_STATIC::$alias_group_tot_pages.'}';
13768 }
13769 return TCPDF_STATIC::$alias_group_tot_pages;
13770 }
13771
13772 /**
13773 * Return the alias for the page number on the current page group.
13774 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13775 * This alias will be replaced by the page number (relative to the belonging group).
13776 * @return alias of the current page group
13777 * @public
13778 * @since 4.5.000 (2009-01-02)
13779 */
13780 public function getPageNumGroupAlias() {
13781 if ($this->isUnicodeFont()) {
13782 return '{'.TCPDF_STATIC::$alias_group_num_page.'}';
13783 }
13784 return TCPDF_STATIC::$alias_group_num_page;
13785 }
13786
13787 /**
13788 * Return the current page in the group.
13789 * @return current page in the group
13790 * @public
13791 * @since 3.0.000 (2008-03-27)
13792 */
13793 public function getGroupPageNo() {
13794 return $this->pagegroups[$this->currpagegroup];
13795 }
13796
13797 /**
13798 * Returns the current group page number formatted as a string.
13799 * @public
13800 * @since 4.3.003 (2008-11-18)
13801 * @see PaneNo(), formatPageNumber()
13802 */
13803 public function getGroupPageNoFormatted() {
13804 return TCPDF_STATIC::formatPageNumber($this->getGroupPageNo());
13805 }
13806
13807 /**
13808 * Returns the current page number formatted as a string.
13809 * @public
13810 * @since 4.2.005 (2008-11-06)
13811 * @see PaneNo(), formatPageNumber()
13812 */
13813 public function PageNoFormatted() {
13814 return TCPDF_STATIC::formatPageNumber($this->PageNo());
13815 }
13816
13817 /**
13818 * Put pdf layers.
13819 * @protected
13820 * @since 3.0.000 (2008-03-27)
13821 */
13822 protected function _putocg() {
13823 if (empty($this->pdflayers)) {
13824 return;
13825 }
13826 foreach ($this->pdflayers as $key => $layer) {
13827 $this->pdflayers[$key]['objid'] = $this->_newobj();
13828 $out = '<< /Type /OCG';
13829 $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
13830 $out .= ' /Usage <<';
13831 if (isset($layer['print']) AND ($layer['print'] !== NULL)) {
13832 $out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
13833 }
13834 $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
13835 $out .= ' >> >>';
13836 $out .= "\n".'endobj';
13837 $this->_out($out);
13838 }
13839 }
13840
13841 /**
13842 * Start a new pdf layer.
13843 * @param $name (string) Layer name (only a-z letters and numbers). Leave empty for automatic name.
13844 * @param $print (boolean|null) Set to TRUE to print this layer, FALSE to not print and NULL to not set this option
13845 * @param $view (boolean) Set to true to view this layer.
13846 * @param $lock (boolean) If true lock the layer
13847 * @public
13848 * @since 5.9.102 (2011-07-13)
13849 */
13850 public function startLayer($name='', $print=true, $view=true, $lock=true) {
13851 if ($this->state != 2) {
13852 return;
13853 }
13854 $layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
13855 if (empty($name)) {
13856 $name = $layer;
13857 } else {
13858 $name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
13859 }
13860 $this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view, 'lock' => $lock);
13861 $this->openMarkedContent = true;
13862 $this->_out('/OC /'.$layer.' BDC');
13863 }
13864
13865 /**
13866 * End the current PDF layer.
13867 * @public
13868 * @since 5.9.102 (2011-07-13)
13869 */
13870 public function endLayer() {
13871 if ($this->state != 2) {
13872 return;
13873 }
13874 if ($this->openMarkedContent) {
13875 // close existing open marked-content layer
13876 $this->_out('EMC');
13877 $this->openMarkedContent = false;
13878 }
13879 }
13880
13881 /**
13882 * Set the visibility of the successive elements.
13883 * This can be useful, for instance, to put a background
13884 * image or color that will show on screen but won't print.
13885 * @param $v (string) visibility mode. Legal values are: all, print, screen or view.
13886 * @public
13887 * @since 3.0.000 (2008-03-27)
13888 */
13889 public function setVisibility($v) {
13890 if ($this->state != 2) {
13891 return;
13892 }
13893 $this->endLayer();
13894 switch($v) {
13895 case 'print': {
13896 $this->startLayer('Print', true, false);
13897 break;
13898 }
13899 case 'view':
13900 case 'screen': {
13901 $this->startLayer('View', false, true);
13902 break;
13903 }
13904 case 'all': {
13905 $this->_out('');
13906 break;
13907 }
13908 default: {
13909 $this->Error('Incorrect visibility: '.$v);
13910 break;
13911 }
13912 }
13913 }
13914
13915 /**
13916 * Add transparency parameters to the current extgstate
13917 * @param $parms (array) parameters
13918 * @return the number of extgstates
13919 * @protected
13920 * @since 3.0.000 (2008-03-27)
13921 */
13922 protected function addExtGState($parms) {
13923 if ($this->pdfa_mode) {
13924 // transparencies are not allowed in PDF/A mode
13925 return;
13926 }
13927 // check if this ExtGState already exist
13928 foreach ($this->extgstates as $i => $ext) {
13929 if ($ext['parms'] == $parms) {
13930 if ($this->inxobj) {
13931 // we are inside an XObject template
13932 $this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
13933 }
13934 // return reference to existing ExtGState
13935 return $i;
13936 }
13937 }
13938 $n = (count($this->extgstates) + 1);
13939 $this->extgstates[$n] = array('parms' => $parms);
13940 if ($this->inxobj) {
13941 // we are inside an XObject template
13942 $this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
13943 }
13944 return $n;
13945 }
13946
13947 /**
13948 * Add an extgstate
13949 * @param $gs (array) extgstate
13950 * @protected
13951 * @since 3.0.000 (2008-03-27)
13952 */
13953 protected function setExtGState($gs) {
13954 if ($this->pdfa_mode OR ($this->state != 2)) {
13955 // transparency is not allowed in PDF/A mode
13956 return;
13957 }
13958 $this->_out(sprintf('/GS%d gs', $gs));
13959 }
13960
13961 /**
13962 * Put extgstates for object transparency
13963 * @protected
13964 * @since 3.0.000 (2008-03-27)
13965 */
13966 protected function _putextgstates() {
13967 foreach ($this->extgstates as $i => $ext) {
13968 $this->extgstates[$i]['n'] = $this->_newobj();
13969 $out = '<< /Type /ExtGState';
13970 foreach ($ext['parms'] as $k => $v) {
13971 if (is_float($v)) {
13972 $v = sprintf('%F', $v);
13973 } elseif ($v === true) {
13974 $v = 'true';
13975 } elseif ($v === false) {
13976 $v = 'false';
13977 }
13978 $out .= ' /'.$k.' '.$v;
13979 }
13980 $out .= ' >>';
13981 $out .= "\n".'endobj';
13982 $this->_out($out);
13983 }
13984 }
13985
13986 /**
13987 * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
13988 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13989 * @param $stroking (boolean) If true apply overprint for stroking operations.
13990 * @param $nonstroking (boolean) If true apply overprint for painting operations other than stroking.
13991 * @param $mode (integer) Overprint mode: (0 = each source colour component value replaces the value previously painted for the corresponding device colorant; 1 = a tint value of 0.0 for a source colour component shall leave the corresponding component of the previously painted colour unchanged).
13992 * @public
13993 * @since 5.9.152 (2012-03-23)
13994 */
13995 public function setOverprint($stroking=true, $nonstroking='', $mode=0) {
13996 if ($this->state != 2) {
13997 return;
13998 }
13999 $stroking = $stroking ? true : false;
14000 if (TCPDF_STATIC::empty_string($nonstroking)) {
14001 // default value if not set
14002 $nonstroking = $stroking;
14003 } else {
14004 $nonstroking = $nonstroking ? true : false;
14005 }
14006 if (($mode != 0) AND ($mode != 1)) {
14007 $mode = 0;
14008 }
14009 $this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
14010 $gs = $this->addExtGState($this->overprint);
14011 $this->setExtGState($gs);
14012 }
14013
14014 /**
14015 * Get the overprint mode array (OP, op, OPM).
14016 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14017 * @return array.
14018 * @public
14019 * @since 5.9.152 (2012-03-23)
14020 */
14021 public function getOverprint() {
14022 return $this->overprint;
14023 }
14024
14025 /**
14026 * Set alpha for stroking (CA) and non-stroking (ca) operations.
14027 * @param $stroking (float) Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
14028 * @param $bm (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity
14029 * @param $nonstroking (float) Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
14030 * @param $ais (boolean)
14031 * @public
14032 * @since 3.0.000 (2008-03-27)
14033 */
14034 public function setAlpha($stroking=1, $bm='Normal', $nonstroking='', $ais=false) {
14035 if ($this->pdfa_mode) {
14036 // transparency is not allowed in PDF/A mode
14037 return;
14038 }
14039 $stroking = floatval($stroking);
14040 if (TCPDF_STATIC::empty_string($nonstroking)) {
14041 // default value if not set
14042 $nonstroking = $stroking;
14043 } else {
14044 $nonstroking = floatval($nonstroking);
14045 }
14046 if ($bm[0] == '/') {
14047 // remove trailing slash
14048 $bm = substr($bm, 1);
14049 }
14050 if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
14051 $bm = 'Normal';
14052 }
14053 $ais = $ais ? true : false;
14054 $this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
14055 $gs = $this->addExtGState($this->alpha);
14056 $this->setExtGState($gs);
14057 }
14058
14059 /**
14060 * Get the alpha mode array (CA, ca, BM, AIS).
14061 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14062 * @return array.
14063 * @public
14064 * @since 5.9.152 (2012-03-23)
14065 */
14066 public function getAlpha() {
14067 return $this->alpha;
14068 }
14069
14070 /**
14071 * Set the default JPEG compression quality (1-100)
14072 * @param $quality (int) JPEG quality, integer between 1 and 100
14073 * @public
14074 * @since 3.0.000 (2008-03-27)
14075 */
14076 public function setJPEGQuality($quality) {
14077 if (($quality < 1) OR ($quality > 100)) {
14078 $quality = 75;
14079 }
14080 $this->jpeg_quality = intval($quality);
14081 }
14082
14083 /**
14084 * Set the default number of columns in a row for HTML tables.
14085 * @param $cols (int) number of columns
14086 * @public
14087 * @since 3.0.014 (2008-06-04)
14088 */
14089 public function setDefaultTableColumns($cols=4) {
14090 $this->default_table_columns = intval($cols);
14091 }
14092
14093 /**
14094 * Set the height of the cell (line height) respect the font height.
14095 * @param $h (int) cell proportion respect font height (typical value = 1.25).
14096 * @public
14097 * @since 3.0.014 (2008-06-04)
14098 */
14099 public function setCellHeightRatio($h) {
14100 $this->cell_height_ratio = $h;
14101 }
14102
14103 /**
14104 * return the height of cell repect font height.
14105 * @public
14106 * @since 4.0.012 (2008-07-24)
14107 */
14108 public function getCellHeightRatio() {
14109 return $this->cell_height_ratio;
14110 }
14111
14112 /**
14113 * Set the PDF version (check PDF reference for valid values).
14114 * @param $version (string) PDF document version.
14115 * @public
14116 * @since 3.1.000 (2008-06-09)
14117 */
14118 public function setPDFVersion($version='1.7') {
14119 if ($this->pdfa_mode) {
14120 // PDF/A mode
14121 $this->PDFVersion = '1.4';
14122 } else {
14123 $this->PDFVersion = $version;
14124 }
14125 }
14126
14127 /**
14128 * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
14129 * (see Section 8.1 of PDF reference, "Viewer Preferences").
14130 * <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>
14131 * @param $preferences (array) array of options.
14132 * @author Nicola Asuni
14133 * @public
14134 * @since 3.1.000 (2008-06-09)
14135 */
14136 public function setViewerPreferences($preferences) {
14137 $this->viewer_preferences = $preferences;
14138 }
14139
14140 /**
14141 * Paints color transition registration bars
14142 * @param $x (float) abscissa of the top left corner of the rectangle.
14143 * @param $y (float) ordinate of the top left corner of the rectangle.
14144 * @param $w (float) width of the rectangle.
14145 * @param $h (float) height of the rectangle.
14146 * @param $transition (boolean) if true prints tcolor transitions to white.
14147 * @param $vertical (boolean) if true prints bar vertically.
14148 * @param $colors (string) colors to print separated by comma. Valid values are: A,W,R,G,B,C,M,Y,K,RGB,CMYK,ALL,ALLSPOT,<SPOT_COLOR_NAME>. Where: A = grayscale black, W = grayscale white, R = RGB red, G RGB green, B RGB blue, C = CMYK cyan, M = CMYK magenta, Y = CMYK yellow, K = CMYK key/black, RGB = RGB registration color, CMYK = CMYK registration color, ALL = Spot registration color, ALLSPOT = print all defined spot colors, <SPOT_COLOR_NAME> = name of the spot color to print.
14149 * @author Nicola Asuni
14150 * @since 4.9.000 (2010-03-26)
14151 * @public
14152 */
14153 public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
14154 if (strpos($colors, 'ALLSPOT') !== false) {
14155 // expand spot colors
14156 $spot_colors = '';
14157 foreach ($this->spot_colors as $spot_color_name => $v) {
14158 $spot_colors .= ','.$spot_color_name;
14159 }
14160 if (!empty($spot_colors)) {
14161 $spot_colors = substr($spot_colors, 1);
14162 $colors = str_replace('ALLSPOT', $spot_colors, $colors);
14163 } else {
14164 $colors = str_replace('ALLSPOT', 'NONE', $colors);
14165 }
14166 }
14167 $bars = explode(',', $colors);
14168 $numbars = count($bars); // number of bars to print
14169 if ($numbars <= 0) {
14170 return;
14171 }
14172 // set bar measures
14173 if ($vertical) {
14174 $coords = array(0, 0, 0, 1);
14175 $wb = $w / $numbars; // bar width
14176 $hb = $h; // bar height
14177 $xd = $wb; // delta x
14178 $yd = 0; // delta y
14179 } else {
14180 $coords = array(1, 0, 0, 0);
14181 $wb = $w; // bar width
14182 $hb = $h / $numbars; // bar height
14183 $xd = 0; // delta x
14184 $yd = $hb; // delta y
14185 }
14186 $xb = $x;
14187 $yb = $y;
14188 foreach ($bars as $col) {
14189 switch ($col) {
14190 // set transition colors
14191 case 'A': { // BLACK (GRAYSCALE)
14192 $col_a = array(255);
14193 $col_b = array(0);
14194 break;
14195 }
14196 case 'W': { // WHITE (GRAYSCALE)
14197 $col_a = array(0);
14198 $col_b = array(255);
14199 break;
14200 }
14201 case 'R': { // RED (RGB)
14202 $col_a = array(255,255,255);
14203 $col_b = array(255,0,0);
14204 break;
14205 }
14206 case 'G': { // GREEN (RGB)
14207 $col_a = array(255,255,255);
14208 $col_b = array(0,255,0);
14209 break;
14210 }
14211 case 'B': { // BLUE (RGB)
14212 $col_a = array(255,255,255);
14213 $col_b = array(0,0,255);
14214 break;
14215 }
14216 case 'C': { // CYAN (CMYK)
14217 $col_a = array(0,0,0,0);
14218 $col_b = array(100,0,0,0);
14219 break;
14220 }
14221 case 'M': { // MAGENTA (CMYK)
14222 $col_a = array(0,0,0,0);
14223 $col_b = array(0,100,0,0);
14224 break;
14225 }
14226 case 'Y': { // YELLOW (CMYK)
14227 $col_a = array(0,0,0,0);
14228 $col_b = array(0,0,100,0);
14229 break;
14230 }
14231 case 'K': { // KEY - BLACK (CMYK)
14232 $col_a = array(0,0,0,0);
14233 $col_b = array(0,0,0,100);
14234 break;
14235 }
14236 case 'RGB': { // BLACK REGISTRATION (RGB)
14237 $col_a = array(255,255,255);
14238 $col_b = array(0,0,0);
14239 break;
14240 }
14241 case 'CMYK': { // BLACK REGISTRATION (CMYK)
14242 $col_a = array(0,0,0,0);
14243 $col_b = array(100,100,100,100);
14244 break;
14245 }
14246 case 'ALL': { // SPOT COLOR REGISTRATION
14247 $col_a = array(0,0,0,0,'None');
14248 $col_b = array(100,100,100,100,'All');
14249 break;
14250 }
14251 case 'NONE': { // SKIP THIS COLOR
14252 $col_a = array(0,0,0,0,'None');
14253 $col_b = array(0,0,0,0,'None');
14254 break;
14255 }
14256 default: { // SPECIFIC SPOT COLOR NAME
14257 $col_a = array(0,0,0,0,'None');
14258 $col_b = TCPDF_COLORS::getSpotColor($col, $this->spot_colors);
14259 if ($col_b === false) {
14260 // in case of error defaults to the registration color
14261 $col_b = array(100,100,100,100,'All');
14262 }
14263 break;
14264 }
14265 }
14266 if ($col != 'NONE') {
14267 if ($transition) {
14268 // color gradient
14269 $this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
14270 } else {
14271 $this->SetFillColorArray($col_b);
14272 // colored rectangle
14273 $this->Rect($xb, $yb, $wb, $hb, 'F', array());
14274 }
14275 $xb += $xd;
14276 $yb += $yd;
14277 }
14278 }
14279 }
14280
14281 /**
14282 * Paints crop marks.
14283 * @param $x (float) abscissa of the crop mark center.
14284 * @param $y (float) ordinate of the crop mark center.
14285 * @param $w (float) width of the crop mark.
14286 * @param $h (float) height of the crop mark.
14287 * @param $type (string) type of crop mark, one symbol per type separated by comma: T = TOP, F = BOTTOM, L = LEFT, R = RIGHT, TL = A = TOP-LEFT, TR = B = TOP-RIGHT, BL = C = BOTTOM-LEFT, BR = D = BOTTOM-RIGHT.
14288 * @param $color (array) crop mark color (default spot registration color).
14289 * @author Nicola Asuni
14290 * @since 4.9.000 (2010-03-26)
14291 * @public
14292 */
14293 public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
14294 $this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
14295 $type = strtoupper($type);
14296 $type = preg_replace('/[^A-Z\-\,]*/', '', $type);
14297 // split type in single components
14298 $type = str_replace('-', ',', $type);
14299 $type = str_replace('TL', 'T,L', $type);
14300 $type = str_replace('TR', 'T,R', $type);
14301 $type = str_replace('BL', 'F,L', $type);
14302 $type = str_replace('BR', 'F,R', $type);
14303 $type = str_replace('A', 'T,L', $type);
14304 $type = str_replace('B', 'T,R', $type);
14305 $type = str_replace('T,RO', 'BO', $type);
14306 $type = str_replace('C', 'F,L', $type);
14307 $type = str_replace('D', 'F,R', $type);
14308 $crops = explode(',', strtoupper($type));
14309 // remove duplicates
14310 $crops = array_unique($crops);
14311 $dw = ($w / 4); // horizontal space to leave before the intersection point
14312 $dh = ($h / 4); // vertical space to leave before the intersection point
14313 foreach ($crops as $crop) {
14314 switch ($crop) {
14315 case 'T':
14316 case 'TOP': {
14317 $x1 = $x;
14318 $y1 = ($y - $h);
14319 $x2 = $x;
14320 $y2 = ($y - $dh);
14321 break;
14322 }
14323 case 'F':
14324 case 'BOTTOM': {
14325 $x1 = $x;
14326 $y1 = ($y + $dh);
14327 $x2 = $x;
14328 $y2 = ($y + $h);
14329 break;
14330 }
14331 case 'L':
14332 case 'LEFT': {
14333 $x1 = ($x - $w);
14334 $y1 = $y;
14335 $x2 = ($x - $dw);
14336 $y2 = $y;
14337 break;
14338 }
14339 case 'R':
14340 case 'RIGHT': {
14341 $x1 = ($x + $dw);
14342 $y1 = $y;
14343 $x2 = ($x + $w);
14344 $y2 = $y;
14345 break;
14346 }
14347 }
14348 $this->Line($x1, $y1, $x2, $y2);
14349 }
14350 }
14351
14352 /**
14353 * Paints a registration mark
14354 * @param $x (float) abscissa of the registration mark center.
14355 * @param $y (float) ordinate of the registration mark center.
14356 * @param $r (float) radius of the crop mark.
14357 * @param $double (boolean) if true print two concentric crop marks.
14358 * @param $cola (array) crop mark color (default spot registration color 'All').
14359 * @param $colb (array) second crop mark color (default spot registration color 'None').
14360 * @author Nicola Asuni
14361 * @since 4.9.000 (2010-03-26)
14362 * @public
14363 */
14364 public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
14365 $line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
14366 $this->SetFillColorArray($cola);
14367 $this->PieSector($x, $y, $r, 90, 180, 'F');
14368 $this->PieSector($x, $y, $r, 270, 360, 'F');
14369 $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14370 if ($double) {
14371 $ri = $r * 0.5;
14372 $this->SetFillColorArray($colb);
14373 $this->PieSector($x, $y, $ri, 90, 180, 'F');
14374 $this->PieSector($x, $y, $ri, 270, 360, 'F');
14375 $this->SetFillColorArray($cola);
14376 $this->PieSector($x, $y, $ri, 0, 90, 'F');
14377 $this->PieSector($x, $y, $ri, 180, 270, 'F');
14378 $this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
14379 }
14380 }
14381
14382 /**
14383 * Paints a CMYK registration mark
14384 * @param $x (float) abscissa of the registration mark center.
14385 * @param $y (float) ordinate of the registration mark center.
14386 * @param $r (float) radius of the crop mark.
14387 * @author Nicola Asuni
14388 * @since 6.0.038 (2013-09-30)
14389 * @public
14390 */
14391 public function registrationMarkCMYK($x, $y, $r) {
14392 // line width
14393 $lw = max((0.5 / $this->k),($r / 8));
14394 // internal radius
14395 $ri = ($r * 0.6);
14396 // external radius
14397 $re = ($r * 1.3);
14398 // Cyan
14399 $this->SetFillColorArray(array(100,0,0,0));
14400 $this->PieSector($x, $y, $ri, 270, 360, 'F');
14401 // Magenta
14402 $this->SetFillColorArray(array(0,100,0,0));
14403 $this->PieSector($x, $y, $ri, 0, 90, 'F');
14404 // Yellow
14405 $this->SetFillColorArray(array(0,0,100,0));
14406 $this->PieSector($x, $y, $ri, 90, 180, 'F');
14407 // Key - black
14408 $this->SetFillColorArray(array(0,0,0,100));
14409 $this->PieSector($x, $y, $ri, 180, 270, 'F');
14410 // registration color
14411 $line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
14412 $this->SetFillColorArray(array(100,100,100,100,'All'));
14413 // external circle
14414 $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14415 // cross lines
14416 $this->Line($x, ($y - $re), $x, ($y - $ri));
14417 $this->Line($x, ($y + $ri), $x, ($y + $re));
14418 $this->Line(($x - $re), $y, ($x - $ri), $y);
14419 $this->Line(($x + $ri), $y, ($x + $re), $y);
14420 }
14421
14422 /**
14423 * Paints a linear colour gradient.
14424 * @param $x (float) abscissa of the top left corner of the rectangle.
14425 * @param $y (float) ordinate of the top left corner of the rectangle.
14426 * @param $w (float) width of the rectangle.
14427 * @param $h (float) height of the rectangle.
14428 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14429 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14430 * @param $coords (array) array of the form (x1, y1, x2, y2) which defines the gradient vector (see linear_gradient_coords.jpg). The default value is from left to right (x1=0, y1=0, x2=1, y2=0).
14431 * @author Andreas W\FCrmser, Nicola Asuni
14432 * @since 3.1.000 (2008-06-09)
14433 * @public
14434 */
14435 public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
14436 $this->Clip($x, $y, $w, $h);
14437 $this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14438 }
14439
14440 /**
14441 * Paints a radial colour gradient.
14442 * @param $x (float) abscissa of the top left corner of the rectangle.
14443 * @param $y (float) ordinate of the top left corner of the rectangle.
14444 * @param $w (float) width of the rectangle.
14445 * @param $h (float) height of the rectangle.
14446 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14447 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14448 * @param $coords (array) array of the form (fx, fy, cx, cy, r) where (fx, fy) is the starting point of the gradient with color1, (cx, cy) is the center of the circle with color2, and r is the radius of the circle (see radial_gradient_coords.jpg). (fx, fy) should be inside the circle, otherwise some areas will not be defined.
14449 * @author Andreas W\FCrmser, Nicola Asuni
14450 * @since 3.1.000 (2008-06-09)
14451 * @public
14452 */
14453 public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
14454 $this->Clip($x, $y, $w, $h);
14455 $this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14456 }
14457
14458 /**
14459 * Paints a coons patch mesh.
14460 * @param $x (float) abscissa of the top left corner of the rectangle.
14461 * @param $y (float) ordinate of the top left corner of the rectangle.
14462 * @param $w (float) width of the rectangle.
14463 * @param $h (float) height of the rectangle.
14464 * @param $col1 (array) first color (lower left corner) (RGB components).
14465 * @param $col2 (array) second color (lower right corner) (RGB components).
14466 * @param $col3 (array) third color (upper right corner) (RGB components).
14467 * @param $col4 (array) fourth color (upper left corner) (RGB components).
14468 * @param $coords (array) <ul><li>for one patch mesh: array(float x1, float y1, .... float x12, float y12): 12 pairs of coordinates (normally from 0 to 1) which specify the Bezier control points that define the patch. First pair is the lower left edge point, next is its right control point (control point 2). Then the other points are defined in the order: control point 1, edge point, control point 2 going counter-clockwise around the patch. Last (x12, y12) is the first edge point's left control point (control point 1).</li><li>for two or more patch meshes: array[number of patches]: arrays with the following keys for each patch: f: where to put that patch (0 = first patch, 1, 2, 3 = right, top and left of precedent patch - I didn't figure this out completely - just try and error ;-) points: 12 pairs of coordinates of the Bezier control points as above for the first patch, 8 pairs of coordinates for the following patches, ignoring the coordinates already defined by the precedent patch (I also didn't figure out the order of these - also: try and see what's happening) colors: must be 4 colors for the first patch, 2 colors for the following patches</li></ul>
14469 * @param $coords_min (array) minimum value used by the coordinates. If a coordinate's value is smaller than this it will be cut to coords_min. default: 0
14470 * @param $coords_max (array) maximum value used by the coordinates. If a coordinate's value is greater than this it will be cut to coords_max. default: 1
14471 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14472 * @author Andreas W\FCrmser, Nicola Asuni
14473 * @since 3.1.000 (2008-06-09)
14474 * @public
14475 */
14476 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) {
14477 if ($this->pdfa_mode OR ($this->state != 2)) {
14478 return;
14479 }
14480 $this->Clip($x, $y, $w, $h);
14481 $n = count($this->gradients) + 1;
14482 $this->gradients[$n] = array();
14483 $this->gradients[$n]['type'] = 6; //coons patch mesh
14484 $this->gradients[$n]['coords'] = array();
14485 $this->gradients[$n]['antialias'] = $antialias;
14486 $this->gradients[$n]['colors'] = array();
14487 $this->gradients[$n]['transparency'] = false;
14488 //check the coords array if it is the simple array or the multi patch array
14489 if (!isset($coords[0]['f'])) {
14490 //simple array -> convert to multi patch array
14491 if (!isset($col1[1])) {
14492 $col1[1] = $col1[2] = $col1[0];
14493 }
14494 if (!isset($col2[1])) {
14495 $col2[1] = $col2[2] = $col2[0];
14496 }
14497 if (!isset($col3[1])) {
14498 $col3[1] = $col3[2] = $col3[0];
14499 }
14500 if (!isset($col4[1])) {
14501 $col4[1] = $col4[2] = $col4[0];
14502 }
14503 $patch_array[0]['f'] = 0;
14504 $patch_array[0]['points'] = $coords;
14505 $patch_array[0]['colors'][0]['r'] = $col1[0];
14506 $patch_array[0]['colors'][0]['g'] = $col1[1];
14507 $patch_array[0]['colors'][0]['b'] = $col1[2];
14508 $patch_array[0]['colors'][1]['r'] = $col2[0];
14509 $patch_array[0]['colors'][1]['g'] = $col2[1];
14510 $patch_array[0]['colors'][1]['b'] = $col2[2];
14511 $patch_array[0]['colors'][2]['r'] = $col3[0];
14512 $patch_array[0]['colors'][2]['g'] = $col3[1];
14513 $patch_array[0]['colors'][2]['b'] = $col3[2];
14514 $patch_array[0]['colors'][3]['r'] = $col4[0];
14515 $patch_array[0]['colors'][3]['g'] = $col4[1];
14516 $patch_array[0]['colors'][3]['b'] = $col4[2];
14517 } else {
14518 //multi patch array
14519 $patch_array = $coords;
14520 }
14521 $bpcd = 65535; //16 bits per coordinate
14522 //build the data stream
14523 $this->gradients[$n]['stream'] = '';
14524 $count_patch = count($patch_array);
14525 for ($i=0; $i < $count_patch; ++$i) {
14526 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
14527 $count_points = count($patch_array[$i]['points']);
14528 for ($j=0; $j < $count_points; ++$j) {
14529 //each point as 16 bit
14530 $patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
14531 if ($patch_array[$i]['points'][$j] < 0) {
14532 $patch_array[$i]['points'][$j] = 0;
14533 }
14534 if ($patch_array[$i]['points'][$j] > $bpcd) {
14535 $patch_array[$i]['points'][$j] = $bpcd;
14536 }
14537 $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
14538 $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
14539 }
14540 $count_cols = count($patch_array[$i]['colors']);
14541 for ($j=0; $j < $count_cols; ++$j) {
14542 //each color component as 8 bit
14543 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
14544 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
14545 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
14546 }
14547 }
14548 //paint the gradient
14549 $this->_out('/Sh'.$n.' sh');
14550 //restore previous Graphic State
14551 $this->_outRestoreGraphicsState();
14552 if ($this->inxobj) {
14553 // we are inside an XObject template
14554 $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14555 }
14556 }
14557
14558 /**
14559 * Set a rectangular clipping area.
14560 * @param $x (float) abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
14561 * @param $y (float) ordinate of the top left corner of the rectangle.
14562 * @param $w (float) width of the rectangle.
14563 * @param $h (float) height of the rectangle.
14564 * @author Andreas W\FCrmser, Nicola Asuni
14565 * @since 3.1.000 (2008-06-09)
14566 * @protected
14567 */
14568 protected function Clip($x, $y, $w, $h) {
14569 if ($this->state != 2) {
14570 return;
14571 }
14572 if ($this->rtl) {
14573 $x = $this->w - $x - $w;
14574 }
14575 //save current Graphic State
14576 $s = 'q';
14577 //set clipping area
14578 $s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
14579 //set up transformation matrix for gradient
14580 $s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
14581 $this->_out($s);
14582 }
14583
14584 /**
14585 * Output gradient.
14586 * @param $type (int) type of gradient (1 Function-based shading; 2 Axial shading; 3 Radial shading; 4 Free-form Gouraud-shaded triangle mesh; 5 Lattice-form Gouraud-shaded triangle mesh; 6 Coons patch mesh; 7 Tensor-product patch mesh). (Not all types are currently supported)
14587 * @param $coords (array) array of coordinates.
14588 * @param $stops (array) array gradient color components: color = array of GRAY, RGB or CMYK color components; offset = (0 to 1) represents a location along the gradient vector; exponent = exponent of the exponential interpolation function (default = 1).
14589 * @param $background (array) An array of colour components appropriate to the colour space, specifying a single background colour value.
14590 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14591 * @author Nicola Asuni
14592 * @since 3.1.000 (2008-06-09)
14593 * @public
14594 */
14595 public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
14596 if ($this->pdfa_mode OR ($this->state != 2)) {
14597 return;
14598 }
14599 $n = count($this->gradients) + 1;
14600 $this->gradients[$n] = array();
14601 $this->gradients[$n]['type'] = $type;
14602 $this->gradients[$n]['coords'] = $coords;
14603 $this->gradients[$n]['antialias'] = $antialias;
14604 $this->gradients[$n]['colors'] = array();
14605 $this->gradients[$n]['transparency'] = false;
14606 // color space
14607 $numcolspace = count($stops[0]['color']);
14608 $bcolor = array_values($background);
14609 switch($numcolspace) {
14610 case 5: // SPOT
14611 case 4: { // CMYK
14612 $this->gradients[$n]['colspace'] = 'DeviceCMYK';
14613 if (!empty($background)) {
14614 $this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
14615 }
14616 break;
14617 }
14618 case 3: { // RGB
14619 $this->gradients[$n]['colspace'] = 'DeviceRGB';
14620 if (!empty($background)) {
14621 $this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
14622 }
14623 break;
14624 }
14625 case 1: { // GRAY SCALE
14626 $this->gradients[$n]['colspace'] = 'DeviceGray';
14627 if (!empty($background)) {
14628 $this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
14629 }
14630 break;
14631 }
14632 }
14633 $num_stops = count($stops);
14634 $last_stop_id = $num_stops - 1;
14635 foreach ($stops as $key => $stop) {
14636 $this->gradients[$n]['colors'][$key] = array();
14637 // offset represents a location along the gradient vector
14638 if (isset($stop['offset'])) {
14639 $this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
14640 } else {
14641 if ($key == 0) {
14642 $this->gradients[$n]['colors'][$key]['offset'] = 0;
14643 } elseif ($key == $last_stop_id) {
14644 $this->gradients[$n]['colors'][$key]['offset'] = 1;
14645 } else {
14646 $offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
14647 $this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
14648 }
14649 }
14650 if (isset($stop['opacity'])) {
14651 $this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
14652 if ((!$this->pdfa_mode) AND ($stop['opacity'] < 1)) {
14653 $this->gradients[$n]['transparency'] = true;
14654 }
14655 } else {
14656 $this->gradients[$n]['colors'][$key]['opacity'] = 1;
14657 }
14658 // exponent for the exponential interpolation function
14659 if (isset($stop['exponent'])) {
14660 $this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
14661 } else {
14662 $this->gradients[$n]['colors'][$key]['exponent'] = 1;
14663 }
14664 // set colors
14665 $color = array_values($stop['color']);
14666 switch($numcolspace) {
14667 case 5: // SPOT
14668 case 4: { // CMYK
14669 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
14670 break;
14671 }
14672 case 3: { // RGB
14673 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
14674 break;
14675 }
14676 case 1: { // GRAY SCALE
14677 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
14678 break;
14679 }
14680 }
14681 }
14682 if ($this->gradients[$n]['transparency']) {
14683 // paint luminosity gradient
14684 $this->_out('/TGS'.$n.' gs');
14685 }
14686 //paint the gradient
14687 $this->_out('/Sh'.$n.' sh');
14688 //restore previous Graphic State
14689 $this->_outRestoreGraphicsState();
14690 if ($this->inxobj) {
14691 // we are inside an XObject template
14692 $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14693 }
14694 }
14695
14696 /**
14697 * Output gradient shaders.
14698 * @author Nicola Asuni
14699 * @since 3.1.000 (2008-06-09)
14700 * @protected
14701 */
14702 function _putshaders() {
14703 if ($this->pdfa_mode) {
14704 return;
14705 }
14706 $idt = count($this->gradients); //index for transparency gradients
14707 foreach ($this->gradients as $id => $grad) {
14708 if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
14709 $fc = $this->_newobj();
14710 $out = '<<';
14711 $out .= ' /FunctionType 3';
14712 $out .= ' /Domain [0 1]';
14713 $functions = '';
14714 $bounds = '';
14715 $encode = '';
14716 $i = 1;
14717 $num_cols = count($grad['colors']);
14718 $lastcols = $num_cols - 1;
14719 for ($i = 1; $i < $num_cols; ++$i) {
14720 $functions .= ($fc + $i).' 0 R ';
14721 if ($i < $lastcols) {
14722 $bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
14723 }
14724 $encode .= '0 1 ';
14725 }
14726 $out .= ' /Functions ['.trim($functions).']';
14727 $out .= ' /Bounds ['.trim($bounds).']';
14728 $out .= ' /Encode ['.trim($encode).']';
14729 $out .= ' >>';
14730 $out .= "\n".'endobj';
14731 $this->_out($out);
14732 for ($i = 1; $i < $num_cols; ++$i) {
14733 $this->_newobj();
14734 $out = '<<';
14735 $out .= ' /FunctionType 2';
14736 $out .= ' /Domain [0 1]';
14737 $out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
14738 $out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
14739 $out .= ' /N '.$grad['colors'][$i]['exponent'];
14740 $out .= ' >>';
14741 $out .= "\n".'endobj';
14742 $this->_out($out);
14743 }
14744 // set transparency fuctions
14745 if ($grad['transparency']) {
14746 $ft = $this->_newobj();
14747 $out = '<<';
14748 $out .= ' /FunctionType 3';
14749 $out .= ' /Domain [0 1]';
14750 $functions = '';
14751 $i = 1;
14752 $num_cols = count($grad['colors']);
14753 for ($i = 1; $i < $num_cols; ++$i) {
14754 $functions .= ($ft + $i).' 0 R ';
14755 }
14756 $out .= ' /Functions ['.trim($functions).']';
14757 $out .= ' /Bounds ['.trim($bounds).']';
14758 $out .= ' /Encode ['.trim($encode).']';
14759 $out .= ' >>';
14760 $out .= "\n".'endobj';
14761 $this->_out($out);
14762 for ($i = 1; $i < $num_cols; ++$i) {
14763 $this->_newobj();
14764 $out = '<<';
14765 $out .= ' /FunctionType 2';
14766 $out .= ' /Domain [0 1]';
14767 $out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
14768 $out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
14769 $out .= ' /N '.$grad['colors'][$i]['exponent'];
14770 $out .= ' >>';
14771 $out .= "\n".'endobj';
14772 $this->_out($out);
14773 }
14774 }
14775 }
14776 // set shading object
14777 $this->_newobj();
14778 $out = '<< /ShadingType '.$grad['type'];
14779 if (isset($grad['colspace'])) {
14780 $out .= ' /ColorSpace /'.$grad['colspace'];
14781 } else {
14782 $out .= ' /ColorSpace /DeviceRGB';
14783 }
14784 if (isset($grad['background']) AND !empty($grad['background'])) {
14785 $out .= ' /Background ['.$grad['background'].']';
14786 }
14787 if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
14788 $out .= ' /AntiAlias true';
14789 }
14790 if ($grad['type'] == 2) {
14791 $out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
14792 $out .= ' /Domain [0 1]';
14793 $out .= ' /Function '.$fc.' 0 R';
14794 $out .= ' /Extend [true true]';
14795 $out .= ' >>';
14796 } elseif ($grad['type'] == 3) {
14797 //x0, y0, r0, x1, y1, r1
14798 //at this this time radius of inner circle is 0
14799 $out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
14800 $out .= ' /Domain [0 1]';
14801 $out .= ' /Function '.$fc.' 0 R';
14802 $out .= ' /Extend [true true]';
14803 $out .= ' >>';
14804 } elseif ($grad['type'] == 6) {
14805 $out .= ' /BitsPerCoordinate 16';
14806 $out .= ' /BitsPerComponent 8';
14807 $out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
14808 $out .= ' /BitsPerFlag 8';
14809 $stream = $this->_getrawstream($grad['stream']);
14810 $out .= ' /Length '.strlen($stream);
14811 $out .= ' >>';
14812 $out .= ' stream'."\n".$stream."\n".'endstream';
14813 }
14814 $out .= "\n".'endobj';
14815 $this->_out($out);
14816 if ($grad['transparency']) {
14817 $shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
14818 $shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
14819 }
14820 $this->gradients[$id]['id'] = $this->n;
14821 // set pattern object
14822 $this->_newobj();
14823 $out = '<< /Type /Pattern /PatternType 2';
14824 $out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
14825 $out .= ' >>';
14826 $out .= "\n".'endobj';
14827 $this->_out($out);
14828 $this->gradients[$id]['pattern'] = $this->n;
14829 // set shading and pattern for transparency mask
14830 if ($grad['transparency']) {
14831 // luminosity pattern
14832 $idgs = $id + $idt;
14833 $this->_newobj();
14834 $this->_out($shading_transparency);
14835 $this->gradients[$idgs]['id'] = $this->n;
14836 $this->_newobj();
14837 $out = '<< /Type /Pattern /PatternType 2';
14838 $out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
14839 $out .= ' >>';
14840 $out .= "\n".'endobj';
14841 $this->_out($out);
14842 $this->gradients[$idgs]['pattern'] = $this->n;
14843 // luminosity XObject
14844 $oid = $this->_newobj();
14845 $this->xobjects['LX'.$oid] = array('n' => $oid);
14846 $filter = '';
14847 $stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
14848 if ($this->compress) {
14849 $filter = ' /Filter /FlateDecode';
14850 $stream = gzcompress($stream);
14851 }
14852 $stream = $this->_getrawstream($stream);
14853 $out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
14854 $out .= ' /Length '.strlen($stream);
14855 $rect = sprintf('%F %F', $this->wPt, $this->hPt);
14856 $out .= ' /BBox [0 0 '.$rect.']';
14857 $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
14858 $out .= ' /Resources <<';
14859 $out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
14860 $out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
14861 $out .= ' >>';
14862 $out .= ' >> ';
14863 $out .= ' stream'."\n".$stream."\n".'endstream';
14864 $out .= "\n".'endobj';
14865 $this->_out($out);
14866 // SMask
14867 $this->_newobj();
14868 $out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
14869 $this->_out($out);
14870 // ExtGState
14871 $this->_newobj();
14872 $out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
14873 $this->_out($out);
14874 $this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
14875 }
14876 }
14877 }
14878
14879 /**
14880 * Draw the sector of a circle.
14881 * It can be used for instance to render pie charts.
14882 * @param $xc (float) abscissa of the center.
14883 * @param $yc (float) ordinate of the center.
14884 * @param $r (float) radius.
14885 * @param $a (float) start angle (in degrees).
14886 * @param $b (float) end angle (in degrees).
14887 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14888 * @param $cw: (float) indicates whether to go clockwise (default: true).
14889 * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). Default: 90.
14890 * @author Maxime Delorme, Nicola Asuni
14891 * @since 3.1.000 (2008-06-09)
14892 * @public
14893 */
14894 public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
14895 $this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
14896 }
14897
14898 /**
14899 * Draw the sector of an ellipse.
14900 * It can be used for instance to render pie charts.
14901 * @param $xc (float) abscissa of the center.
14902 * @param $yc (float) ordinate of the center.
14903 * @param $rx (float) the x-axis radius.
14904 * @param $ry (float) the y-axis radius.
14905 * @param $a (float) start angle (in degrees).
14906 * @param $b (float) end angle (in degrees).
14907 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14908 * @param $cw: (float) indicates whether to go clockwise.
14909 * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
14910 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of arc.
14911 * @author Maxime Delorme, Nicola Asuni
14912 * @since 3.1.000 (2008-06-09)
14913 * @public
14914 */
14915 public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
14916 if ($this->state != 2) {
14917 return;
14918 }
14919 if ($this->rtl) {
14920 $xc = ($this->w - $xc);
14921 }
14922 $op = TCPDF_STATIC::getPathPaintOperator($style);
14923 if ($op == 'f') {
14924 $line_style = array();
14925 }
14926 if ($cw) {
14927 $d = $b;
14928 $b = (360 - $a + $o);
14929 $a = (360 - $d + $o);
14930 } else {
14931 $b += $o;
14932 $a += $o;
14933 }
14934 $this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
14935 $this->_out($op);
14936 }
14937
14938 /**
14939 * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
14940 * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
14941 * Only vector drawing is supported, not text or bitmap.
14942 * 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).
14943 * @param $file (string) Name of the file containing the image or a '@' character followed by the EPS/AI data string.
14944 * @param $x (float) Abscissa of the upper-left corner.
14945 * @param $y (float) Ordinate of the upper-left corner.
14946 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
14947 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
14948 * @param $link (mixed) URL or identifier returned by AddLink().
14949 * @param $useBoundingBox (boolean) specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
14950 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
14951 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
14952 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
14953 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
14954 * @param $fixoutvals (boolean) if true remove values outside the bounding box.
14955 * @author Valentin Schmidt, Nicola Asuni
14956 * @since 3.1.000 (2008-06-09)
14957 * @public
14958 */
14959 public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
14960 if ($this->state != 2) {
14961 return;
14962 }
14963 if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
14964 // convert EPS to raster image using GD or ImageMagick libraries
14965 return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14966 }
14967 if ($x === '') {
14968 $x = $this->x;
14969 }
14970 if ($y === '') {
14971 $y = $this->y;
14972 }
14973 // check page for no-write regions and adapt page margins if necessary
14974 list($x, $y) = $this->checkPageRegions($h, $x, $y);
14975 $k = $this->k;
14976 if ($file[0] === '@') { // image from string
14977 $data = substr($file, 1);
14978 } else { // EPS/AI file
14979 $data = TCPDF_STATIC::fileGetContents($file);
14980 }
14981 if ($data === FALSE) {
14982 $this->Error('EPS file not found: '.$file);
14983 }
14984 $regs = array();
14985 // EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
14986 preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
14987 if (count($regs) > 1) {
14988 $version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
14989 if (strpos($version_str, 'Adobe Illustrator') !== false) {
14990 $versexp = explode(' ', $version_str);
14991 $version = (float)array_pop($versexp);
14992 if ($version >= 9) {
14993 $this->Error('This version of Adobe Illustrator file is not supported: '.$file);
14994 }
14995 }
14996 }
14997 // strip binary bytes in front of PS-header
14998 $start = strpos($data, '%!PS-Adobe');
14999 if ($start > 0) {
15000 $data = substr($data, $start);
15001 }
15002 // find BoundingBox params
15003 preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
15004 if (count($regs) > 1) {
15005 list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
15006 } else {
15007 $this->Error('No BoundingBox found in EPS/AI file: '.$file);
15008 }
15009 $start = strpos($data, '%%EndSetup');
15010 if ($start === false) {
15011 $start = strpos($data, '%%EndProlog');
15012 }
15013 if ($start === false) {
15014 $start = strpos($data, '%%BoundingBox');
15015 }
15016 $data = substr($data, $start);
15017 $end = strpos($data, '%%PageTrailer');
15018 if ($end===false) {
15019 $end = strpos($data, 'showpage');
15020 }
15021 if ($end) {
15022 $data = substr($data, 0, $end);
15023 }
15024 // calculate image width and height on document
15025 if (($w <= 0) AND ($h <= 0)) {
15026 $w = ($x2 - $x1) / $k;
15027 $h = ($y2 - $y1) / $k;
15028 } elseif ($w <= 0) {
15029 $w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
15030 } elseif ($h <= 0) {
15031 $h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
15032 }
15033 // fit the image on available space
15034 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
15035 if ($this->rasterize_vector_images) {
15036 // convert EPS to raster image using GD or ImageMagick libraries
15037 return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
15038 }
15039 // set scaling factors
15040 $scale_x = $w / (($x2 - $x1) / $k);
15041 $scale_y = $h / (($y2 - $y1) / $k);
15042 // set alignment
15043 $this->img_rb_y = $y + $h;
15044 // set alignment
15045 if ($this->rtl) {
15046 if ($palign == 'L') {
15047 $ximg = $this->lMargin;
15048 } elseif ($palign == 'C') {
15049 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15050 } elseif ($palign == 'R') {
15051 $ximg = $this->w - $this->rMargin - $w;
15052 } else {
15053 $ximg = $x - $w;
15054 }
15055 $this->img_rb_x = $ximg;
15056 } else {
15057 if ($palign == 'L') {
15058 $ximg = $this->lMargin;
15059 } elseif ($palign == 'C') {
15060 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15061 } elseif ($palign == 'R') {
15062 $ximg = $this->w - $this->rMargin - $w;
15063 } else {
15064 $ximg = $x;
15065 }
15066 $this->img_rb_x = $ximg + $w;
15067 }
15068 if ($useBoundingBox) {
15069 $dx = $ximg * $k - $x1;
15070 $dy = $y * $k - $y1;
15071 } else {
15072 $dx = $ximg * $k;
15073 $dy = $y * $k;
15074 }
15075 // save the current graphic state
15076 $this->_out('q'.$this->epsmarker);
15077 // translate
15078 $this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
15079 // scale
15080 if (isset($scale_x)) {
15081 $this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
15082 }
15083 // handle pc/unix/mac line endings
15084 $lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
15085 $u=0;
15086 $cnt = count($lines);
15087 for ($i=0; $i < $cnt; ++$i) {
15088 $line = $lines[$i];
15089 if (($line == '') OR ($line[0] == '%')) {
15090 continue;
15091 }
15092 $len = strlen($line);
15093 // check for spot color names
15094 $color_name = '';
15095 if (strcasecmp('x', substr(trim($line), -1)) == 0) {
15096 if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
15097 // extract spot color name
15098 $color_name = $matches[0];
15099 // remove color name from string
15100 $line = str_replace(' '.$color_name, '', $line);
15101 // remove pharentesis from color name
15102 $color_name = substr($color_name, 1, -1);
15103 }
15104 }
15105 $chunks = explode(' ', $line);
15106 $cmd = trim(array_pop($chunks));
15107 // RGB
15108 if (($cmd == 'Xa') OR ($cmd == 'XA')) {
15109 $b = array_pop($chunks);
15110 $g = array_pop($chunks);
15111 $r = array_pop($chunks);
15112 $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!
15113 continue;
15114 }
15115 $skip = false;
15116 if ($fixoutvals) {
15117 // check for values outside the bounding box
15118 switch ($cmd) {
15119 case 'm':
15120 case 'l':
15121 case 'L': {
15122 // skip values outside bounding box
15123 foreach ($chunks as $key => $val) {
15124 if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
15125 $skip = true;
15126 } elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
15127 $skip = true;
15128 }
15129 }
15130 }
15131 }
15132 }
15133 switch ($cmd) {
15134 case 'm':
15135 case 'l':
15136 case 'v':
15137 case 'y':
15138 case 'c':
15139 case 'k':
15140 case 'K':
15141 case 'g':
15142 case 'G':
15143 case 's':
15144 case 'S':
15145 case 'J':
15146 case 'j':
15147 case 'w':
15148 case 'M':
15149 case 'd':
15150 case 'n': {
15151 if ($skip) {
15152 break;
15153 }
15154 $this->_out($line);
15155 break;
15156 }
15157 case 'x': {// custom fill color
15158 if (empty($color_name)) {
15159 // CMYK color
15160 list($col_c, $col_m, $col_y, $col_k) = $chunks;
15161 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
15162 } else {
15163 // Spot Color (CMYK + tint)
15164 list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15165 $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15166 $color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15167 $this->_out($color_cmd);
15168 }
15169 break;
15170 }
15171 case 'X': { // custom stroke color
15172 if (empty($color_name)) {
15173 // CMYK color
15174 list($col_c, $col_m, $col_y, $col_k) = $chunks;
15175 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
15176 } else {
15177 // Spot Color (CMYK + tint)
15178 list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15179 $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15180 $color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15181 $this->_out($color_cmd);
15182 }
15183 break;
15184 }
15185 case 'Y':
15186 case 'N':
15187 case 'V':
15188 case 'L':
15189 case 'C': {
15190 if ($skip) {
15191 break;
15192 }
15193 $line[($len - 1)] = strtolower($cmd);
15194 $this->_out($line);
15195 break;
15196 }
15197 case 'b':
15198 case 'B': {
15199 $this->_out($cmd . '*');
15200 break;
15201 }
15202 case 'f':
15203 case 'F': {
15204 if ($u > 0) {
15205 $isU = false;
15206 $max = min(($i + 5), $cnt);
15207 for ($j = ($i + 1); $j < $max; ++$j) {
15208 $isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
15209 }
15210 if ($isU) {
15211 $this->_out('f*');
15212 }
15213 } else {
15214 $this->_out('f*');
15215 }
15216 break;
15217 }
15218 case '*u': {
15219 ++$u;
15220 break;
15221 }
15222 case '*U': {
15223 --$u;
15224 break;
15225 }
15226 }
15227 }
15228 // restore previous graphic state
15229 $this->_out($this->epsmarker.'Q');
15230 if (!empty($border)) {
15231 $bx = $this->x;
15232 $by = $this->y;
15233 $this->x = $ximg;
15234 if ($this->rtl) {
15235 $this->x += $w;
15236 }
15237 $this->y = $y;
15238 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
15239 $this->x = $bx;
15240 $this->y = $by;
15241 }
15242 if ($link) {
15243 $this->Link($ximg, $y, $w, $h, $link, 0);
15244 }
15245 // set pointer to align the next text/objects
15246 switch($align) {
15247 case 'T':{
15248 $this->y = $y;
15249 $this->x = $this->img_rb_x;
15250 break;
15251 }
15252 case 'M':{
15253 $this->y = $y + round($h/2);
15254 $this->x = $this->img_rb_x;
15255 break;
15256 }
15257 case 'B':{
15258 $this->y = $this->img_rb_y;
15259 $this->x = $this->img_rb_x;
15260 break;
15261 }
15262 case 'N':{
15263 $this->SetY($this->img_rb_y);
15264 break;
15265 }
15266 default:{
15267 break;
15268 }
15269 }
15270 $this->endlinex = $this->img_rb_x;
15271 }
15272
15273 /**
15274 * Set document barcode.
15275 * @param $bc (string) barcode
15276 * @public
15277 */
15278 public function setBarcode($bc='') {
15279 $this->barcode = $bc;
15280 }
15281
15282 /**
15283 * Get current barcode.
15284 * @return string
15285 * @public
15286 * @since 4.0.012 (2008-07-24)
15287 */
15288 public function getBarcode() {
15289 return $this->barcode;
15290 }
15291
15292 /**
15293 * Print a Linear Barcode.
15294 * @param $code (string) code to print
15295 * @param $type (string) type of barcode (see tcpdf_barcodes_1d.php for supported formats).
15296 * @param $x (int) x position in user units (empty string = current x position)
15297 * @param $y (int) y position in user units (empty string = current y position)
15298 * @param $w (int) width in user units (empty string = remaining page width)
15299 * @param $h (int) height in user units (empty string = remaining page height)
15300 * @param $xres (float) width of the smallest bar in user units (empty string = default value = 0.4mm)
15301 * @param $style (array) array of options:<ul>
15302 * <li>boolean $style['border'] if true prints a border</li>
15303 * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
15304 * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
15305 * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
15306 * <li>array $style['fgcolor'] color array for bars and text</li>
15307 * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
15308 * <li>boolean $style['text'] if true prints text below the barcode</li>
15309 * <li>string $style['label'] override default label</li>
15310 * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
15311 * <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>
15312 * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
15313 * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
15314 * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
15315 * <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>
15316 * <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>
15317 * @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
15318 * @author Nicola Asuni
15319 * @since 3.1.000 (2008-06-09)
15320 * @public
15321 */
15322 public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style='', $align='') {
15323 if (TCPDF_STATIC::empty_string(trim($code))) {
15324 return;
15325 }
15326 require_once(dirname(__FILE__).'/tcpdf_barcodes_1d.php');
15327 // save current graphic settings
15328 $gvars = $this->getGraphicVars();
15329 // create new barcode object
15330 $barcodeobj = new TCPDFBarcode($code, $type);
15331 $arrcode = $barcodeobj->getBarcodeArray();
15332 if (($arrcode === false) OR empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
15333 $this->Error('Error in 1D barcode string');
15334 }
15335 if ($arrcode['maxh'] <= 0) {
15336 $arrcode['maxh'] = 1;
15337 }
15338 // set default values
15339 if (!isset($style['position'])) {
15340 $style['position'] = '';
15341 } elseif ($style['position'] == 'S') {
15342 // keep this for backward compatibility
15343 $style['position'] = '';
15344 $style['stretch'] = true;
15345 }
15346 if (!isset($style['fitwidth'])) {
15347 if (!isset($style['stretch'])) {
15348 $style['fitwidth'] = true;
15349 } else {
15350 $style['fitwidth'] = false;
15351 }
15352 }
15353 if ($style['fitwidth']) {
15354 // disable stretch
15355 $style['stretch'] = false;
15356 }
15357 if (!isset($style['stretch'])) {
15358 if (($w === '') OR ($w <= 0)) {
15359 $style['stretch'] = false;
15360 } else {
15361 $style['stretch'] = true;
15362 }
15363 }
15364 if (!isset($style['fgcolor'])) {
15365 $style['fgcolor'] = array(0,0,0); // default black
15366 }
15367 if (!isset($style['bgcolor'])) {
15368 $style['bgcolor'] = false; // default transparent
15369 }
15370 if (!isset($style['border'])) {
15371 $style['border'] = false;
15372 }
15373 $fontsize = 0;
15374 if (!isset($style['text'])) {
15375 $style['text'] = false;
15376 }
15377 if ($style['text'] AND isset($style['font'])) {
15378 if (isset($style['fontsize'])) {
15379 $fontsize = $style['fontsize'];
15380 }
15381 $this->SetFont($style['font'], '', $fontsize);
15382 }
15383 if (!isset($style['stretchtext'])) {
15384 $style['stretchtext'] = 4;
15385 }
15386 if ($x === '') {
15387 $x = $this->x;
15388 }
15389 if ($y === '') {
15390 $y = $this->y;
15391 }
15392 // check page for no-write regions and adapt page margins if necessary
15393 list($x, $y) = $this->checkPageRegions($h, $x, $y);
15394 if (($w === '') OR ($w <= 0)) {
15395 if ($this->rtl) {
15396 $w = $x - $this->lMargin;
15397 } else {
15398 $w = $this->w - $this->rMargin - $x;
15399 }
15400 }
15401 // padding
15402 if (!isset($style['padding'])) {
15403 $padding = 0;
15404 } elseif ($style['padding'] === 'auto') {
15405 $padding = 10 * ($w / ($arrcode['maxw'] + 20));
15406 } else {
15407 $padding = floatval($style['padding']);
15408 }
15409 // horizontal padding
15410 if (!isset($style['hpadding'])) {
15411 $hpadding = $padding;
15412 } elseif ($style['hpadding'] === 'auto') {
15413 $hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
15414 } else {
15415 $hpadding = floatval($style['hpadding']);
15416 }
15417 // vertical padding
15418 if (!isset($style['vpadding'])) {
15419 $vpadding = $padding;
15420 } elseif ($style['vpadding'] === 'auto') {
15421 $vpadding = ($hpadding / 2);
15422 } else {
15423 $vpadding = floatval($style['vpadding']);
15424 }
15425 // calculate xres (single bar width)
15426 $max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
15427 if ($style['stretch']) {
15428 $xres = $max_xres;
15429 } else {
15430 if (TCPDF_STATIC::empty_string($xres)) {
15431 $xres = (0.141 * $this->k); // default bar width = 0.4 mm
15432 }
15433 if ($xres > $max_xres) {
15434 // correct xres to fit on $w
15435 $xres = $max_xres;
15436 }
15437 if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
15438 OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
15439 $hpadding = 10 * $xres;
15440 if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
15441 $vpadding = ($hpadding / 2);
15442 }
15443 }
15444 }
15445 if ($style['fitwidth']) {
15446 $wold = $w;
15447 $w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
15448 if (isset($style['cellfitalign'])) {
15449 switch ($style['cellfitalign']) {
15450 case 'L': {
15451 if ($this->rtl) {
15452 $x -= ($wold - $w);
15453 }
15454 break;
15455 }
15456 case 'R': {
15457 if (!$this->rtl) {
15458 $x += ($wold - $w);
15459 }
15460 break;
15461 }
15462 case 'C': {
15463 if ($this->rtl) {
15464 $x -= (($wold - $w) / 2);
15465 } else {
15466 $x += (($wold - $w) / 2);
15467 }
15468 break;
15469 }
15470 default : {
15471 break;
15472 }
15473 }
15474 }
15475 }
15476 $text_height = $this->getCellHeight($fontsize / $this->k);
15477 // height
15478 if (($h === '') OR ($h <= 0)) {
15479 // set default height
15480 $h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
15481 }
15482 $barh = $h - $text_height - (2 * $vpadding);
15483 if ($barh <=0) {
15484 // try to reduce font or padding to fit barcode on available height
15485 if ($text_height > $h) {
15486 $fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
15487 $text_height = $this->getCellHeight($fontsize / $this->k);
15488 $this->SetFont($style['font'], '', $fontsize);
15489 }
15490 if ($vpadding > 0) {
15491 $vpadding = (($h - $text_height) / 4);
15492 }
15493 $barh = $h - $text_height - (2 * $vpadding);
15494 }
15495 // fit the barcode on available space
15496 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15497 // set alignment
15498 $this->img_rb_y = $y + $h;
15499 // set alignment
15500 if ($this->rtl) {
15501 if ($style['position'] == 'L') {
15502 $xpos = $this->lMargin;
15503 } elseif ($style['position'] == 'C') {
15504 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15505 } elseif ($style['position'] == 'R') {
15506 $xpos = $this->w - $this->rMargin - $w;
15507 } else {
15508 $xpos = $x - $w;
15509 }
15510 $this->img_rb_x = $xpos;
15511 } else {
15512 if ($style['position'] == 'L') {
15513 $xpos = $this->lMargin;
15514 } elseif ($style['position'] == 'C') {
15515 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15516 } elseif ($style['position'] == 'R') {
15517 $xpos = $this->w - $this->rMargin - $w;
15518 } else {
15519 $xpos = $x;
15520 }
15521 $this->img_rb_x = $xpos + $w;
15522 }
15523 $xpos_rect = $xpos;
15524 if (!isset($style['align'])) {
15525 $style['align'] = 'C';
15526 }
15527 switch ($style['align']) {
15528 case 'L': {
15529 $xpos = $xpos_rect + $hpadding;
15530 break;
15531 }
15532 case 'R': {
15533 $xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
15534 break;
15535 }
15536 case 'C':
15537 default : {
15538 $xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
15539 break;
15540 }
15541 }
15542 $xpos_text = $xpos;
15543 // barcode is always printed in LTR direction
15544 $tempRTL = $this->rtl;
15545 $this->rtl = false;
15546 // print background color
15547 if ($style['bgcolor']) {
15548 $this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15549 } elseif ($style['border']) {
15550 $this->Rect($xpos_rect, $y, $w, $h, 'D');
15551 }
15552 // set foreground color
15553 $this->SetDrawColorArray($style['fgcolor']);
15554 $this->SetTextColorArray($style['fgcolor']);
15555 // print bars
15556 foreach ($arrcode['bcode'] as $k => $v) {
15557 $bw = ($v['w'] * $xres);
15558 if ($v['t']) {
15559 // draw a vertical bar
15560 $ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
15561 $this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
15562 }
15563 $xpos += $bw;
15564 }
15565 // print text
15566 if ($style['text']) {
15567 if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
15568 $label = $style['label'];
15569 } else {
15570 $label = $code;
15571 }
15572 $txtwidth = ($arrcode['maxw'] * $xres);
15573 if ($this->GetStringWidth($label) > $txtwidth) {
15574 $style['stretchtext'] = 2;
15575 }
15576 // print text
15577 $this->x = $xpos_text;
15578 $this->y = $y + $vpadding + $barh;
15579 $cellpadding = $this->cell_padding;
15580 $this->SetCellPadding(0);
15581 $this->Cell($txtwidth, '', $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
15582 $this->cell_padding = $cellpadding;
15583 }
15584 // restore original direction
15585 $this->rtl = $tempRTL;
15586 // restore previous settings
15587 $this->setGraphicVars($gvars);
15588 // set pointer to align the next text/objects
15589 switch($align) {
15590 case 'T':{
15591 $this->y = $y;
15592 $this->x = $this->img_rb_x;
15593 break;
15594 }
15595 case 'M':{
15596 $this->y = $y + round($h / 2);
15597 $this->x = $this->img_rb_x;
15598 break;
15599 }
15600 case 'B':{
15601 $this->y = $this->img_rb_y;
15602 $this->x = $this->img_rb_x;
15603 break;
15604 }
15605 case 'N':{
15606 $this->SetY($this->img_rb_y);
15607 break;
15608 }
15609 default:{
15610 break;
15611 }
15612 }
15613 $this->endlinex = $this->img_rb_x;
15614 }
15615
15616 /**
15617 * Print 2D Barcode.
15618 * @param $code (string) code to print
15619 * @param $type (string) type of barcode (see tcpdf_barcodes_2d.php for supported formats).
15620 * @param $x (int) x position in user units
15621 * @param $y (int) y position in user units
15622 * @param $w (int) width in user units
15623 * @param $h (int) height in user units
15624 * @param $style (array) array of options:<ul>
15625 * <li>boolean $style['border'] if true prints a border around the barcode</li>
15626 * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
15627 * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
15628 * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
15629 * <li>int $style['module_width'] width of a single module in points</li>
15630 * <li>int $style['module_height'] height of a single module in points</li>
15631 * <li>array $style['fgcolor'] color array for bars and text</li>
15632 * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
15633 * <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>
15634 * <li>$style['module_height'] height of a single module in points</li></ul>
15635 * @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
15636 * @param $distort (boolean) if true distort the barcode to fit width and height, otherwise preserve aspect ratio
15637 * @author Nicola Asuni
15638 * @since 4.5.037 (2009-04-07)
15639 * @public
15640 */
15641 public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style='', $align='', $distort=false) {
15642 if (TCPDF_STATIC::empty_string(trim($code))) {
15643 return;
15644 }
15645 require_once(dirname(__FILE__).'/tcpdf_barcodes_2d.php');
15646 // save current graphic settings
15647 $gvars = $this->getGraphicVars();
15648 // create new barcode object
15649 $barcodeobj = new TCPDF2DBarcode($code, $type);
15650 $arrcode = $barcodeobj->getBarcodeArray();
15651 if (($arrcode === false) OR empty($arrcode) OR !isset($arrcode['num_rows']) OR ($arrcode['num_rows'] == 0) OR !isset($arrcode['num_cols']) OR ($arrcode['num_cols'] == 0)) {
15652 $this->Error('Error in 2D barcode string');
15653 }
15654 // set default values
15655 if (!isset($style['position'])) {
15656 $style['position'] = '';
15657 }
15658 if (!isset($style['fgcolor'])) {
15659 $style['fgcolor'] = array(0,0,0); // default black
15660 }
15661 if (!isset($style['bgcolor'])) {
15662 $style['bgcolor'] = false; // default transparent
15663 }
15664 if (!isset($style['border'])) {
15665 $style['border'] = false;
15666 }
15667 // padding
15668 if (!isset($style['padding'])) {
15669 $style['padding'] = 0;
15670 } elseif ($style['padding'] === 'auto') {
15671 $style['padding'] = 4;
15672 }
15673 if (!isset($style['hpadding'])) {
15674 $style['hpadding'] = $style['padding'];
15675 } elseif ($style['hpadding'] === 'auto') {
15676 $style['hpadding'] = 4;
15677 }
15678 if (!isset($style['vpadding'])) {
15679 $style['vpadding'] = $style['padding'];
15680 } elseif ($style['vpadding'] === 'auto') {
15681 $style['vpadding'] = 4;
15682 }
15683 $hpad = (2 * $style['hpadding']);
15684 $vpad = (2 * $style['vpadding']);
15685 // cell (module) dimension
15686 if (!isset($style['module_width'])) {
15687 $style['module_width'] = 1; // width of a single module in points
15688 }
15689 if (!isset($style['module_height'])) {
15690 $style['module_height'] = 1; // height of a single module in points
15691 }
15692 if ($x === '') {
15693 $x = $this->x;
15694 }
15695 if ($y === '') {
15696 $y = $this->y;
15697 }
15698 // check page for no-write regions and adapt page margins if necessary
15699 list($x, $y) = $this->checkPageRegions($h, $x, $y);
15700 // number of barcode columns and rows
15701 $rows = $arrcode['num_rows'];
15702 $cols = $arrcode['num_cols'];
15703 if (($rows <= 0) || ($cols <= 0)){
15704 $this->Error('Error in 2D barcode string');
15705 }
15706 // module width and height
15707 $mw = $style['module_width'];
15708 $mh = $style['module_height'];
15709 if (($mw <= 0) OR ($mh <= 0)) {
15710 $this->Error('Error in 2D barcode string');
15711 }
15712 // get max dimensions
15713 if ($this->rtl) {
15714 $maxw = $x - $this->lMargin;
15715 } else {
15716 $maxw = $this->w - $this->rMargin - $x;
15717 }
15718 $maxh = ($this->h - $this->tMargin - $this->bMargin);
15719 $ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
15720 $ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
15721 if (!$distort) {
15722 if (($maxw * $ratioHW) > $maxh) {
15723 $maxw = $maxh * $ratioWH;
15724 }
15725 if (($maxh * $ratioWH) > $maxw) {
15726 $maxh = $maxw * $ratioHW;
15727 }
15728 }
15729 // set maximum dimesions
15730 if ($w > $maxw) {
15731 $w = $maxw;
15732 }
15733 if ($h > $maxh) {
15734 $h = $maxh;
15735 }
15736 // set dimensions
15737 if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
15738 $w = ($cols + $hpad) * ($mw / $this->k);
15739 $h = ($rows + $vpad) * ($mh / $this->k);
15740 } elseif (($w === '') OR ($w <= 0)) {
15741 $w = $h * $ratioWH;
15742 } elseif (($h === '') OR ($h <= 0)) {
15743 $h = $w * $ratioHW;
15744 }
15745 // barcode size (excluding padding)
15746 $bw = ($w * $cols) / ($cols + $hpad);
15747 $bh = ($h * $rows) / ($rows + $vpad);
15748 // dimension of single barcode cell unit
15749 $cw = $bw / $cols;
15750 $ch = $bh / $rows;
15751 if (!$distort) {
15752 if (($cw / $ch) > ($mw / $mh)) {
15753 // correct horizontal distortion
15754 $cw = $ch * $mw / $mh;
15755 $bw = $cw * $cols;
15756 $style['hpadding'] = ($w - $bw) / (2 * $cw);
15757 } else {
15758 // correct vertical distortion
15759 $ch = $cw * $mh / $mw;
15760 $bh = $ch * $rows;
15761 $style['vpadding'] = ($h - $bh) / (2 * $ch);
15762 }
15763 }
15764 // fit the barcode on available space
15765 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15766 // set alignment
15767 $this->img_rb_y = $y + $h;
15768 // set alignment
15769 if ($this->rtl) {
15770 if ($style['position'] == 'L') {
15771 $xpos = $this->lMargin;
15772 } elseif ($style['position'] == 'C') {
15773 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15774 } elseif ($style['position'] == 'R') {
15775 $xpos = $this->w - $this->rMargin - $w;
15776 } else {
15777 $xpos = $x - $w;
15778 }
15779 $this->img_rb_x = $xpos;
15780 } else {
15781 if ($style['position'] == 'L') {
15782 $xpos = $this->lMargin;
15783 } elseif ($style['position'] == 'C') {
15784 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15785 } elseif ($style['position'] == 'R') {
15786 $xpos = $this->w - $this->rMargin - $w;
15787 } else {
15788 $xpos = $x;
15789 }
15790 $this->img_rb_x = $xpos + $w;
15791 }
15792 $xstart = $xpos + ($style['hpadding'] * $cw);
15793 $ystart = $y + ($style['vpadding'] * $ch);
15794 // barcode is always printed in LTR direction
15795 $tempRTL = $this->rtl;
15796 $this->rtl = false;
15797 // print background color
15798 if ($style['bgcolor']) {
15799 $this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15800 } elseif ($style['border']) {
15801 $this->Rect($xpos, $y, $w, $h, 'D');
15802 }
15803 // set foreground color
15804 $this->SetDrawColorArray($style['fgcolor']);
15805 // print barcode cells
15806 // for each row
15807 for ($r = 0; $r < $rows; ++$r) {
15808 $xr = $xstart;
15809 // for each column
15810 for ($c = 0; $c < $cols; ++$c) {
15811 if ($arrcode['bcode'][$r][$c] == 1) {
15812 // draw a single barcode cell
15813 $this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
15814 }
15815 $xr += $cw;
15816 }
15817 $ystart += $ch;
15818 }
15819 // restore original direction
15820 $this->rtl = $tempRTL;
15821 // restore previous settings
15822 $this->setGraphicVars($gvars);
15823 // set pointer to align the next text/objects
15824 switch($align) {
15825 case 'T':{
15826 $this->y = $y;
15827 $this->x = $this->img_rb_x;
15828 break;
15829 }
15830 case 'M':{
15831 $this->y = $y + round($h/2);
15832 $this->x = $this->img_rb_x;
15833 break;
15834 }
15835 case 'B':{
15836 $this->y = $this->img_rb_y;
15837 $this->x = $this->img_rb_x;
15838 break;
15839 }
15840 case 'N':{
15841 $this->SetY($this->img_rb_y);
15842 break;
15843 }
15844 default:{
15845 break;
15846 }
15847 }
15848 $this->endlinex = $this->img_rb_x;
15849 }
15850
15851 /**
15852 * Returns an array containing current margins:
15853 * <ul>
15854 <li>$ret['left'] = left margin</li>
15855 <li>$ret['right'] = right margin</li>
15856 <li>$ret['top'] = top margin</li>
15857 <li>$ret['bottom'] = bottom margin</li>
15858 <li>$ret['header'] = header margin</li>
15859 <li>$ret['footer'] = footer margin</li>
15860 <li>$ret['cell'] = cell padding array</li>
15861 <li>$ret['padding_left'] = cell left padding</li>
15862 <li>$ret['padding_top'] = cell top padding</li>
15863 <li>$ret['padding_right'] = cell right padding</li>
15864 <li>$ret['padding_bottom'] = cell bottom padding</li>
15865 * </ul>
15866 * @return array containing all margins measures
15867 * @public
15868 * @since 3.2.000 (2008-06-23)
15869 */
15870 public function getMargins() {
15871 $ret = array(
15872 'left' => $this->lMargin,
15873 'right' => $this->rMargin,
15874 'top' => $this->tMargin,
15875 'bottom' => $this->bMargin,
15876 'header' => $this->header_margin,
15877 'footer' => $this->footer_margin,
15878 'cell' => $this->cell_padding,
15879 'padding_left' => $this->cell_padding['L'],
15880 'padding_top' => $this->cell_padding['T'],
15881 'padding_right' => $this->cell_padding['R'],
15882 'padding_bottom' => $this->cell_padding['B']
15883 );
15884 return $ret;
15885 }
15886
15887 /**
15888 * Returns an array containing original margins:
15889 * <ul>
15890 <li>$ret['left'] = left margin</li>
15891 <li>$ret['right'] = right margin</li>
15892 * </ul>
15893 * @return array containing all margins measures
15894 * @public
15895 * @since 4.0.012 (2008-07-24)
15896 */
15897 public function getOriginalMargins() {
15898 $ret = array(
15899 'left' => $this->original_lMargin,
15900 'right' => $this->original_rMargin
15901 );
15902 return $ret;
15903 }
15904
15905 /**
15906 * Returns the current font size.
15907 * @return current font size
15908 * @public
15909 * @since 3.2.000 (2008-06-23)
15910 */
15911 public function getFontSize() {
15912 return $this->FontSize;
15913 }
15914
15915 /**
15916 * Returns the current font size in points unit.
15917 * @return current font size in points unit
15918 * @public
15919 * @since 3.2.000 (2008-06-23)
15920 */
15921 public function getFontSizePt() {
15922 return $this->FontSizePt;
15923 }
15924
15925 /**
15926 * Returns the current font family name.
15927 * @return string current font family name
15928 * @public
15929 * @since 4.3.008 (2008-12-05)
15930 */
15931 public function getFontFamily() {
15932 return $this->FontFamily;
15933 }
15934
15935 /**
15936 * Returns the current font style.
15937 * @return string current font style
15938 * @public
15939 * @since 4.3.008 (2008-12-05)
15940 */
15941 public function getFontStyle() {
15942 return $this->FontStyle;
15943 }
15944
15945 /**
15946 * Cleanup HTML code (requires HTML Tidy library).
15947 * @param $html (string) htmlcode to fix
15948 * @param $default_css (string) CSS commands to add
15949 * @param $tagvs (array) parameters for setHtmlVSpace method
15950 * @param $tidy_options (array) options for tidy_parse_string function
15951 * @return string XHTML code cleaned up
15952 * @author Nicola Asuni
15953 * @public
15954 * @since 5.9.017 (2010-11-16)
15955 * @see setHtmlVSpace()
15956 */
15957 public function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='') {
15958 return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces);
15959 }
15960
15961 /**
15962 * Returns the border width from CSS property
15963 * @param $width (string) border width
15964 * @return int with in user units
15965 * @protected
15966 * @since 5.7.000 (2010-08-02)
15967 */
15968 protected function getCSSBorderWidth($width) {
15969 if ($width == 'thin') {
15970 $width = (2 / $this->k);
15971 } elseif ($width == 'medium') {
15972 $width = (4 / $this->k);
15973 } elseif ($width == 'thick') {
15974 $width = (6 / $this->k);
15975 } else {
15976 $width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
15977 }
15978 return $width;
15979 }
15980
15981 /**
15982 * Returns the border dash style from CSS property
15983 * @param $style (string) border style to convert
15984 * @return int sash style (return -1 in case of none or hidden border)
15985 * @protected
15986 * @since 5.7.000 (2010-08-02)
15987 */
15988 protected function getCSSBorderDashStyle($style) {
15989 switch (strtolower($style)) {
15990 case 'none':
15991 case 'hidden': {
15992 $dash = -1;
15993 break;
15994 }
15995 case 'dotted': {
15996 $dash = 1;
15997 break;
15998 }
15999 case 'dashed': {
16000 $dash = 3;
16001 break;
16002 }
16003 case 'double':
16004 case 'groove':
16005 case 'ridge':
16006 case 'inset':
16007 case 'outset':
16008 case 'solid':
16009 default: {
16010 $dash = 0;
16011 break;
16012 }
16013 }
16014 return $dash;
16015 }
16016
16017 /**
16018 * Returns the border style array from CSS border properties
16019 * @param $cssborder (string) border properties
16020 * @return array containing border properties
16021 * @protected
16022 * @since 5.7.000 (2010-08-02)
16023 */
16024 protected function getCSSBorderStyle($cssborder) {
16025 $bprop = preg_split('/[\s]+/', trim($cssborder));
16026 $border = array(); // value to be returned
16027 switch (count($bprop)) {
16028 case 3: {
16029 $width = $bprop[0];
16030 $style = $bprop[1];
16031 $color = $bprop[2];
16032 break;
16033 }
16034 case 2: {
16035 $width = 'medium';
16036 $style = $bprop[0];
16037 $color = $bprop[1];
16038 break;
16039 }
16040 case 1: {
16041 $width = 'medium';
16042 $style = $bprop[0];
16043 $color = 'black';
16044 break;
16045 }
16046 default: {
16047 $width = 'medium';
16048 $style = 'solid';
16049 $color = 'black';
16050 break;
16051 }
16052 }
16053 if ($style == 'none') {
16054 return array();
16055 }
16056 $border['cap'] = 'square';
16057 $border['join'] = 'miter';
16058 $border['dash'] = $this->getCSSBorderDashStyle($style);
16059 if ($border['dash'] < 0) {
16060 return array();
16061 }
16062 $border['width'] = $this->getCSSBorderWidth($width);
16063 $border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
16064 return $border;
16065 }
16066
16067 /**
16068 * Get the internal Cell padding from CSS attribute.
16069 * @param $csspadding (string) padding properties
16070 * @param $width (float) width of the containing element
16071 * @return array of cell paddings
16072 * @public
16073 * @since 5.9.000 (2010-10-04)
16074 */
16075 public function getCSSPadding($csspadding, $width=0) {
16076 $padding = preg_split('/[\s]+/', trim($csspadding));
16077 $cell_padding = array(); // value to be returned
16078 switch (count($padding)) {
16079 case 4: {
16080 $cell_padding['T'] = $padding[0];
16081 $cell_padding['R'] = $padding[1];
16082 $cell_padding['B'] = $padding[2];
16083 $cell_padding['L'] = $padding[3];
16084 break;
16085 }
16086 case 3: {
16087 $cell_padding['T'] = $padding[0];
16088 $cell_padding['R'] = $padding[1];
16089 $cell_padding['B'] = $padding[2];
16090 $cell_padding['L'] = $padding[1];
16091 break;
16092 }
16093 case 2: {
16094 $cell_padding['T'] = $padding[0];
16095 $cell_padding['R'] = $padding[1];
16096 $cell_padding['B'] = $padding[0];
16097 $cell_padding['L'] = $padding[1];
16098 break;
16099 }
16100 case 1: {
16101 $cell_padding['T'] = $padding[0];
16102 $cell_padding['R'] = $padding[0];
16103 $cell_padding['B'] = $padding[0];
16104 $cell_padding['L'] = $padding[0];
16105 break;
16106 }
16107 default: {
16108 return $this->cell_padding;
16109 }
16110 }
16111 if ($width == 0) {
16112 $width = $this->w - $this->lMargin - $this->rMargin;
16113 }
16114 $cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
16115 $cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
16116 $cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
16117 $cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
16118 return $cell_padding;
16119 }
16120
16121 /**
16122 * Get the internal Cell margin from CSS attribute.
16123 * @param $cssmargin (string) margin properties
16124 * @param $width (float) width of the containing element
16125 * @return array of cell margins
16126 * @public
16127 * @since 5.9.000 (2010-10-04)
16128 */
16129 public function getCSSMargin($cssmargin, $width=0) {
16130 $margin = preg_split('/[\s]+/', trim($cssmargin));
16131 $cell_margin = array(); // value to be returned
16132 switch (count($margin)) {
16133 case 4: {
16134 $cell_margin['T'] = $margin[0];
16135 $cell_margin['R'] = $margin[1];
16136 $cell_margin['B'] = $margin[2];
16137 $cell_margin['L'] = $margin[3];
16138 break;
16139 }
16140 case 3: {
16141 $cell_margin['T'] = $margin[0];
16142 $cell_margin['R'] = $margin[1];
16143 $cell_margin['B'] = $margin[2];
16144 $cell_margin['L'] = $margin[1];
16145 break;
16146 }
16147 case 2: {
16148 $cell_margin['T'] = $margin[0];
16149 $cell_margin['R'] = $margin[1];
16150 $cell_margin['B'] = $margin[0];
16151 $cell_margin['L'] = $margin[1];
16152 break;
16153 }
16154 case 1: {
16155 $cell_margin['T'] = $margin[0];
16156 $cell_margin['R'] = $margin[0];
16157 $cell_margin['B'] = $margin[0];
16158 $cell_margin['L'] = $margin[0];
16159 break;
16160 }
16161 default: {
16162 return $this->cell_margin;
16163 }
16164 }
16165 if ($width == 0) {
16166 $width = $this->w - $this->lMargin - $this->rMargin;
16167 }
16168 $cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
16169 $cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
16170 $cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
16171 $cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
16172 return $cell_margin;
16173 }
16174
16175 /**
16176 * Get the border-spacing from CSS attribute.
16177 * @param $cssbspace (string) border-spacing CSS properties
16178 * @param $width (float) width of the containing element
16179 * @return array of border spacings
16180 * @public
16181 * @since 5.9.010 (2010-10-27)
16182 */
16183 public function getCSSBorderMargin($cssbspace, $width=0) {
16184 $space = preg_split('/[\s]+/', trim($cssbspace));
16185 $border_spacing = array(); // value to be returned
16186 switch (count($space)) {
16187 case 2: {
16188 $border_spacing['H'] = $space[0];
16189 $border_spacing['V'] = $space[1];
16190 break;
16191 }
16192 case 1: {
16193 $border_spacing['H'] = $space[0];
16194 $border_spacing['V'] = $space[0];
16195 break;
16196 }
16197 default: {
16198 return array('H' => 0, 'V' => 0);
16199 }
16200 }
16201 if ($width == 0) {
16202 $width = $this->w - $this->lMargin - $this->rMargin;
16203 }
16204 $border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
16205 $border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
16206 return $border_spacing;
16207 }
16208
16209 /**
16210 * Returns the letter-spacing value from CSS value
16211 * @param $spacing (string) letter-spacing value
16212 * @param $parent (float) font spacing (tracking) value of the parent element
16213 * @return float quantity to increases or decreases the space between characters in a text.
16214 * @protected
16215 * @since 5.9.000 (2010-10-02)
16216 */
16217 protected function getCSSFontSpacing($spacing, $parent=0) {
16218 $val = 0; // value to be returned
16219 $spacing = trim($spacing);
16220 switch ($spacing) {
16221 case 'normal': {
16222 $val = 0;
16223 break;
16224 }
16225 case 'inherit': {
16226 if ($parent == 'normal') {
16227 $val = 0;
16228 } else {
16229 $val = $parent;
16230 }
16231 break;
16232 }
16233 default: {
16234 $val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
16235 }
16236 }
16237 return $val;
16238 }
16239
16240 /**
16241 * Returns the percentage of font stretching from CSS value
16242 * @param $stretch (string) stretch mode
16243 * @param $parent (float) stretch value of the parent element
16244 * @return float font stretching percentage
16245 * @protected
16246 * @since 5.9.000 (2010-10-02)
16247 */
16248 protected function getCSSFontStretching($stretch, $parent=100) {
16249 $val = 100; // value to be returned
16250 $stretch = trim($stretch);
16251 switch ($stretch) {
16252 case 'ultra-condensed': {
16253 $val = 40;
16254 break;
16255 }
16256 case 'extra-condensed': {
16257 $val = 55;
16258 break;
16259 }
16260 case 'condensed': {
16261 $val = 70;
16262 break;
16263 }
16264 case 'semi-condensed': {
16265 $val = 85;
16266 break;
16267 }
16268 case 'normal': {
16269 $val = 100;
16270 break;
16271 }
16272 case 'semi-expanded': {
16273 $val = 115;
16274 break;
16275 }
16276 case 'expanded': {
16277 $val = 130;
16278 break;
16279 }
16280 case 'extra-expanded': {
16281 $val = 145;
16282 break;
16283 }
16284 case 'ultra-expanded': {
16285 $val = 160;
16286 break;
16287 }
16288 case 'wider': {
16289 $val = ($parent + 10);
16290 break;
16291 }
16292 case 'narrower': {
16293 $val = ($parent - 10);
16294 break;
16295 }
16296 case 'inherit': {
16297 if ($parent == 'normal') {
16298 $val = 100;
16299 } else {
16300 $val = $parent;
16301 }
16302 break;
16303 }
16304 default: {
16305 $val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
16306 }
16307 }
16308 return $val;
16309 }
16310
16311 /**
16312 * Convert HTML string containing font size value to points
16313 * @param $val (string) String containing font size value and unit.
16314 * @param $refsize (float) Reference font size in points.
16315 * @param $parent_size (float) Parent font size in points.
16316 * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
16317 * @return float value in points
16318 * @public
16319 */
16320 public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
16321 $refsize = TCPDF_FONTS::getFontRefSize($refsize);
16322 $parent_size = TCPDF_FONTS::getFontRefSize($parent_size, $refsize);
16323 switch ($val) {
16324 case 'xx-small': {
16325 $size = ($refsize - 4);
16326 break;
16327 }
16328 case 'x-small': {
16329 $size = ($refsize - 3);
16330 break;
16331 }
16332 case 'small': {
16333 $size = ($refsize - 2);
16334 break;
16335 }
16336 case 'medium': {
16337 $size = $refsize;
16338 break;
16339 }
16340 case 'large': {
16341 $size = ($refsize + 2);
16342 break;
16343 }
16344 case 'x-large': {
16345 $size = ($refsize + 4);
16346 break;
16347 }
16348 case 'xx-large': {
16349 $size = ($refsize + 6);
16350 break;
16351 }
16352 case 'smaller': {
16353 $size = ($parent_size - 3);
16354 break;
16355 }
16356 case 'larger': {
16357 $size = ($parent_size + 3);
16358 break;
16359 }
16360 default: {
16361 $size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
16362 }
16363 }
16364 return $size;
16365 }
16366
16367 /**
16368 * Returns the HTML DOM array.
16369 * @param $html (string) html code
16370 * @return array
16371 * @protected
16372 * @since 3.2.000 (2008-06-20)
16373 */
16374 protected function getHtmlDomArray($html) {
16375 // array of CSS styles ( selector => properties).
16376 $css = array();
16377 // get CSS array defined at previous call
16378 $matches = array();
16379 if (preg_match_all('/<cssarray>([^\<]*)<\/cssarray>/isU', $html, $matches) > 0) {
16380 if (isset($matches[1][0])) {
16381 $css = array_merge($css, json_decode($this->unhtmlentities($matches[1][0]), true));
16382 }
16383 $html = preg_replace('/<cssarray>(.*?)<\/cssarray>/isU', '', $html);
16384 }
16385 // extract external CSS files
16386 $matches = array();
16387 if (preg_match_all('/<link([^\>]*)>/isU', $html, $matches) > 0) {
16388 foreach ($matches[1] as $key => $link) {
16389 $type = array();
16390 if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
16391 $type = array();
16392 preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
16393 // get 'all' and 'print' media, other media types are discarded
16394 // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16395 if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16396 $type = array();
16397 if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
16398 // read CSS data file
16399 $cssdata = TCPDF_STATIC::fileGetContents(trim($type[1]));
16400 if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
16401 $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16402 }
16403 }
16404 }
16405 }
16406 }
16407 }
16408 // extract style tags
16409 $matches = array();
16410 if (preg_match_all('/<style([^\>]*)>([^\<]*)<\/style>/isU', $html, $matches) > 0) {
16411 foreach ($matches[1] as $key => $media) {
16412 $type = array();
16413 preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
16414 // get 'all' and 'print' media, other media types are discarded
16415 // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16416 if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16417 $cssdata = $matches[2][$key];
16418 $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16419 }
16420 }
16421 }
16422 // create a special tag to contain the CSS array (used for table content)
16423 $csstagarray = '<cssarray>'.htmlentities(json_encode($css)).'</cssarray>';
16424 // remove head and style blocks
16425 $html = preg_replace('/<head([^\>]*)>(.*?)<\/head>/siU', '', $html);
16426 $html = preg_replace('/<style([^\>]*)>([^\<]*)<\/style>/isU', '', $html);
16427 // define block tags
16428 $blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
16429 // define self-closing tags
16430 $selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
16431 // remove all unsupported tags (the line below lists all supported tags)
16432 $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>');
16433 //replace some blank characters
16434 $html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
16435 $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);
16436 $html = preg_replace('@(\r\n|\r)@', "\n", $html);
16437 $repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
16438 $html = strtr($html, $repTable);
16439 $offset = 0;
16440 while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
16441 $html_a = substr($html, 0, $offset);
16442 $html_b = substr($html, $offset, ($pos - $offset + 6));
16443 while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
16444 // preserve newlines on <pre> tag
16445 $html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
16446 }
16447 while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
16448 // preserve spaces on <pre> tag
16449 $html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
16450 }
16451 $html = $html_a.$html_b.substr($html, $pos + 6);
16452 $offset = strlen($html_a.$html_b);
16453 }
16454 $offset = 0;
16455 while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
16456 $html_a = substr($html, 0, $offset);
16457 $html_b = substr($html, $offset, ($pos - $offset + 11));
16458 while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
16459 // preserve newlines on <textarea> tag
16460 $html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
16461 $html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
16462 }
16463 $html = $html_a.$html_b.substr($html, $pos + 11);
16464 $offset = strlen($html_a.$html_b);
16465 }
16466 $html = preg_replace('/([\s]*)<option/si', '<option', $html);
16467 $html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
16468 $offset = 0;
16469 while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
16470 $html_a = substr($html, 0, $offset);
16471 $html_b = substr($html, $offset, ($pos - $offset + 9));
16472 while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
16473 $html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
16474 $html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
16475 }
16476 $html = $html_a.$html_b.substr($html, $pos + 9);
16477 $offset = strlen($html_a.$html_b);
16478 }
16479 if (preg_match("'</select'si", $html)) {
16480 $html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
16481 $html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
16482 }
16483 $html = str_replace("\n", ' ', $html);
16484 // restore textarea newlines
16485 $html = str_replace('<TBR>', "\n", $html);
16486 // remove extra spaces from code
16487 $html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
16488 $html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
16489 $html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
16490 $html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
16491 $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);
16492 $html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
16493 $html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
16494 $html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
16495 $html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
16496 $html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
16497 $html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
16498 $html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
16499 $html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
16500 $html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
16501 $html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
16502 $html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
16503 $html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
16504 $html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
16505 $html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
16506 // trim string
16507 $html = $this->stringTrim($html);
16508 // fix br tag after li
16509 $html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
16510 // fix first image tag alignment
16511 $html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
16512 // pattern for generic tag
16513 $tagpattern = '/(<[^>]+>)/';
16514 // explodes the string
16515 $a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
16516 // count elements
16517 $maxel = count($a);
16518 $elkey = 0;
16519 $key = 0;
16520 // create an array of elements
16521 $dom = array();
16522 $dom[$key] = array();
16523 // set inheritable properties fot the first void element
16524 // 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
16525 $dom[$key]['tag'] = false;
16526 $dom[$key]['block'] = false;
16527 $dom[$key]['value'] = '';
16528 $dom[$key]['parent'] = 0;
16529 $dom[$key]['hide'] = false;
16530 $dom[$key]['fontname'] = $this->FontFamily;
16531 $dom[$key]['fontstyle'] = $this->FontStyle;
16532 $dom[$key]['fontsize'] = $this->FontSizePt;
16533 $dom[$key]['font-stretch'] = $this->font_stretching;
16534 $dom[$key]['letter-spacing'] = $this->font_spacing;
16535 $dom[$key]['stroke'] = $this->textstrokewidth;
16536 $dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
16537 $dom[$key]['clip'] = ($this->textrendermode > 3);
16538 $dom[$key]['line-height'] = $this->cell_height_ratio;
16539 $dom[$key]['bgcolor'] = false;
16540 $dom[$key]['fgcolor'] = $this->fgcolor; // color
16541 $dom[$key]['strokecolor'] = $this->strokecolor;
16542 $dom[$key]['align'] = '';
16543 $dom[$key]['listtype'] = '';
16544 $dom[$key]['text-indent'] = 0;
16545 $dom[$key]['text-transform'] = '';
16546 $dom[$key]['border'] = array();
16547 $dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
16548 $thead = false; // true when we are inside the THEAD tag
16549 ++$key;
16550 $level = array();
16551 array_push($level, 0); // root
16552 while ($elkey < $maxel) {
16553 $dom[$key] = array();
16554 $element = $a[$elkey];
16555 $dom[$key]['elkey'] = $elkey;
16556 if (preg_match($tagpattern, $element)) {
16557 // html tag
16558 $element = substr($element, 1, -1);
16559 // get tag name
16560 preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
16561 $tagname = strtolower($tag[1]);
16562 // check if we are inside a table header
16563 if ($tagname == 'thead') {
16564 if ($element[0] == '/') {
16565 $thead = false;
16566 } else {
16567 $thead = true;
16568 }
16569 ++$elkey;
16570 continue;
16571 }
16572 $dom[$key]['tag'] = true;
16573 $dom[$key]['value'] = $tagname;
16574 if (in_array($dom[$key]['value'], $blocktags)) {
16575 $dom[$key]['block'] = true;
16576 } else {
16577 $dom[$key]['block'] = false;
16578 }
16579 if ($element[0] == '/') {
16580 // *** closing html tag
16581 $dom[$key]['opening'] = false;
16582 $dom[$key]['parent'] = end($level);
16583 array_pop($level);
16584 $dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
16585 $dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
16586 $dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
16587 $dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
16588 $dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
16589 $dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
16590 $dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
16591 $dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
16592 $dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
16593 $dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
16594 $dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
16595 $dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
16596 $dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
16597 $dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
16598 $dom[$key]['text-transform'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['text-transform'];
16599 $dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
16600 if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
16601 $dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
16602 }
16603 // set the number of columns in table tag
16604 if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
16605 $dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
16606 }
16607 if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
16608 $dom[($dom[$key]['parent'])]['content'] = $csstagarray;
16609 for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
16610 $dom[($dom[$key]['parent'])]['content'] .= $a[$dom[$i]['elkey']];
16611 }
16612 $key = $i;
16613 // mark nested tables
16614 $dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
16615 // remove thead sections from nested tables
16616 $dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
16617 $dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
16618 }
16619 // store header rows on a new table
16620 if (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['thead'] === true)) {
16621 if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
16622 $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
16623 }
16624 for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
16625 $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
16626 }
16627 if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
16628 $dom[($dom[$key]['parent'])]['attribute'] = array();
16629 }
16630 // header elements must be always contained in a single page
16631 $dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
16632 }
16633 if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
16634 // remove the nobr attributes from the table header
16635 $dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
16636 $dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
16637 }
16638 } else {
16639 // *** opening or self-closing html tag
16640 $dom[$key]['opening'] = true;
16641 $dom[$key]['parent'] = end($level);
16642 if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
16643 // self-closing tag
16644 $dom[$key]['self'] = true;
16645 } else {
16646 // opening tag
16647 array_push($level, $key);
16648 $dom[$key]['self'] = false;
16649 }
16650 // copy some values from parent
16651 $parentkey = 0;
16652 if ($key > 0) {
16653 $parentkey = $dom[$key]['parent'];
16654 $dom[$key]['hide'] = $dom[$parentkey]['hide'];
16655 $dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
16656 $dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
16657 $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
16658 $dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
16659 $dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
16660 $dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
16661 $dom[$key]['fill'] = $dom[$parentkey]['fill'];
16662 $dom[$key]['clip'] = $dom[$parentkey]['clip'];
16663 $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16664 $dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
16665 $dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
16666 $dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
16667 $dom[$key]['align'] = $dom[$parentkey]['align'];
16668 $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16669 $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16670 $dom[$key]['text-transform'] = $dom[$parentkey]['text-transform'];
16671 $dom[$key]['border'] = array();
16672 $dom[$key]['dir'] = $dom[$parentkey]['dir'];
16673 }
16674 // get attributes
16675 preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
16676 $dom[$key]['attribute'] = array(); // reset attribute array
16677 while (list($id, $name) = each($attr_array[1])) {
16678 $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
16679 }
16680 if (!empty($css)) {
16681 // merge CSS style to current style
16682 list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC::getCSSdataArray($dom, $key, $css);
16683 $dom[$key]['attribute']['style'] = TCPDF_STATIC::getTagStyleFromCSSarray($dom[$key]['cssdata']);
16684 }
16685 // split style attributes
16686 if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
16687 // get style attributes
16688 preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
16689 $dom[$key]['style'] = array(); // reset style attribute array
16690 while (list($id, $name) = each($style_array[1])) {
16691 // in case of duplicate attribute the last replace the previous
16692 $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
16693 }
16694 // --- get some style attributes ---
16695 // text direction
16696 if (isset($dom[$key]['style']['direction'])) {
16697 $dom[$key]['dir'] = $dom[$key]['style']['direction'];
16698 }
16699 // display
16700 if (isset($dom[$key]['style']['display'])) {
16701 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
16702 }
16703 // font family
16704 if (isset($dom[$key]['style']['font-family'])) {
16705 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
16706 }
16707 // list-style-type
16708 if (isset($dom[$key]['style']['list-style-type'])) {
16709 $dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
16710 if ($dom[$key]['listtype'] == 'inherit') {
16711 $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16712 }
16713 }
16714 // text-indent
16715 if (isset($dom[$key]['style']['text-indent'])) {
16716 $dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
16717 if ($dom[$key]['text-indent'] == 'inherit') {
16718 $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16719 }
16720 }
16721 // text-transform
16722 if (isset($dom[$key]['style']['text-transform'])) {
16723 $dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
16724 }
16725 // font size
16726 if (isset($dom[$key]['style']['font-size'])) {
16727 $fsize = trim($dom[$key]['style']['font-size']);
16728 $dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
16729 }
16730 // font-stretch
16731 if (isset($dom[$key]['style']['font-stretch'])) {
16732 $dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
16733 }
16734 // letter-spacing
16735 if (isset($dom[$key]['style']['letter-spacing'])) {
16736 $dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
16737 }
16738 // line-height (internally is the cell height ratio)
16739 if (isset($dom[$key]['style']['line-height'])) {
16740 $lineheight = trim($dom[$key]['style']['line-height']);
16741 switch ($lineheight) {
16742 // A normal line height. This is default
16743 case 'normal': {
16744 $dom[$key]['line-height'] = $dom[0]['line-height'];
16745 break;
16746 }
16747 case 'inherit': {
16748 $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16749 }
16750 default: {
16751 if (is_numeric($lineheight)) {
16752 // convert to percentage of font height
16753 $lineheight = ($lineheight * 100).'%';
16754 }
16755 $dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
16756 if (substr($lineheight, -1) !== '%') {
16757 if ($dom[$key]['fontsize'] <= 0) {
16758 $dom[$key]['line-height'] = 1;
16759 } else {
16760 $dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding['T'] - $this->cell_padding['B']) / $dom[$key]['fontsize']);
16761 }
16762 }
16763 }
16764 }
16765 }
16766 // font style
16767 if (isset($dom[$key]['style']['font-weight'])) {
16768 if (strtolower($dom[$key]['style']['font-weight'][0]) == 'n') {
16769 if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
16770 $dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
16771 }
16772 } elseif (strtolower($dom[$key]['style']['font-weight'][0]) == 'b') {
16773 $dom[$key]['fontstyle'] .= 'B';
16774 }
16775 }
16776 if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style'][0]) == 'i')) {
16777 $dom[$key]['fontstyle'] .= 'I';
16778 }
16779 // font color
16780 if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['color']))) {
16781 $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors);
16782 } elseif ($dom[$key]['value'] == 'a') {
16783 $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16784 }
16785 // background color
16786 if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['background-color']))) {
16787 $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors);
16788 }
16789 // text-decoration
16790 if (isset($dom[$key]['style']['text-decoration'])) {
16791 $decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
16792 foreach ($decors as $dec) {
16793 $dec = trim($dec);
16794 if (!TCPDF_STATIC::empty_string($dec)) {
16795 if ($dec[0] == 'u') {
16796 // underline
16797 $dom[$key]['fontstyle'] .= 'U';
16798 } elseif ($dec[0] == 'l') {
16799 // line-through
16800 $dom[$key]['fontstyle'] .= 'D';
16801 } elseif ($dec[0] == 'o') {
16802 // overline
16803 $dom[$key]['fontstyle'] .= 'O';
16804 }
16805 }
16806 }
16807 } elseif ($dom[$key]['value'] == 'a') {
16808 $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16809 }
16810 // check for width attribute
16811 if (isset($dom[$key]['style']['width'])) {
16812 $dom[$key]['width'] = $dom[$key]['style']['width'];
16813 }
16814 // check for height attribute
16815 if (isset($dom[$key]['style']['height'])) {
16816 $dom[$key]['height'] = $dom[$key]['style']['height'];
16817 }
16818 // check for text alignment
16819 if (isset($dom[$key]['style']['text-align'])) {
16820 $dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align'][0]);
16821 }
16822 // check for CSS border properties
16823 if (isset($dom[$key]['style']['border'])) {
16824 $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
16825 if (!empty($borderstyle)) {
16826 $dom[$key]['border']['LTRB'] = $borderstyle;
16827 }
16828 }
16829 if (isset($dom[$key]['style']['border-color'])) {
16830 $brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
16831 if (isset($brd_colors[3])) {
16832 $dom[$key]['border']['L']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[3], $this->spot_colors);
16833 }
16834 if (isset($brd_colors[1])) {
16835 $dom[$key]['border']['R']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[1], $this->spot_colors);
16836 }
16837 if (isset($brd_colors[0])) {
16838 $dom[$key]['border']['T']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[0], $this->spot_colors);
16839 }
16840 if (isset($brd_colors[2])) {
16841 $dom[$key]['border']['B']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[2], $this->spot_colors);
16842 }
16843 }
16844 if (isset($dom[$key]['style']['border-width'])) {
16845 $brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
16846 if (isset($brd_widths[3])) {
16847 $dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
16848 }
16849 if (isset($brd_widths[1])) {
16850 $dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
16851 }
16852 if (isset($brd_widths[0])) {
16853 $dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
16854 }
16855 if (isset($brd_widths[2])) {
16856 $dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
16857 }
16858 }
16859 if (isset($dom[$key]['style']['border-style'])) {
16860 $brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
16861 if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
16862 $dom[$key]['border']['L']['cap'] = 'square';
16863 $dom[$key]['border']['L']['join'] = 'miter';
16864 $dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
16865 if ($dom[$key]['border']['L']['dash'] < 0) {
16866 $dom[$key]['border']['L'] = array();
16867 }
16868 }
16869 if (isset($brd_styles[1])) {
16870 $dom[$key]['border']['R']['cap'] = 'square';
16871 $dom[$key]['border']['R']['join'] = 'miter';
16872 $dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
16873 if ($dom[$key]['border']['R']['dash'] < 0) {
16874 $dom[$key]['border']['R'] = array();
16875 }
16876 }
16877 if (isset($brd_styles[0])) {
16878 $dom[$key]['border']['T']['cap'] = 'square';
16879 $dom[$key]['border']['T']['join'] = 'miter';
16880 $dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
16881 if ($dom[$key]['border']['T']['dash'] < 0) {
16882 $dom[$key]['border']['T'] = array();
16883 }
16884 }
16885 if (isset($brd_styles[2])) {
16886 $dom[$key]['border']['B']['cap'] = 'square';
16887 $dom[$key]['border']['B']['join'] = 'miter';
16888 $dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
16889 if ($dom[$key]['border']['B']['dash'] < 0) {
16890 $dom[$key]['border']['B'] = array();
16891 }
16892 }
16893 }
16894 $cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
16895 foreach ($cellside as $bsk => $bsv) {
16896 if (isset($dom[$key]['style']['border-'.$bsv])) {
16897 $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
16898 if (!empty($borderstyle)) {
16899 $dom[$key]['border'][$bsk] = $borderstyle;
16900 }
16901 }
16902 if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
16903 $dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors);
16904 }
16905 if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
16906 $dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
16907 }
16908 if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
16909 $dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
16910 if ($dom[$key]['border'][$bsk]['dash'] < 0) {
16911 $dom[$key]['border'][$bsk] = array();
16912 }
16913 }
16914 }
16915 // check for CSS padding properties
16916 if (isset($dom[$key]['style']['padding'])) {
16917 $dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
16918 } else {
16919 $dom[$key]['padding'] = $this->cell_padding;
16920 }
16921 foreach ($cellside as $psk => $psv) {
16922 if (isset($dom[$key]['style']['padding-'.$psv])) {
16923 $dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
16924 }
16925 }
16926 // check for CSS margin properties
16927 if (isset($dom[$key]['style']['margin'])) {
16928 $dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
16929 } else {
16930 $dom[$key]['margin'] = $this->cell_margin;
16931 }
16932 foreach ($cellside as $psk => $psv) {
16933 if (isset($dom[$key]['style']['margin-'.$psv])) {
16934 $dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
16935 }
16936 }
16937 // check for CSS border-spacing properties
16938 if (isset($dom[$key]['style']['border-spacing'])) {
16939 $dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
16940 }
16941 // page-break-inside
16942 if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
16943 $dom[$key]['attribute']['nobr'] = 'true';
16944 }
16945 // page-break-before
16946 if (isset($dom[$key]['style']['page-break-before'])) {
16947 if ($dom[$key]['style']['page-break-before'] == 'always') {
16948 $dom[$key]['attribute']['pagebreak'] = 'true';
16949 } elseif ($dom[$key]['style']['page-break-before'] == 'left') {
16950 $dom[$key]['attribute']['pagebreak'] = 'left';
16951 } elseif ($dom[$key]['style']['page-break-before'] == 'right') {
16952 $dom[$key]['attribute']['pagebreak'] = 'right';
16953 }
16954 }
16955 // page-break-after
16956 if (isset($dom[$key]['style']['page-break-after'])) {
16957 if ($dom[$key]['style']['page-break-after'] == 'always') {
16958 $dom[$key]['attribute']['pagebreakafter'] = 'true';
16959 } elseif ($dom[$key]['style']['page-break-after'] == 'left') {
16960 $dom[$key]['attribute']['pagebreakafter'] = 'left';
16961 } elseif ($dom[$key]['style']['page-break-after'] == 'right') {
16962 $dom[$key]['attribute']['pagebreakafter'] = 'right';
16963 }
16964 }
16965 }
16966 if (isset($dom[$key]['attribute']['display'])) {
16967 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
16968 }
16969 if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
16970 $borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
16971 if (!empty($borderstyle)) {
16972 $dom[$key]['border']['LTRB'] = $borderstyle;
16973 }
16974 }
16975 // check for font tag
16976 if ($dom[$key]['value'] == 'font') {
16977 // font family
16978 if (isset($dom[$key]['attribute']['face'])) {
16979 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
16980 }
16981 // font size
16982 if (isset($dom[$key]['attribute']['size'])) {
16983 if ($key > 0) {
16984 if ($dom[$key]['attribute']['size'][0] == '+') {
16985 $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
16986 } elseif ($dom[$key]['attribute']['size'][0] == '-') {
16987 $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
16988 } else {
16989 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16990 }
16991 } else {
16992 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16993 }
16994 }
16995 }
16996 // force natural alignment for lists
16997 if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
16998 AND (!isset($dom[$key]['align']) OR TCPDF_STATIC::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
16999 if ($this->rtl) {
17000 $dom[$key]['align'] = 'R';
17001 } else {
17002 $dom[$key]['align'] = 'L';
17003 }
17004 }
17005 if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
17006 if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
17007 $dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
17008 }
17009 }
17010 if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
17011 $dom[$key]['fontstyle'] .= 'B';
17012 }
17013 if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
17014 $dom[$key]['fontstyle'] .= 'I';
17015 }
17016 if ($dom[$key]['value'] == 'u') {
17017 $dom[$key]['fontstyle'] .= 'U';
17018 }
17019 if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
17020 $dom[$key]['fontstyle'] .= 'D';
17021 }
17022 if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
17023 $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
17024 }
17025 if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
17026 $dom[$key]['fontname'] = $this->default_monospaced_font;
17027 }
17028 if (!empty($dom[$key]['value']) AND ($dom[$key]['value'][0] == 'h') AND (intval($dom[$key]['value']{1}) > 0) AND (intval($dom[$key]['value']{1}) < 7)) {
17029 // headings h1, h2, h3, h4, h5, h6
17030 if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
17031 $headsize = (4 - intval($dom[$key]['value']{1})) * 2;
17032 $dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
17033 }
17034 if (!isset($dom[$key]['style']['font-weight'])) {
17035 $dom[$key]['fontstyle'] .= 'B';
17036 }
17037 }
17038 if (($dom[$key]['value'] == 'table')) {
17039 $dom[$key]['rows'] = 0; // number of rows
17040 $dom[$key]['trids'] = array(); // IDs of TR elements
17041 $dom[$key]['thead'] = ''; // table header rows
17042 }
17043 if (($dom[$key]['value'] == 'tr')) {
17044 $dom[$key]['cols'] = 0;
17045 if ($thead) {
17046 $dom[$key]['thead'] = true;
17047 // rows on thead block are printed as a separate table
17048 } else {
17049 $dom[$key]['thead'] = false;
17050 // store the number of rows on table element
17051 ++$dom[($dom[$key]['parent'])]['rows'];
17052 // store the TR elements IDs on table element
17053 array_push($dom[($dom[$key]['parent'])]['trids'], $key);
17054 }
17055 }
17056 if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
17057 if (isset($dom[$key]['attribute']['colspan'])) {
17058 $colspan = intval($dom[$key]['attribute']['colspan']);
17059 } else {
17060 $colspan = 1;
17061 }
17062 $dom[$key]['attribute']['colspan'] = $colspan;
17063 $dom[($dom[$key]['parent'])]['cols'] += $colspan;
17064 }
17065 // text direction
17066 if (isset($dom[$key]['attribute']['dir'])) {
17067 $dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
17068 }
17069 // set foreground color attribute
17070 if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['color']))) {
17071 $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors);
17072 } elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
17073 $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
17074 }
17075 // set background color attribute
17076 if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['bgcolor']))) {
17077 $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors);
17078 }
17079 // set stroke color attribute
17080 if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['strokecolor']))) {
17081 $dom[$key]['strokecolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors);
17082 }
17083 // check for width attribute
17084 if (isset($dom[$key]['attribute']['width'])) {
17085 $dom[$key]['width'] = $dom[$key]['attribute']['width'];
17086 }
17087 // check for height attribute
17088 if (isset($dom[$key]['attribute']['height'])) {
17089 $dom[$key]['height'] = $dom[$key]['attribute']['height'];
17090 }
17091 // check for text alignment
17092 if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
17093 $dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align'][0]);
17094 }
17095 // check for text rendering mode (the following attributes do not exist in HTML)
17096 if (isset($dom[$key]['attribute']['stroke'])) {
17097 // font stroke width
17098 $dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
17099 }
17100 if (isset($dom[$key]['attribute']['fill'])) {
17101 // font fill
17102 if ($dom[$key]['attribute']['fill'] == 'true') {
17103 $dom[$key]['fill'] = true;
17104 } else {
17105 $dom[$key]['fill'] = false;
17106 }
17107 }
17108 if (isset($dom[$key]['attribute']['clip'])) {
17109 // clipping mode
17110 if ($dom[$key]['attribute']['clip'] == 'true') {
17111 $dom[$key]['clip'] = true;
17112 } else {
17113 $dom[$key]['clip'] = false;
17114 }
17115 }
17116 } // end opening tag
17117 } else {
17118 // text
17119 $dom[$key]['tag'] = false;
17120 $dom[$key]['block'] = false;
17121 $dom[$key]['parent'] = end($level);
17122 $dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
17123 if (!empty($dom[$dom[$key]['parent']]['text-transform'])) {
17124 // text-transform for unicode requires mb_convert_case (Multibyte String Functions)
17125 if (function_exists('mb_convert_case')) {
17126 $ttm = array('capitalize' => MB_CASE_TITLE, 'uppercase' => MB_CASE_UPPER, 'lowercase' => MB_CASE_LOWER);
17127 if (isset($ttm[$dom[$dom[$key]['parent']]['text-transform']])) {
17128 $element = mb_convert_case($element, $ttm[$dom[$dom[$key]['parent']]['text-transform']], $this->encoding);
17129 }
17130 } elseif (!$this->isunicode) {
17131 switch ($dom[$dom[$key]['parent']]['text-transform']) {
17132 case 'capitalize': {
17133 $element = ucwords(strtolower($element));
17134 break;
17135 }
17136 case 'uppercase': {
17137 $element = strtoupper($element);
17138 break;
17139 }
17140 case 'lowercase': {
17141 $element = strtolower($element);
17142 break;
17143 }
17144 }
17145 }
17146 }
17147 $dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
17148 }
17149 ++$elkey;
17150 ++$key;
17151 }
17152 return $dom;
17153 }
17154
17155 /**
17156 * Returns the string used to find spaces
17157 * @return string
17158 * @protected
17159 * @author Nicola Asuni
17160 * @since 4.8.024 (2010-01-15)
17161 */
17162 protected function getSpaceString() {
17163 $spacestr = chr(32);
17164 if ($this->isUnicodeFont()) {
17165 $spacestr = chr(0).chr(32);
17166 }
17167 return $spacestr;
17168 }
17169
17170 /**
17171 * Serialize an array of parameters to be used with TCPDF tag in HTML code.
17172 * @param $pararray (array) parameters array
17173 * @return sting containing serialized data
17174 * @since 4.9.006 (2010-04-02)
17175 * @public
17176 * @deprecated
17177 */
17178 public function serializeTCPDFtagParameters($pararray) {
17179 return TCPDF_STATIC::serializeTCPDFtagParameters($pararray);
17180 }
17181
17182 /**
17183 * Prints a cell (rectangular area) with optional borders, background color and html text string.
17184 * 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 />
17185 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
17186 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17187 * 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
17188 * NOTE: all the HTML attributes must be enclosed in double-quote.
17189 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
17190 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
17191 * @param $x (float) upper-left corner X coordinate
17192 * @param $y (float) upper-left corner Y coordinate
17193 * @param $html (string) html text to print. Default value: empty string.
17194 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
17195 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL language)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
17196Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
17197 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
17198 * @param $reseth (boolean) if true reset the last cell height (default true).
17199 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
17200 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
17201 * @see Multicell(), writeHTML()
17202 * @public
17203 */
17204 public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
17205 return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
17206 }
17207
17208 /**
17209 * Allows to preserve some HTML formatting (limited support).<br />
17210 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17211 * 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
17212 * NOTE: all the HTML attributes must be enclosed in double-quote.
17213 * @param $html (string) text to display
17214 * @param $ln (boolean) if true add a new line after text (default = true)
17215 * @param $fill (boolean) Indicates if the background must be painted (true) or transparent (false).
17216 * @param $reseth (boolean) if true reset the last cell height (default false).
17217 * @param $cell (boolean) if true add the current left (or right for RTL) padding to each Write (default false).
17218 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
17219 * @public
17220 */
17221 public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
17222 $gvars = $this->getGraphicVars();
17223 // store current values
17224 $prev_cell_margin = $this->cell_margin;
17225 $prev_cell_padding = $this->cell_padding;
17226 $prevPage = $this->page;
17227 $prevlMargin = $this->lMargin;
17228 $prevrMargin = $this->rMargin;
17229 $curfontname = $this->FontFamily;
17230 $curfontstyle = $this->FontStyle;
17231 $curfontsize = $this->FontSizePt;
17232 $curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
17233 $curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
17234 $curfontstretcing = $this->font_stretching;
17235 $curfonttracking = $this->font_spacing;
17236 $this->newline = true;
17237 $newline = true;
17238 $startlinepage = $this->page;
17239 $minstartliney = $this->y;
17240 $maxbottomliney = 0;
17241 $startlinex = $this->x;
17242 $startliney = $this->y;
17243 $yshift = 0;
17244 $loop = 0;
17245 $curpos = 0;
17246 $this_method_vars = array();
17247 $undo = false;
17248 $fontaligned = false;
17249 $reverse_dir = false; // true when the text direction is reversed
17250 $this->premode = false;
17251 if ($this->inxobj) {
17252 // we are inside an XObject template
17253 $pask = count($this->xobjects[$this->xobjid]['annotations']);
17254 } elseif (isset($this->PageAnnots[$this->page])) {
17255 $pask = count($this->PageAnnots[$this->page]);
17256 } else {
17257 $pask = 0;
17258 }
17259 if ($this->inxobj) {
17260 // we are inside an XObject template
17261 $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17262 } elseif (!$this->InFooter) {
17263 if (isset($this->footerlen[$this->page])) {
17264 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17265 } else {
17266 $this->footerpos[$this->page] = $this->pagelen[$this->page];
17267 }
17268 $startlinepos = $this->footerpos[$this->page];
17269 } else {
17270 // we are inside the footer
17271 $startlinepos = $this->pagelen[$this->page];
17272 }
17273 $lalign = $align;
17274 $plalign = $align;
17275 if ($this->rtl) {
17276 $w = $this->x - $this->lMargin;
17277 } else {
17278 $w = $this->w - $this->rMargin - $this->x;
17279 }
17280 $w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
17281 if ($cell) {
17282 if ($this->rtl) {
17283 $this->x -= $this->cell_padding['R'];
17284 $this->lMargin += $this->cell_padding['R'];
17285 } else {
17286 $this->x += $this->cell_padding['L'];
17287 $this->rMargin += $this->cell_padding['L'];
17288 }
17289 }
17290 if ($this->customlistindent >= 0) {
17291 $this->listindent = $this->customlistindent;
17292 } else {
17293 $this->listindent = $this->GetStringWidth('000000');
17294 }
17295 $this->listindentlevel = 0;
17296 // save previous states
17297 $prev_cell_height_ratio = $this->cell_height_ratio;
17298 $prev_listnum = $this->listnum;
17299 $prev_listordered = $this->listordered;
17300 $prev_listcount = $this->listcount;
17301 $prev_lispacer = $this->lispacer;
17302 $this->listnum = 0;
17303 $this->listordered = array();
17304 $this->listcount = array();
17305 $this->lispacer = '';
17306 if ((TCPDF_STATIC::empty_string($this->lasth)) OR ($reseth)) {
17307 // reset row height
17308 $this->resetLastH();
17309 }
17310 $dom = $this->getHtmlDomArray($html);
17311 $maxel = count($dom);
17312 $key = 0;
17313 while ($key < $maxel) {
17314 if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND $dom[$key]['hide']) {
17315 // store the node key
17316 $hidden_node_key = $key;
17317 if ($dom[$key]['self']) {
17318 // skip just this self-closing tag
17319 ++$key;
17320 } else {
17321 // skip this and all children tags
17322 while (($key < $maxel) AND (!$dom[$key]['tag'] OR $dom[$key]['opening'] OR ($dom[$key]['parent'] != $hidden_node_key))) {
17323 // skip hidden objects
17324 ++$key;
17325 }
17326 ++$key;
17327 }
17328 }
17329 if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
17330 // check for pagebreak
17331 if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
17332 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17333 $this->checkPageBreak($this->PageBreakTrigger + 1);
17334 $this->htmlvspace = ($this->PageBreakTrigger + 1);
17335 }
17336 if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
17337 OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
17338 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17339 $this->checkPageBreak($this->PageBreakTrigger + 1);
17340 $this->htmlvspace = ($this->PageBreakTrigger + 1);
17341 }
17342 }
17343 if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
17344 if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
17345 $dom[$key]['attribute']['nobr'] = false;
17346 } else {
17347 // store current object
17348 $this->startTransaction();
17349 // save this method vars
17350 $this_method_vars['html'] = $html;
17351 $this_method_vars['ln'] = $ln;
17352 $this_method_vars['fill'] = $fill;
17353 $this_method_vars['reseth'] = $reseth;
17354 $this_method_vars['cell'] = $cell;
17355 $this_method_vars['align'] = $align;
17356 $this_method_vars['gvars'] = $gvars;
17357 $this_method_vars['prevPage'] = $prevPage;
17358 $this_method_vars['prev_cell_margin'] = $prev_cell_margin;
17359 $this_method_vars['prev_cell_padding'] = $prev_cell_padding;
17360 $this_method_vars['prevlMargin'] = $prevlMargin;
17361 $this_method_vars['prevrMargin'] = $prevrMargin;
17362 $this_method_vars['curfontname'] = $curfontname;
17363 $this_method_vars['curfontstyle'] = $curfontstyle;
17364 $this_method_vars['curfontsize'] = $curfontsize;
17365 $this_method_vars['curfontascent'] = $curfontascent;
17366 $this_method_vars['curfontdescent'] = $curfontdescent;
17367 $this_method_vars['curfontstretcing'] = $curfontstretcing;
17368 $this_method_vars['curfonttracking'] = $curfonttracking;
17369 $this_method_vars['minstartliney'] = $minstartliney;
17370 $this_method_vars['maxbottomliney'] = $maxbottomliney;
17371 $this_method_vars['yshift'] = $yshift;
17372 $this_method_vars['startlinepage'] = $startlinepage;
17373 $this_method_vars['startlinepos'] = $startlinepos;
17374 $this_method_vars['startlinex'] = $startlinex;
17375 $this_method_vars['startliney'] = $startliney;
17376 $this_method_vars['newline'] = $newline;
17377 $this_method_vars['loop'] = $loop;
17378 $this_method_vars['curpos'] = $curpos;
17379 $this_method_vars['pask'] = $pask;
17380 $this_method_vars['lalign'] = $lalign;
17381 $this_method_vars['plalign'] = $plalign;
17382 $this_method_vars['w'] = $w;
17383 $this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
17384 $this_method_vars['prev_listnum'] = $prev_listnum;
17385 $this_method_vars['prev_listordered'] = $prev_listordered;
17386 $this_method_vars['prev_listcount'] = $prev_listcount;
17387 $this_method_vars['prev_lispacer'] = $prev_lispacer;
17388 $this_method_vars['fontaligned'] = $fontaligned;
17389 $this_method_vars['key'] = $key;
17390 $this_method_vars['dom'] = $dom;
17391 }
17392 }
17393 // print THEAD block
17394 if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
17395 if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC::empty_string($dom[$dom[$key]['parent']]['thead'])) {
17396 $this->inthead = true;
17397 // print table header (thead)
17398 $this->writeHTML($this->thead, false, false, false, false, '');
17399 // check if we are on a new page or on a new column
17400 if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
17401 // we are on a new page or on a new column and the total object height is less than the available vertical space.
17402 // restore previous object
17403 $this->rollbackTransaction(true);
17404 // restore previous values
17405 foreach ($this_method_vars as $vkey => $vval) {
17406 $$vkey = $vval;
17407 }
17408 // disable table header
17409 $tmp_thead = $this->thead;
17410 $this->thead = '';
17411 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17412 $pre_y = $this->y;
17413 if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
17414 // fix for multicolumn mode
17415 $startliney = $this->y;
17416 }
17417 $this->start_transaction_page = $this->page;
17418 $this->start_transaction_y = $this->y;
17419 // restore table header
17420 $this->thead = $tmp_thead;
17421 // fix table border properties
17422 if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
17423 $tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
17424 } elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
17425 $tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
17426 } else {
17427 $tmp_cellspacing = 0;
17428 }
17429 $dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
17430 $dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
17431 $dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
17432 $xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
17433 $dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
17434 $dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
17435 // print table header (thead)
17436 $this->writeHTML($this->thead, false, false, false, false, '');
17437 }
17438 }
17439 // move $key index forward to skip THEAD block
17440 while ( ($key < $maxel) AND (!(
17441 ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
17442 OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
17443 ++$key;
17444 }
17445 }
17446 if ($dom[$key]['tag'] OR ($key == 0)) {
17447 if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
17448 $dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
17449 }
17450 // vertically align image in line
17451 if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
17452 // get image height
17453 $imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], ($dom[$key]['fontsize'] / $this->k), 'px');
17454 $autolinebreak = false;
17455 if (!empty($dom[$key]['width'])) {
17456 $imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], ($dom[$key]['fontsize'] / $this->k), 'px', false);
17457 if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
17458 AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
17459 OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
17460 // add automatic line break
17461 $autolinebreak = true;
17462 $this->Ln('', $cell);
17463 if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
17464 // go back to evaluate this line break
17465 --$key;
17466 }
17467 }
17468 }
17469 if (!$autolinebreak) {
17470 if ($this->inPageBody()) {
17471 $pre_y = $this->y;
17472 // check for page break
17473 if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
17474 // fix for multicolumn mode
17475 $startliney = $this->y;
17476 }
17477 }
17478 if ($this->page > $startlinepage) {
17479 // fix line splitted over two pages
17480 if (isset($this->footerlen[$startlinepage])) {
17481 $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17482 }
17483 // line to be moved one page forward
17484 $pagebuff = $this->getPageBuffer($startlinepage);
17485 $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17486 $tstart = substr($pagebuff, 0, $startlinepos);
17487 $tend = substr($this->getPageBuffer($startlinepage), $curpos);
17488 // remove line from previous page
17489 $this->setPageBuffer($startlinepage, $tstart.''.$tend);
17490 $pagebuff = $this->getPageBuffer($this->page);
17491 $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17492 $tend = substr($pagebuff, $this->cntmrk[$this->page]);
17493 // add line start to current page
17494 $yshift = ($minstartliney - $this->y);
17495 if ($fontaligned) {
17496 $yshift += ($curfontsize / $this->k);
17497 }
17498 $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17499 $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17500 // shift the annotations and links
17501 if (isset($this->PageAnnots[$this->page])) {
17502 $next_pask = count($this->PageAnnots[$this->page]);
17503 } else {
17504 $next_pask = 0;
17505 }
17506 if (isset($this->PageAnnots[$startlinepage])) {
17507 foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17508 if ($pak >= $pask) {
17509 $this->PageAnnots[$this->page][] = $pac;
17510 unset($this->PageAnnots[$startlinepage][$pak]);
17511 $npak = count($this->PageAnnots[$this->page]) - 1;
17512 $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17513 }
17514 }
17515 }
17516 $pask = $next_pask;
17517 $startlinepos = $this->cntmrk[$this->page];
17518 $startlinepage = $this->page;
17519 $startliney = $this->y;
17520 $this->newline = false;
17521 }
17522 $this->y += ($this->getCellHeight($curfontsize / $this->k) - ($curfontdescent * $this->cell_height_ratio) - $imgh);
17523 $minstartliney = min($this->y, $minstartliney);
17524 $maxbottomliney = ($startliney + $this->getCellHeight($curfontsize / $this->k));
17525 }
17526 } elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
17527 // account for different font size
17528 $pfontname = $curfontname;
17529 $pfontstyle = $curfontstyle;
17530 $pfontsize = $curfontsize;
17531 $fontname = (isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname);
17532 $fontstyle = (isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle);
17533 $fontsize = (isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize);
17534 $fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
17535 $fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
17536 if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
17537 OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17538 OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
17539 if (($key < ($maxel - 1)) AND (
17540 ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
17541 OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17542 OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize)
17543 AND ($fontsize >= 0) AND ($curfontsize >= 0)
17544 AND (($fontsize != $curfontsize) OR ($fontstyle != $curfontstyle) OR ($fontname != $curfontname)))
17545 )) {
17546 if ($this->page > $startlinepage) {
17547 // fix lines splitted over two pages
17548 if (isset($this->footerlen[$startlinepage])) {
17549 $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17550 }
17551 // line to be moved one page forward
17552 $pagebuff = $this->getPageBuffer($startlinepage);
17553 $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17554 $tstart = substr($pagebuff, 0, $startlinepos);
17555 $tend = substr($this->getPageBuffer($startlinepage), $curpos);
17556 // remove line start from previous page
17557 $this->setPageBuffer($startlinepage, $tstart.''.$tend);
17558 $pagebuff = $this->getPageBuffer($this->page);
17559 $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17560 $tend = substr($pagebuff, $this->cntmrk[$this->page]);
17561 // add line start to current page
17562 $yshift = ($minstartliney - $this->y);
17563 $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17564 $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17565 // shift the annotations and links
17566 if (isset($this->PageAnnots[$this->page])) {
17567 $next_pask = count($this->PageAnnots[$this->page]);
17568 } else {
17569 $next_pask = 0;
17570 }
17571 if (isset($this->PageAnnots[$startlinepage])) {
17572 foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17573 if ($pak >= $pask) {
17574 $this->PageAnnots[$this->page][] = $pac;
17575 unset($this->PageAnnots[$startlinepage][$pak]);
17576 $npak = count($this->PageAnnots[$this->page]) - 1;
17577 $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17578 }
17579 }
17580 }
17581 $pask = $next_pask;
17582 $startlinepos = $this->cntmrk[$this->page];
17583 $startlinepage = $this->page;
17584 $startliney = $this->y;
17585 }
17586 if (!isset($dom[$key]['line-height'])) {
17587 $dom[$key]['line-height'] = $this->cell_height_ratio;
17588 }
17589 if (!$dom[$key]['block']) {
17590 if (!(isset($dom[($key + 1)]) AND $dom[($key + 1)]['tag'] AND (!$dom[($key + 1)]['opening']) AND ($dom[($key + 1)]['value'] != 'li') AND $dom[$key]['tag'] AND (!$dom[$key]['opening']))) {
17591 $this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
17592 }
17593 if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
17594 $current_line_align_data = array($key, $minstartliney, $maxbottomliney);
17595 if (isset($line_align_data) AND (($line_align_data[0] == ($key - 1)) OR (($line_align_data[0] == ($key - 2)) AND (isset($dom[($key - 1)])) AND (preg_match('/^([\s]+)$/', $dom[($key - 1)]['value']) > 0)))) {
17596 $minstartliney = min($this->y, $line_align_data[1]);
17597 $maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $line_align_data[2]);
17598 } else {
17599 $minstartliney = min($this->y, $minstartliney);
17600 $maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $maxbottomliney);
17601 }
17602 $line_align_data = $current_line_align_data;
17603 }
17604 }
17605 $this->cell_height_ratio = $dom[$key]['line-height'];
17606 $fontaligned = true;
17607 }
17608 $this->SetFont($fontname, $fontstyle, $fontsize);
17609 // reset row height
17610 $this->resetLastH();
17611 $curfontname = $fontname;
17612 $curfontstyle = $fontstyle;
17613 $curfontsize = $fontsize;
17614 $curfontascent = $fontascent;
17615 $curfontdescent = $fontdescent;
17616 }
17617 }
17618 // set text rendering mode
17619 $textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
17620 $textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
17621 $textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
17622 $this->setTextRenderingMode($textstroke, $textfill, $textclip);
17623 if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
17624 $this->setFontStretching($dom[$key]['font-stretch']);
17625 }
17626 if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
17627 $this->setFontSpacing($dom[$key]['letter-spacing']);
17628 }
17629 if (($plalign == 'J') AND $dom[$key]['block']) {
17630 $plalign = '';
17631 }
17632 // get current position on page buffer
17633 $curpos = $this->pagelen[$startlinepage];
17634 if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
17635 $this->SetFillColorArray($dom[$key]['bgcolor']);
17636 $wfill = true;
17637 } else {
17638 $wfill = $fill | false;
17639 }
17640 if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
17641 $this->SetTextColorArray($dom[$key]['fgcolor']);
17642 }
17643 if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
17644 $this->SetDrawColorArray($dom[$key]['strokecolor']);
17645 }
17646 if (isset($dom[$key]['align'])) {
17647 $lalign = $dom[$key]['align'];
17648 }
17649 if (TCPDF_STATIC::empty_string($lalign)) {
17650 $lalign = $align;
17651 }
17652 }
17653 // align lines
17654 if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
17655 $newline = true;
17656 $fontaligned = false;
17657 // we are at the beginning of a new line
17658 if (isset($startlinex)) {
17659 $yshift = ($minstartliney - $startliney);
17660 if (($yshift > 0) OR ($this->page > $startlinepage)) {
17661 $yshift = 0;
17662 }
17663 $t_x = 0;
17664 // the last line must be shifted to be aligned as requested
17665 $linew = abs($this->endlinex - $startlinex);
17666 if ($this->inxobj) {
17667 // we are inside an XObject template
17668 $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
17669 if (isset($opentagpos)) {
17670 $midpos = $opentagpos;
17671 } else {
17672 $midpos = 0;
17673 }
17674 if ($midpos > 0) {
17675 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
17676 $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
17677 } else {
17678 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
17679 $pend = '';
17680 }
17681 } else {
17682 $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
17683 if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17684 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17685 $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
17686 } elseif (isset($opentagpos)) {
17687 $midpos = $opentagpos;
17688 } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17689 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17690 $midpos = $this->footerpos[$startlinepage];
17691 } else {
17692 $midpos = 0;
17693 }
17694 if ($midpos > 0) {
17695 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
17696 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
17697 } else {
17698 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
17699 $pend = '';
17700 }
17701 }
17702 if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
17703 // calculate shifting amount
17704 $tw = $w;
17705 if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
17706 $tw += $this->cell_padding['R'];
17707 }
17708 if ($this->lMargin != $prevlMargin) {
17709 $tw += ($prevlMargin - $this->lMargin);
17710 }
17711 if ($this->rMargin != $prevrMargin) {
17712 $tw += ($prevrMargin - $this->rMargin);
17713 }
17714 $one_space_width = $this->GetStringWidth(chr(32));
17715 $no = 0; // number of spaces on a line contained on a single block
17716 if ($this->isRTLTextDir()) { // RTL
17717 // remove left space if exist
17718 $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
17719 if ($pos1 > 0) {
17720 $pos1 = intval($pos1);
17721 if ($this->isUnicodeFont()) {
17722 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
17723 $spacelen = 2;
17724 } else {
17725 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
17726 $spacelen = 1;
17727 }
17728 if ($pos1 == $pos2) {
17729 $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
17730 if (substr($pmid, $pos1, 4) == '[()]') {
17731 $linew -= $one_space_width;
17732 } elseif ($pos1 == strpos($pmid, '[(')) {
17733 $no = 1;
17734 }
17735 }
17736 }
17737 } else { // LTR
17738 // remove right space if exist
17739 $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
17740 if ($pos1 > 0) {
17741 $pos1 = intval($pos1);
17742 if ($this->isUnicodeFont()) {
17743 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
17744 $spacelen = 2;
17745 } else {
17746 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
17747 $spacelen = 1;
17748 }
17749 if ($pos1 == $pos2) {
17750 $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
17751 $linew -= $one_space_width;
17752 }
17753 }
17754 }
17755 $mdiff = ($tw - $linew);
17756 if ($plalign == 'C') {
17757 if ($this->rtl) {
17758 $t_x = -($mdiff / 2);
17759 } else {
17760 $t_x = ($mdiff / 2);
17761 }
17762 } elseif ($plalign == 'R') {
17763 // right alignment on LTR document
17764 $t_x = $mdiff;
17765 } elseif ($plalign == 'L') {
17766 // left alignment on RTL document
17767 $t_x = -$mdiff;
17768 } elseif (($plalign == 'J') AND ($plalign == $lalign)) {
17769 // Justification
17770 if ($this->isRTLTextDir()) {
17771 // align text on the left
17772 $t_x = -$mdiff;
17773 }
17774 $ns = 0; // number of spaces
17775 $pmidtemp = $pmid;
17776 // escape special characters
17777 $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
17778 $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
17779 // search spaces
17780 if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
17781 $spacestr = $this->getSpaceString();
17782 $maxkk = count($lnstring[1]) - 1;
17783 for ($kk=0; $kk <= $maxkk; ++$kk) {
17784 // restore special characters
17785 $lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
17786 $lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
17787 // store number of spaces on the strings
17788 $lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
17789 // count total spaces on line
17790 $ns += $lnstring[2][$kk];
17791 $lnstring[3][$kk] = $ns;
17792 }
17793 if ($ns == 0) {
17794 $ns = 1;
17795 }
17796 // calculate additional space to add to each existing space
17797 $spacewidth = ($mdiff / ($ns - $no)) * $this->k;
17798 if ($this->FontSize <= 0) {
17799 $this->FontSize = 1;
17800 }
17801 $spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
17802 if ($this->font_spacing != 0) {
17803 // fixed spacing mode
17804 $osw = -1000 * $this->font_spacing / $this->FontSize;
17805 $spacewidthu += $osw;
17806 }
17807 $nsmax = $ns;
17808 $ns = 0;
17809 reset($lnstring);
17810 $offset = 0;
17811 $strcount = 0;
17812 $prev_epsposbeg = 0;
17813 $textpos = 0;
17814 if ($this->isRTLTextDir()) {
17815 $textpos = $this->wPt;
17816 }
17817 while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
17818 // check if we are inside a string section '[( ... )]'
17819 $stroffset = strpos($pmid, '[(', $offset);
17820 if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
17821 // set offset to the end of string section
17822 $offset = strpos($pmid, ')]', $stroffset);
17823 while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
17824 $offset = strpos($pmid, ')]', ($offset + 1));
17825 }
17826 if ($offset === false) {
17827 $this->Error('HTML Justification: malformed PDF code.');
17828 }
17829 continue;
17830 }
17831 if ($this->isRTLTextDir()) {
17832 $spacew = ($spacewidth * ($nsmax - $ns));
17833 } else {
17834 $spacew = ($spacewidth * $ns);
17835 }
17836 $offset = $strpiece[2][1] + strlen($strpiece[2][0]);
17837 $epsposend = strpos($pmid, $this->epsmarker.'Q', $offset);
17838 if ($epsposend !== null) {
17839 $epsposend += strlen($this->epsmarker.'Q');
17840 $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
17841 if ($epsposbeg === null) {
17842 $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
17843 $prev_epsposbeg = $epsposbeg;
17844 }
17845 if (($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend)) {
17846 // shift EPS images
17847 $trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
17848 $pmid_b = substr($pmid, 0, $epsposbeg);
17849 $pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
17850 $pmid_e = substr($pmid, $epsposend);
17851 $pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
17852 $offset = $epsposend;
17853 continue;
17854 }
17855 }
17856 $currentxpos = 0;
17857 // shift blocks of code
17858 switch ($strpiece[2][0]) {
17859 case 'Td':
17860 case 'cm':
17861 case 'm':
17862 case 'l': {
17863 // get current X position
17864 preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
17865 if (!isset($xmatches[1])) {
17866 break;
17867 }
17868 $currentxpos = $xmatches[1];
17869 $textpos = $currentxpos;
17870 if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
17871 $ns = $lnstring[3][$strcount];
17872 if ($this->isRTLTextDir()) {
17873 $spacew = ($spacewidth * ($nsmax - $ns));
17874 }
17875 ++$strcount;
17876 }
17877 // justify block
17878 if (preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $pmatch) == 1) {
17879 $newpmid = sprintf('%F',(floatval($pmatch[1]) + $spacew)).' '.$pmatch[2].' x*#!#*x'.$pmatch[3].$pmatch[4];
17880 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17881 unset($pmatch, $newpmid);
17882 }
17883 break;
17884 }
17885 case 're': {
17886 // justify block
17887 if (!TCPDF_STATIC::empty_string($this->lispacer)) {
17888 $this->lispacer = '';
17889 continue;
17890 }
17891 preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
17892 if (!isset($xmatches[1])) {
17893 break;
17894 }
17895 $currentxpos = $xmatches[1];
17896 $x_diff = 0;
17897 $w_diff = 0;
17898 if ($this->isRTLTextDir()) { // RTL
17899 if ($currentxpos < $textpos) {
17900 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
17901 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
17902 } else {
17903 if ($strcount > 0) {
17904 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
17905 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17906 }
17907 }
17908 } else { // LTR
17909 if ($currentxpos > $textpos) {
17910 if ($strcount > 0) {
17911 $x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
17912 }
17913 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
17914 } else {
17915 if ($strcount > 1) {
17916 $x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
17917 }
17918 if ($strcount > 0) {
17919 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17920 }
17921 }
17922 }
17923 if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $pmatch) == 1) {
17924 $newx = sprintf('%F',(floatval($pmatch[1]) + $x_diff));
17925 $neww = sprintf('%F',(floatval($pmatch[3]) + $w_diff));
17926 $newpmid = $newx.' '.$pmatch[2].' '.$neww.' '.$pmatch[4].' x*#!#*x'.$pmatch[5].$pmatch[6];
17927 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17928 unset($pmatch, $newpmid, $newx, $neww);
17929 }
17930 break;
17931 }
17932 case 'c': {
17933 // get current X position
17934 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);
17935 if (!isset($xmatches[1])) {
17936 break;
17937 }
17938 $currentxpos = $xmatches[1];
17939 // justify block
17940 if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$xmatches[4].')[\s]('.$xmatches[5].')[\s]('.$strpiece[1][0].')[\s](c)([\s]*)/x', $pmid, $pmatch) == 1) {
17941 $newx1 = sprintf('%F',(floatval($pmatch[1]) + $spacew));
17942 $newx2 = sprintf('%F',(floatval($pmatch[3]) + $spacew));
17943 $newx3 = sprintf('%F',(floatval($pmatch[5]) + $spacew));
17944 $newpmid = $newx1.' '.$pmatch[2].' '.$newx2.' '.$pmatch[4].' '.$newx3.' '.$pmatch[6].' x*#!#*x'.$pmatch[7].$pmatch[8];
17945 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17946 unset($pmatch, $newpmid, $newx1, $newx2, $newx3);
17947 }
17948 break;
17949 }
17950 }
17951 // shift the annotations and links
17952 $cxpos = ($currentxpos / $this->k);
17953 $lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
17954 if ($this->inxobj) {
17955 // we are inside an XObject template
17956 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
17957 if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17958 if ($cxpos > $lmpos) {
17959 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
17960 $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17961 } else {
17962 $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17963 }
17964 break;
17965 }
17966 }
17967 } elseif (isset($this->PageAnnots[$this->page])) {
17968 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
17969 if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17970 if ($cxpos > $lmpos) {
17971 $this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
17972 $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17973 } else {
17974 $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17975 }
17976 break;
17977 }
17978 }
17979 }
17980 } // end of while
17981 // remove markers
17982 $pmid = str_replace('x*#!#*x', '', $pmid);
17983 if ($this->isUnicodeFont()) {
17984 // multibyte characters
17985 $spacew = $spacewidthu;
17986 if ($this->font_stretching != 100) {
17987 // word spacing is affected by stretching
17988 $spacew /= ($this->font_stretching / 100);
17989 }
17990 // escape special characters
17991 $pos = 0;
17992 $pmid = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmid);
17993 $pmid = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmid);
17994 if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmid, $pamatch) > 0) {
17995 foreach($pamatch[0] as $pk => $pmatch) {
17996 $replace = $pamatch[1][$pk];
17997 $replace = str_replace('#!#OP#!#', '(', $replace);
17998 $replace = str_replace('#!#CP#!#', ')', $replace);
17999 $newpmid = '[('.str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacew).' (', $replace).')]';
18000 $pos = strpos($pmid, $pmatch, $pos);
18001 if ($pos !== FALSE) {
18002 $pmid = substr_replace($pmid, $newpmid, $pos, strlen($pmatch));
18003 }
18004 ++$pos;
18005 }
18006 unset($pamatch);
18007 }
18008 if ($this->inxobj) {
18009 // we are inside an XObject template
18010 $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
18011 } else {
18012 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
18013 }
18014 $endlinepos = strlen($pstart."\n".$pmid."\n");
18015 } else {
18016 // non-unicode (single-byte characters)
18017 if ($this->font_stretching != 100) {
18018 // word spacing (Tw) is affected by stretching
18019 $spacewidth /= ($this->font_stretching / 100);
18020 }
18021 $rs = sprintf('%F Tw', $spacewidth);
18022 $pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
18023 if ($this->inxobj) {
18024 // we are inside an XObject template
18025 $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
18026 } else {
18027 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
18028 }
18029 $endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
18030 }
18031 }
18032 } // end of J
18033 } // end if $startlinex
18034 if (($t_x != 0) OR ($yshift < 0)) {
18035 // shift the line
18036 $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18037 $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18038 $endlinepos = strlen($pstart);
18039 if ($this->inxobj) {
18040 // we are inside an XObject template
18041 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18042 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18043 if ($pak >= $pask) {
18044 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18045 $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18046 }
18047 }
18048 } else {
18049 $this->setPageBuffer($startlinepage, $pstart.$pend);
18050 // shift the annotations and links
18051 if (isset($this->PageAnnots[$this->page])) {
18052 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18053 if ($pak >= $pask) {
18054 $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18055 $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18056 }
18057 }
18058 }
18059 }
18060 $this->y -= $yshift;
18061 }
18062 }
18063 $pbrk = $this->checkPageBreak($this->lasth);
18064 $this->newline = false;
18065 $startlinex = $this->x;
18066 $startliney = $this->y;
18067 if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
18068 $startliney -= ((0.3 * $this->FontSizePt) / $this->k);
18069 } elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
18070 $startliney -= (($this->FontSizePt / 0.7) / $this->k);
18071 } else {
18072 $minstartliney = $startliney;
18073 $maxbottomliney = ($this->y + $this->getCellHeight($fontsize / $this->k));
18074 }
18075 $startlinepage = $this->page;
18076 if (isset($endlinepos) AND (!$pbrk)) {
18077 $startlinepos = $endlinepos;
18078 } else {
18079 if ($this->inxobj) {
18080 // we are inside an XObject template
18081 $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
18082 } elseif (!$this->InFooter) {
18083 if (isset($this->footerlen[$this->page])) {
18084 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18085 } else {
18086 $this->footerpos[$this->page] = $this->pagelen[$this->page];
18087 }
18088 $startlinepos = $this->footerpos[$this->page];
18089 } else {
18090 $startlinepos = $this->pagelen[$this->page];
18091 }
18092 }
18093 unset($endlinepos);
18094 $plalign = $lalign;
18095 if (isset($this->PageAnnots[$this->page])) {
18096 $pask = count($this->PageAnnots[$this->page]);
18097 } else {
18098 $pask = 0;
18099 }
18100 if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
18101 AND (isset($this->emptypagemrk[$this->page]))
18102 AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
18103 $this->SetFont($fontname, $fontstyle, $fontsize);
18104 if ($wfill) {
18105 $this->SetFillColorArray($this->bgcolor);
18106 }
18107 }
18108 } // end newline
18109 if (isset($opentagpos)) {
18110 unset($opentagpos);
18111 }
18112 if ($dom[$key]['tag']) {
18113 if ($dom[$key]['opening']) {
18114 // get text indentation (if any)
18115 if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
18116 $this->textindent = $dom[$key]['text-indent'];
18117 $this->newline = true;
18118 }
18119 // table
18120 if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
18121 // available page width
18122 if ($this->rtl) {
18123 $wtmp = $this->x - $this->lMargin;
18124 } else {
18125 $wtmp = $this->w - $this->rMargin - $this->x;
18126 }
18127 // get cell spacing
18128 if (isset($dom[$key]['attribute']['cellspacing'])) {
18129 $clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
18130 $cellspacing = array('H' => $clsp, 'V' => $clsp);
18131 } elseif (isset($dom[$key]['border-spacing'])) {
18132 $cellspacing = $dom[$key]['border-spacing'];
18133 } else {
18134 $cellspacing = array('H' => 0, 'V' => 0);
18135 }
18136 // table width
18137 if (isset($dom[$key]['width'])) {
18138 $table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
18139 } else {
18140 $table_width = $wtmp;
18141 }
18142 $table_width -= (2 * $cellspacing['H']);
18143 if (!$this->inthead) {
18144 $this->y += $cellspacing['V'];
18145 }
18146 if ($this->rtl) {
18147 $cellspacingx = -$cellspacing['H'];
18148 } else {
18149 $cellspacingx = $cellspacing['H'];
18150 }
18151 // total table width without cellspaces
18152 $table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
18153 // minimum column width
18154 $table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
18155 // array of custom column widths
18156 $table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
18157 }
18158 // table row
18159 if ($dom[$key]['value'] == 'tr') {
18160 // reset column counter
18161 $colid = 0;
18162 }
18163 // table cell
18164 if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18165 $trid = $dom[$key]['parent'];
18166 $table_el = $dom[$trid]['parent'];
18167 if (!isset($dom[$table_el]['cols'])) {
18168 $dom[$table_el]['cols'] = $dom[$trid]['cols'];
18169 }
18170 // store border info
18171 $tdborder = 0;
18172 if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
18173 $tdborder = $dom[$key]['border'];
18174 }
18175 $colspan = intval($dom[$key]['attribute']['colspan']);
18176 if ($colspan <= 0) {
18177 $colspan = 1;
18178 }
18179 $old_cell_padding = $this->cell_padding;
18180 if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
18181 $crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
18182 $current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
18183 } elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
18184 $current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
18185 } else {
18186 $current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
18187 }
18188 $this->cell_padding = $current_cell_padding;
18189 if (isset($dom[$key]['height'])) {
18190 // minimum cell height
18191 $cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
18192 } else {
18193 $cellh = 0;
18194 }
18195 if (isset($dom[$key]['content'])) {
18196 $cell_content = stripslashes($dom[$key]['content']);
18197 } else {
18198 $cell_content = '&nbsp;';
18199 }
18200 $tagtype = $dom[$key]['value'];
18201 $parentid = $key;
18202 while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
18203 // move $key index forward
18204 ++$key;
18205 }
18206 if (!isset($dom[$trid]['startpage'])) {
18207 $dom[$trid]['startpage'] = $this->page;
18208 } else {
18209 $this->setPage($dom[$trid]['startpage']);
18210 }
18211 if (!isset($dom[$trid]['startcolumn'])) {
18212 $dom[$trid]['startcolumn'] = $this->current_column;
18213 } elseif ($this->current_column != $dom[$trid]['startcolumn']) {
18214 $tmpx = $this->x;
18215 $this->selectColumn($dom[$trid]['startcolumn']);
18216 $this->x = $tmpx;
18217 }
18218 if (!isset($dom[$trid]['starty'])) {
18219 $dom[$trid]['starty'] = $this->y;
18220 } else {
18221 $this->y = $dom[$trid]['starty'];
18222 }
18223 if (!isset($dom[$trid]['startx'])) {
18224 $dom[$trid]['startx'] = $this->x;
18225 $this->x += $cellspacingx;
18226 } else {
18227 $this->x += ($cellspacingx / 2);
18228 }
18229 if (isset($dom[$parentid]['attribute']['rowspan'])) {
18230 $rowspan = intval($dom[$parentid]['attribute']['rowspan']);
18231 } else {
18232 $rowspan = 1;
18233 }
18234 // skip row-spanned cells started on the previous rows
18235 if (isset($dom[$table_el]['rowspans'])) {
18236 $rsk = 0;
18237 $rskmax = count($dom[$table_el]['rowspans']);
18238 while ($rsk < $rskmax) {
18239 $trwsp = $dom[$table_el]['rowspans'][$rsk];
18240 $rsstartx = $trwsp['startx'];
18241 $rsendx = $trwsp['endx'];
18242 // account for margin changes
18243 if ($trwsp['startpage'] < $this->page) {
18244 if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
18245 $dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
18246 $rsstartx -= $dl;
18247 $rsendx -= $dl;
18248 } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
18249 $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
18250 $rsstartx += $dl;
18251 $rsendx += $dl;
18252 }
18253 }
18254 if (($trwsp['rowspan'] > 0)
18255 AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
18256 AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
18257 AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
18258 // set the starting X position of the current cell
18259 $this->x = $rsendx + $cellspacingx;
18260 // increment column indicator
18261 $colid += $trwsp['colspan'];
18262 if (($trwsp['rowspan'] == 1)
18263 AND (isset($dom[$trid]['endy']))
18264 AND (isset($dom[$trid]['endpage']))
18265 AND (isset($dom[$trid]['endcolumn']))
18266 AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
18267 AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18268 // set ending Y position for row
18269 $dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18270 $dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
18271 }
18272 $rsk = 0;
18273 } else {
18274 ++$rsk;
18275 }
18276 }
18277 }
18278 if (isset($dom[$parentid]['width'])) {
18279 // user specified width
18280 $cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
18281 $tmpcw = ($cellw / $colspan);
18282 for ($i = 0; $i < $colspan; ++$i) {
18283 $table_colwidths[($colid + $i)] = $tmpcw;
18284 }
18285 } else {
18286 // inherit column width
18287 $cellw = 0;
18288 for ($i = 0; $i < $colspan; ++$i) {
18289 $cellw += $table_colwidths[($colid + $i)];
18290 }
18291 }
18292 $cellw += (($colspan - 1) * $cellspacing['H']);
18293 // increment column indicator
18294 $colid += $colspan;
18295 // add rowspan information to table element
18296 if ($rowspan > 1) {
18297 $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));
18298 }
18299 $cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
18300 if ($rowspan > 1) {
18301 $dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
18302 }
18303 // push background colors
18304 if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
18305 $dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
18306 }
18307 // store border info
18308 if (isset($tdborder) AND !empty($tdborder)) {
18309 $dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
18310 }
18311 $prevLastH = $this->lasth;
18312 // store some info for multicolumn mode
18313 if ($this->rtl) {
18314 $this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
18315 } else {
18316 $this->colxshift['x'] = $this->x - $this->lMargin;
18317 }
18318 $this->colxshift['s'] = $cellspacing;
18319 $this->colxshift['p'] = $current_cell_padding;
18320 // ****** write the cell content ******
18321 $this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
18322 // restore some values
18323 $this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
18324 $this->lasth = $prevLastH;
18325 $this->cell_padding = $old_cell_padding;
18326 $dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
18327 // update the end of row position
18328 if ($rowspan <= 1) {
18329 if (isset($dom[$trid]['endy'])) {
18330 if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
18331 $dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
18332 } elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
18333 $dom[$trid]['endy'] = $this->y;
18334 }
18335 } else {
18336 $dom[$trid]['endy'] = $this->y;
18337 }
18338 if (isset($dom[$trid]['endpage'])) {
18339 $dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
18340 } else {
18341 $dom[$trid]['endpage'] = $this->page;
18342 }
18343 if (isset($dom[$trid]['endcolumn'])) {
18344 $dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
18345 } else {
18346 $dom[$trid]['endcolumn'] = $this->current_column;
18347 }
18348 } else {
18349 // account for row-spanned cells
18350 $dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
18351 $dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
18352 $dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
18353 $dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
18354 }
18355 if (isset($dom[$table_el]['rowspans'])) {
18356 // update endy and endpage on rowspanned cells
18357 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
18358 if ($trwsp['rowspan'] > 0) {
18359 if (isset($dom[$trid]['endpage'])) {
18360 if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18361 $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18362 } elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
18363 $dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
18364 $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
18365 $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
18366 } else {
18367 $dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
18368 }
18369 }
18370 }
18371 }
18372 }
18373 $this->x += ($cellspacingx / 2);
18374 } else {
18375 // opening tag (or self-closing tag)
18376 if (!isset($opentagpos)) {
18377 if ($this->inxobj) {
18378 // we are inside an XObject template
18379 $opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
18380 } elseif (!$this->InFooter) {
18381 if (isset($this->footerlen[$this->page])) {
18382 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18383 } else {
18384 $this->footerpos[$this->page] = $this->pagelen[$this->page];
18385 }
18386 $opentagpos = $this->footerpos[$this->page];
18387 }
18388 }
18389 $dom = $this->openHTMLTagHandler($dom, $key, $cell);
18390 }
18391 } else { // closing tag
18392 $prev_numpages = $this->numpages;
18393 $old_bordermrk = $this->bordermrk[$this->page];
18394 $dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
18395 if ($this->bordermrk[$this->page] > $old_bordermrk) {
18396 $startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
18397 }
18398 if ($prev_numpages > $this->numpages) {
18399 $startlinepage = $this->page;
18400 }
18401 }
18402 } elseif (strlen($dom[$key]['value']) > 0) {
18403 // print list-item
18404 if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) {
18405 $this->SetFont($pfontname, $pfontstyle, $pfontsize);
18406 $this->resetLastH();
18407 $minstartliney = $this->y;
18408 $maxbottomliney = ($startliney + $this->getCellHeight($this->FontSize));
18409 if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
18410 $this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
18411 }
18412 $this->SetFont($curfontname, $curfontstyle, $curfontsize);
18413 $this->resetLastH();
18414 if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
18415 $pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
18416 $pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
18417 $this->y += ($this->getCellHeight(($pfontsize - $curfontsize) / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
18418 $minstartliney = min($this->y, $minstartliney);
18419 $maxbottomliney = max(($this->y + $this->getCellHeight($pfontsize / $this->k)), $maxbottomliney);
18420 }
18421 }
18422 // text
18423 $this->htmlvspace = 0;
18424 if ((!$this->premode) AND $this->isRTLTextDir()) {
18425 // reverse spaces order
18426 $lsp = ''; // left spaces
18427 $rsp = ''; // right spaces
18428 if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18429 $lsp = $matches[1];
18430 }
18431 if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18432 $rsp = $matches[1];
18433 }
18434 $dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
18435 }
18436 if ($newline) {
18437 if (!$this->premode) {
18438 $prelen = strlen($dom[$key]['value']);
18439 if ($this->isRTLTextDir()) {
18440 // right trim except non-breaking space
18441 $dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
18442 } else {
18443 // left trim except non-breaking space
18444 $dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
18445 }
18446 $postlen = strlen($dom[$key]['value']);
18447 if (($postlen == 0) AND ($prelen > 0)) {
18448 $dom[$key]['trimmed_space'] = true;
18449 }
18450 }
18451 $newline = false;
18452 $firstblock = true;
18453 } else {
18454 $firstblock = false;
18455 // replace empty multiple spaces string with a single space
18456 $dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
18457 }
18458 $strrest = '';
18459 if ($this->rtl) {
18460 $this->x -= $this->textindent;
18461 } else {
18462 $this->x += $this->textindent;
18463 }
18464 if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
18465 $strlinelen = $this->GetStringWidth($dom[$key]['value']);
18466 if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
18467 // HTML <a> Link
18468 $hrefcolor = '';
18469 if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
18470 $hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
18471 }
18472 $hrefstyle = -1;
18473 if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
18474 $hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
18475 }
18476 $strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
18477 } else {
18478 $wadj = 0; // space to leave for block continuity
18479 if ($this->rtl) {
18480 $cwa = ($this->x - $this->lMargin);
18481 } else {
18482 $cwa = ($this->w - $this->rMargin - $this->x);
18483 }
18484 if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
18485 // check the next text blocks for continuity
18486 $nkey = ($key + 1);
18487 $write_block = true;
18488 $same_textdir = true;
18489 $tmp_fontname = $this->FontFamily;
18490 $tmp_fontstyle = $this->FontStyle;
18491 $tmp_fontsize = $this->FontSizePt;
18492 while ($write_block AND isset($dom[$nkey])) {
18493 if ($dom[$nkey]['tag']) {
18494 if ($dom[$nkey]['block']) {
18495 // end of block
18496 $write_block = false;
18497 }
18498 $tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
18499 $tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
18500 $tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
18501 $same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
18502 } else {
18503 $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'+/', $this->re_space['m'], $dom[$nkey]['value']);
18504 if (isset($nextstr[0]) AND $same_textdir) {
18505 $wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
18506 if (isset($nextstr[1])) {
18507 $write_block = false;
18508 }
18509 }
18510 }
18511 ++$nkey;
18512 }
18513 }
18514 if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
18515 $wadj = 0;
18516 $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $dom[$key]['value']);
18517 $numblks = count($nextstr);
18518 if ($numblks > 1) {
18519 // try to split on blank spaces
18520 $wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
18521 } else {
18522 // set the entire block on new line
18523 $wadj = $this->GetStringWidth($nextstr[0]);
18524 }
18525 }
18526 // check for reversed text direction
18527 if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
18528 // LTR text on RTL direction or RTL text on LTR direction
18529 $reverse_dir = true;
18530 $this->rtl = !$this->rtl;
18531 $revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
18532 if ($this->rtl) {
18533 $this->x += $revshift;
18534 } else {
18535 $this->x -= $revshift;
18536 }
18537 $xws = $this->x;
18538 }
18539 // ****** write only until the end of the line and get the rest ******
18540 $strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
18541 // restore default direction
18542 if ($reverse_dir AND ($wadj == 0)) {
18543 $this->x = $xws;
18544 $this->rtl = !$this->rtl;
18545 $reverse_dir = false;
18546 }
18547 }
18548 }
18549 $this->textindent = 0;
18550 if (strlen($strrest) > 0) {
18551 // store the remaining string on the previous $key position
18552 $this->newline = true;
18553 if ($strrest == $dom[$key]['value']) {
18554 // used to avoid infinite loop
18555 ++$loop;
18556 } else {
18557 $loop = 0;
18558 }
18559 $dom[$key]['value'] = $strrest;
18560 if ($cell) {
18561 if ($this->rtl) {
18562 $this->x -= $this->cell_padding['R'];
18563 } else {
18564 $this->x += $this->cell_padding['L'];
18565 }
18566 }
18567 if ($loop < 3) {
18568 --$key;
18569 }
18570 } else {
18571 $loop = 0;
18572 // add the positive font spacing of the last character (if any)
18573 if ($this->font_spacing > 0) {
18574 if ($this->rtl) {
18575 $this->x -= $this->font_spacing;
18576 } else {
18577 $this->x += $this->font_spacing;
18578 }
18579 }
18580 }
18581 }
18582 ++$key;
18583 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')) {
18584 // check if we are on a new page or on a new column
18585 if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
18586 // we are on a new page or on a new column and the total object height is less than the available vertical space.
18587 // restore previous object
18588 $this->rollbackTransaction(true);
18589 // restore previous values
18590 foreach ($this_method_vars as $vkey => $vval) {
18591 $$vkey = $vval;
18592 }
18593 if (!empty($dom[$key]['thead'])) {
18594 $this->inthead = true;
18595 }
18596 // add a page (or trig AcceptPageBreak() for multicolumn mode)
18597 $pre_y = $this->y;
18598 if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
18599 $startliney = $this->y;
18600 }
18601 $undo = true; // avoid infinite loop
18602 } else {
18603 $undo = false;
18604 }
18605 }
18606 } // end for each $key
18607 // align the last line
18608 if (isset($startlinex)) {
18609 $yshift = ($minstartliney - $startliney);
18610 if (($yshift > 0) OR ($this->page > $startlinepage)) {
18611 $yshift = 0;
18612 }
18613 $t_x = 0;
18614 // the last line must be shifted to be aligned as requested
18615 $linew = abs($this->endlinex - $startlinex);
18616 if ($this->inxobj) {
18617 // we are inside an XObject template
18618 $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
18619 if (isset($opentagpos)) {
18620 $midpos = $opentagpos;
18621 } else {
18622 $midpos = 0;
18623 }
18624 if ($midpos > 0) {
18625 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
18626 $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
18627 } else {
18628 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
18629 $pend = '';
18630 }
18631 } else {
18632 $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
18633 if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18634 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18635 $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
18636 } elseif (isset($opentagpos)) {
18637 $midpos = $opentagpos;
18638 } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18639 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18640 $midpos = $this->footerpos[$startlinepage];
18641 } else {
18642 $midpos = 0;
18643 }
18644 if ($midpos > 0) {
18645 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
18646 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
18647 } else {
18648 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
18649 $pend = '';
18650 }
18651 }
18652 if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
18653 // calculate shifting amount
18654 $tw = $w;
18655 if ($this->lMargin != $prevlMargin) {
18656 $tw += ($prevlMargin - $this->lMargin);
18657 }
18658 if ($this->rMargin != $prevrMargin) {
18659 $tw += ($prevrMargin - $this->rMargin);
18660 }
18661 $one_space_width = $this->GetStringWidth(chr(32));
18662 $no = 0; // number of spaces on a line contained on a single block
18663 if ($this->isRTLTextDir()) { // RTL
18664 // remove left space if exist
18665 $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
18666 if ($pos1 > 0) {
18667 $pos1 = intval($pos1);
18668 if ($this->isUnicodeFont()) {
18669 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
18670 $spacelen = 2;
18671 } else {
18672 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
18673 $spacelen = 1;
18674 }
18675 if ($pos1 == $pos2) {
18676 $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
18677 if (substr($pmid, $pos1, 4) == '[()]') {
18678 $linew -= $one_space_width;
18679 } elseif ($pos1 == strpos($pmid, '[(')) {
18680 $no = 1;
18681 }
18682 }
18683 }
18684 } else { // LTR
18685 // remove right space if exist
18686 $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
18687 if ($pos1 > 0) {
18688 $pos1 = intval($pos1);
18689 if ($this->isUnicodeFont()) {
18690 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
18691 $spacelen = 2;
18692 } else {
18693 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
18694 $spacelen = 1;
18695 }
18696 if ($pos1 == $pos2) {
18697 $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
18698 $linew -= $one_space_width;
18699 }
18700 }
18701 }
18702 $mdiff = ($tw - $linew);
18703 if ($plalign == 'C') {
18704 if ($this->rtl) {
18705 $t_x = -($mdiff / 2);
18706 } else {
18707 $t_x = ($mdiff / 2);
18708 }
18709 } elseif ($plalign == 'R') {
18710 // right alignment on LTR document
18711 $t_x = $mdiff;
18712 } elseif ($plalign == 'L') {
18713 // left alignment on RTL document
18714 $t_x = -$mdiff;
18715 }
18716 } // end if startlinex
18717 if (($t_x != 0) OR ($yshift < 0)) {
18718 // shift the line
18719 $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18720 $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18721 $endlinepos = strlen($pstart);
18722 if ($this->inxobj) {
18723 // we are inside an XObject template
18724 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18725 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18726 if ($pak >= $pask) {
18727 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18728 $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18729 }
18730 }
18731 } else {
18732 $this->setPageBuffer($startlinepage, $pstart.$pend);
18733 // shift the annotations and links
18734 if (isset($this->PageAnnots[$this->page])) {
18735 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18736 if ($pak >= $pask) {
18737 $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18738 $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18739 }
18740 }
18741 }
18742 }
18743 $this->y -= $yshift;
18744 $yshift = 0;
18745 }
18746 }
18747 // restore previous values
18748 $this->setGraphicVars($gvars);
18749 if ($this->num_columns > 1) {
18750 $this->selectColumn();
18751 } elseif ($this->page > $prevPage) {
18752 $this->lMargin = $this->pagedim[$this->page]['olm'];
18753 $this->rMargin = $this->pagedim[$this->page]['orm'];
18754 }
18755 // restore previous list state
18756 $this->cell_height_ratio = $prev_cell_height_ratio;
18757 $this->listnum = $prev_listnum;
18758 $this->listordered = $prev_listordered;
18759 $this->listcount = $prev_listcount;
18760 $this->lispacer = $prev_lispacer;
18761 if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
18762 $this->Ln($this->lasth);
18763 if ($this->y < $maxbottomliney) {
18764 $this->y = $maxbottomliney;
18765 }
18766 }
18767 unset($dom);
18768 }
18769
18770 /**
18771 * Process opening tags.
18772 * @param $dom (array) html dom array
18773 * @param $key (int) current element id
18774 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
18775 * @return $dom array
18776 * @protected
18777 */
18778 protected function openHTMLTagHandler($dom, $key, $cell) {
18779 $tag = $dom[$key];
18780 $parent = $dom[($dom[$key]['parent'])];
18781 $firsttag = ($key == 1);
18782 // check for text direction attribute
18783 if (isset($tag['dir'])) {
18784 $this->setTempRTL($tag['dir']);
18785 } else {
18786 $this->tmprtl = false;
18787 }
18788 if ($tag['block']) {
18789 $hbz = 0; // distance from y to line bottom
18790 $hb = 0; // vertical space between block tags
18791 // calculate vertical space for block tags
18792 if (isset($this->tagvspaces[$tag['value']][0]['h']) AND ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
18793 $cur_h = $this->tagvspaces[$tag['value']][0]['h'];
18794 } elseif (isset($tag['fontsize'])) {
18795 $cur_h = $this->getCellHeight($tag['fontsize'] / $this->k);
18796 } else {
18797 $cur_h = $this->getCellHeight($this->FontSize);
18798 }
18799 if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
18800 $on = $this->tagvspaces[$tag['value']][0]['n'];
18801 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18802 $on = 0.6;
18803 } else {
18804 $on = 1;
18805 }
18806 if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br', 'hr')))) {
18807 $hb = 0;
18808 } else {
18809 $hb = ($on * $cur_h);
18810 }
18811 if (($this->htmlvspace <= 0) AND ($on > 0)) {
18812 if (isset($parent['fontsize'])) {
18813 $hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
18814 } else {
18815 $hbz = $this->getCellHeight($this->FontSize);
18816 }
18817 }
18818 if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
18819 // fix vertical space after table
18820 $hbz = 0;
18821 }
18822 // closing vertical space
18823 $hbc = 0;
18824 if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
18825 $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
18826 } elseif (isset($parent['fontsize'])) {
18827 $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
18828 } else {
18829 $pre_h = $this->getCellHeight($this->FontSize);
18830 }
18831 if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
18832 $cn = $this->tagvspaces[$tag['value']][1]['n'];
18833 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18834 $cn = 0.6;
18835 } else {
18836 $cn = 1;
18837 }
18838 if (isset($this->tagvspaces[$tag['value']][1])) {
18839 $hbc = ($cn * $pre_h);
18840 }
18841 }
18842 // Opening tag
18843 switch($tag['value']) {
18844 case 'table': {
18845 $cp = 0;
18846 $cs = 0;
18847 $dom[$key]['rowspans'] = array();
18848 if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
18849 $this->htmlvspace = 0;
18850 // set table header
18851 if (!TCPDF_STATIC::empty_string($dom[$key]['thead'])) {
18852 // set table header
18853 $this->thead = $dom[$key]['thead'];
18854 if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
18855 $this->theadMargins = array();
18856 $this->theadMargins['cell_padding'] = $this->cell_padding;
18857 $this->theadMargins['lmargin'] = $this->lMargin;
18858 $this->theadMargins['rmargin'] = $this->rMargin;
18859 $this->theadMargins['page'] = $this->page;
18860 $this->theadMargins['cell'] = $cell;
18861 $this->theadMargins['gvars'] = $this->getGraphicVars();
18862 }
18863 }
18864 }
18865 // store current margins and page
18866 $dom[$key]['old_cell_padding'] = $this->cell_padding;
18867 if (isset($tag['attribute']['cellpadding'])) {
18868 $pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
18869 $this->SetCellPadding($pad);
18870 } elseif (isset($tag['padding'])) {
18871 $this->cell_padding = $tag['padding'];
18872 }
18873 if (isset($tag['attribute']['cellspacing'])) {
18874 $cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
18875 } elseif (isset($tag['border-spacing'])) {
18876 $cs = $tag['border-spacing']['V'];
18877 }
18878 $prev_y = $this->y;
18879 if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
18880 $this->inthead = true;
18881 // add a page (or trig AcceptPageBreak() for multicolumn mode)
18882 $this->checkPageBreak($this->PageBreakTrigger + 1);
18883 }
18884 break;
18885 }
18886 case 'tr': {
18887 // array of columns positions
18888 $dom[$key]['cellpos'] = array();
18889 break;
18890 }
18891 case 'hr': {
18892 if ((isset($tag['height'])) AND ($tag['height'] != '')) {
18893 $hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
18894 } else {
18895 $hrHeight = $this->GetLineWidth();
18896 }
18897 $this->addHTMLVertSpace($hbz, max($hb, ($hrHeight / 2)), $cell, $firsttag);
18898 $x = $this->GetX();
18899 $y = $this->GetY();
18900 $wtmp = $this->w - $this->lMargin - $this->rMargin;
18901 if ($cell) {
18902 $wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
18903 }
18904 if ((isset($tag['width'])) AND ($tag['width'] != '')) {
18905 $hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
18906 } else {
18907 $hrWidth = $wtmp;
18908 }
18909 $prevlinewidth = $this->GetLineWidth();
18910 $this->SetLineWidth($hrHeight);
18911 $this->Line($x, $y, $x + $hrWidth, $y);
18912 $this->SetLineWidth($prevlinewidth);
18913 $this->addHTMLVertSpace(max($hbc, ($hrHeight / 2)), 0, $cell, !isset($dom[($key + 1)]));
18914 break;
18915 }
18916 case 'a': {
18917 if (array_key_exists('href', $tag['attribute'])) {
18918 $this->HREF['url'] = $tag['attribute']['href'];
18919 }
18920 break;
18921 }
18922 case 'img': {
18923 if (!empty($tag['attribute']['src'])) {
18924 if ($tag['attribute']['src'][0] === '@') {
18925 // data stream
18926 $tag['attribute']['src'] = '@'.base64_decode(substr($tag['attribute']['src'], 1));
18927 $type = '';
18928 } else {
18929 // get image type
18930 $type = TCPDF_IMAGES::getImageFileType($tag['attribute']['src']);
18931 }
18932 if (!isset($tag['width'])) {
18933 $tag['width'] = 0;
18934 }
18935 if (!isset($tag['height'])) {
18936 $tag['height'] = 0;
18937 }
18938 //if (!isset($tag['attribute']['align'])) {
18939 // the only alignment supported is "bottom"
18940 // further development is required for other modes.
18941 $tag['attribute']['align'] = 'bottom';
18942 //}
18943 switch($tag['attribute']['align']) {
18944 case 'top': {
18945 $align = 'T';
18946 break;
18947 }
18948 case 'middle': {
18949 $align = 'M';
18950 break;
18951 }
18952 case 'bottom': {
18953 $align = 'B';
18954 break;
18955 }
18956 default: {
18957 $align = 'B';
18958 break;
18959 }
18960 }
18961 $prevy = $this->y;
18962 $xpos = $this->x;
18963 $imglink = '';
18964 if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
18965 $imglink = $this->HREF['url'];
18966 if ($imglink[0] == '#') {
18967 // convert url to internal link
18968 $lnkdata = explode(',', $imglink);
18969 if (isset($lnkdata[0])) {
18970 $page = intval(substr($lnkdata[0], 1));
18971 if (empty($page) OR ($page <= 0)) {
18972 $page = $this->page;
18973 }
18974 if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
18975 $lnky = floatval($lnkdata[1]);
18976 } else {
18977 $lnky = 0;
18978 }
18979 $imglink = $this->AddLink();
18980 $this->SetLink($imglink, $lnky, $page);
18981 }
18982 }
18983 }
18984 $border = 0;
18985 if (isset($tag['border']) AND !empty($tag['border'])) {
18986 // currently only support 1 (frame) or a combination of 'LTRB'
18987 $border = $tag['border'];
18988 }
18989 $iw = '';
18990 if (isset($tag['width'])) {
18991 $iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k), 'px', false);
18992 }
18993 $ih = '';
18994 if (isset($tag['height'])) {
18995 $ih = $this->getHTMLUnitToUnits($tag['height'], ($tag['fontsize'] / $this->k), 'px', false);
18996 }
18997 if (($type == 'eps') OR ($type == 'ai')) {
18998 $this->ImageEps($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
18999 } elseif ($type == 'svg') {
19000 $this->ImageSVG($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
19001 } else {
19002 $this->Image($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
19003 }
19004 switch($align) {
19005 case 'T': {
19006 $this->y = $prevy;
19007 break;
19008 }
19009 case 'M': {
19010 $this->y = (($this->img_rb_y + $prevy - ($this->getCellHeight($tag['fontsize'] / $this->k))) / 2);
19011 break;
19012 }
19013 case 'B': {
19014 $this->y = $this->img_rb_y - ($this->getCellHeight($tag['fontsize'] / $this->k) - ($this->getFontDescent($tag['fontname'], $tag['fontstyle'], $tag['fontsize']) * $this->cell_height_ratio));
19015 break;
19016 }
19017 }
19018 }
19019 break;
19020 }
19021 case 'dl': {
19022 ++$this->listnum;
19023 if ($this->listnum == 1) {
19024 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19025 } else {
19026 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19027 }
19028 break;
19029 }
19030 case 'dt': {
19031 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19032 break;
19033 }
19034 case 'dd': {
19035 if ($this->rtl) {
19036 $this->rMargin += $this->listindent;
19037 } else {
19038 $this->lMargin += $this->listindent;
19039 }
19040 ++$this->listindentlevel;
19041 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19042 break;
19043 }
19044 case 'ul':
19045 case 'ol': {
19046 ++$this->listnum;
19047 if ($tag['value'] == 'ol') {
19048 $this->listordered[$this->listnum] = true;
19049 } else {
19050 $this->listordered[$this->listnum] = false;
19051 }
19052 if (isset($tag['attribute']['start'])) {
19053 $this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
19054 } else {
19055 $this->listcount[$this->listnum] = 0;
19056 }
19057 if ($this->rtl) {
19058 $this->rMargin += $this->listindent;
19059 $this->x -= $this->listindent;
19060 } else {
19061 $this->lMargin += $this->listindent;
19062 $this->x += $this->listindent;
19063 }
19064 ++$this->listindentlevel;
19065 if ($this->listnum == 1) {
19066 if ($key > 1) {
19067 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19068 }
19069 } else {
19070 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19071 }
19072 break;
19073 }
19074 case 'li': {
19075 if ($key > 2) {
19076 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19077 }
19078 if ($this->listordered[$this->listnum]) {
19079 // ordered item
19080 if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19081 $this->lispacer = $parent['attribute']['type'];
19082 } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19083 $this->lispacer = $parent['listtype'];
19084 } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19085 $this->lispacer = $this->lisymbol;
19086 } else {
19087 $this->lispacer = '#';
19088 }
19089 ++$this->listcount[$this->listnum];
19090 if (isset($tag['attribute']['value'])) {
19091 $this->listcount[$this->listnum] = intval($tag['attribute']['value']);
19092 }
19093 } else {
19094 // unordered item
19095 if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19096 $this->lispacer = $parent['attribute']['type'];
19097 } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19098 $this->lispacer = $parent['listtype'];
19099 } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19100 $this->lispacer = $this->lisymbol;
19101 } else {
19102 $this->lispacer = '!';
19103 }
19104 }
19105 break;
19106 }
19107 case 'blockquote': {
19108 if ($this->rtl) {
19109 $this->rMargin += $this->listindent;
19110 } else {
19111 $this->lMargin += $this->listindent;
19112 }
19113 ++$this->listindentlevel;
19114 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19115 break;
19116 }
19117 case 'br': {
19118 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19119 break;
19120 }
19121 case 'div': {
19122 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19123 break;
19124 }
19125 case 'p': {
19126 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19127 break;
19128 }
19129 case 'pre': {
19130 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19131 $this->premode = true;
19132 break;
19133 }
19134 case 'sup': {
19135 $this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
19136 break;
19137 }
19138 case 'sub': {
19139 $this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
19140 break;
19141 }
19142 case 'h1':
19143 case 'h2':
19144 case 'h3':
19145 case 'h4':
19146 case 'h5':
19147 case 'h6': {
19148 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19149 break;
19150 }
19151 // Form fields (since 4.8.000 - 2009-09-07)
19152 case 'form': {
19153 if (isset($tag['attribute']['action'])) {
19154 $this->form_action = $tag['attribute']['action'];
19155 } else {
19156 $this->Error('Please explicitly set action attribute path!');
19157 }
19158 if (isset($tag['attribute']['enctype'])) {
19159 $this->form_enctype = $tag['attribute']['enctype'];
19160 } else {
19161 $this->form_enctype = 'application/x-www-form-urlencoded';
19162 }
19163 if (isset($tag['attribute']['method'])) {
19164 $this->form_mode = $tag['attribute']['method'];
19165 } else {
19166 $this->form_mode = 'post';
19167 }
19168 break;
19169 }
19170 case 'input': {
19171 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19172 $name = $tag['attribute']['name'];
19173 } else {
19174 break;
19175 }
19176 $prop = array();
19177 $opt = array();
19178 if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19179 $prop['readonly'] = true;
19180 }
19181 if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19182 $value = $tag['attribute']['value'];
19183 }
19184 if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC::empty_string($tag['attribute']['maxlength'])) {
19185 $opt['maxlen'] = intval($tag['attribute']['maxlength']);
19186 }
19187 $h = $this->getCellHeight($this->FontSize);
19188 if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19189 $w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
19190 } else {
19191 $w = $h;
19192 }
19193 if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
19194 $checked = true;
19195 } else {
19196 $checked = false;
19197 }
19198 if (isset($tag['align'])) {
19199 switch ($tag['align']) {
19200 case 'C': {
19201 $opt['q'] = 1;
19202 break;
19203 }
19204 case 'R': {
19205 $opt['q'] = 2;
19206 break;
19207 }
19208 case 'L':
19209 default: {
19210 break;
19211 }
19212 }
19213 }
19214 switch ($tag['attribute']['type']) {
19215 case 'text': {
19216 if (isset($value)) {
19217 $opt['v'] = $value;
19218 }
19219 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19220 break;
19221 }
19222 case 'password': {
19223 if (isset($value)) {
19224 $opt['v'] = $value;
19225 }
19226 $prop['password'] = 'true';
19227 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19228 break;
19229 }
19230 case 'checkbox': {
19231 if (!isset($value)) {
19232 break;
19233 }
19234 $this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
19235 break;
19236 }
19237 case 'radio': {
19238 if (!isset($value)) {
19239 break;
19240 }
19241 $this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
19242 break;
19243 }
19244 case 'submit': {
19245 if (!isset($value)) {
19246 $value = 'submit';
19247 }
19248 $w = $this->GetStringWidth($value) * 1.5;
19249 $h *= 1.6;
19250 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19251 $action = array();
19252 $action['S'] = 'SubmitForm';
19253 $action['F'] = $this->form_action;
19254 if ($this->form_enctype != 'FDF') {
19255 $action['Flags'] = array('ExportFormat');
19256 }
19257 if ($this->form_mode == 'get') {
19258 $action['Flags'] = array('GetMethod');
19259 }
19260 $this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
19261 break;
19262 }
19263 case 'reset': {
19264 if (!isset($value)) {
19265 $value = 'reset';
19266 }
19267 $w = $this->GetStringWidth($value) * 1.5;
19268 $h *= 1.6;
19269 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19270 $this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
19271 break;
19272 }
19273 case 'file': {
19274 $prop['fileSelect'] = 'true';
19275 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19276 if (!isset($value)) {
19277 $value = '*';
19278 }
19279 $w = $this->GetStringWidth($value) * 2;
19280 $h *= 1.2;
19281 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19282 $jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
19283 $this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19284 break;
19285 }
19286 case 'hidden': {
19287 if (isset($value)) {
19288 $opt['v'] = $value;
19289 }
19290 $opt['f'] = array('invisible', 'hidden');
19291 $this->TextField($name, 0, 0, $prop, $opt, '', '', false);
19292 break;
19293 }
19294 case 'image': {
19295 // THIS TYPE MUST BE FIXED
19296 if (isset($tag['attribute']['src']) AND !TCPDF_STATIC::empty_string($tag['attribute']['src'])) {
19297 $img = $tag['attribute']['src'];
19298 } else {
19299 break;
19300 }
19301 $value = 'img';
19302 //$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
19303 if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19304 $jsaction = $tag['attribute']['onclick'];
19305 } else {
19306 $jsaction = '';
19307 }
19308 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19309 break;
19310 }
19311 case 'button': {
19312 if (!isset($value)) {
19313 $value = ' ';
19314 }
19315 $w = $this->GetStringWidth($value) * 1.5;
19316 $h *= 1.6;
19317 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19318 if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19319 $jsaction = $tag['attribute']['onclick'];
19320 } else {
19321 $jsaction = '';
19322 }
19323 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19324 break;
19325 }
19326 }
19327 break;
19328 }
19329 case 'textarea': {
19330 $prop = array();
19331 $opt = array();
19332 if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19333 $prop['readonly'] = true;
19334 }
19335 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19336 $name = $tag['attribute']['name'];
19337 } else {
19338 break;
19339 }
19340 if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19341 $opt['v'] = $tag['attribute']['value'];
19342 }
19343 if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC::empty_string($tag['attribute']['cols'])) {
19344 $w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
19345 } else {
19346 $w = 40;
19347 }
19348 if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
19349 $h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize);
19350 } else {
19351 $h = 10;
19352 }
19353 $prop['multiline'] = 'true';
19354 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19355 break;
19356 }
19357 case 'select': {
19358 $h = $this->getCellHeight($this->FontSize);
19359 if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19360 $h *= ($tag['attribute']['size'] + 1);
19361 }
19362 $prop = array();
19363 $opt = array();
19364 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19365 $name = $tag['attribute']['name'];
19366 } else {
19367 break;
19368 }
19369 $w = 0;
19370 if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
19371 $options = explode('#!NwL!#', $tag['attribute']['opt']);
19372 $values = array();
19373 foreach ($options as $val) {
19374 if (strpos($val, '#!TaB!#') !== false) {
19375 $opts = explode('#!TaB!#', $val);
19376 $values[] = $opts;
19377 $w = max($w, $this->GetStringWidth($opts[1]));
19378 } else {
19379 $values[] = $val;
19380 $w = max($w, $this->GetStringWidth($val));
19381 }
19382 }
19383 } else {
19384 break;
19385 }
19386 $w *= 2;
19387 if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
19388 $prop['multipleSelection'] = 'true';
19389 $this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19390 } else {
19391 $this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19392 }
19393 break;
19394 }
19395 case 'tcpdf': {
19396 if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
19397 // Special tag used to call TCPDF methods
19398 if (isset($tag['attribute']['method'])) {
19399 $tcpdf_method = $tag['attribute']['method'];
19400 if (method_exists($this, $tcpdf_method)) {
19401 if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
19402 $params = TCPDF_STATIC::unserializeTCPDFtagParameters($tag['attribute']['params']);
19403 call_user_func_array(array($this, $tcpdf_method), $params);
19404 } else {
19405 $this->$tcpdf_method();
19406 }
19407 $this->newline = true;
19408 }
19409 }
19410 }
19411 break;
19412 }
19413 default: {
19414 break;
19415 }
19416 }
19417 // define tags that support borders and background colors
19418 $bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
19419 if (in_array($tag['value'], $bordertags)) {
19420 // set border
19421 $dom[$key]['borderposition'] = $this->getBorderStartPosition();
19422 }
19423 if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
19424 $pba = $dom[$key]['attribute']['pagebreakafter'];
19425 // check for pagebreak
19426 if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19427 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19428 $this->checkPageBreak($this->PageBreakTrigger + 1);
19429 }
19430 if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19431 OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19432 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19433 $this->checkPageBreak($this->PageBreakTrigger + 1);
19434 }
19435 }
19436 return $dom;
19437 }
19438
19439 /**
19440 * Process closing tags.
19441 * @param $dom (array) html dom array
19442 * @param $key (int) current element id
19443 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19444 * @param $maxbottomliney (int) maximum y value of current line
19445 * @return $dom array
19446 * @protected
19447 */
19448 protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
19449 $tag = $dom[$key];
19450 $parent = $dom[($dom[$key]['parent'])];
19451 $lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
19452 $in_table_head = false;
19453 // maximum x position (used to draw borders)
19454 if ($this->rtl) {
19455 $xmax = $this->w;
19456 } else {
19457 $xmax = 0;
19458 }
19459 if ($tag['block']) {
19460 $hbz = 0; // distance from y to line bottom
19461 $hb = 0; // vertical space between block tags
19462 // calculate vertical space for block tags
19463 if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
19464 $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
19465 } elseif (isset($parent['fontsize'])) {
19466 $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
19467 } else {
19468 $pre_h = $this->getCellHeight($this->FontSize);
19469 }
19470 if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
19471 $cn = $this->tagvspaces[$tag['value']][1]['n'];
19472 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19473 $cn = 0.6;
19474 } else {
19475 $cn = 1;
19476 }
19477 if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
19478 $hb = 0;
19479 } else {
19480 $hb = ($cn * $pre_h);
19481 }
19482 if ($maxbottomliney > $this->PageBreakTrigger) {
19483 $hbz = $this->getCellHeight($this->FontSize);
19484 } elseif ($this->y < $maxbottomliney) {
19485 $hbz = ($maxbottomliney - $this->y);
19486 }
19487 }
19488 // Closing tag
19489 switch($tag['value']) {
19490 case 'tr': {
19491 $table_el = $dom[($dom[$key]['parent'])]['parent'];
19492 if (!isset($parent['endy'])) {
19493 $dom[($dom[$key]['parent'])]['endy'] = $this->y;
19494 $parent['endy'] = $this->y;
19495 }
19496 if (!isset($parent['endpage'])) {
19497 $dom[($dom[$key]['parent'])]['endpage'] = $this->page;
19498 $parent['endpage'] = $this->page;
19499 }
19500 if (!isset($parent['endcolumn'])) {
19501 $dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
19502 $parent['endcolumn'] = $this->current_column;
19503 }
19504 // update row-spanned cells
19505 if (isset($dom[$table_el]['rowspans'])) {
19506 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19507 $dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
19508 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19509 if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
19510 $dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
19511 } elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
19512 $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19513 $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19514 $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19515 }
19516 }
19517 }
19518 // report new endy and endpage to the rowspanned cells
19519 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19520 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19521 $dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
19522 $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19523 $dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
19524 $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19525 $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
19526 $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19527 }
19528 }
19529 // update remaining rowspanned cells
19530 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19531 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19532 $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
19533 $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
19534 $dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
19535 }
19536 }
19537 }
19538 $prev_page = $this->page;
19539 $this->setPage($dom[($dom[$key]['parent'])]['endpage']);
19540 if ($this->num_columns > 1) {
19541 if ((($this->current_column == 0) AND ($dom[($dom[$key]['parent'])]['endcolumn'] == ($this->num_columns - 1)))
19542 OR (($this->current_column == $dom[($dom[$key]['parent'])]['endcolumn']) AND ($prev_page < $this->page))) {
19543 // page jump
19544 $this->selectColumn(0);
19545 $dom[($dom[$key]['parent'])]['endcolumn'] = 0;
19546 $dom[($dom[$key]['parent'])]['endy'] = $this->y;
19547 } else {
19548 $this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
19549 $this->y = $dom[($dom[$key]['parent'])]['endy'];
19550 }
19551 } else {
19552 $this->y = $dom[($dom[$key]['parent'])]['endy'];
19553 }
19554 if (isset($dom[$table_el]['attribute']['cellspacing'])) {
19555 $this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
19556 } elseif (isset($dom[$table_el]['border-spacing'])) {
19557 $this->y += $dom[$table_el]['border-spacing']['V'];
19558 }
19559 $this->Ln(0, $cell);
19560 if ($this->current_column == $parent['startcolumn']) {
19561 $this->x = $parent['startx'];
19562 }
19563 // account for booklet mode
19564 if ($this->page > $parent['startpage']) {
19565 if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
19566 $this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
19567 } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
19568 $this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
19569 }
19570 }
19571 break;
19572 }
19573 case 'tablehead':
19574 // closing tag used for the thead part
19575 $in_table_head = true;
19576 $this->inthead = false;
19577 case 'table': {
19578 $table_el = $parent;
19579 // set default border
19580 if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
19581 // set default border
19582 $border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
19583 } else {
19584 $border = 0;
19585 }
19586 $default_border = $border;
19587 // fix bottom line alignment of last line before page break
19588 foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
19589 // update row-spanned cells
19590 if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19591 foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19592 if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
19593 $dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
19594 }
19595 if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
19596 $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
19597 }
19598 }
19599 }
19600 if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
19601 $pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
19602 $dom[$prevtrkey]['endy'] = $pgendy;
19603 // update row-spanned cells
19604 if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19605 foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19606 if (($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
19607 $dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
19608 $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
19609 }
19610 }
19611 }
19612 }
19613 $prevtrkey = $trkey;
19614 $table_el = $dom[($dom[$key]['parent'])];
19615 }
19616 // for each row
19617 if (count($table_el['trids']) > 0) {
19618 unset($xmax);
19619 }
19620 foreach ($table_el['trids'] as $j => $trkey) {
19621 $parent = $dom[$trkey];
19622 if (!isset($xmax)) {
19623 $xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
19624 }
19625 // for each cell on the row
19626 foreach ($parent['cellpos'] as $k => $cellpos) {
19627 if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
19628 $cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
19629 $cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
19630 $endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
19631 $startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
19632 $endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
19633 $startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
19634 $endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
19635 } else {
19636 $endy = $parent['endy'];
19637 $startpage = $parent['startpage'];
19638 $endpage = $parent['endpage'];
19639 $startcolumn = $parent['startcolumn'];
19640 $endcolumn = $parent['endcolumn'];
19641 }
19642 if ($this->num_columns == 0) {
19643 $this->num_columns = 1;
19644 }
19645 if (isset($cellpos['border'])) {
19646 $border = $cellpos['border'];
19647 }
19648 if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
19649 $this->SetFillColorArray($cellpos['bgcolor']);
19650 $fill = true;
19651 } else {
19652 $fill = false;
19653 }
19654 $x = $cellpos['startx'];
19655 $y = $parent['starty'];
19656 $starty = $y;
19657 $w = abs($cellpos['endx'] - $cellpos['startx']);
19658 // get border modes
19659 $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
19660 $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
19661 $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19662 // design borders around HTML cells.
19663 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
19664 $ccode = '';
19665 $this->setPage($page);
19666 if ($this->num_columns < 2) {
19667 // single-column mode
19668 $this->x = $x;
19669 $this->y = $this->tMargin;
19670 }
19671 // account for margin changes
19672 if ($page > $startpage) {
19673 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
19674 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
19675 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
19676 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
19677 }
19678 }
19679 if ($startpage == $endpage) { // single page
19680 $deltacol = 0;
19681 $deltath = 0;
19682 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
19683 $this->selectColumn($column);
19684 if ($startcolumn == $endcolumn) { // single column
19685 $cborder = $border;
19686 $h = $endy - $parent['starty'];
19687 $this->y = $y;
19688 $this->x = $x;
19689 } elseif ($column == $startcolumn) { // first column
19690 $cborder = $border_start;
19691 $this->y = $starty;
19692 $this->x = $x;
19693 $h = $this->h - $this->y - $this->bMargin;
19694 if ($this->rtl) {
19695 $deltacol = $this->x + $this->rMargin - $this->w;
19696 } else {
19697 $deltacol = $this->x - $this->lMargin;
19698 }
19699 } elseif ($column == $endcolumn) { // end column
19700 $cborder = $border_end;
19701 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19702 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19703 }
19704 $this->x += $deltacol;
19705 $h = $endy - $this->y;
19706 } else { // middle column
19707 $cborder = $border_middle;
19708 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19709 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19710 }
19711 $this->x += $deltacol;
19712 $h = $this->h - $this->y - $this->bMargin;
19713 }
19714 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19715 } // end for each column
19716 } elseif ($page == $startpage) { // first page
19717 $deltacol = 0;
19718 $deltath = 0;
19719 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
19720 $this->selectColumn($column);
19721 if ($column == $startcolumn) { // first column
19722 $cborder = $border_start;
19723 $this->y = $starty;
19724 $this->x = $x;
19725 $h = $this->h - $this->y - $this->bMargin;
19726 if ($this->rtl) {
19727 $deltacol = $this->x + $this->rMargin - $this->w;
19728 } else {
19729 $deltacol = $this->x - $this->lMargin;
19730 }
19731 } else { // middle column
19732 $cborder = $border_middle;
19733 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19734 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19735 }
19736 $this->x += $deltacol;
19737 $h = $this->h - $this->y - $this->bMargin;
19738 }
19739 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19740 } // end for each column
19741 } elseif ($page == $endpage) { // last page
19742 $deltacol = 0;
19743 $deltath = 0;
19744 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
19745 $this->selectColumn($column);
19746 if ($column == $endcolumn) { // end column
19747 $cborder = $border_end;
19748 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19749 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19750 }
19751 $this->x += $deltacol;
19752 $h = $endy - $this->y;
19753 } else { // middle column
19754 $cborder = $border_middle;
19755 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19756 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19757 }
19758 $this->x += $deltacol;
19759 $h = $this->h - $this->y - $this->bMargin;
19760 }
19761 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19762 } // end for each column
19763 } else { // middle page
19764 $deltacol = 0;
19765 $deltath = 0;
19766 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
19767 $this->selectColumn($column);
19768 $cborder = $border_middle;
19769 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19770 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19771 }
19772 $this->x += $deltacol;
19773 $h = $this->h - $this->y - $this->bMargin;
19774 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19775 } // end for each column
19776 }
19777 if ($cborder OR $fill) {
19778 $offsetlen = strlen($ccode);
19779 // draw border and fill
19780 if ($this->inxobj) {
19781 // we are inside an XObject template
19782 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
19783 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
19784 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
19785 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
19786 } else {
19787 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
19788 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
19789 }
19790 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
19791 $pstart = substr($pagebuff, 0, $pagemark);
19792 $pend = substr($pagebuff, $pagemark);
19793 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
19794 } else {
19795 // draw border and fill
19796 if (end($this->transfmrk[$this->page]) !== false) {
19797 $pagemarkkey = key($this->transfmrk[$this->page]);
19798 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
19799 } elseif ($this->InFooter) {
19800 $pagemark = $this->footerpos[$this->page];
19801 } else {
19802 $pagemark = $this->intmrk[$this->page];
19803 }
19804 $pagebuff = $this->getPageBuffer($this->page);
19805 $pstart = substr($pagebuff, 0, $pagemark);
19806 $pend = substr($pagebuff, $pagemark);
19807 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
19808 }
19809 }
19810 } // end for each page
19811 // restore default border
19812 $border = $default_border;
19813 } // end for each cell on the row
19814 if (isset($table_el['attribute']['cellspacing'])) {
19815 $this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
19816 } elseif (isset($table_el['border-spacing'])) {
19817 $this->y += $table_el['border-spacing']['V'];
19818 }
19819 $this->Ln(0, $cell);
19820 $this->x = $parent['startx'];
19821 if ($endpage > $startpage) {
19822 if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
19823 $this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
19824 } elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
19825 $this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
19826 }
19827 }
19828 }
19829 if (!$in_table_head) { // we are not inside a thead section
19830 $this->cell_padding = $table_el['old_cell_padding'];
19831 // reset row height
19832 $this->resetLastH();
19833 if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
19834 $plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
19835 if (($plendiff > 0) AND ($plendiff < 60)) {
19836 $pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
19837 if (substr($pagediff, 0, 5) == 'BT /F') {
19838 // the difference is only a font setting
19839 $plendiff = 0;
19840 }
19841 }
19842 if ($plendiff == 0) {
19843 // remove last blank page
19844 $this->deletePage($this->numpages);
19845 }
19846 }
19847 if (isset($this->theadMargins['top'])) {
19848 // restore top margin
19849 $this->tMargin = $this->theadMargins['top'];
19850 }
19851 if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
19852 // reset main table header
19853 $this->thead = '';
19854 $this->theadMargins = array();
19855 $this->pagedim[$this->page]['tm'] = $this->tMargin;
19856 }
19857 }
19858 $parent = $table_el;
19859 break;
19860 }
19861 case 'a': {
19862 $this->HREF = '';
19863 break;
19864 }
19865 case 'sup': {
19866 $this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
19867 break;
19868 }
19869 case 'sub': {
19870 $this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
19871 break;
19872 }
19873 case 'div': {
19874 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19875 break;
19876 }
19877 case 'blockquote': {
19878 if ($this->rtl) {
19879 $this->rMargin -= $this->listindent;
19880 } else {
19881 $this->lMargin -= $this->listindent;
19882 }
19883 --$this->listindentlevel;
19884 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19885 break;
19886 }
19887 case 'p': {
19888 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19889 break;
19890 }
19891 case 'pre': {
19892 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19893 $this->premode = false;
19894 break;
19895 }
19896 case 'dl': {
19897 --$this->listnum;
19898 if ($this->listnum <= 0) {
19899 $this->listnum = 0;
19900 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19901 } else {
19902 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19903 }
19904 $this->resetLastH();
19905 break;
19906 }
19907 case 'dt': {
19908 $this->lispacer = '';
19909 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19910 break;
19911 }
19912 case 'dd': {
19913 $this->lispacer = '';
19914 if ($this->rtl) {
19915 $this->rMargin -= $this->listindent;
19916 } else {
19917 $this->lMargin -= $this->listindent;
19918 }
19919 --$this->listindentlevel;
19920 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19921 break;
19922 }
19923 case 'ul':
19924 case 'ol': {
19925 --$this->listnum;
19926 $this->lispacer = '';
19927 if ($this->rtl) {
19928 $this->rMargin -= $this->listindent;
19929 } else {
19930 $this->lMargin -= $this->listindent;
19931 }
19932 --$this->listindentlevel;
19933 if ($this->listnum <= 0) {
19934 $this->listnum = 0;
19935 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19936 } else {
19937 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19938 }
19939 $this->resetLastH();
19940 break;
19941 }
19942 case 'li': {
19943 $this->lispacer = '';
19944 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19945 break;
19946 }
19947 case 'h1':
19948 case 'h2':
19949 case 'h3':
19950 case 'h4':
19951 case 'h5':
19952 case 'h6': {
19953 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19954 break;
19955 }
19956 // Form fields (since 4.8.000 - 2009-09-07)
19957 case 'form': {
19958 $this->form_action = '';
19959 $this->form_enctype = 'application/x-www-form-urlencoded';
19960 break;
19961 }
19962 default : {
19963 break;
19964 }
19965 }
19966 // draw border and background (if any)
19967 $this->drawHTMLTagBorder($parent, $xmax);
19968 if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
19969 $pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
19970 // check for pagebreak
19971 if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19972 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19973 $this->checkPageBreak($this->PageBreakTrigger + 1);
19974 }
19975 if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19976 OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19977 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19978 $this->checkPageBreak($this->PageBreakTrigger + 1);
19979 }
19980 }
19981 $this->tmprtl = false;
19982 return $dom;
19983 }
19984
19985 /**
19986 * Add vertical spaces if needed.
19987 * @param $hbz (string) Distance between current y and line bottom.
19988 * @param $hb (string) The height of the break.
19989 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19990 * @param $firsttag (boolean) set to true when the tag is the first.
19991 * @param $lasttag (boolean) set to true when the tag is the last.
19992 * @protected
19993 */
19994 protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
19995 if ($firsttag) {
19996 $this->Ln(0, $cell);
19997 $this->htmlvspace = 0;
19998 return;
19999 }
20000 if ($lasttag) {
20001 $this->Ln($hbz, $cell);
20002 $this->htmlvspace = 0;
20003 return;
20004 }
20005 if ($hb < $this->htmlvspace) {
20006 $hd = 0;
20007 } else {
20008 $hd = $hb - $this->htmlvspace;
20009 $this->htmlvspace = $hb;
20010 }
20011 $this->Ln(($hbz + $hd), $cell);
20012 }
20013
20014 /**
20015 * Return the starting coordinates to draw an html border
20016 * @return array containing top-left border coordinates
20017 * @protected
20018 * @since 5.7.000 (2010-08-03)
20019 */
20020 protected function getBorderStartPosition() {
20021 if ($this->rtl) {
20022 $xmax = $this->lMargin;
20023 } else {
20024 $xmax = $this->w - $this->rMargin;
20025 }
20026 return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
20027 }
20028
20029 /**
20030 * Draw an HTML block border and fill
20031 * @param $tag (array) array of tag properties.
20032 * @param $xmax (int) end X coordinate for border.
20033 * @protected
20034 * @since 5.7.000 (2010-08-03)
20035 */
20036 protected function drawHTMLTagBorder($tag, $xmax) {
20037 if (!isset($tag['borderposition'])) {
20038 // nothing to draw
20039 return;
20040 }
20041 $prev_x = $this->x;
20042 $prev_y = $this->y;
20043 $prev_lasth = $this->lasth;
20044 $border = 0;
20045 $fill = false;
20046 $this->lasth = 0;
20047 if (isset($tag['border']) AND !empty($tag['border'])) {
20048 // get border style
20049 $border = $tag['border'];
20050 if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
20051 // border for table header
20052 $border = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20053 }
20054 }
20055 if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
20056 // get background color
20057 $old_bgcolor = $this->bgcolor;
20058 $this->SetFillColorArray($tag['bgcolor']);
20059 $fill = true;
20060 }
20061 if (!$border AND !$fill) {
20062 // nothing to draw
20063 return;
20064 }
20065 if (isset($tag['attribute']['cellspacing'])) {
20066 $clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
20067 $cellspacing = array('H' => $clsp, 'V' => $clsp);
20068 } elseif (isset($tag['border-spacing'])) {
20069 $cellspacing = $tag['border-spacing'];
20070 } else {
20071 $cellspacing = array('H' => 0, 'V' => 0);
20072 }
20073 if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
20074 // draw the border externally respect the sqare edge.
20075 $border['mode'] = 'ext';
20076 }
20077 if ($this->rtl) {
20078 if ($xmax >= $tag['borderposition']['x']) {
20079 $xmax = $tag['borderposition']['xmax'];
20080 }
20081 $w = ($tag['borderposition']['x'] - $xmax);
20082 } else {
20083 if ($xmax <= $tag['borderposition']['x']) {
20084 $xmax = $tag['borderposition']['xmax'];
20085 }
20086 $w = ($xmax - $tag['borderposition']['x']);
20087 }
20088 if ($w <= 0) {
20089 return;
20090 }
20091 $w += $cellspacing['H'];
20092 $startpage = $tag['borderposition']['page'];
20093 $startcolumn = $tag['borderposition']['column'];
20094 $x = $tag['borderposition']['x'];
20095 $y = $tag['borderposition']['y'];
20096 $endpage = $this->page;
20097 $starty = $tag['borderposition']['y'] - $cellspacing['V'];
20098 $currentY = $this->y;
20099 $this->x = $x;
20100 // get latest column
20101 $endcolumn = $this->current_column;
20102 if ($this->num_columns == 0) {
20103 $this->num_columns = 1;
20104 }
20105 // get border modes
20106 $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
20107 $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
20108 $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20109 // temporary disable page regions
20110 $temp_page_regions = $this->page_regions;
20111 $this->page_regions = array();
20112 // design borders around HTML cells.
20113 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
20114 $ccode = '';
20115 $this->setPage($page);
20116 if ($this->num_columns < 2) {
20117 // single-column mode
20118 $this->x = $x;
20119 $this->y = $this->tMargin;
20120 }
20121 // account for margin changes
20122 if ($page > $startpage) {
20123 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
20124 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
20125 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
20126 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
20127 }
20128 }
20129 if ($startpage == $endpage) {
20130 // single page
20131 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
20132 $this->selectColumn($column);
20133 if ($startcolumn == $endcolumn) { // single column
20134 $cborder = $border;
20135 $h = ($currentY - $y) + $cellspacing['V'];
20136 $this->y = $starty;
20137 } elseif ($column == $startcolumn) { // first column
20138 $cborder = $border_start;
20139 $this->y = $starty;
20140 $h = $this->h - $this->y - $this->bMargin;
20141 } elseif ($column == $endcolumn) { // end column
20142 $cborder = $border_end;
20143 $h = $currentY - $this->y;
20144 } else { // middle column
20145 $cborder = $border_middle;
20146 $h = $this->h - $this->y - $this->bMargin;
20147 }
20148 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20149 } // end for each column
20150 } elseif ($page == $startpage) { // first page
20151 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
20152 $this->selectColumn($column);
20153 if ($column == $startcolumn) { // first column
20154 $cborder = $border_start;
20155 $this->y = $starty;
20156 $h = $this->h - $this->y - $this->bMargin;
20157 } else { // middle column
20158 $cborder = $border_middle;
20159 $h = $this->h - $this->y - $this->bMargin;
20160 }
20161 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20162 } // end for each column
20163 } elseif ($page == $endpage) { // last page
20164 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
20165 $this->selectColumn($column);
20166 if ($column == $endcolumn) {
20167 // end column
20168 $cborder = $border_end;
20169 $h = $currentY - $this->y;
20170 } else {
20171 // middle column
20172 $cborder = $border_middle;
20173 $h = $this->h - $this->y - $this->bMargin;
20174 }
20175 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20176 } // end for each column
20177 } else { // middle page
20178 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
20179 $this->selectColumn($column);
20180 $cborder = $border_middle;
20181 $h = $this->h - $this->y - $this->bMargin;
20182 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20183 } // end for each column
20184 }
20185 if ($cborder OR $fill) {
20186 $offsetlen = strlen($ccode);
20187 // draw border and fill
20188 if ($this->inxobj) {
20189 // we are inside an XObject template
20190 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
20191 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
20192 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
20193 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
20194 } else {
20195 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
20196 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
20197 }
20198 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
20199 $pstart = substr($pagebuff, 0, $pagemark);
20200 $pend = substr($pagebuff, $pagemark);
20201 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
20202 } else {
20203 if (end($this->transfmrk[$this->page]) !== false) {
20204 $pagemarkkey = key($this->transfmrk[$this->page]);
20205 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
20206 } elseif ($this->InFooter) {
20207 $pagemark = $this->footerpos[$this->page];
20208 } else {
20209 $pagemark = $this->intmrk[$this->page];
20210 }
20211 $pagebuff = $this->getPageBuffer($this->page);
20212 $pstart = substr($pagebuff, 0, $pagemark);
20213 $pend = substr($pagebuff, $pagemark);
20214 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
20215 $this->bordermrk[$this->page] += $offsetlen;
20216 $this->cntmrk[$this->page] += $offsetlen;
20217 }
20218 }
20219 } // end for each page
20220 // restore page regions
20221 $this->page_regions = $temp_page_regions;
20222 if (isset($old_bgcolor)) {
20223 // restore background color
20224 $this->SetFillColorArray($old_bgcolor);
20225 }
20226 // restore pointer position
20227 $this->x = $prev_x;
20228 $this->y = $prev_y;
20229 $this->lasth = $prev_lasth;
20230 }
20231
20232 /**
20233 * Set the default bullet to be used as LI bullet symbol
20234 * @param $symbol (string) character or string to be used (legal values are: '' = automatic, '!' = auto bullet, '#' = auto numbering, 'disc', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek', 'img|type|width|height|image.ext')
20235 * @public
20236 * @since 4.0.028 (2008-09-26)
20237 */
20238 public function setLIsymbol($symbol='!') {
20239 // check for custom image symbol
20240 if (substr($symbol, 0, 4) == 'img|') {
20241 $this->lisymbol = $symbol;
20242 return;
20243 }
20244 $symbol = strtolower($symbol);
20245 $valid_symbols = array('!', '#', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek');
20246 if (in_array($symbol, $valid_symbols)) {
20247 $this->lisymbol = $symbol;
20248 } else {
20249 $this->lisymbol = '';
20250 }
20251 }
20252
20253 /**
20254 * Set the booklet mode for double-sided pages.
20255 * @param $booklet (boolean) true set the booklet mode on, false otherwise.
20256 * @param $inner (float) Inner page margin.
20257 * @param $outer (float) Outer page margin.
20258 * @public
20259 * @since 4.2.000 (2008-10-29)
20260 */
20261 public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
20262 $this->booklet = $booklet;
20263 if ($inner >= 0) {
20264 $this->lMargin = $inner;
20265 }
20266 if ($outer >= 0) {
20267 $this->rMargin = $outer;
20268 }
20269 }
20270
20271 /**
20272 * Swap the left and right margins.
20273 * @param $reverse (boolean) if true swap left and right margins.
20274 * @protected
20275 * @since 4.2.000 (2008-10-29)
20276 */
20277 protected function swapMargins($reverse=true) {
20278 if ($reverse) {
20279 // swap left and right margins
20280 $mtemp = $this->original_lMargin;
20281 $this->original_lMargin = $this->original_rMargin;
20282 $this->original_rMargin = $mtemp;
20283 $deltam = $this->original_lMargin - $this->original_rMargin;
20284 $this->lMargin += $deltam;
20285 $this->rMargin -= $deltam;
20286 }
20287 }
20288
20289 /**
20290 * Set the vertical spaces for HTML tags.
20291 * The array must have the following structure (example):
20292 * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
20293 * The first array level contains the tag names,
20294 * the second level contains 0 for opening tags or 1 for closing tags,
20295 * the third level contains the vertical space unit (h) and the number spaces to add (n).
20296 * If the h parameter is not specified, default values are used.
20297 * @param $tagvs (array) array of tags and relative vertical spaces.
20298 * @public
20299 * @since 4.2.001 (2008-10-30)
20300 */
20301 public function setHtmlVSpace($tagvs) {
20302 $this->tagvspaces = $tagvs;
20303 }
20304
20305 /**
20306 * Set custom width for list indentation.
20307 * @param $width (float) width of the indentation. Use negative value to disable it.
20308 * @public
20309 * @since 4.2.007 (2008-11-12)
20310 */
20311 public function setListIndentWidth($width) {
20312 return $this->customlistindent = floatval($width);
20313 }
20314
20315 /**
20316 * Set the top/bottom cell sides to be open or closed when the cell cross the page.
20317 * @param $isopen (boolean) if true keeps the top/bottom border open for the cell sides that cross the page.
20318 * @public
20319 * @since 4.2.010 (2008-11-14)
20320 */
20321 public function setOpenCell($isopen) {
20322 $this->opencell = $isopen;
20323 }
20324
20325 /**
20326 * Set the color and font style for HTML links.
20327 * @param $color (array) RGB array of colors
20328 * @param $fontstyle (string) additional font styles to add
20329 * @public
20330 * @since 4.4.003 (2008-12-09)
20331 */
20332 public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
20333 $this->htmlLinkColorArray = $color;
20334 $this->htmlLinkFontStyle = $fontstyle;
20335 }
20336
20337 /**
20338 * Convert HTML string containing value and unit of measure to user's units or points.
20339 * @param $htmlval (string) String containing values and unit.
20340 * @param $refsize (string) Reference value in points.
20341 * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
20342 * @param $points (boolean) If true returns points, otherwise returns value in user's units.
20343 * @return float value in user's unit or point if $points=true
20344 * @public
20345 * @since 4.4.004 (2008-12-10)
20346 */
20347 public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
20348 $supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
20349 $retval = 0;
20350 $value = 0;
20351 $unit = 'px';
20352 if ($points) {
20353 $k = 1;
20354 } else {
20355 $k = $this->k;
20356 }
20357 if (in_array($defaultunit, $supportedunits)) {
20358 $unit = $defaultunit;
20359 }
20360 if (is_numeric($htmlval)) {
20361 $value = floatval($htmlval);
20362 } elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
20363 $value = floatval($mnum[1]);
20364 if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
20365 if (in_array($munit[1], $supportedunits)) {
20366 $unit = $munit[1];
20367 }
20368 }
20369 }
20370 switch ($unit) {
20371 // percentage
20372 case '%': {
20373 $retval = (($value * $refsize) / 100);
20374 break;
20375 }
20376 // relative-size
20377 case 'em': {
20378 $retval = ($value * $refsize);
20379 break;
20380 }
20381 // height of lower case 'x' (about half the font-size)
20382 case 'ex': {
20383 $retval = ($value * ($refsize / 2));
20384 break;
20385 }
20386 // absolute-size
20387 case 'in': {
20388 $retval = (($value * $this->dpi) / $k);
20389 break;
20390 }
20391 // centimeters
20392 case 'cm': {
20393 $retval = (($value / 2.54 * $this->dpi) / $k);
20394 break;
20395 }
20396 // millimeters
20397 case 'mm': {
20398 $retval = (($value / 25.4 * $this->dpi) / $k);
20399 break;
20400 }
20401 // one pica is 12 points
20402 case 'pc': {
20403 $retval = (($value * 12) / $k);
20404 break;
20405 }
20406 // points
20407 case 'pt': {
20408 $retval = ($value / $k);
20409 break;
20410 }
20411 // pixels
20412 case 'px': {
20413 $retval = $this->pixelsToUnits($value);
20414 if ($points) {
20415 $retval *= $this->k;
20416 }
20417 break;
20418 }
20419 }
20420 return $retval;
20421 }
20422
20423 /**
20424 * Output an HTML list bullet or ordered item symbol
20425 * @param $listdepth (int) list nesting level
20426 * @param $listtype (string) type of list
20427 * @param $size (float) current font size
20428 * @protected
20429 * @since 4.4.004 (2008-12-10)
20430 */
20431 protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
20432 if ($this->state != 2) {
20433 return;
20434 }
20435 $size /= $this->k;
20436 $fill = '';
20437 $bgcolor = $this->bgcolor;
20438 $color = $this->fgcolor;
20439 $strokecolor = $this->strokecolor;
20440 $width = 0;
20441 $textitem = '';
20442 $tmpx = $this->x;
20443 $lspace = $this->GetStringWidth(' ');
20444 if ($listtype == '^') {
20445 // special symbol used for avoid justification of rect bullet
20446 $this->lispacer = '';
20447 return;
20448 } elseif ($listtype == '!') {
20449 // set default list type for unordered list
20450 $deftypes = array('disc', 'circle', 'square');
20451 $listtype = $deftypes[($listdepth - 1) % 3];
20452 } elseif ($listtype == '#') {
20453 // set default list type for ordered list
20454 $listtype = 'decimal';
20455 } elseif (substr($listtype, 0, 4) == 'img|') {
20456 // custom image type ('img|type|width|height|image.ext')
20457 $img = explode('|', $listtype);
20458 $listtype = 'img';
20459 }
20460 switch ($listtype) {
20461 // unordered types
20462 case 'none': {
20463 break;
20464 }
20465 case 'disc': {
20466 $r = $size / 6;
20467 $lspace += (2 * $r);
20468 if ($this->rtl) {
20469 $this->x += $lspace;
20470 } else {
20471 $this->x -= $lspace;
20472 }
20473 $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
20474 break;
20475 }
20476 case 'circle': {
20477 $r = $size / 6;
20478 $lspace += (2 * $r);
20479 if ($this->rtl) {
20480 $this->x += $lspace;
20481 } else {
20482 $this->x -= $lspace;
20483 }
20484 $prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
20485 $new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
20486 $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
20487 $this->_out($prev_line_style); // restore line settings
20488 break;
20489 }
20490 case 'square': {
20491 $l = $size / 3;
20492 $lspace += $l;
20493 if ($this->rtl) {;
20494 $this->x += $lspace;
20495 } else {
20496 $this->x -= $lspace;
20497 }
20498 $this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
20499 break;
20500 }
20501 case 'img': {
20502 // 1=>type, 2=>width, 3=>height, 4=>image.ext
20503 $lspace += $img[2];
20504 if ($this->rtl) {;
20505 $this->x += $lspace;
20506 } else {
20507 $this->x -= $lspace;
20508 }
20509 $imgtype = strtolower($img[1]);
20510 $prev_y = $this->y;
20511 switch ($imgtype) {
20512 case 'svg': {
20513 $this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
20514 break;
20515 }
20516 case 'ai':
20517 case 'eps': {
20518 $this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
20519 break;
20520 }
20521 default: {
20522 $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);
20523 break;
20524 }
20525 }
20526 $this->y = $prev_y;
20527 break;
20528 }
20529 // ordered types
20530 // $this->listcount[$this->listnum];
20531 // $textitem
20532 case '1':
20533 case 'decimal': {
20534 $textitem = $this->listcount[$this->listnum];
20535 break;
20536 }
20537 case 'decimal-leading-zero': {
20538 $textitem = sprintf('%02d', $this->listcount[$this->listnum]);
20539 break;
20540 }
20541 case 'i':
20542 case 'lower-roman': {
20543 $textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
20544 break;
20545 }
20546 case 'I':
20547 case 'upper-roman': {
20548 $textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
20549 break;
20550 }
20551 case 'a':
20552 case 'lower-alpha':
20553 case 'lower-latin': {
20554 $textitem = chr(97 + $this->listcount[$this->listnum] - 1);
20555 break;
20556 }
20557 case 'A':
20558 case 'upper-alpha':
20559 case 'upper-latin': {
20560 $textitem = chr(65 + $this->listcount[$this->listnum] - 1);
20561 break;
20562 }
20563 case 'lower-greek': {
20564 $textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
20565 break;
20566 }
20567 /*
20568 // Types to be implemented (special handling)
20569 case 'hebrew': {
20570 break;
20571 }
20572 case 'armenian': {
20573 break;
20574 }
20575 case 'georgian': {
20576 break;
20577 }
20578 case 'cjk-ideographic': {
20579 break;
20580 }
20581 case 'hiragana': {
20582 break;
20583 }
20584 case 'katakana': {
20585 break;
20586 }
20587 case 'hiragana-iroha': {
20588 break;
20589 }
20590 case 'katakana-iroha': {
20591 break;
20592 }
20593 */
20594 default: {
20595 $textitem = $this->listcount[$this->listnum];
20596 }
20597 }
20598 if (!TCPDF_STATIC::empty_string($textitem)) {
20599 // Check whether we need a new page or new column
20600 $prev_y = $this->y;
20601 $h = $this->getCellHeight($this->FontSize);
20602 if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
20603 $tmpx = $this->x;
20604 }
20605 // print ordered item
20606 if ($this->rtl) {
20607 $textitem = '.'.$textitem;
20608 } else {
20609 $textitem = $textitem.'.';
20610 }
20611 $lspace += $this->GetStringWidth($textitem);
20612 if ($this->rtl) {
20613 $this->x += $lspace;
20614 } else {
20615 $this->x -= $lspace;
20616 }
20617 $this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
20618 }
20619 $this->x = $tmpx;
20620 $this->lispacer = '^';
20621 // restore colors
20622 $this->SetFillColorArray($bgcolor);
20623 $this->SetDrawColorArray($strokecolor);
20624 $this->SettextColorArray($color);
20625 }
20626
20627 /**
20628 * Returns current graphic variables as array.
20629 * @return array of graphic variables
20630 * @protected
20631 * @since 4.2.010 (2008-11-14)
20632 */
20633 protected function getGraphicVars() {
20634 $grapvars = array(
20635 'FontFamily' => $this->FontFamily,
20636 'FontStyle' => $this->FontStyle,
20637 'FontSizePt' => $this->FontSizePt,
20638 'rMargin' => $this->rMargin,
20639 'lMargin' => $this->lMargin,
20640 'cell_padding' => $this->cell_padding,
20641 'cell_margin' => $this->cell_margin,
20642 'LineWidth' => $this->LineWidth,
20643 'linestyleWidth' => $this->linestyleWidth,
20644 'linestyleCap' => $this->linestyleCap,
20645 'linestyleJoin' => $this->linestyleJoin,
20646 'linestyleDash' => $this->linestyleDash,
20647 'textrendermode' => $this->textrendermode,
20648 'textstrokewidth' => $this->textstrokewidth,
20649 'DrawColor' => $this->DrawColor,
20650 'FillColor' => $this->FillColor,
20651 'TextColor' => $this->TextColor,
20652 'ColorFlag' => $this->ColorFlag,
20653 'bgcolor' => $this->bgcolor,
20654 'fgcolor' => $this->fgcolor,
20655 'htmlvspace' => $this->htmlvspace,
20656 'listindent' => $this->listindent,
20657 'listindentlevel' => $this->listindentlevel,
20658 'listnum' => $this->listnum,
20659 'listordered' => $this->listordered,
20660 'listcount' => $this->listcount,
20661 'lispacer' => $this->lispacer,
20662 'cell_height_ratio' => $this->cell_height_ratio,
20663 'font_stretching' => $this->font_stretching,
20664 'font_spacing' => $this->font_spacing,
20665 'alpha' => $this->alpha,
20666 // extended
20667 'lasth' => $this->lasth,
20668 'tMargin' => $this->tMargin,
20669 'bMargin' => $this->bMargin,
20670 'AutoPageBreak' => $this->AutoPageBreak,
20671 'PageBreakTrigger' => $this->PageBreakTrigger,
20672 'x' => $this->x,
20673 'y' => $this->y,
20674 'w' => $this->w,
20675 'h' => $this->h,
20676 'wPt' => $this->wPt,
20677 'hPt' => $this->hPt,
20678 'fwPt' => $this->fwPt,
20679 'fhPt' => $this->fhPt,
20680 'page' => $this->page,
20681 'current_column' => $this->current_column,
20682 'num_columns' => $this->num_columns
20683 );
20684 return $grapvars;
20685 }
20686
20687 /**
20688 * Set graphic variables.
20689 * @param $gvars (array) array of graphic variablesto restore
20690 * @param $extended (boolean) if true restore extended graphic variables
20691 * @protected
20692 * @since 4.2.010 (2008-11-14)
20693 */
20694 protected function setGraphicVars($gvars, $extended=false) {
20695 if ($this->state != 2) {
20696 return;
20697 }
20698 $this->FontFamily = $gvars['FontFamily'];
20699 $this->FontStyle = $gvars['FontStyle'];
20700 $this->FontSizePt = $gvars['FontSizePt'];
20701 $this->rMargin = $gvars['rMargin'];
20702 $this->lMargin = $gvars['lMargin'];
20703 $this->cell_padding = $gvars['cell_padding'];
20704 $this->cell_margin = $gvars['cell_margin'];
20705 $this->LineWidth = $gvars['LineWidth'];
20706 $this->linestyleWidth = $gvars['linestyleWidth'];
20707 $this->linestyleCap = $gvars['linestyleCap'];
20708 $this->linestyleJoin = $gvars['linestyleJoin'];
20709 $this->linestyleDash = $gvars['linestyleDash'];
20710 $this->textrendermode = $gvars['textrendermode'];
20711 $this->textstrokewidth = $gvars['textstrokewidth'];
20712 $this->DrawColor = $gvars['DrawColor'];
20713 $this->FillColor = $gvars['FillColor'];
20714 $this->TextColor = $gvars['TextColor'];
20715 $this->ColorFlag = $gvars['ColorFlag'];
20716 $this->bgcolor = $gvars['bgcolor'];
20717 $this->fgcolor = $gvars['fgcolor'];
20718 $this->htmlvspace = $gvars['htmlvspace'];
20719 $this->listindent = $gvars['listindent'];
20720 $this->listindentlevel = $gvars['listindentlevel'];
20721 $this->listnum = $gvars['listnum'];
20722 $this->listordered = $gvars['listordered'];
20723 $this->listcount = $gvars['listcount'];
20724 $this->lispacer = $gvars['lispacer'];
20725 $this->cell_height_ratio = $gvars['cell_height_ratio'];
20726 $this->font_stretching = $gvars['font_stretching'];
20727 $this->font_spacing = $gvars['font_spacing'];
20728 $this->alpha = $gvars['alpha'];
20729 if ($extended) {
20730 // restore extended values
20731 $this->lasth = $gvars['lasth'];
20732 $this->tMargin = $gvars['tMargin'];
20733 $this->bMargin = $gvars['bMargin'];
20734 $this->AutoPageBreak = $gvars['AutoPageBreak'];
20735 $this->PageBreakTrigger = $gvars['PageBreakTrigger'];
20736 $this->x = $gvars['x'];
20737 $this->y = $gvars['y'];
20738 $this->w = $gvars['w'];
20739 $this->h = $gvars['h'];
20740 $this->wPt = $gvars['wPt'];
20741 $this->hPt = $gvars['hPt'];
20742 $this->fwPt = $gvars['fwPt'];
20743 $this->fhPt = $gvars['fhPt'];
20744 $this->page = $gvars['page'];
20745 $this->current_column = $gvars['current_column'];
20746 $this->num_columns = $gvars['num_columns'];
20747 }
20748 $this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
20749 if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
20750 $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
20751 }
20752 }
20753
20754 /**
20755 * Outputs the "save graphics state" operator 'q'
20756 * @protected
20757 */
20758 protected function _outSaveGraphicsState() {
20759 $this->_out('q');
20760 }
20761
20762 /**
20763 * Outputs the "restore graphics state" operator 'Q'
20764 * @protected
20765 */
20766 protected function _outRestoreGraphicsState() {
20767 $this->_out('Q');
20768 }
20769
20770 /**
20771 * Writes data to a temporary file on filesystem.
20772 * @param $filename (string) file name
20773 * @param $data (mixed) data to write on file
20774 * @param $append (boolean) if true append data, false replace.
20775 * @param $serialize (boolean) if true serialize data.
20776 * @since 4.5.000 (2008-12-31)
20777 * @protected
20778 */
20779 protected function writeDiskCache($filename, $data, $append=false, $serialize=false) {
20780 if ($append) {
20781 $fmode = 'ab+';
20782 } else {
20783 $fmode = 'wb+';
20784 }
20785 $f = @fopen($filename, $fmode);
20786 if (!$f) {
20787 $this->Error('Unable to write cache file: '.$filename);
20788 }
20789 if ($serialize) {
20790 $data = $this->file_id.serialize($data);
20791 }
20792 fwrite($f, $data);
20793 fclose($f);
20794 // update file length (needed for transactions)
20795 if (!isset($this->cache_file_length['_'.$filename])) {
20796 $this->cache_file_length['_'.$filename] = strlen($data);
20797 } else {
20798 $this->cache_file_length['_'.$filename] += strlen($data);
20799 }
20800 }
20801
20802 /**
20803 * Read data from a temporary file on filesystem.
20804 * @param $filename (string) file name
20805 * @param $unserialize (boolean) if true unserialize data.
20806 * @return mixed retrieved data
20807 * @since 4.5.000 (2008-12-31)
20808 * @protected
20809 */
20810 protected function readDiskCache($filename, $unserialize=false) {
20811 $data = file_get_contents($filename);
20812 if ($data === FALSE) {
20813 $this->Error('Unable to read the file: '.$filename);
20814 }
20815 if ($unserialize) {
20816 if (substr($data, 0, 32) != $this->file_id) {
20817 $this->Error('Invalid cache file: '.$filename);
20818 }
20819 $data = unserialize(substr($data, 32));
20820 }
20821 return $data;
20822 }
20823
20824 /**
20825 * Set buffer content (always append data).
20826 * @param $data (string) data
20827 * @protected
20828 * @since 4.5.000 (2009-01-02)
20829 */
20830 protected function setBuffer($data) {
20831 $this->bufferlen += strlen($data);
20832 if ($this->diskcache) {
20833 if (!isset($this->buffer) OR TCPDF_STATIC::empty_string($this->buffer)) {
20834 $this->buffer = TCPDF_STATIC::getObjFilename('buf');
20835 }
20836 $this->writeDiskCache($this->buffer, $data, true, false);
20837 } else {
20838 $this->buffer .= $data;
20839 }
20840 }
20841
20842 /**
20843 * Replace the buffer content
20844 * @param $data (string) data
20845 * @protected
20846 * @since 5.5.000 (2010-06-22)
20847 */
20848 protected function replaceBuffer($data) {
20849 $this->bufferlen = strlen($data);
20850 if ($this->diskcache) {
20851 if (!isset($this->buffer) OR TCPDF_STATIC::empty_string($this->buffer)) {
20852 $this->buffer = TCPDF_STATIC::getObjFilename('buf');
20853 }
20854 $this->writeDiskCache($this->buffer, $data, false, false);
20855 } else {
20856 $this->buffer = $data;
20857 }
20858 }
20859
20860 /**
20861 * Get buffer content.
20862 * @return string buffer content
20863 * @protected
20864 * @since 4.5.000 (2009-01-02)
20865 */
20866 protected function getBuffer() {
20867 if ($this->diskcache) {
20868 return $this->readDiskCache($this->buffer, false);
20869 } else {
20870 return $this->buffer;
20871 }
20872 }
20873
20874 /**
20875 * Set page buffer content.
20876 * @param $page (int) page number
20877 * @param $data (string) page data
20878 * @param $append (boolean) if true append data, false replace.
20879 * @protected
20880 * @since 4.5.000 (2008-12-31)
20881 */
20882 protected function setPageBuffer($page, $data, $append=false) {
20883 if ($this->diskcache) {
20884 if (!isset($this->pages[$page])) {
20885 $this->pages[$page] = TCPDF_STATIC::getObjFilename('page');
20886 }
20887 $this->writeDiskCache($this->pages[$page], $data, $append, false);
20888 } else {
20889 if ($append) {
20890 $this->pages[$page] .= $data;
20891 } else {
20892 $this->pages[$page] = $data;
20893 }
20894 }
20895 if ($append AND isset($this->pagelen[$page])) {
20896 $this->pagelen[$page] += strlen($data);
20897 } else {
20898 $this->pagelen[$page] = strlen($data);
20899 }
20900 }
20901
20902 /**
20903 * Get page buffer content.
20904 * @param $page (int) page number
20905 * @return string page buffer content or false in case of error
20906 * @protected
20907 * @since 4.5.000 (2008-12-31)
20908 */
20909 protected function getPageBuffer($page) {
20910 if ($this->diskcache) {
20911 return $this->readDiskCache($this->pages[$page], false);
20912 } elseif (isset($this->pages[$page])) {
20913 return $this->pages[$page];
20914 }
20915 return false;
20916 }
20917
20918 /**
20919 * Set image buffer content.
20920 * @param $image (string) image key
20921 * @param $data (array) image data
20922 * @return int image index number
20923 * @protected
20924 * @since 4.5.000 (2008-12-31)
20925 */
20926 protected function setImageBuffer($image, $data) {
20927 if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
20928 $this->imagekeys[$this->numimages] = $image;
20929 $data['i'] = $this->numimages;
20930 ++$this->numimages;
20931 }
20932 if ($this->diskcache) {
20933 if (!isset($this->images[$image])) {
20934 $this->images[$image] = TCPDF_STATIC::getObjFilename('img');
20935 }
20936 $this->writeDiskCache($this->images[$image], $data, false, true);
20937 } else {
20938 $this->images[$image] = $data;
20939 }
20940 return $data['i'];
20941 }
20942
20943 /**
20944 * Set image buffer content for a specified sub-key.
20945 * @param $image (string) image key
20946 * @param $key (string) image sub-key
20947 * @param $data (array) image data
20948 * @protected
20949 * @since 4.5.000 (2008-12-31)
20950 */
20951 protected function setImageSubBuffer($image, $key, $data) {
20952 if (!isset($this->images[$image])) {
20953 $this->setImageBuffer($image, array());
20954 }
20955 if ($this->diskcache) {
20956 $tmpimg = $this->getImageBuffer($image);
20957 $tmpimg[$key] = $data;
20958 $this->writeDiskCache($this->images[$image], $tmpimg, false, true);
20959 } else {
20960 $this->images[$image][$key] = $data;
20961 }
20962 }
20963
20964 /**
20965 * Get image buffer content.
20966 * @param $image (string) image key
20967 * @return string image buffer content or false in case of error
20968 * @protected
20969 * @since 4.5.000 (2008-12-31)
20970 */
20971 protected function getImageBuffer($image) {
20972 if ($this->diskcache AND isset($this->images[$image])) {
20973 return $this->readDiskCache($this->images[$image], true);
20974 } elseif (isset($this->images[$image])) {
20975 return $this->images[$image];
20976 }
20977 return false;
20978 }
20979
20980 /**
20981 * Set font buffer content.
20982 * @param $font (string) font key
20983 * @param $data (array) font data
20984 * @protected
20985 * @since 4.5.000 (2009-01-02)
20986 */
20987 protected function setFontBuffer($font, $data) {
20988 if ($this->diskcache) {
20989 if (!isset($this->fonts[$font])) {
20990 $this->fonts[$font] = TCPDF_STATIC::getObjFilename('font');
20991 }
20992 $this->writeDiskCache($this->fonts[$font], $data, false, true);
20993 } else {
20994 $this->fonts[$font] = $data;
20995 }
20996 if (!in_array($font, $this->fontkeys)) {
20997 $this->fontkeys[] = $font;
20998 // store object ID for current font
20999 ++$this->n;
21000 $this->font_obj_ids[$font] = $this->n;
21001 $this->setFontSubBuffer($font, 'n', $this->n);
21002 }
21003 }
21004
21005 /**
21006 * Set font buffer content.
21007 * @param $font (string) font key
21008 * @param $key (string) font sub-key
21009 * @param $data (array) font data
21010 * @protected
21011 * @since 4.5.000 (2009-01-02)
21012 */
21013 protected function setFontSubBuffer($font, $key, $data) {
21014 if (!isset($this->fonts[$font])) {
21015 $this->setFontBuffer($font, array());
21016 }
21017 if ($this->diskcache) {
21018 $tmpfont = $this->getFontBuffer($font);
21019 $tmpfont[$key] = $data;
21020 $this->writeDiskCache($this->fonts[$font], $tmpfont, false, true);
21021 } else {
21022 $this->fonts[$font][$key] = $data;
21023 }
21024 }
21025
21026 /**
21027 * Get font buffer content.
21028 * @param $font (string) font key
21029 * @return string font buffer content or false in case of error
21030 * @protected
21031 * @since 4.5.000 (2009-01-02)
21032 */
21033 protected function getFontBuffer($font) {
21034 if ($this->diskcache AND isset($this->fonts[$font])) {
21035 return $this->readDiskCache($this->fonts[$font], true);
21036 } elseif (isset($this->fonts[$font])) {
21037 return $this->fonts[$font];
21038 }
21039 return false;
21040 }
21041
21042 /**
21043 * Move a page to a previous position.
21044 * @param $frompage (int) number of the source page
21045 * @param $topage (int) number of the destination page (must be less than $frompage)
21046 * @return true in case of success, false in case of error.
21047 * @public
21048 * @since 4.5.000 (2009-01-02)
21049 */
21050 public function movePage($frompage, $topage) {
21051 if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
21052 return false;
21053 }
21054 if ($frompage == $this->page) {
21055 // close the page before moving it
21056 $this->endPage();
21057 }
21058 // move all page-related states
21059 $tmppage = $this->getPageBuffer($frompage);
21060 $tmppagedim = $this->pagedim[$frompage];
21061 $tmppagelen = $this->pagelen[$frompage];
21062 $tmpintmrk = $this->intmrk[$frompage];
21063 $tmpbordermrk = $this->bordermrk[$frompage];
21064 $tmpcntmrk = $this->cntmrk[$frompage];
21065 $tmppageobjects = $this->pageobjects[$frompage];
21066 if (isset($this->footerpos[$frompage])) {
21067 $tmpfooterpos = $this->footerpos[$frompage];
21068 }
21069 if (isset($this->footerlen[$frompage])) {
21070 $tmpfooterlen = $this->footerlen[$frompage];
21071 }
21072 if (isset($this->transfmrk[$frompage])) {
21073 $tmptransfmrk = $this->transfmrk[$frompage];
21074 }
21075 if (isset($this->PageAnnots[$frompage])) {
21076 $tmpannots = $this->PageAnnots[$frompage];
21077 }
21078 if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21079 for ($i = $frompage; $i > $topage; --$i) {
21080 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
21081 --$this->pagegroups[$this->newpagegroup[$i]];
21082 break;
21083 }
21084 }
21085 for ($i = $topage; $i > 0; --$i) {
21086 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
21087 ++$this->pagegroups[$this->newpagegroup[$i]];
21088 break;
21089 }
21090 }
21091 }
21092 for ($i = $frompage; $i > $topage; --$i) {
21093 $j = $i - 1;
21094 // shift pages down
21095 $this->setPageBuffer($i, $this->getPageBuffer($j));
21096 $this->pagedim[$i] = $this->pagedim[$j];
21097 $this->pagelen[$i] = $this->pagelen[$j];
21098 $this->intmrk[$i] = $this->intmrk[$j];
21099 $this->bordermrk[$i] = $this->bordermrk[$j];
21100 $this->cntmrk[$i] = $this->cntmrk[$j];
21101 $this->pageobjects[$i] = $this->pageobjects[$j];
21102 if (isset($this->footerpos[$j])) {
21103 $this->footerpos[$i] = $this->footerpos[$j];
21104 } elseif (isset($this->footerpos[$i])) {
21105 unset($this->footerpos[$i]);
21106 }
21107 if (isset($this->footerlen[$j])) {
21108 $this->footerlen[$i] = $this->footerlen[$j];
21109 } elseif (isset($this->footerlen[$i])) {
21110 unset($this->footerlen[$i]);
21111 }
21112 if (isset($this->transfmrk[$j])) {
21113 $this->transfmrk[$i] = $this->transfmrk[$j];
21114 } elseif (isset($this->transfmrk[$i])) {
21115 unset($this->transfmrk[$i]);
21116 }
21117 if (isset($this->PageAnnots[$j])) {
21118 $this->PageAnnots[$i] = $this->PageAnnots[$j];
21119 } elseif (isset($this->PageAnnots[$i])) {
21120 unset($this->PageAnnots[$i]);
21121 }
21122 if (isset($this->newpagegroup[$j])) {
21123 $this->newpagegroup[$i] = $this->newpagegroup[$j];
21124 unset($this->newpagegroup[$j]);
21125 }
21126 if ($this->currpagegroup == $j) {
21127 $this->currpagegroup = $i;
21128 }
21129 }
21130 $this->setPageBuffer($topage, $tmppage);
21131 $this->pagedim[$topage] = $tmppagedim;
21132 $this->pagelen[$topage] = $tmppagelen;
21133 $this->intmrk[$topage] = $tmpintmrk;
21134 $this->bordermrk[$topage] = $tmpbordermrk;
21135 $this->cntmrk[$topage] = $tmpcntmrk;
21136 $this->pageobjects[$topage] = $tmppageobjects;
21137 if (isset($tmpfooterpos)) {
21138 $this->footerpos[$topage] = $tmpfooterpos;
21139 } elseif (isset($this->footerpos[$topage])) {
21140 unset($this->footerpos[$topage]);
21141 }
21142 if (isset($tmpfooterlen)) {
21143 $this->footerlen[$topage] = $tmpfooterlen;
21144 } elseif (isset($this->footerlen[$topage])) {
21145 unset($this->footerlen[$topage]);
21146 }
21147 if (isset($tmptransfmrk)) {
21148 $this->transfmrk[$topage] = $tmptransfmrk;
21149 } elseif (isset($this->transfmrk[$topage])) {
21150 unset($this->transfmrk[$topage]);
21151 }
21152 if (isset($tmpannots)) {
21153 $this->PageAnnots[$topage] = $tmpannots;
21154 } elseif (isset($this->PageAnnots[$topage])) {
21155 unset($this->PageAnnots[$topage]);
21156 }
21157 // adjust outlines
21158 $tmpoutlines = $this->outlines;
21159 foreach ($tmpoutlines as $key => $outline) {
21160 if (!$outline['f']) {
21161 if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
21162 $this->outlines[$key]['p'] = ($outline['p'] + 1);
21163 } elseif ($outline['p'] == $frompage) {
21164 $this->outlines[$key]['p'] = $topage;
21165 }
21166 }
21167 }
21168 // adjust dests
21169 $tmpdests = $this->dests;
21170 foreach ($tmpdests as $key => $dest) {
21171 if (!$dest['f']) {
21172 if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
21173 $this->dests[$key]['p'] = ($dest['p'] + 1);
21174 } elseif ($dest['p'] == $frompage) {
21175 $this->dests[$key]['p'] = $topage;
21176 }
21177 }
21178 }
21179 // adjust links
21180 $tmplinks = $this->links;
21181 foreach ($tmplinks as $key => $link) {
21182 if (!$link['f']) {
21183 if (($link['p'] >= $topage) AND ($link['p'] < $frompage)) {
21184 $this->links[$key]['p'] = ($link['p'] + 1);
21185 } elseif ($link['p'] == $frompage) {
21186 $this->links[$key]['p'] = $topage;
21187 }
21188 }
21189 }
21190 // adjust javascript
21191 $jfrompage = $frompage;
21192 $jtopage = $topage;
21193 if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21194 foreach($pamatch[0] as $pk => $pmatch) {
21195 $pagenum = intval($pamatch[3][$pk]) + 1;
21196 if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
21197 $newpage = ($pagenum + 1);
21198 } elseif ($pagenum == $jfrompage) {
21199 $newpage = $jtopage;
21200 } else {
21201 $newpage = $pagenum;
21202 }
21203 --$newpage;
21204 $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21205 $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21206 }
21207 unset($pamatch);
21208 }
21209 // return to last page
21210 $this->lastPage(true);
21211 return true;
21212 }
21213
21214 /**
21215 * Remove the specified page.
21216 * @param $page (int) page to remove
21217 * @return true in case of success, false in case of error.
21218 * @public
21219 * @since 4.6.004 (2009-04-23)
21220 */
21221 public function deletePage($page) {
21222 if (($page < 1) OR ($page > $this->numpages)) {
21223 return false;
21224 }
21225 // delete current page
21226 unset($this->pages[$page]);
21227 unset($this->pagedim[$page]);
21228 unset($this->pagelen[$page]);
21229 unset($this->intmrk[$page]);
21230 unset($this->bordermrk[$page]);
21231 unset($this->cntmrk[$page]);
21232 foreach ($this->pageobjects[$page] as $oid) {
21233 if (isset($this->offsets[$oid])){
21234 unset($this->offsets[$oid]);
21235 }
21236 }
21237 unset($this->pageobjects[$page]);
21238 if (isset($this->footerpos[$page])) {
21239 unset($this->footerpos[$page]);
21240 }
21241 if (isset($this->footerlen[$page])) {
21242 unset($this->footerlen[$page]);
21243 }
21244 if (isset($this->transfmrk[$page])) {
21245 unset($this->transfmrk[$page]);
21246 }
21247 if (isset($this->PageAnnots[$page])) {
21248 unset($this->PageAnnots[$page]);
21249 }
21250 if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21251 for ($i = $page; $i > 0; --$i) {
21252 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
21253 --$this->pagegroups[$this->newpagegroup[$i]];
21254 break;
21255 }
21256 }
21257 }
21258 if (isset($this->pageopen[$page])) {
21259 unset($this->pageopen[$page]);
21260 }
21261 if ($page < $this->numpages) {
21262 // update remaining pages
21263 for ($i = $page; $i < $this->numpages; ++$i) {
21264 $j = $i + 1;
21265 // shift pages
21266 $this->setPageBuffer($i, $this->getPageBuffer($j));
21267 $this->pagedim[$i] = $this->pagedim[$j];
21268 $this->pagelen[$i] = $this->pagelen[$j];
21269 $this->intmrk[$i] = $this->intmrk[$j];
21270 $this->bordermrk[$i] = $this->bordermrk[$j];
21271 $this->cntmrk[$i] = $this->cntmrk[$j];
21272 $this->pageobjects[$i] = $this->pageobjects[$j];
21273 if (isset($this->footerpos[$j])) {
21274 $this->footerpos[$i] = $this->footerpos[$j];
21275 } elseif (isset($this->footerpos[$i])) {
21276 unset($this->footerpos[$i]);
21277 }
21278 if (isset($this->footerlen[$j])) {
21279 $this->footerlen[$i] = $this->footerlen[$j];
21280 } elseif (isset($this->footerlen[$i])) {
21281 unset($this->footerlen[$i]);
21282 }
21283 if (isset($this->transfmrk[$j])) {
21284 $this->transfmrk[$i] = $this->transfmrk[$j];
21285 } elseif (isset($this->transfmrk[$i])) {
21286 unset($this->transfmrk[$i]);
21287 }
21288 if (isset($this->PageAnnots[$j])) {
21289 $this->PageAnnots[$i] = $this->PageAnnots[$j];
21290 } elseif (isset($this->PageAnnots[$i])) {
21291 unset($this->PageAnnots[$i]);
21292 }
21293 if (isset($this->newpagegroup[$j])) {
21294 $this->newpagegroup[$i] = $this->newpagegroup[$j];
21295 unset($this->newpagegroup[$j]);
21296 }
21297 if ($this->currpagegroup == $j) {
21298 $this->currpagegroup = $i;
21299 }
21300 if (isset($this->pageopen[$j])) {
21301 $this->pageopen[$i] = $this->pageopen[$j];
21302 } elseif (isset($this->pageopen[$i])) {
21303 unset($this->pageopen[$i]);
21304 }
21305 }
21306 // remove last page
21307 unset($this->pages[$this->numpages]);
21308 unset($this->pagedim[$this->numpages]);
21309 unset($this->pagelen[$this->numpages]);
21310 unset($this->intmrk[$this->numpages]);
21311 unset($this->bordermrk[$this->numpages]);
21312 unset($this->cntmrk[$this->numpages]);
21313 foreach ($this->pageobjects[$this->numpages] as $oid) {
21314 if (isset($this->offsets[$oid])){
21315 unset($this->offsets[$oid]);
21316 }
21317 }
21318 unset($this->pageobjects[$this->numpages]);
21319 if (isset($this->footerpos[$this->numpages])) {
21320 unset($this->footerpos[$this->numpages]);
21321 }
21322 if (isset($this->footerlen[$this->numpages])) {
21323 unset($this->footerlen[$this->numpages]);
21324 }
21325 if (isset($this->transfmrk[$this->numpages])) {
21326 unset($this->transfmrk[$this->numpages]);
21327 }
21328 if (isset($this->PageAnnots[$this->numpages])) {
21329 unset($this->PageAnnots[$this->numpages]);
21330 }
21331 if (isset($this->newpagegroup[$this->numpages])) {
21332 unset($this->newpagegroup[$this->numpages]);
21333 }
21334 if ($this->currpagegroup == $this->numpages) {
21335 $this->currpagegroup = ($this->numpages - 1);
21336 }
21337 if (isset($this->pagegroups[$this->numpages])) {
21338 unset($this->pagegroups[$this->numpages]);
21339 }
21340 if (isset($this->pageopen[$this->numpages])) {
21341 unset($this->pageopen[$this->numpages]);
21342 }
21343 }
21344 --$this->numpages;
21345 $this->page = $this->numpages;
21346 // adjust outlines
21347 $tmpoutlines = $this->outlines;
21348 foreach ($tmpoutlines as $key => $outline) {
21349 if (!$outline['f']) {
21350 if ($outline['p'] > $page) {
21351 $this->outlines[$key]['p'] = $outline['p'] - 1;
21352 } elseif ($outline['p'] == $page) {
21353 unset($this->outlines[$key]);
21354 }
21355 }
21356 }
21357 // adjust dests
21358 $tmpdests = $this->dests;
21359 foreach ($tmpdests as $key => $dest) {
21360 if (!$dest['f']) {
21361 if ($dest['p'] > $page) {
21362 $this->dests[$key]['p'] = $dest['p'] - 1;
21363 } elseif ($dest['p'] == $page) {
21364 unset($this->dests[$key]);
21365 }
21366 }
21367 }
21368 // adjust links
21369 $tmplinks = $this->links;
21370 foreach ($tmplinks as $key => $link) {
21371 if (!$link['f']) {
21372 if ($link['p'] > $page) {
21373 $this->links[$key]['p'] = $link['p'] - 1;
21374 } elseif ($link['p'] == $page) {
21375 unset($this->links[$key]);
21376 }
21377 }
21378 }
21379 // adjust javascript
21380 $jpage = $page;
21381 if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21382 foreach($pamatch[0] as $pk => $pmatch) {
21383 $pagenum = intval($pamatch[3][$pk]) + 1;
21384 if ($pagenum >= $jpage) {
21385 $newpage = ($pagenum - 1);
21386 } elseif ($pagenum == $jpage) {
21387 $newpage = 1;
21388 } else {
21389 $newpage = $pagenum;
21390 }
21391 --$newpage;
21392 $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21393 $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21394 }
21395 unset($pamatch);
21396 }
21397 // return to last page
21398 if ($this->numpages > 0) {
21399 $this->lastPage(true);
21400 }
21401 return true;
21402 }
21403
21404 /**
21405 * Clone the specified page to a new page.
21406 * @param $page (int) number of page to copy (0 = current page)
21407 * @return true in case of success, false in case of error.
21408 * @public
21409 * @since 4.9.015 (2010-04-20)
21410 */
21411 public function copyPage($page=0) {
21412 if ($page == 0) {
21413 // default value
21414 $page = $this->page;
21415 }
21416 if (($page < 1) OR ($page > $this->numpages)) {
21417 return false;
21418 }
21419 // close the last page
21420 $this->endPage();
21421 // copy all page-related states
21422 ++$this->numpages;
21423 $this->page = $this->numpages;
21424 $this->setPageBuffer($this->page, $this->getPageBuffer($page));
21425 $this->pagedim[$this->page] = $this->pagedim[$page];
21426 $this->pagelen[$this->page] = $this->pagelen[$page];
21427 $this->intmrk[$this->page] = $this->intmrk[$page];
21428 $this->bordermrk[$this->page] = $this->bordermrk[$page];
21429 $this->cntmrk[$this->page] = $this->cntmrk[$page];
21430 $this->pageobjects[$this->page] = $this->pageobjects[$page];
21431 $this->pageopen[$this->page] = false;
21432 if (isset($this->footerpos[$page])) {
21433 $this->footerpos[$this->page] = $this->footerpos[$page];
21434 }
21435 if (isset($this->footerlen[$page])) {
21436 $this->footerlen[$this->page] = $this->footerlen[$page];
21437 }
21438 if (isset($this->transfmrk[$page])) {
21439 $this->transfmrk[$this->page] = $this->transfmrk[$page];
21440 }
21441 if (isset($this->PageAnnots[$page])) {
21442 $this->PageAnnots[$this->page] = $this->PageAnnots[$page];
21443 }
21444 if (isset($this->newpagegroup[$page])) {
21445 // start a new group
21446 $this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
21447 $this->currpagegroup = $this->newpagegroup[$this->page];
21448 $this->pagegroups[$this->currpagegroup] = 1;
21449 } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
21450 ++$this->pagegroups[$this->currpagegroup];
21451 }
21452 // copy outlines
21453 $tmpoutlines = $this->outlines;
21454 foreach ($tmpoutlines as $key => $outline) {
21455 if ($outline['p'] == $page) {
21456 $this->outlines[] = array('t' => $outline['t'], 'l' => $outline['l'], 'x' => $outline['x'], 'y' => $outline['y'], 'p' => $this->page, 'f' => $outline['f'], 's' => $outline['s'], 'c' => $outline['c']);
21457 }
21458 }
21459 // copy links
21460 $tmplinks = $this->links;
21461 foreach ($tmplinks as $key => $link) {
21462 if ($link['p'] == $page) {
21463 $this->links[] = array('p' => $this->page, 'y' => $link['y'], 'f' => $link['f']);
21464 }
21465 }
21466 // return to last page
21467 $this->lastPage(true);
21468 return true;
21469 }
21470
21471 /**
21472 * Output a Table of Content Index (TOC).
21473 * This method must be called after all Bookmarks were set.
21474 * Before calling this method you have to open the page using the addTOCPage() method.
21475 * After calling this method you have to call endTOCPage() to close the TOC page.
21476 * You can override this method to achieve different styles.
21477 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21478 * @param $numbersfont (string) set the font for page numbers (please use monospaced font for better alignment).
21479 * @param $filler (string) string used to fill the space between text and page number.
21480 * @param $toc_name (string) name to use for TOC bookmark.
21481 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21482 * @param $color (array) RGB color array for bookmark title (values from 0 to 255).
21483 * @public
21484 * @author Nicola Asuni
21485 * @since 4.5.000 (2009-01-02)
21486 * @see addTOCPage(), endTOCPage(), addHTMLTOC()
21487 */
21488 public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
21489 $fontsize = $this->FontSizePt;
21490 $fontfamily = $this->FontFamily;
21491 $fontstyle = $this->FontStyle;
21492 $w = $this->w - $this->lMargin - $this->rMargin;
21493 $spacer = $this->GetStringWidth(chr(32)) * 4;
21494 $lmargin = $this->lMargin;
21495 $rmargin = $this->rMargin;
21496 $x_start = $this->GetX();
21497 $page_first = $this->page;
21498 $current_page = $this->page;
21499 $page_fill_start = false;
21500 $page_fill_end = false;
21501 $current_column = $this->current_column;
21502 if (TCPDF_STATIC::empty_string($numbersfont)) {
21503 $numbersfont = $this->default_monospaced_font;
21504 }
21505 if (TCPDF_STATIC::empty_string($filler)) {
21506 $filler = ' ';
21507 }
21508 if (TCPDF_STATIC::empty_string($page)) {
21509 $gap = ' ';
21510 } else {
21511 $gap = '';
21512 if ($page < 1) {
21513 $page = 1;
21514 }
21515 }
21516 $this->SetFont($numbersfont, $fontstyle, $fontsize);
21517 $numwidth = $this->GetStringWidth('00000');
21518 $maxpage = 0; //used for pages on attached documents
21519 foreach ($this->outlines as $key => $outline) {
21520 // check for extra pages (used for attachments)
21521 if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
21522 $outline['p'] += ($this->page - $page_first);
21523 }
21524 if ($this->rtl) {
21525 $aligntext = 'R';
21526 $alignnum = 'L';
21527 } else {
21528 $aligntext = 'L';
21529 $alignnum = 'R';
21530 }
21531 if ($outline['l'] == 0) {
21532 $this->SetFont($fontfamily, $outline['s'].'B', $fontsize);
21533 } else {
21534 $this->SetFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
21535 }
21536 $this->SetTextColorArray($outline['c']);
21537 // check for page break
21538 $this->checkPageBreak(2 * $this->getCellHeight($this->FontSize));
21539 // set margins and X position
21540 if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
21541 $this->lMargin = $lmargin;
21542 $this->rMargin = $rmargin;
21543 } else {
21544 if ($this->current_column != $current_column) {
21545 if ($this->rtl) {
21546 $x_start = $this->w - $this->columns[$this->current_column]['x'];
21547 } else {
21548 $x_start = $this->columns[$this->current_column]['x'];
21549 }
21550 }
21551 $lmargin = $this->lMargin;
21552 $rmargin = $this->rMargin;
21553 $current_page = $this->page;
21554 $current_column = $this->current_column;
21555 }
21556 $this->SetX($x_start);
21557 $indent = ($spacer * $outline['l']);
21558 if ($this->rtl) {
21559 $this->x -= $indent;
21560 $this->rMargin = $this->w - $this->x;
21561 } else {
21562 $this->x += $indent;
21563 $this->lMargin = $this->x;
21564 }
21565 $link = $this->AddLink();
21566 $this->SetLink($link, $outline['y'], $outline['p']);
21567 // write the text
21568 if ($this->rtl) {
21569 $txt = ' '.$outline['t'];
21570 } else {
21571 $txt = $outline['t'].' ';
21572 }
21573 $this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
21574 if ($this->rtl) {
21575 $tw = $this->x - $this->lMargin;
21576 } else {
21577 $tw = $this->w - $this->rMargin - $this->x;
21578 }
21579 $this->SetFont($numbersfont, $fontstyle, $fontsize);
21580 if (TCPDF_STATIC::empty_string($page)) {
21581 $pagenum = $outline['p'];
21582 } else {
21583 // placemark to be replaced with the correct number
21584 $pagenum = '{#'.($outline['p']).'}';
21585 if ($this->isUnicodeFont()) {
21586 $pagenum = '{'.$pagenum.'}';
21587 }
21588 $maxpage = max($maxpage, $outline['p']);
21589 }
21590 $fw = ($tw - $this->GetStringWidth($pagenum.$filler));
21591 $wfiller = $this->GetStringWidth($filler);
21592 if ($wfiller > 0) {
21593 $numfills = floor($fw / $wfiller);
21594 } else {
21595 $numfills = 0;
21596 }
21597 if ($numfills > 0) {
21598 $rowfill = str_repeat($filler, $numfills);
21599 } else {
21600 $rowfill = '';
21601 }
21602 if ($this->rtl) {
21603 $pagenum = $pagenum.$gap.$rowfill;
21604 } else {
21605 $pagenum = $rowfill.$gap.$pagenum;
21606 }
21607 // write the number
21608 $this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
21609 }
21610 $page_last = $this->getPage();
21611 $numpages = ($page_last - $page_first + 1);
21612 // account for booklet mode
21613 if ($this->booklet) {
21614 // check if a blank page is required before TOC
21615 $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21616 $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21617 if ($page_fill_start) {
21618 // add a page at the end (to be moved before TOC)
21619 $this->addPage();
21620 ++$page_last;
21621 ++$numpages;
21622 }
21623 if ($page_fill_end) {
21624 // add a page at the end
21625 $this->addPage();
21626 ++$page_last;
21627 ++$numpages;
21628 }
21629 }
21630 $maxpage = max($maxpage, $page_last);
21631 if (!TCPDF_STATIC::empty_string($page)) {
21632 for ($p = $page_first; $p <= $page_last; ++$p) {
21633 // get page data
21634 $temppage = $this->getPageBuffer($p);
21635 for ($n = 1; $n <= $maxpage; ++$n) {
21636 // update page numbers
21637 $a = '{#'.$n.'}';
21638 // get page number aliases
21639 $pnalias = $this->getInternalPageNumberAliases($a);
21640 // calculate replacement number
21641 if (($n >= $page) AND ($n <= $this->numpages)) {
21642 $np = $n + $numpages;
21643 } else {
21644 $np = $n;
21645 }
21646 $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21647 $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21648 // replace aliases with numbers
21649 foreach ($pnalias['u'] as $u) {
21650 $sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
21651 if ($this->rtl) {
21652 $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21653 } else {
21654 $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21655 }
21656 $temppage = str_replace($u, $nr, $temppage);
21657 }
21658 foreach ($pnalias['a'] as $a) {
21659 $sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
21660 if ($this->rtl) {
21661 $nr = $na.' '.$sfill;
21662 } else {
21663 $nr = $sfill.' '.$na;
21664 }
21665 $temppage = str_replace($a, $nr, $temppage);
21666 }
21667 }
21668 // save changes
21669 $this->setPageBuffer($p, $temppage);
21670 }
21671 // move pages
21672 $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21673 if ($page_fill_start) {
21674 $this->movePage($page_last, $page_first);
21675 }
21676 for ($i = 0; $i < $numpages; ++$i) {
21677 $this->movePage($page_last, $page);
21678 }
21679 }
21680 }
21681
21682 /**
21683 * Output a Table Of Content Index (TOC) using HTML templates.
21684 * This method must be called after all Bookmarks were set.
21685 * Before calling this method you have to open the page using the addTOCPage() method.
21686 * After calling this method you have to call endTOCPage() to close the TOC page.
21687 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21688 * @param $toc_name (string) name to use for TOC bookmark.
21689 * @param $templates (array) array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
21690 * @param $correct_align (boolean) if true correct the number alignment (numbers must be in monospaced font like courier and right aligned on LTR, or left aligned on RTL)
21691 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21692 * @param $color (array) RGB color array for title (values from 0 to 255).
21693 * @public
21694 * @author Nicola Asuni
21695 * @since 5.0.001 (2010-05-06)
21696 * @see addTOCPage(), endTOCPage(), addTOC()
21697 */
21698 public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
21699 $filler = ' ';
21700 $prev_htmlLinkColorArray = $this->htmlLinkColorArray;
21701 $prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
21702 // set new style for link
21703 $this->htmlLinkColorArray = array();
21704 $this->htmlLinkFontStyle = '';
21705 $page_first = $this->getPage();
21706 $page_fill_start = false;
21707 $page_fill_end = false;
21708 // get the font type used for numbers in each template
21709 $current_font = $this->FontFamily;
21710 foreach ($templates as $level => $html) {
21711 $dom = $this->getHtmlDomArray($html);
21712 foreach ($dom as $key => $value) {
21713 if ($value['value'] == '#TOC_PAGE_NUMBER#') {
21714 $this->SetFont($dom[($key - 1)]['fontname']);
21715 $templates['F'.$level] = $this->isUnicodeFont();
21716 }
21717 }
21718 }
21719 $this->SetFont($current_font);
21720 $maxpage = 0; //used for pages on attached documents
21721 foreach ($this->outlines as $key => $outline) {
21722 // get HTML template
21723 $row = $templates[$outline['l']];
21724 if (TCPDF_STATIC::empty_string($page)) {
21725 $pagenum = $outline['p'];
21726 } else {
21727 // placemark to be replaced with the correct number
21728 $pagenum = '{#'.($outline['p']).'}';
21729 if ($templates['F'.$outline['l']]) {
21730 $pagenum = '{'.$pagenum.'}';
21731 }
21732 $maxpage = max($maxpage, $outline['p']);
21733 }
21734 // replace templates with current values
21735 $row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
21736 $row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
21737 // add link to page
21738 $row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
21739 // write bookmark entry
21740 $this->writeHTML($row, false, false, true, false, '');
21741 }
21742 // restore link styles
21743 $this->htmlLinkColorArray = $prev_htmlLinkColorArray;
21744 $this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
21745 // move TOC page and replace numbers
21746 $page_last = $this->getPage();
21747 $numpages = ($page_last - $page_first + 1);
21748 // account for booklet mode
21749 if ($this->booklet) {
21750 // check if a blank page is required before TOC
21751 $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21752 $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21753 if ($page_fill_start) {
21754 // add a page at the end (to be moved before TOC)
21755 $this->addPage();
21756 ++$page_last;
21757 ++$numpages;
21758 }
21759 if ($page_fill_end) {
21760 // add a page at the end
21761 $this->addPage();
21762 ++$page_last;
21763 ++$numpages;
21764 }
21765 }
21766 $maxpage = max($maxpage, $page_last);
21767 if (!TCPDF_STATIC::empty_string($page)) {
21768 for ($p = $page_first; $p <= $page_last; ++$p) {
21769 // get page data
21770 $temppage = $this->getPageBuffer($p);
21771 for ($n = 1; $n <= $maxpage; ++$n) {
21772 // update page numbers
21773 $a = '{#'.$n.'}';
21774 // get page number aliases
21775 $pnalias = $this->getInternalPageNumberAliases($a);
21776 // calculate replacement number
21777 if ($n >= $page) {
21778 $np = $n + $numpages;
21779 } else {
21780 $np = $n;
21781 }
21782 $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21783 $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21784 // replace aliases with numbers
21785 foreach ($pnalias['u'] as $u) {
21786 if ($correct_align) {
21787 $sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
21788 if ($this->rtl) {
21789 $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21790 } else {
21791 $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21792 }
21793 } else {
21794 $nr = $nu;
21795 }
21796 $temppage = str_replace($u, $nr, $temppage);
21797 }
21798 foreach ($pnalias['a'] as $a) {
21799 if ($correct_align) {
21800 $sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
21801 if ($this->rtl) {
21802 $nr = $na.' '.$sfill;
21803 } else {
21804 $nr = $sfill.' '.$na;
21805 }
21806 } else {
21807 $nr = $na;
21808 }
21809 $temppage = str_replace($a, $nr, $temppage);
21810 }
21811 }
21812 // save changes
21813 $this->setPageBuffer($p, $temppage);
21814 }
21815 // move pages
21816 $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21817 if ($page_fill_start) {
21818 $this->movePage($page_last, $page_first);
21819 }
21820 for ($i = 0; $i < $numpages; ++$i) {
21821 $this->movePage($page_last, $page);
21822 }
21823 }
21824 }
21825
21826 /**
21827 * Stores a copy of the current TCPDF object used for undo operation.
21828 * @public
21829 * @since 4.5.029 (2009-03-19)
21830 */
21831 public function startTransaction() {
21832 if (isset($this->objcopy)) {
21833 // remove previous copy
21834 $this->commitTransaction();
21835 }
21836 // record current page number and Y position
21837 $this->start_transaction_page = $this->page;
21838 $this->start_transaction_y = $this->y;
21839 // clone current object
21840 $this->objcopy = TCPDF_STATIC::objclone($this);
21841 }
21842
21843 /**
21844 * Delete the copy of the current TCPDF object used for undo operation.
21845 * @public
21846 * @since 4.5.029 (2009-03-19)
21847 */
21848 public function commitTransaction() {
21849 if (isset($this->objcopy)) {
21850 $this->objcopy->_destroy(true, true);
21851 unset($this->objcopy);
21852 }
21853 }
21854
21855 /**
21856 * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
21857 * @param $self (boolean) if true restores current class object to previous state without the need of reassignment via the returned value.
21858 * @return TCPDF object.
21859 * @public
21860 * @since 4.5.029 (2009-03-19)
21861 */
21862 public function rollbackTransaction($self=false) {
21863 if (isset($this->objcopy)) {
21864 if (isset($this->objcopy->diskcache) AND $this->objcopy->diskcache) {
21865 // truncate files to previous values
21866 foreach ($this->objcopy->cache_file_length as $file => $length) {
21867 $file = substr($file, 1);
21868 $handle = fopen($file, 'r+');
21869 ftruncate($handle, $length);
21870 }
21871 }
21872 $this->_destroy(true, true);
21873 if ($self) {
21874 $objvars = get_object_vars($this->objcopy);
21875 foreach ($objvars as $key => $value) {
21876 $this->$key = $value;
21877 }
21878 }
21879 return $this->objcopy;
21880 }
21881 return $this;
21882 }
21883
21884 // --- MULTI COLUMNS METHODS -----------------------
21885
21886 /**
21887 * Set multiple columns of the same size
21888 * @param $numcols (int) number of columns (set to zero to disable columns mode)
21889 * @param $width (int) column width
21890 * @param $y (int) column starting Y position (leave empty for current Y position)
21891 * @public
21892 * @since 4.9.001 (2010-03-28)
21893 */
21894 public function setEqualColumns($numcols=0, $width=0, $y='') {
21895 $this->columns = array();
21896 if ($numcols < 2) {
21897 $numcols = 0;
21898 $this->columns = array();
21899 } else {
21900 // maximum column width
21901 $maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
21902 if (($width == 0) OR ($width > $maxwidth)) {
21903 $width = $maxwidth;
21904 }
21905 if (TCPDF_STATIC::empty_string($y)) {
21906 $y = $this->y;
21907 }
21908 // space between columns
21909 $space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
21910 // fill the columns array (with, space, starting Y position)
21911 for ($i = 0; $i < $numcols; ++$i) {
21912 $this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
21913 }
21914 }
21915 $this->num_columns = $numcols;
21916 $this->current_column = 0;
21917 $this->column_start_page = $this->page;
21918 $this->selectColumn(0);
21919 }
21920
21921 /**
21922 * Remove columns and reset page margins.
21923 * @public
21924 * @since 5.9.072 (2011-04-26)
21925 */
21926 public function resetColumns() {
21927 $this->lMargin = $this->original_lMargin;
21928 $this->rMargin = $this->original_rMargin;
21929 $this->setEqualColumns();
21930 }
21931
21932 /**
21933 * Set columns array.
21934 * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
21935 * @param $columns (array)
21936 * @public
21937 * @since 4.9.001 (2010-03-28)
21938 */
21939 public function setColumnsArray($columns) {
21940 $this->columns = $columns;
21941 $this->num_columns = count($columns);
21942 $this->current_column = 0;
21943 $this->column_start_page = $this->page;
21944 $this->selectColumn(0);
21945 }
21946
21947 /**
21948 * Set position at a given column
21949 * @param $col (int) column number (from 0 to getNumberOfColumns()-1); empty string = current column.
21950 * @public
21951 * @since 4.9.001 (2010-03-28)
21952 */
21953 public function selectColumn($col='') {
21954 if (is_string($col)) {
21955 $col = $this->current_column;
21956 } elseif ($col >= $this->num_columns) {
21957 $col = 0;
21958 }
21959 $xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
21960 $enable_thead = false;
21961 if ($this->num_columns > 1) {
21962 if ($col != $this->current_column) {
21963 // move Y pointer at the top of the column
21964 if ($this->column_start_page == $this->page) {
21965 $this->y = $this->columns[$col]['y'];
21966 } else {
21967 $this->y = $this->tMargin;
21968 }
21969 // Avoid to write table headers more than once
21970 if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
21971 $enable_thead = true;
21972 $this->maxselcol['page'] = $this->page;
21973 $this->maxselcol['column'] = $col;
21974 }
21975 }
21976 $xshift = $this->colxshift;
21977 // set X position of the current column by case
21978 $listindent = ($this->listindentlevel * $this->listindent);
21979 // calculate column X position
21980 $colpos = 0;
21981 for ($i = 0; $i < $col; ++$i) {
21982 $colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
21983 }
21984 if ($this->rtl) {
21985 $x = $this->w - $this->original_rMargin - $colpos;
21986 $this->rMargin = ($this->w - $x + $listindent);
21987 $this->lMargin = ($x - $this->columns[$col]['w']);
21988 $this->x = $x - $listindent;
21989 } else {
21990 $x = $this->original_lMargin + $colpos;
21991 $this->lMargin = ($x + $listindent);
21992 $this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
21993 $this->x = $x + $listindent;
21994 }
21995 $this->columns[$col]['x'] = $x;
21996 }
21997 $this->current_column = $col;
21998 // fix for HTML mode
21999 $this->newline = true;
22000 // print HTML table header (if any)
22001 if ((!TCPDF_STATIC::empty_string($this->thead)) AND (!$this->inthead)) {
22002 if ($enable_thead) {
22003 // print table header
22004 $this->writeHTML($this->thead, false, false, false, false, '');
22005 $this->y += $xshift['s']['V'];
22006 // store end of header position
22007 if (!isset($this->columns[$col]['th'])) {
22008 $this->columns[$col]['th'] = array();
22009 }
22010 $this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
22011 $this->lasth = 0;
22012 } elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
22013 $this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
22014 }
22015 }
22016 // account for an html table cell over multiple columns
22017 if ($this->rtl) {
22018 $this->rMargin += $xshift['x'];
22019 $this->x -= ($xshift['x'] + $xshift['p']['R']);
22020 } else {
22021 $this->lMargin += $xshift['x'];
22022 $this->x += $xshift['x'] + $xshift['p']['L'];
22023 }
22024 }
22025
22026 /**
22027 * Return the current column number
22028 * @return int current column number
22029 * @public
22030 * @since 5.5.011 (2010-07-08)
22031 */
22032 public function getColumn() {
22033 return $this->current_column;
22034 }
22035
22036 /**
22037 * Return the current number of columns.
22038 * @return int number of columns
22039 * @public
22040 * @since 5.8.018 (2010-08-25)
22041 */
22042 public function getNumberOfColumns() {
22043 return $this->num_columns;
22044 }
22045
22046 /**
22047 * Set Text rendering mode.
22048 * @param $stroke (int) outline size in user units (0 = disable).
22049 * @param $fill (boolean) if true fills the text (default).
22050 * @param $clip (boolean) if true activate clipping mode
22051 * @public
22052 * @since 4.9.008 (2009-04-02)
22053 */
22054 public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
22055 // Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
22056 // convert text rendering parameters
22057 if ($stroke < 0) {
22058 $stroke = 0;
22059 }
22060 if ($fill === true) {
22061 if ($stroke > 0) {
22062 if ($clip === true) {
22063 // Fill, then stroke text and add to path for clipping
22064 $textrendermode = 6;
22065 } else {
22066 // Fill, then stroke text
22067 $textrendermode = 2;
22068 }
22069 $textstrokewidth = $stroke;
22070 } else {
22071 if ($clip === true) {
22072 // Fill text and add to path for clipping
22073 $textrendermode = 4;
22074 } else {
22075 // Fill text
22076 $textrendermode = 0;
22077 }
22078 }
22079 } else {
22080 if ($stroke > 0) {
22081 if ($clip === true) {
22082 // Stroke text and add to path for clipping
22083 $textrendermode = 5;
22084 } else {
22085 // Stroke text
22086 $textrendermode = 1;
22087 }
22088 $textstrokewidth = $stroke;
22089 } else {
22090 if ($clip === true) {
22091 // Add text to path for clipping
22092 $textrendermode = 7;
22093 } else {
22094 // Neither fill nor stroke text (invisible)
22095 $textrendermode = 3;
22096 }
22097 }
22098 }
22099 $this->textrendermode = $textrendermode;
22100 $this->textstrokewidth = $stroke;
22101 }
22102
22103 /**
22104 * Set parameters for drop shadow effect for text.
22105 * @param $params (array) Array of parameters: enabled (boolean) set to true to enable shadow; depth_w (float) shadow width in user units; depth_h (float) shadow height in user units; color (array) shadow color or false to use the stroke color; opacity (float) Alpha value: real value from 0 (transparent) to 1 (opaque); blend_mode (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity.
22106 * @since 5.9.174 (2012-07-25)
22107 * @public
22108 */
22109 public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
22110 if (isset($params['enabled'])) {
22111 $this->txtshadow['enabled'] = $params['enabled']?true:false;
22112 } else {
22113 $this->txtshadow['enabled'] = false;
22114 }
22115 if (isset($params['depth_w'])) {
22116 $this->txtshadow['depth_w'] = floatval($params['depth_w']);
22117 } else {
22118 $this->txtshadow['depth_w'] = 0;
22119 }
22120 if (isset($params['depth_h'])) {
22121 $this->txtshadow['depth_h'] = floatval($params['depth_h']);
22122 } else {
22123 $this->txtshadow['depth_h'] = 0;
22124 }
22125 if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
22126 $this->txtshadow['color'] = $params['color'];
22127 } else {
22128 $this->txtshadow['color'] = $this->strokecolor;
22129 }
22130 if (isset($params['opacity'])) {
22131 $this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
22132 } else {
22133 $this->txtshadow['opacity'] = 1;
22134 }
22135 if (isset($params['blend_mode']) AND in_array($params['blend_mode'], array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
22136 $this->txtshadow['blend_mode'] = $params['blend_mode'];
22137 } else {
22138 $this->txtshadow['blend_mode'] = 'Normal';
22139 }
22140 if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
22141 $this->txtshadow['enabled'] = false;
22142 }
22143 }
22144
22145 /**
22146 * Return the text shadow parameters array.
22147 * @return Array of parameters.
22148 * @since 5.9.174 (2012-07-25)
22149 * @public
22150 */
22151 public function getTextShadow() {
22152 return $this->txtshadow;
22153 }
22154
22155 /**
22156 * Returns an array of chars containing soft hyphens.
22157 * @param $word (array) array of chars
22158 * @param $patterns (array) Array of hypenation patterns.
22159 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algoritm.
22160 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
22161 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
22162 * @param $charmin (int) Minimum word length to apply the hyphenation algoritm.
22163 * @param $charmax (int) Maximum length of broken piece of word.
22164 * @return array text with soft hyphens
22165 * @author Nicola Asuni
22166 * @since 4.9.012 (2010-04-12)
22167 * @protected
22168 */
22169 protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22170 $hyphenword = array(); // hyphens positions
22171 $numchars = count($word);
22172 if ($numchars <= $charmin) {
22173 return $word;
22174 }
22175 $word_string = TCPDF_FONTS::UTF8ArrSubString($word, '', '', $this->isunicode);
22176 // some words will be returned as-is
22177 $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})(\]?)$/';
22178 if (preg_match($pattern, $word_string) > 0) {
22179 // email
22180 return $word;
22181 }
22182 $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})(\]?)$/';
22183 if (preg_match($pattern, $word_string) > 0) {
22184 // URL
22185 return $word;
22186 }
22187 if (isset($dictionary[$word_string])) {
22188 return TCPDF_FONTS::UTF8StringToArray($dictionary[$word_string], $this->isunicode, $this->CurrentFont);
22189 }
22190 // surround word with '_' characters
22191 $tmpword = array_merge(array(46), $word, array(46));
22192 $tmpnumchars = $numchars + 2;
22193 $maxpos = $tmpnumchars - $charmin;
22194 for ($pos = 0; $pos < $maxpos; ++$pos) {
22195 $imax = min(($tmpnumchars - $pos), $charmax);
22196 for ($i = $charmin; $i <= $imax; ++$i) {
22197 $subword = strtolower(TCPDF_FONTS::UTF8ArrSubString($tmpword, $pos, ($pos + $i), $this->isunicode));
22198 if (isset($patterns[$subword])) {
22199 $pattern = TCPDF_FONTS::UTF8StringToArray($patterns[$subword], $this->isunicode, $this->CurrentFont);
22200 $pattern_length = count($pattern);
22201 $digits = 1;
22202 for ($j = 0; $j < $pattern_length; ++$j) {
22203 // check if $pattern[$j] is a number = hyphenation level (only numbers from 1 to 5 are valid)
22204 if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
22205 if ($j == 0) {
22206 $zero = $pos - 1;
22207 } else {
22208 $zero = $pos + $j - $digits;
22209 }
22210 // get hyphenation level
22211 $level = ($pattern[$j] - 48);
22212 // if two levels from two different patterns match at the same point, the higher one is selected.
22213 if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] < $level)) {
22214 $hyphenword[$zero] = $level;
22215 }
22216 ++$digits;
22217 }
22218 }
22219 }
22220 }
22221 }
22222 $inserted = 0;
22223 $maxpos = $numchars - $rightmin;
22224 for ($i = $leftmin; $i <= $maxpos; ++$i) {
22225 // only odd levels indicate allowed hyphenation points
22226 if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
22227 // 173 = soft hyphen character
22228 array_splice($word, $i + $inserted, 0, 173);
22229 ++$inserted;
22230 }
22231 }
22232 return $word;
22233 }
22234
22235 /**
22236 * Returns text with soft hyphens.
22237 * @param $text (string) text to process
22238 * @param $patterns (mixed) Array of hypenation patterns or a TEX file containing hypenation patterns. TEX patterns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
22239 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algoritm.
22240 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
22241 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
22242 * @param $charmin (int) Minimum word length to apply the hyphenation algoritm.
22243 * @param $charmax (int) Maximum length of broken piece of word.
22244 * @return array text with soft hyphens
22245 * @author Nicola Asuni
22246 * @since 4.9.012 (2010-04-12)
22247 * @public
22248 */
22249 public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22250 $text = $this->unhtmlentities($text);
22251 $word = array(); // last word
22252 $txtarr = array(); // text to be returned
22253 $intag = false; // true if we are inside an HTML tag
22254 $skip = false; // true to skip hyphenation
22255 if (!is_array($patterns)) {
22256 $patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patterns);
22257 }
22258 // get array of characters
22259 $unichars = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
22260 // for each char
22261 foreach ($unichars as $char) {
22262 if ((!$intag) AND (!$skip) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
22263 // letter character
22264 $word[] = $char;
22265 } else {
22266 // other type of character
22267 if (!TCPDF_STATIC::empty_string($word)) {
22268 // hypenate the word
22269 $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22270 $word = array();
22271 }
22272 $txtarr[] = $char;
22273 if (chr($char) == '<') {
22274 // we are inside an HTML tag
22275 $intag = true;
22276 } elseif ($intag AND (chr($char) == '>')) {
22277 // end of HTML tag
22278 $intag = false;
22279 // check for style tag
22280 $expected = array(115, 116, 121, 108, 101); // = 'style'
22281 $current = array_slice($txtarr, -6, 5); // last 5 chars
22282 $compare = array_diff($expected, $current);
22283 if (empty($compare)) {
22284 // check if it is a closing tag
22285 $expected = array(47); // = '/'
22286 $current = array_slice($txtarr, -7, 1);
22287 $compare = array_diff($expected, $current);
22288 if (empty($compare)) {
22289 // closing style tag
22290 $skip = false;
22291 } else {
22292 // opening style tag
22293 $skip = true;
22294 }
22295 }
22296 }
22297 }
22298 }
22299 if (!TCPDF_STATIC::empty_string($word)) {
22300 // hypenate the word
22301 $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22302 }
22303 // convert char array to string and return
22304 return TCPDF_FONTS::UTF8ArrSubString($txtarr, '', '', $this->isunicode);
22305 }
22306
22307 /**
22308 * Enable/disable rasterization of vector images using ImageMagick library.
22309 * @param $mode (boolean) if true enable rasterization, false otherwise.
22310 * @public
22311 * @since 5.0.000 (2010-04-27)
22312 */
22313 public function setRasterizeVectorImages($mode) {
22314 $this->rasterize_vector_images = $mode;
22315 }
22316
22317 /**
22318 * Enable or disable default option for font subsetting.
22319 * @param $enable (boolean) if true enable font subsetting by default.
22320 * @author Nicola Asuni
22321 * @public
22322 * @since 5.3.002 (2010-06-07)
22323 */
22324 public function setFontSubsetting($enable=true) {
22325 if ($this->pdfa_mode) {
22326 $this->font_subsetting = false;
22327 } else {
22328 $this->font_subsetting = $enable ? true : false;
22329 }
22330 }
22331
22332 /**
22333 * Return the default option for font subsetting.
22334 * @return boolean default font subsetting state.
22335 * @author Nicola Asuni
22336 * @public
22337 * @since 5.3.002 (2010-06-07)
22338 */
22339 public function getFontSubsetting() {
22340 return $this->font_subsetting;
22341 }
22342
22343 /**
22344 * Left trim the input string
22345 * @param $str (string) string to trim
22346 * @param $replace (string) string that replace spaces.
22347 * @return left trimmed string
22348 * @author Nicola Asuni
22349 * @public
22350 * @since 5.8.000 (2010-08-11)
22351 */
22352 public function stringLeftTrim($str, $replace='') {
22353 return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
22354 }
22355
22356 /**
22357 * Right trim the input string
22358 * @param $str (string) string to trim
22359 * @param $replace (string) string that replace spaces.
22360 * @return right trimmed string
22361 * @author Nicola Asuni
22362 * @public
22363 * @since 5.8.000 (2010-08-11)
22364 */
22365 public function stringRightTrim($str, $replace='') {
22366 return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
22367 }
22368
22369 /**
22370 * Trim the input string
22371 * @param $str (string) string to trim
22372 * @param $replace (string) string that replace spaces.
22373 * @return trimmed string
22374 * @author Nicola Asuni
22375 * @public
22376 * @since 5.8.000 (2010-08-11)
22377 */
22378 public function stringTrim($str, $replace='') {
22379 $str = $this->stringLeftTrim($str, $replace);
22380 $str = $this->stringRightTrim($str, $replace);
22381 return $str;
22382 }
22383
22384 /**
22385 * Return true if the current font is unicode type.
22386 * @return true for unicode font, false otherwise.
22387 * @author Nicola Asuni
22388 * @public
22389 * @since 5.8.002 (2010-08-14)
22390 */
22391 public function isUnicodeFont() {
22392 return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
22393 }
22394
22395 /**
22396 * Return normalized font name
22397 * @param $fontfamily (string) property string containing font family names
22398 * @return string normalized font name
22399 * @author Nicola Asuni
22400 * @public
22401 * @since 5.8.004 (2010-08-17)
22402 */
22403 public function getFontFamilyName($fontfamily) {
22404 // remove spaces and symbols
22405 $fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
22406 // extract all font names
22407 $fontslist = preg_split('/[,]/', $fontfamily);
22408 // find first valid font name
22409 foreach ($fontslist as $font) {
22410 // replace font variations
22411 $font = preg_replace('/regular$/', '', $font);
22412 $font = preg_replace('/italic$/', 'I', $font);
22413 $font = preg_replace('/oblique$/', 'I', $font);
22414 $font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
22415 // replace common family names and core fonts
22416 $pattern = array();
22417 $replacement = array();
22418 $pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
22419 $replacement[] = 'times';
22420 $pattern[] = '/^sansserif/';
22421 $replacement[] = 'helvetica';
22422 $pattern[] = '/^monospace/';
22423 $replacement[] = 'courier';
22424 $font = preg_replace($pattern, $replacement, $font);
22425 if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
22426 return $font;
22427 }
22428 }
22429 // return current font as default
22430 return $this->CurrentFont['fontkey'];
22431 }
22432
22433 /**
22434 * Start a new XObject Template.
22435 * 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).
22436 * 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.
22437 * Note: X,Y coordinates will be reset to 0,0.
22438 * @param $w (int) Template width in user units (empty string or zero = page width less margins).
22439 * @param $h (int) Template height in user units (empty string or zero = page height less margins).
22440 * @param $group (mixed) Set transparency group. Can be a boolean value or an array specifying optional parameters: 'CS' (solour space name), 'I' (boolean flag to indicate isolated group) and 'K' (boolean flag to indicate knockout group).
22441 * @return int the XObject Template ID in case of success or false in case of error.
22442 * @author Nicola Asuni
22443 * @public
22444 * @since 5.8.017 (2010-08-24)
22445 * @see endTemplate(), printTemplate()
22446 */
22447 public function startTemplate($w=0, $h=0, $group=false) {
22448 if ($this->inxobj) {
22449 // we are already inside an XObject template
22450 return false;
22451 }
22452 $this->inxobj = true;
22453 ++$this->n;
22454 // XObject ID
22455 $this->xobjid = 'XT'.$this->n;
22456 // object ID
22457 $this->xobjects[$this->xobjid] = array('n' => $this->n);
22458 // store current graphic state
22459 $this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
22460 // initialize data
22461 $this->xobjects[$this->xobjid]['intmrk'] = 0;
22462 $this->xobjects[$this->xobjid]['transfmrk'] = array();
22463 $this->xobjects[$this->xobjid]['outdata'] = '';
22464 $this->xobjects[$this->xobjid]['xobjects'] = array();
22465 $this->xobjects[$this->xobjid]['images'] = array();
22466 $this->xobjects[$this->xobjid]['fonts'] = array();
22467 $this->xobjects[$this->xobjid]['annotations'] = array();
22468 $this->xobjects[$this->xobjid]['extgstates'] = array();
22469 $this->xobjects[$this->xobjid]['gradients'] = array();
22470 $this->xobjects[$this->xobjid]['spot_colors'] = array();
22471 // set new environment
22472 $this->num_columns = 1;
22473 $this->current_column = 0;
22474 $this->SetAutoPageBreak(false);
22475 if (($w === '') OR ($w <= 0)) {
22476 $w = $this->w - $this->lMargin - $this->rMargin;
22477 }
22478 if (($h === '') OR ($h <= 0)) {
22479 $h = $this->h - $this->tMargin - $this->bMargin;
22480 }
22481 $this->xobjects[$this->xobjid]['x'] = 0;
22482 $this->xobjects[$this->xobjid]['y'] = 0;
22483 $this->xobjects[$this->xobjid]['w'] = $w;
22484 $this->xobjects[$this->xobjid]['h'] = $h;
22485 $this->w = $w;
22486 $this->h = $h;
22487 $this->wPt = $this->w * $this->k;
22488 $this->hPt = $this->h * $this->k;
22489 $this->fwPt = $this->wPt;
22490 $this->fhPt = $this->hPt;
22491 $this->x = 0;
22492 $this->y = 0;
22493 $this->lMargin = 0;
22494 $this->rMargin = 0;
22495 $this->tMargin = 0;
22496 $this->bMargin = 0;
22497 // set group mode
22498 $this->xobjects[$this->xobjid]['group'] = $group;
22499 return $this->xobjid;
22500 }
22501
22502 /**
22503 * End the current XObject Template started with startTemplate() and restore the previous graphic state.
22504 * 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).
22505 * 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.
22506 * @return int the XObject Template ID in case of success or false in case of error.
22507 * @author Nicola Asuni
22508 * @public
22509 * @since 5.8.017 (2010-08-24)
22510 * @see startTemplate(), printTemplate()
22511 */
22512 public function endTemplate() {
22513 if (!$this->inxobj) {
22514 // we are not inside a template
22515 return false;
22516 }
22517 $this->inxobj = false;
22518 // restore previous graphic state
22519 $this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
22520 return $this->xobjid;
22521 }
22522
22523 /**
22524 * Print an XObject Template.
22525 * You can print an XObject Template inside the currently opened Template.
22526 * 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).
22527 * 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.
22528 * @param $id (string) The ID of XObject Template to print.
22529 * @param $x (int) X position in user units (empty string = current x position)
22530 * @param $y (int) Y position in user units (empty string = current y position)
22531 * @param $w (int) Width in user units (zero = remaining page width)
22532 * @param $h (int) Height in user units (zero = remaining page height)
22533 * @param $align (string) Indicates the alignment of the pointer next to template insertion relative to template height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
22534 * @param $palign (string) Allows to center or align the template on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
22535 * @param $fitonpage (boolean) If true the template is resized to not exceed page dimensions.
22536 * @author Nicola Asuni
22537 * @public
22538 * @since 5.8.017 (2010-08-24)
22539 * @see startTemplate(), endTemplate()
22540 */
22541 public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
22542 if ($this->state != 2) {
22543 return;
22544 }
22545 if (!isset($this->xobjects[$id])) {
22546 $this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
22547 }
22548 if ($this->inxobj) {
22549 if ($id == $this->xobjid) {
22550 // close current template
22551 $this->endTemplate();
22552 } else {
22553 // use the template as resource for the template currently opened
22554 $this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
22555 }
22556 }
22557 // set default values
22558 if ($x === '') {
22559 $x = $this->x;
22560 }
22561 if ($y === '') {
22562 $y = $this->y;
22563 }
22564 // check page for no-write regions and adapt page margins if necessary
22565 list($x, $y) = $this->checkPageRegions($h, $x, $y);
22566 $ow = $this->xobjects[$id]['w'];
22567 if ($ow <= 0) {
22568 $ow = 1;
22569 }
22570 $oh = $this->xobjects[$id]['h'];
22571 if ($oh <= 0) {
22572 $oh = 1;
22573 }
22574 // calculate template width and height on document
22575 if (($w <= 0) AND ($h <= 0)) {
22576 $w = $ow;
22577 $h = $oh;
22578 } elseif ($w <= 0) {
22579 $w = $h * $ow / $oh;
22580 } elseif ($h <= 0) {
22581 $h = $w * $oh / $ow;
22582 }
22583 // fit the template on available space
22584 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22585 // set page alignment
22586 $rb_y = $y + $h;
22587 // set alignment
22588 if ($this->rtl) {
22589 if ($palign == 'L') {
22590 $xt = $this->lMargin;
22591 } elseif ($palign == 'C') {
22592 $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22593 } elseif ($palign == 'R') {
22594 $xt = $this->w - $this->rMargin - $w;
22595 } else {
22596 $xt = $x - $w;
22597 }
22598 $rb_x = $xt;
22599 } else {
22600 if ($palign == 'L') {
22601 $xt = $this->lMargin;
22602 } elseif ($palign == 'C') {
22603 $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22604 } elseif ($palign == 'R') {
22605 $xt = $this->w - $this->rMargin - $w;
22606 } else {
22607 $xt = $x;
22608 }
22609 $rb_x = $xt + $w;
22610 }
22611 // print XObject Template + Transformation matrix
22612 $this->StartTransform();
22613 // translate and scale
22614 $sx = ($w / $ow);
22615 $sy = ($h / $oh);
22616 $tm = array();
22617 $tm[0] = $sx;
22618 $tm[1] = 0;
22619 $tm[2] = 0;
22620 $tm[3] = $sy;
22621 $tm[4] = $xt * $this->k;
22622 $tm[5] = ($this->h - $h - $y) * $this->k;
22623 $this->Transform($tm);
22624 // set object
22625 $this->_out('/'.$id.' Do');
22626 $this->StopTransform();
22627 // add annotations
22628 if (!empty($this->xobjects[$id]['annotations'])) {
22629 foreach ($this->xobjects[$id]['annotations'] as $annot) {
22630 // transform original coordinates
22631 $coordlt = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
22632 $ax = ($coordlt[4] / $this->k);
22633 $ay = ($this->h - $h - ($coordlt[5] / $this->k));
22634 $coordrb = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
22635 $aw = ($coordrb[4] / $this->k) - $ax;
22636 $ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
22637 $this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
22638 }
22639 }
22640 // set pointer to align the next text/objects
22641 switch($align) {
22642 case 'T': {
22643 $this->y = $y;
22644 $this->x = $rb_x;
22645 break;
22646 }
22647 case 'M': {
22648 $this->y = $y + round($h/2);
22649 $this->x = $rb_x;
22650 break;
22651 }
22652 case 'B': {
22653 $this->y = $rb_y;
22654 $this->x = $rb_x;
22655 break;
22656 }
22657 case 'N': {
22658 $this->SetY($rb_y);
22659 break;
22660 }
22661 default:{
22662 break;
22663 }
22664 }
22665 }
22666
22667 /**
22668 * Set the percentage of character stretching.
22669 * @param $perc (int) percentage of stretching (100 = no stretching)
22670 * @author Nicola Asuni
22671 * @public
22672 * @since 5.9.000 (2010-09-29)
22673 */
22674 public function setFontStretching($perc=100) {
22675 $this->font_stretching = $perc;
22676 }
22677
22678 /**
22679 * Get the percentage of character stretching.
22680 * @return float stretching value
22681 * @author Nicola Asuni
22682 * @public
22683 * @since 5.9.000 (2010-09-29)
22684 */
22685 public function getFontStretching() {
22686 return $this->font_stretching;
22687 }
22688
22689 /**
22690 * Set the amount to increase or decrease the space between characters in a text.
22691 * @param $spacing (float) amount to increase or decrease the space between characters in a text (0 = default spacing)
22692 * @author Nicola Asuni
22693 * @public
22694 * @since 5.9.000 (2010-09-29)
22695 */
22696 public function setFontSpacing($spacing=0) {
22697 $this->font_spacing = $spacing;
22698 }
22699
22700 /**
22701 * Get the amount to increase or decrease the space between characters in a text.
22702 * @return int font spacing (tracking) value
22703 * @author Nicola Asuni
22704 * @public
22705 * @since 5.9.000 (2010-09-29)
22706 */
22707 public function getFontSpacing() {
22708 return $this->font_spacing;
22709 }
22710
22711 /**
22712 * Return an array of no-write page regions
22713 * @return array of no-write page regions
22714 * @author Nicola Asuni
22715 * @public
22716 * @since 5.9.003 (2010-10-13)
22717 * @see setPageRegions(), addPageRegion()
22718 */
22719 public function getPageRegions() {
22720 return $this->page_regions;
22721 }
22722
22723 /**
22724 * Set no-write regions on page.
22725 * 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.
22726 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22727 * You can set multiple regions for the same page.
22728 * @param $regions (array) array of no-write regions. For each region you can define an array as follow: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right). Omit this parameter to remove all regions.
22729 * @author Nicola Asuni
22730 * @public
22731 * @since 5.9.003 (2010-10-13)
22732 * @see addPageRegion(), getPageRegions()
22733 */
22734 public function setPageRegions($regions=array()) {
22735 // empty current regions array
22736 $this->page_regions = array();
22737 // add regions
22738 foreach ($regions as $data) {
22739 $this->addPageRegion($data);
22740 }
22741 }
22742
22743 /**
22744 * Add a single no-write region on selected page.
22745 * 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.
22746 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22747 * You can set multiple regions for the same page.
22748 * @param $region (array) array of a single no-write region array: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right).
22749 * @author Nicola Asuni
22750 * @public
22751 * @since 5.9.003 (2010-10-13)
22752 * @see setPageRegions(), getPageRegions()
22753 */
22754 public function addPageRegion($region) {
22755 if (!isset($region['page']) OR empty($region['page'])) {
22756 $region['page'] = $this->page;
22757 }
22758 if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
22759 AND isset($region['yt']) AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
22760 AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
22761 $this->page_regions[] = $region;
22762 }
22763 }
22764
22765 /**
22766 * Remove a single no-write region.
22767 * @param $key (int) region key
22768 * @author Nicola Asuni
22769 * @public
22770 * @since 5.9.003 (2010-10-13)
22771 * @see setPageRegions(), getPageRegions()
22772 */
22773 public function removePageRegion($key) {
22774 if (isset($this->page_regions[$key])) {
22775 unset($this->page_regions[$key]);
22776 }
22777 }
22778
22779 /**
22780 * Check page for no-write regions and adapt current coordinates and page margins if necessary.
22781 * 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.
22782 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22783 * @param $h (float) height of the text/image/object to print in user units
22784 * @param $x (float) current X coordinate in user units
22785 * @param $y (float) current Y coordinate in user units
22786 * @return array($x, $y)
22787 * @author Nicola Asuni
22788 * @protected
22789 * @since 5.9.003 (2010-10-13)
22790 */
22791 protected function checkPageRegions($h, $x, $y) {
22792 // set default values
22793 if ($x === '') {
22794 $x = $this->x;
22795 }
22796 if ($y === '') {
22797 $y = $this->y;
22798 }
22799 if (!$this->check_page_regions OR empty($this->page_regions)) {
22800 // no page regions defined
22801 return array($x, $y);
22802 }
22803 if (empty($h)) {
22804 $h = $this->getCellHeight($this->FontSize);
22805 }
22806 // check for page break
22807 if ($this->checkPageBreak($h, $y)) {
22808 // the content will be printed on a new page
22809 $x = $this->x;
22810 $y = $this->y;
22811 }
22812 if ($this->num_columns > 1) {
22813 if ($this->rtl) {
22814 $this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22815 } else {
22816 $this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22817 }
22818 } else {
22819 if ($this->rtl) {
22820 $this->lMargin = max($this->clMargin, $this->original_lMargin);
22821 } else {
22822 $this->rMargin = max($this->crMargin, $this->original_rMargin);
22823 }
22824 }
22825 // adjust coordinates and page margins
22826 foreach ($this->page_regions as $regid => $regdata) {
22827 if ($regdata['page'] == $this->page) {
22828 // check region boundaries
22829 if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
22830 // Y is inside the region
22831 $minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
22832 $yt = max($y, $regdata['yt']);
22833 $yb = min(($yt + $h), $regdata['yb']);
22834 $xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
22835 $xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
22836 if ($regdata['side'] == 'L') { // left side
22837 $new_margin = max($xt, $xb);
22838 if ($this->lMargin < $new_margin) {
22839 if ($this->rtl) {
22840 // adjust left page margin
22841 $this->lMargin = max(0, $new_margin);
22842 }
22843 if ($x < $new_margin) {
22844 // adjust x position
22845 $x = $new_margin;
22846 if ($new_margin > ($this->w - $this->rMargin)) {
22847 // adjust y position
22848 $y = $regdata['yb'] - $h;
22849 }
22850 }
22851 }
22852 } elseif ($regdata['side'] == 'R') { // right side
22853 $new_margin = min($xt, $xb);
22854 if (($this->w - $this->rMargin) > $new_margin) {
22855 if (!$this->rtl) {
22856 // adjust right page margin
22857 $this->rMargin = max(0, ($this->w - $new_margin));
22858 }
22859 if ($x > $new_margin) {
22860 // adjust x position
22861 $x = $new_margin;
22862 if ($new_margin > $this->lMargin) {
22863 // adjust y position
22864 $y = $regdata['yb'] - $h;
22865 }
22866 }
22867 }
22868 }
22869 }
22870 }
22871 }
22872 return array($x, $y);
22873 }
22874
22875 // --- SVG METHODS ---------------------------------------------------------
22876
22877 /**
22878 * Embedd a Scalable Vector Graphics (SVG) image.
22879 * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
22880 * @param $file (string) Name of the SVG file or a '@' character followed by the SVG data string.
22881 * @param $x (float) Abscissa of the upper-left corner.
22882 * @param $y (float) Ordinate of the upper-left corner.
22883 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
22884 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
22885 * @param $link (mixed) URL or identifier returned by AddLink().
22886 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul> If the alignment is an empty string, then the pointer will be restored on the starting SVG position.
22887 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
22888 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
22889 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
22890 * @author Nicola Asuni
22891 * @since 5.0.000 (2010-05-02)
22892 * @public
22893 */
22894 public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
22895 if ($this->state != 2) {
22896 return;
22897 }
22898 // reseet SVG vars
22899 $this->svggradients = array();
22900 $this->svggradientid = 0;
22901 $this->svgdefsmode = false;
22902 $this->svgdefs = array();
22903 $this->svgclipmode = false;
22904 $this->svgclippaths = array();
22905 $this->svgcliptm = array();
22906 $this->svgclipid = 0;
22907 $this->svgtext = '';
22908 $this->svgtextmode = array();
22909 if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
22910 // convert SVG to raster image using GD or ImageMagick libraries
22911 return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22912 }
22913 if ($file[0] === '@') { // image from string
22914 $this->svgdir = '';
22915 $svgdata = substr($file, 1);
22916 } else { // SVG file
22917 $this->svgdir = dirname($file);
22918 $svgdata = TCPDF_STATIC::fileGetContents($file);
22919 }
22920 if ($svgdata === FALSE) {
22921 $this->Error('SVG file not found: '.$file);
22922 }
22923 if ($x === '') {
22924 $x = $this->x;
22925 }
22926 if ($y === '') {
22927 $y = $this->y;
22928 }
22929 // check page for no-write regions and adapt page margins if necessary
22930 list($x, $y) = $this->checkPageRegions($h, $x, $y);
22931 $k = $this->k;
22932 $ox = 0;
22933 $oy = 0;
22934 $ow = $w;
22935 $oh = $h;
22936 $aspect_ratio_align = 'xMidYMid';
22937 $aspect_ratio_ms = 'meet';
22938 $regs = array();
22939 // get original image width and height
22940 preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
22941 if (isset($regs[1]) AND !empty($regs[1])) {
22942 $tmp = array();
22943 if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22944 $ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22945 }
22946 $tmp = array();
22947 if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22948 $oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22949 }
22950 $tmp = array();
22951 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22952 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22953 }
22954 $tmp = array();
22955 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22956 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22957 }
22958 $tmp = array();
22959 $view_box = array();
22960 if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
22961 if (count($tmp) == 5) {
22962 array_shift($tmp);
22963 foreach ($tmp as $key => $val) {
22964 $view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
22965 }
22966 $ox = $view_box[0];
22967 $oy = $view_box[1];
22968 }
22969 // get aspect ratio
22970 $tmp = array();
22971 if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22972 $aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
22973 switch (count($aspect_ratio)) {
22974 case 3: {
22975 $aspect_ratio_align = $aspect_ratio[1];
22976 $aspect_ratio_ms = $aspect_ratio[2];
22977 break;
22978 }
22979 case 2: {
22980 $aspect_ratio_align = $aspect_ratio[0];
22981 $aspect_ratio_ms = $aspect_ratio[1];
22982 break;
22983 }
22984 case 1: {
22985 $aspect_ratio_align = $aspect_ratio[0];
22986 $aspect_ratio_ms = 'meet';
22987 break;
22988 }
22989 }
22990 }
22991 }
22992 }
22993 if ($ow <= 0) {
22994 $ow = 1;
22995 }
22996 if ($oh <= 0) {
22997 $oh = 1;
22998 }
22999 // calculate image width and height on document
23000 if (($w <= 0) AND ($h <= 0)) {
23001 // convert image size to document unit
23002 $w = $ow;
23003 $h = $oh;
23004 } elseif ($w <= 0) {
23005 $w = $h * $ow / $oh;
23006 } elseif ($h <= 0) {
23007 $h = $w * $oh / $ow;
23008 }
23009 // fit the image on available space
23010 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
23011 if ($this->rasterize_vector_images) {
23012 // convert SVG to raster image using GD or ImageMagick libraries
23013 return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
23014 }
23015 // set alignment
23016 $this->img_rb_y = $y + $h;
23017 // set alignment
23018 if ($this->rtl) {
23019 if ($palign == 'L') {
23020 $ximg = $this->lMargin;
23021 } elseif ($palign == 'C') {
23022 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
23023 } elseif ($palign == 'R') {
23024 $ximg = $this->w - $this->rMargin - $w;
23025 } else {
23026 $ximg = $x - $w;
23027 }
23028 $this->img_rb_x = $ximg;
23029 } else {
23030 if ($palign == 'L') {
23031 $ximg = $this->lMargin;
23032 } elseif ($palign == 'C') {
23033 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
23034 } elseif ($palign == 'R') {
23035 $ximg = $this->w - $this->rMargin - $w;
23036 } else {
23037 $ximg = $x;
23038 }
23039 $this->img_rb_x = $ximg + $w;
23040 }
23041 // store current graphic vars
23042 $gvars = $this->getGraphicVars();
23043 // store SVG position and scale factors
23044 $svgoffset_x = ($ximg - $ox) * $this->k;
23045 $svgoffset_y = -($y - $oy) * $this->k;
23046 if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
23047 $ow = $view_box[2];
23048 $oh = $view_box[3];
23049 } else {
23050 if ($ow <= 0) {
23051 $ow = $w;
23052 }
23053 if ($oh <= 0) {
23054 $oh = $h;
23055 }
23056 }
23057 $svgscale_x = $w / $ow;
23058 $svgscale_y = $h / $oh;
23059 // scaling and alignment
23060 if ($aspect_ratio_align != 'none') {
23061 // store current scaling values
23062 $svgscale_old_x = $svgscale_x;
23063 $svgscale_old_y = $svgscale_y;
23064 // force uniform scaling
23065 if ($aspect_ratio_ms == 'slice') {
23066 // the entire viewport is covered by the viewBox
23067 if ($svgscale_x > $svgscale_y) {
23068 $svgscale_y = $svgscale_x;
23069 } elseif ($svgscale_x < $svgscale_y) {
23070 $svgscale_x = $svgscale_y;
23071 }
23072 } else { // meet
23073 // the entire viewBox is visible within the viewport
23074 if ($svgscale_x < $svgscale_y) {
23075 $svgscale_y = $svgscale_x;
23076 } elseif ($svgscale_x > $svgscale_y) {
23077 $svgscale_x = $svgscale_y;
23078 }
23079 }
23080 // correct X alignment
23081 switch (substr($aspect_ratio_align, 1, 3)) {
23082 case 'Min': {
23083 // do nothing
23084 break;
23085 }
23086 case 'Max': {
23087 $svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
23088 break;
23089 }
23090 default:
23091 case 'Mid': {
23092 $svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
23093 break;
23094 }
23095 }
23096 // correct Y alignment
23097 switch (substr($aspect_ratio_align, 5)) {
23098 case 'Min': {
23099 // do nothing
23100 break;
23101 }
23102 case 'Max': {
23103 $svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
23104 break;
23105 }
23106 default:
23107 case 'Mid': {
23108 $svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
23109 break;
23110 }
23111 }
23112 }
23113 // store current page break mode
23114 $page_break_mode = $this->AutoPageBreak;
23115 $page_break_margin = $this->getBreakMargin();
23116 $cell_padding = $this->cell_padding;
23117 $this->SetCellPadding(0);
23118 $this->SetAutoPageBreak(false);
23119 // save the current graphic state
23120 $this->_out('q'.$this->epsmarker);
23121 // set initial clipping mask
23122 $this->Rect($ximg, $y, $w, $h, 'CNZ', array(), array());
23123 // scale and translate
23124 $e = $ox * $this->k * (1 - $svgscale_x);
23125 $f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
23126 $this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
23127 // creates a new XML parser to be used by the other XML functions
23128 $this->parser = xml_parser_create('UTF-8');
23129 // the following function allows to use parser inside object
23130 xml_set_object($this->parser, $this);
23131 // disable case-folding for this XML parser
23132 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
23133 // sets the element handler functions for the XML parser
23134 xml_set_element_handler($this->parser, 'startSVGElementHandler', 'endSVGElementHandler');
23135 // sets the character data handler function for the XML parser
23136 xml_set_character_data_handler($this->parser, 'segSVGContentHandler');
23137 // start parsing an XML document
23138 if (!xml_parse($this->parser, $svgdata)) {
23139 $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));
23140 $this->Error($error_message);
23141 }
23142 // free this XML parser
23143 xml_parser_free($this->parser);
23144 // restore previous graphic state
23145 $this->_out($this->epsmarker.'Q');
23146 // restore graphic vars
23147 $this->setGraphicVars($gvars);
23148 $this->lasth = $gvars['lasth'];
23149 if (!empty($border)) {
23150 $bx = $this->x;
23151 $by = $this->y;
23152 $this->x = $ximg;
23153 if ($this->rtl) {
23154 $this->x += $w;
23155 }
23156 $this->y = $y;
23157 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
23158 $this->x = $bx;
23159 $this->y = $by;
23160 }
23161 if ($link) {
23162 $this->Link($ximg, $y, $w, $h, $link, 0);
23163 }
23164 // set pointer to align the next text/objects
23165 switch($align) {
23166 case 'T':{
23167 $this->y = $y;
23168 $this->x = $this->img_rb_x;
23169 break;
23170 }
23171 case 'M':{
23172 $this->y = $y + round($h/2);
23173 $this->x = $this->img_rb_x;
23174 break;
23175 }
23176 case 'B':{
23177 $this->y = $this->img_rb_y;
23178 $this->x = $this->img_rb_x;
23179 break;
23180 }
23181 case 'N':{
23182 $this->SetY($this->img_rb_y);
23183 break;
23184 }
23185 default:{
23186 // restore pointer to starting position
23187 $this->x = $gvars['x'];
23188 $this->y = $gvars['y'];
23189 $this->page = $gvars['page'];
23190 $this->current_column = $gvars['current_column'];
23191 $this->tMargin = $gvars['tMargin'];
23192 $this->bMargin = $gvars['bMargin'];
23193 $this->w = $gvars['w'];
23194 $this->h = $gvars['h'];
23195 $this->wPt = $gvars['wPt'];
23196 $this->hPt = $gvars['hPt'];
23197 $this->fwPt = $gvars['fwPt'];
23198 $this->fhPt = $gvars['fhPt'];
23199 break;
23200 }
23201 }
23202 $this->endlinex = $this->img_rb_x;
23203 // restore page break
23204 $this->SetAutoPageBreak($page_break_mode, $page_break_margin);
23205 $this->cell_padding = $cell_padding;
23206 }
23207
23208 /**
23209 * Convert SVG transformation matrix to PDF.
23210 * @param $tm (array) original SVG transformation matrix
23211 * @return array transformation matrix
23212 * @protected
23213 * @since 5.0.000 (2010-05-02)
23214 */
23215 protected function convertSVGtMatrix($tm) {
23216 $a = $tm[0];
23217 $b = -$tm[1];
23218 $c = -$tm[2];
23219 $d = $tm[3];
23220 $e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
23221 $f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
23222 $x = 0;
23223 $y = $this->h * $this->k;
23224 $e = ($x * (1 - $a)) - ($y * $c) + $e;
23225 $f = ($y * (1 - $d)) - ($x * $b) + $f;
23226 return array($a, $b, $c, $d, $e, $f);
23227 }
23228
23229 /**
23230 * Apply SVG graphic transformation matrix.
23231 * @param $tm (array) original SVG transformation matrix
23232 * @protected
23233 * @since 5.0.000 (2010-05-02)
23234 */
23235 protected function SVGTransform($tm) {
23236 $this->Transform($this->convertSVGtMatrix($tm));
23237 }
23238
23239 /**
23240 * Apply the requested SVG styles (*** TO BE COMPLETED ***)
23241 * @param $svgstyle (array) array of SVG styles to apply
23242 * @param $prevsvgstyle (array) array of previous SVG style
23243 * @param $x (int) X origin of the bounding box
23244 * @param $y (int) Y origin of the bounding box
23245 * @param $w (int) width of the bounding box
23246 * @param $h (int) height of the bounding box
23247 * @param $clip_function (string) clip function
23248 * @param $clip_params (array) array of parameters for clipping function
23249 * @return object style
23250 * @author Nicola Asuni
23251 * @since 5.0.000 (2010-05-02)
23252 * @protected
23253 */
23254 protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
23255 if ($this->state != 2) {
23256 return;
23257 }
23258 $objstyle = '';
23259 $minlen = (0.01 / $this->k); // minimum acceptable length
23260 if (!isset($svgstyle['opacity'])) {
23261 return $objstyle;
23262 }
23263 // clip-path
23264 $regs = array();
23265 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
23266 $clip_path = $this->svgclippaths[$regs[1]];
23267 foreach ($clip_path as $cp) {
23268 $this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
23269 }
23270 }
23271 // opacity
23272 if ($svgstyle['opacity'] != 1) {
23273 $this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
23274 }
23275 // color
23276 $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
23277 $this->SetFillColorArray($fill_color);
23278 // text color
23279 $text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
23280 $this->SetTextColorArray($text_color);
23281 // clip
23282 if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
23283 $top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
23284 $right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
23285 $bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
23286 $left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
23287 $cx = $x + $left;
23288 $cy = $y + $top;
23289 $cw = $w - $left - $right;
23290 $ch = $h - $top - $bottom;
23291 if ($svgstyle['clip-rule'] == 'evenodd') {
23292 $clip_rule = 'CNZ';
23293 } else {
23294 $clip_rule = 'CEO';
23295 }
23296 $this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
23297 }
23298 // fill
23299 $regs = array();
23300 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
23301 // gradient
23302 $gradient = $this->svggradients[$regs[1]];
23303 if (isset($gradient['xref'])) {
23304 // reference to another gradient definition
23305 $newgradient = $this->svggradients[$gradient['xref']];
23306 $newgradient['coords'] = $gradient['coords'];
23307 $newgradient['mode'] = $gradient['mode'];
23308 $newgradient['type'] = $gradient['type'];
23309 $newgradient['gradientUnits'] = $gradient['gradientUnits'];
23310 if (isset($gradient['gradientTransform'])) {
23311 $newgradient['gradientTransform'] = $gradient['gradientTransform'];
23312 }
23313 $gradient = $newgradient;
23314 }
23315 //save current Graphic State
23316 $this->_outSaveGraphicsState();
23317 //set clipping area
23318 if (!empty($clip_function) AND method_exists($this, $clip_function)) {
23319 $bbox = call_user_func_array(array($this, $clip_function), $clip_params);
23320 if ((!isset($gradient['type']) OR ($gradient['type'] != 3)) AND is_array($bbox) AND (count($bbox) == 4)) {
23321 list($x, $y, $w, $h) = $bbox;
23322 }
23323 }
23324 if ($gradient['mode'] == 'measure') {
23325 if (!isset($gradient['coords'][4])) {
23326 $gradient['coords'][4] = 0.5;
23327 }
23328 if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
23329 $gtm = $gradient['gradientTransform'];
23330 // apply transformation matrix
23331 $xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
23332 $ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
23333 $xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
23334 $yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
23335 $r = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
23336 $gradient['coords'][0] = $xa;
23337 $gradient['coords'][1] = $ya;
23338 $gradient['coords'][2] = $xb;
23339 $gradient['coords'][3] = $yb;
23340 $gradient['coords'][4] = $r;
23341 }
23342 // convert SVG coordinates to user units
23343 $gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
23344 $gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
23345 $gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
23346 $gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
23347 $gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
23348 if ($w <= $minlen) {
23349 $w = $minlen;
23350 }
23351 if ($h <= $minlen) {
23352 $h = $minlen;
23353 }
23354 // shift units
23355 if ($gradient['gradientUnits'] == 'objectBoundingBox') {
23356 // convert to SVG coordinate system
23357 $gradient['coords'][0] += $x;
23358 $gradient['coords'][1] += $y;
23359 $gradient['coords'][2] += $x;
23360 $gradient['coords'][3] += $y;
23361 }
23362 // calculate percentages
23363 $gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
23364 $gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
23365 $gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
23366 $gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
23367 $gradient['coords'][4] /= $w;
23368 } elseif ($gradient['mode'] == 'percentage') {
23369 foreach($gradient['coords'] as $key => $val) {
23370 $gradient['coords'][$key] = (intval($val) / 100);
23371 if ($val < 0) {
23372 $gradient['coords'][$key] = 0;
23373 } elseif ($val > 1) {
23374 $gradient['coords'][$key] = 1;
23375 }
23376 }
23377 }
23378 if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
23379 // single color (no shading)
23380 $gradient['coords'][0] = 1;
23381 $gradient['coords'][1] = 0;
23382 $gradient['coords'][2] = 0.999;
23383 $gradient['coords'][3] = 0;
23384 }
23385 // swap Y coordinates
23386 $tmp = $gradient['coords'][1];
23387 $gradient['coords'][1] = $gradient['coords'][3];
23388 $gradient['coords'][3] = $tmp;
23389 // set transformation map for gradient
23390 $cy = ($this->h - $y);
23391 if ($gradient['type'] == 3) {
23392 // circular gradient
23393 $cy -= ($gradient['coords'][1] * ($w + $h));
23394 } else {
23395 $cy -= $h;
23396 }
23397 $this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), ($cy * $this->k)));
23398 if (count($gradient['stops']) > 1) {
23399 $this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
23400 }
23401 } elseif ($svgstyle['fill'] != 'none') {
23402 $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
23403 if ($svgstyle['fill-opacity'] != 1) {
23404 $this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
23405 }
23406 $this->SetFillColorArray($fill_color);
23407 if ($svgstyle['fill-rule'] == 'evenodd') {
23408 $objstyle .= 'F*';
23409 } else {
23410 $objstyle .= 'F';
23411 }
23412 }
23413 // stroke
23414 if ($svgstyle['stroke'] != 'none') {
23415 if ($svgstyle['stroke-opacity'] != 1) {
23416 $this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
23417 } elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['stroke'], $rgba_matches)) {
23418 $this->setAlpha($rgba_matches[1], 'Normal', $this->alpha['ca'], false);
23419 }
23420 $stroke_style = array(
23421 'color' => TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors),
23422 'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
23423 'cap' => $svgstyle['stroke-linecap'],
23424 'join' => $svgstyle['stroke-linejoin']
23425 );
23426 if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
23427 $stroke_style['dash'] = $svgstyle['stroke-dasharray'];
23428 }
23429 $this->SetLineStyle($stroke_style);
23430 $objstyle .= 'D';
23431 }
23432 // font
23433 $regs = array();
23434 if (!empty($svgstyle['font'])) {
23435 if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
23436 $font_family = $this->getFontFamilyName($regs[1]);
23437 } else {
23438 $font_family = $svgstyle['font-family'];
23439 }
23440 if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23441 $font_size = trim($regs[1]);
23442 } else {
23443 $font_size = $svgstyle['font-size'];
23444 }
23445 if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23446 $font_style = trim($regs[1]);
23447 } else {
23448 $font_style = $svgstyle['font-style'];
23449 }
23450 if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23451 $font_weight = trim($regs[1]);
23452 } else {
23453 $font_weight = $svgstyle['font-weight'];
23454 }
23455 if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23456 $font_stretch = trim($regs[1]);
23457 } else {
23458 $font_stretch = $svgstyle['font-stretch'];
23459 }
23460 if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23461 $font_spacing = trim($regs[1]);
23462 } else {
23463 $font_spacing = $svgstyle['letter-spacing'];
23464 }
23465 } else {
23466 $font_family = $this->getFontFamilyName($svgstyle['font-family']);
23467 $font_size = $svgstyle['font-size'];
23468 $font_style = $svgstyle['font-style'];
23469 $font_weight = $svgstyle['font-weight'];
23470 $font_stretch = $svgstyle['font-stretch'];
23471 $font_spacing = $svgstyle['letter-spacing'];
23472 }
23473 $font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit);
23474 $font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
23475 $font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
23476 switch ($font_style) {
23477 case 'italic': {
23478 $font_style = 'I';
23479 break;
23480 }
23481 case 'oblique': {
23482 $font_style = 'I';
23483 break;
23484 }
23485 default:
23486 case 'normal': {
23487 $font_style = '';
23488 break;
23489 }
23490 }
23491 switch ($font_weight) {
23492 case 'bold':
23493 case 'bolder': {
23494 $font_style .= 'B';
23495 break;
23496 }
23497 }
23498 switch ($svgstyle['text-decoration']) {
23499 case 'underline': {
23500 $font_style .= 'U';
23501 break;
23502 }
23503 case 'overline': {
23504 $font_style .= 'O';
23505 break;
23506 }
23507 case 'line-through': {
23508 $font_style .= 'D';
23509 break;
23510 }
23511 default:
23512 case 'none': {
23513 break;
23514 }
23515 }
23516 $this->SetFont($font_family, $font_style, $font_size);
23517 $this->setFontStretching($font_stretch);
23518 $this->setFontSpacing($font_spacing);
23519 return $objstyle;
23520 }
23521
23522 /**
23523 * Draws an SVG path
23524 * @param $d (string) attribute d of the path SVG element
23525 * @param $style (string) Style of rendering. Possible values are:
23526 * <ul>
23527 * <li>D or empty string: Draw (default).</li>
23528 * <li>F: Fill.</li>
23529 * <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23530 * <li>DF or FD: Draw and fill.</li>
23531 * <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23532 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
23533 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
23534 * </ul>
23535 * @return array of container box measures (x, y, w, h)
23536 * @author Nicola Asuni
23537 * @since 5.0.000 (2010-05-02)
23538 * @protected
23539 */
23540 protected function SVGPath($d, $style='') {
23541 if ($this->state != 2) {
23542 return;
23543 }
23544 // set fill/stroke style
23545 $op = TCPDF_STATIC::getPathPaintOperator($style, '');
23546 if (empty($op)) {
23547 return;
23548 }
23549 $paths = array();
23550 $d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
23551 preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
23552 $x = 0;
23553 $y = 0;
23554 $x1 = 0;
23555 $y1 = 0;
23556 $x2 = 0;
23557 $y2 = 0;
23558 $xmin = 2147483647;
23559 $xmax = 0;
23560 $ymin = 2147483647;
23561 $ymax = 0;
23562 $relcoord = false;
23563 $minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
23564 $firstcmd = true; // used to print first point
23565 // draw curve pieces
23566 foreach ($paths as $key => $val) {
23567 // get curve type
23568 $cmd = trim($val[1]);
23569 if (strtolower($cmd) == $cmd) {
23570 // use relative coordinated instead of absolute
23571 $relcoord = true;
23572 $xoffset = $x;
23573 $yoffset = $y;
23574 } else {
23575 $relcoord = false;
23576 $xoffset = 0;
23577 $yoffset = 0;
23578 }
23579 $params = array();
23580 if (isset($val[2])) {
23581 // get curve parameters
23582 $rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
23583 $params = array();
23584 foreach ($rawparams as $ck => $cp) {
23585 $params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
23586 if (abs($params[$ck]) < $minlen) {
23587 // aproximate little values to zero
23588 $params[$ck] = 0;
23589 }
23590 }
23591 }
23592 // store current origin point
23593 $x0 = $x;
23594 $y0 = $y;
23595 switch (strtoupper($cmd)) {
23596 case 'M': { // moveto
23597 foreach ($params as $ck => $cp) {
23598 if (($ck % 2) == 0) {
23599 $x = $cp + $xoffset;
23600 } else {
23601 $y = $cp + $yoffset;
23602 if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23603 if ($ck == 1) {
23604 $this->_outPoint($x, $y);
23605 $firstcmd = false;
23606 } else {
23607 $this->_outLine($x, $y);
23608 }
23609 $x0 = $x;
23610 $y0 = $y;
23611 }
23612 $xmin = min($xmin, $x);
23613 $ymin = min($ymin, $y);
23614 $xmax = max($xmax, $x);
23615 $ymax = max($ymax, $y);
23616 if ($relcoord) {
23617 $xoffset = $x;
23618 $yoffset = $y;
23619 }
23620 }
23621 }
23622 break;
23623 }
23624 case 'L': { // lineto
23625 foreach ($params as $ck => $cp) {
23626 if (($ck % 2) == 0) {
23627 $x = $cp + $xoffset;
23628 } else {
23629 $y = $cp + $yoffset;
23630 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23631 $this->_outLine($x, $y);
23632 $x0 = $x;
23633 $y0 = $y;
23634 }
23635 $xmin = min($xmin, $x);
23636 $ymin = min($ymin, $y);
23637 $xmax = max($xmax, $x);
23638 $ymax = max($ymax, $y);
23639 if ($relcoord) {
23640 $xoffset = $x;
23641 $yoffset = $y;
23642 }
23643 }
23644 }
23645 break;
23646 }
23647 case 'H': { // horizontal lineto
23648 foreach ($params as $ck => $cp) {
23649 $x = $cp + $xoffset;
23650 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23651 $this->_outLine($x, $y);
23652 $x0 = $x;
23653 $y0 = $y;
23654 }
23655 $xmin = min($xmin, $x);
23656 $xmax = max($xmax, $x);
23657 if ($relcoord) {
23658 $xoffset = $x;
23659 }
23660 }
23661 break;
23662 }
23663 case 'V': { // vertical lineto
23664 foreach ($params as $ck => $cp) {
23665 $y = $cp + $yoffset;
23666 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23667 $this->_outLine($x, $y);
23668 $x0 = $x;
23669 $y0 = $y;
23670 }
23671 $ymin = min($ymin, $y);
23672 $ymax = max($ymax, $y);
23673 if ($relcoord) {
23674 $yoffset = $y;
23675 }
23676 }
23677 break;
23678 }
23679 case 'C': { // curveto
23680 foreach ($params as $ck => $cp) {
23681 $params[$ck] = $cp;
23682 if ((($ck + 1) % 6) == 0) {
23683 $x1 = $params[($ck - 5)] + $xoffset;
23684 $y1 = $params[($ck - 4)] + $yoffset;
23685 $x2 = $params[($ck - 3)] + $xoffset;
23686 $y2 = $params[($ck - 2)] + $yoffset;
23687 $x = $params[($ck - 1)] + $xoffset;
23688 $y = $params[($ck)] + $yoffset;
23689 $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23690 $xmin = min($xmin, $x, $x1, $x2);
23691 $ymin = min($ymin, $y, $y1, $y2);
23692 $xmax = max($xmax, $x, $x1, $x2);
23693 $ymax = max($ymax, $y, $y1, $y2);
23694 if ($relcoord) {
23695 $xoffset = $x;
23696 $yoffset = $y;
23697 }
23698 }
23699 }
23700 break;
23701 }
23702 case 'S': { // shorthand/smooth curveto
23703 foreach ($params as $ck => $cp) {
23704 $params[$ck] = $cp;
23705 if ((($ck + 1) % 4) == 0) {
23706 if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
23707 $x1 = (2 * $x) - $x2;
23708 $y1 = (2 * $y) - $y2;
23709 } else {
23710 $x1 = $x;
23711 $y1 = $y;
23712 }
23713 $x2 = $params[($ck - 3)] + $xoffset;
23714 $y2 = $params[($ck - 2)] + $yoffset;
23715 $x = $params[($ck - 1)] + $xoffset;
23716 $y = $params[($ck)] + $yoffset;
23717 $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23718 $xmin = min($xmin, $x, $x1, $x2);
23719 $ymin = min($ymin, $y, $y1, $y2);
23720 $xmax = max($xmax, $x, $x1, $x2);
23721 $ymax = max($ymax, $y, $y1, $y2);
23722 if ($relcoord) {
23723 $xoffset = $x;
23724 $yoffset = $y;
23725 }
23726 }
23727 }
23728 break;
23729 }
23730 case 'Q': { // quadratic Bezier curveto
23731 foreach ($params as $ck => $cp) {
23732 $params[$ck] = $cp;
23733 if ((($ck + 1) % 4) == 0) {
23734 // convert quadratic points to cubic points
23735 $x1 = $params[($ck - 3)] + $xoffset;
23736 $y1 = $params[($ck - 2)] + $yoffset;
23737 $xa = ($x + (2 * $x1)) / 3;
23738 $ya = ($y + (2 * $y1)) / 3;
23739 $x = $params[($ck - 1)] + $xoffset;
23740 $y = $params[($ck)] + $yoffset;
23741 $xb = ($x + (2 * $x1)) / 3;
23742 $yb = ($y + (2 * $y1)) / 3;
23743 $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23744 $xmin = min($xmin, $x, $xa, $xb);
23745 $ymin = min($ymin, $y, $ya, $yb);
23746 $xmax = max($xmax, $x, $xa, $xb);
23747 $ymax = max($ymax, $y, $ya, $yb);
23748 if ($relcoord) {
23749 $xoffset = $x;
23750 $yoffset = $y;
23751 }
23752 }
23753 }
23754 break;
23755 }
23756 case 'T': { // shorthand/smooth quadratic Bezier curveto
23757 foreach ($params as $ck => $cp) {
23758 $params[$ck] = $cp;
23759 if (($ck % 2) != 0) {
23760 if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
23761 $x1 = (2 * $x) - $x1;
23762 $y1 = (2 * $y) - $y1;
23763 } else {
23764 $x1 = $x;
23765 $y1 = $y;
23766 }
23767 // convert quadratic points to cubic points
23768 $xa = ($x + (2 * $x1)) / 3;
23769 $ya = ($y + (2 * $y1)) / 3;
23770 $x = $params[($ck - 1)] + $xoffset;
23771 $y = $params[($ck)] + $yoffset;
23772 $xb = ($x + (2 * $x1)) / 3;
23773 $yb = ($y + (2 * $y1)) / 3;
23774 $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23775 $xmin = min($xmin, $x, $xa, $xb);
23776 $ymin = min($ymin, $y, $ya, $yb);
23777 $xmax = max($xmax, $x, $xa, $xb);
23778 $ymax = max($ymax, $y, $ya, $yb);
23779 if ($relcoord) {
23780 $xoffset = $x;
23781 $yoffset = $y;
23782 }
23783 }
23784 }
23785 break;
23786 }
23787 case 'A': { // elliptical arc
23788 foreach ($params as $ck => $cp) {
23789 $params[$ck] = $cp;
23790 if ((($ck + 1) % 7) == 0) {
23791 $x0 = $x;
23792 $y0 = $y;
23793 $rx = abs($params[($ck - 6)]);
23794 $ry = abs($params[($ck - 5)]);
23795 $ang = -$rawparams[($ck - 4)];
23796 $angle = deg2rad($ang);
23797 $fa = $rawparams[($ck - 3)]; // large-arc-flag
23798 $fs = $rawparams[($ck - 2)]; // sweep-flag
23799 $x = $params[($ck - 1)] + $xoffset;
23800 $y = $params[$ck] + $yoffset;
23801 if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
23802 // endpoints are almost identical
23803 $xmin = min($xmin, $x);
23804 $ymin = min($ymin, $y);
23805 $xmax = max($xmax, $x);
23806 $ymax = max($ymax, $y);
23807 } else {
23808 $cos_ang = cos($angle);
23809 $sin_ang = sin($angle);
23810 $a = (($x0 - $x) / 2);
23811 $b = (($y0 - $y) / 2);
23812 $xa = ($a * $cos_ang) - ($b * $sin_ang);
23813 $ya = ($a * $sin_ang) + ($b * $cos_ang);
23814 $rx2 = $rx * $rx;
23815 $ry2 = $ry * $ry;
23816 $xa2 = $xa * $xa;
23817 $ya2 = $ya * $ya;
23818 $delta = ($xa2 / $rx2) + ($ya2 / $ry2);
23819 if ($delta > 1) {
23820 $rx *= sqrt($delta);
23821 $ry *= sqrt($delta);
23822 $rx2 = $rx * $rx;
23823 $ry2 = $ry * $ry;
23824 }
23825 $numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
23826 if ($numerator < 0) {
23827 $root = 0;
23828 } else {
23829 $root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
23830 }
23831 if ($fa == $fs){
23832 $root *= -1;
23833 }
23834 $cax = $root * (($rx * $ya) / $ry);
23835 $cay = -$root * (($ry * $xa) / $rx);
23836 // coordinates of ellipse center
23837 $cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
23838 $cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
23839 // get angles
23840 $angs = TCPDF_STATIC::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
23841 $dang = TCPDF_STATIC::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
23842 if (($fs == 0) AND ($dang > 0)) {
23843 $dang -= (2 * M_PI);
23844 } elseif (($fs == 1) AND ($dang < 0)) {
23845 $dang += (2 * M_PI);
23846 }
23847 $angf = $angs - $dang;
23848 if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
23849 // reverse angles
23850 $tmp = $angs;
23851 $angs = $angf;
23852 $angf = $tmp;
23853 }
23854 $angs = round(rad2deg($angs), 6);
23855 $angf = round(rad2deg($angf), 6);
23856 // covent angles to positive values
23857 if (($angs < 0) AND ($angf < 0)) {
23858 $angs += 360;
23859 $angf += 360;
23860 }
23861 $pie = false;
23862 if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
23863 $pie = true;
23864 }
23865 list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
23866 $xmin = min($xmin, $x, $axmin);
23867 $ymin = min($ymin, $y, $aymin);
23868 $xmax = max($xmax, $x, $axmax);
23869 $ymax = max($ymax, $y, $aymax);
23870 }
23871 if ($relcoord) {
23872 $xoffset = $x;
23873 $yoffset = $y;
23874 }
23875 }
23876 }
23877 break;
23878 }
23879 case 'Z': {
23880 $this->_out('h');
23881 break;
23882 }
23883 }
23884 $firstcmd = false;
23885 } // end foreach
23886 if (!empty($op)) {
23887 $this->_out($op);
23888 }
23889 return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
23890 }
23891
23892 /**
23893 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
23894 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
23895 * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
23896 * @param $attribs (array) The third parameter, attribs, contains an associative array with the element's attributes (if any). The keys of this array are the attribute names, the values are the attribute values. Attribute names are case-folded on the same criteria as element names. Attribute values are not case-folded. The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). The first key in the array was the first attribute, and so on.
23897 * @param $ctm (array) tranformation matrix for clipping mode (starting transformation matrix).
23898 * @author Nicola Asuni
23899 * @since 5.0.000 (2010-05-02)
23900 * @protected
23901 */
23902 protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
23903 // check if we are in clip mode
23904 if ($this->svgclipmode) {
23905 $this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
23906 return;
23907 }
23908 if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
23909 if (isset($attribs['id'])) {
23910 $attribs['child_elements'] = array();
23911 $this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23912 return;
23913 }
23914 if (end($this->svgdefs) !== FALSE) {
23915 $last_svgdefs_id = key($this->svgdefs);
23916 if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
23917 $attribs['id'] = 'DF_'.(count($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements']) + 1);
23918 $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23919 return;
23920 }
23921 }
23922 return;
23923 }
23924 $clipping = false;
23925 if ($parser == 'clip-path') {
23926 // set clipping mode
23927 $clipping = true;
23928 }
23929 // get styling properties
23930 $prev_svgstyle = $this->svgstyles[max(0,(count($this->svgstyles) - 1))]; // previous style
23931 $svgstyle = $this->svgstyles[0]; // set default style
23932 if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
23933 // default fill attribute for clipping
23934 $attribs['fill'] = 'none';
23935 }
23936 if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
23937 // fix style for regular expression
23938 $attribs['style'] = ';'.$attribs['style'];
23939 }
23940 foreach ($prev_svgstyle as $key => $val) {
23941 if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
23942 // inherit previous value
23943 $svgstyle[$key] = $val;
23944 }
23945 if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
23946 // specific attribute settings
23947 if ($attribs[$key] == 'inherit') {
23948 $svgstyle[$key] = $val;
23949 } else {
23950 $svgstyle[$key] = $attribs[$key];
23951 }
23952 } elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
23953 // CSS style syntax
23954 $attrval = array();
23955 if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
23956 if ($attrval[1] == 'inherit') {
23957 $svgstyle[$key] = $val;
23958 } else {
23959 $svgstyle[$key] = $attrval[1];
23960 }
23961 }
23962 }
23963 }
23964 // transformation matrix
23965 if (!empty($ctm)) {
23966 $tm = $ctm;
23967 } else {
23968 $tm = array(1,0,0,1,0,0);
23969 }
23970 if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
23971 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
23972 }
23973 $svgstyle['transfmatrix'] = $tm;
23974 $invisible = false;
23975 if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
23976 // the current graphics element is invisible (nothing is painted)
23977 $invisible = true;
23978 }
23979 // process tag
23980 switch($name) {
23981 case 'defs': {
23982 $this->svgdefsmode = true;
23983 break;
23984 }
23985 // clipPath
23986 case 'clipPath': {
23987 if ($invisible) {
23988 break;
23989 }
23990 $this->svgclipmode = true;
23991 if (!isset($attribs['id'])) {
23992 $attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
23993 }
23994 $this->svgclipid = $attribs['id'];
23995 $this->svgclippaths[$this->svgclipid] = array();
23996 $this->svgcliptm[$this->svgclipid] = $tm;
23997 break;
23998 }
23999 case 'svg': {
24000 // start of SVG object
24001 break;
24002 }
24003 case 'g': {
24004 // group together related graphics elements
24005 array_push($this->svgstyles, $svgstyle);
24006 $this->StartTransform();
24007 $x = (isset($attribs['x'])?$attribs['x']:0);
24008 $y = (isset($attribs['y'])?$attribs['y']:0);
24009 $w = 1;//(isset($attribs['width'])?$attribs['width']:1);
24010 $h = 1;//(isset($attribs['height'])?$attribs['height']:1);
24011 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24012 $this->SVGTransform($tm);
24013 $this->setSVGStyles($svgstyle, $prev_svgstyle);
24014 break;
24015 }
24016 case 'linearGradient': {
24017 if ($this->pdfa_mode) {
24018 break;
24019 }
24020 if (!isset($attribs['id'])) {
24021 $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24022 }
24023 $this->svggradientid = $attribs['id'];
24024 $this->svggradients[$this->svggradientid] = array();
24025 $this->svggradients[$this->svggradientid]['type'] = 2;
24026 $this->svggradients[$this->svggradientid]['stops'] = array();
24027 if (isset($attribs['gradientUnits'])) {
24028 $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24029 } else {
24030 $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24031 }
24032 //$attribs['spreadMethod']
24033 if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
24034 OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
24035 OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
24036 OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
24037 OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
24038 $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24039 } else {
24040 $this->svggradients[$this->svggradientid]['mode'] = 'measure';
24041 }
24042 $x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
24043 $y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
24044 $x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
24045 $y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
24046 if (isset($attribs['gradientTransform'])) {
24047 $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24048 }
24049 $this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
24050 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24051 // gradient is defined on another place
24052 $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24053 }
24054 break;
24055 }
24056 case 'radialGradient': {
24057 if ($this->pdfa_mode) {
24058 break;
24059 }
24060 if (!isset($attribs['id'])) {
24061 $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24062 }
24063 $this->svggradientid = $attribs['id'];
24064 $this->svggradients[$this->svggradientid] = array();
24065 $this->svggradients[$this->svggradientid]['type'] = 3;
24066 $this->svggradients[$this->svggradientid]['stops'] = array();
24067 if (isset($attribs['gradientUnits'])) {
24068 $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24069 } else {
24070 $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24071 }
24072 //$attribs['spreadMethod']
24073 if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
24074 OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
24075 OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')))) {
24076 $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24077 } elseif (isset($attribs['r']) AND is_numeric($attribs['r']) AND ($attribs['r']) <= 1) {
24078 $this->svggradients[$this->svggradientid]['mode'] = 'ratio';
24079 } else {
24080 $this->svggradients[$this->svggradientid]['mode'] = 'measure';
24081 }
24082 $cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
24083 $cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
24084 $fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
24085 $fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
24086 $r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
24087 if (isset($attribs['gradientTransform'])) {
24088 $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24089 }
24090 $this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
24091 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24092 // gradient is defined on another place
24093 $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24094 }
24095 break;
24096 }
24097 case 'stop': {
24098 // gradient stops
24099 if (substr($attribs['offset'], -1) == '%') {
24100 $offset = floatval(substr($attribs['offset'], -1)) / 100;
24101 } else {
24102 $offset = floatval($attribs['offset']);
24103 if ($offset > 1) {
24104 $offset /= 100;
24105 }
24106 }
24107 $stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
24108 $opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
24109 $this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
24110 break;
24111 }
24112 // paths
24113 case 'path': {
24114 if ($invisible) {
24115 break;
24116 }
24117 if (isset($attribs['d'])) {
24118 $d = trim($attribs['d']);
24119 if (!empty($d)) {
24120 $x = (isset($attribs['x'])?$attribs['x']:0);
24121 $y = (isset($attribs['y'])?$attribs['y']:0);
24122 $w = (isset($attribs['width'])?$attribs['width']:1);
24123 $h = (isset($attribs['height'])?$attribs['height']:1);
24124 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24125 if ($clipping) {
24126 $this->SVGTransform($tm);
24127 $this->SVGPath($d, 'CNZ');
24128 } else {
24129 $this->StartTransform();
24130 $this->SVGTransform($tm);
24131 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
24132 if (!empty($obstyle)) {
24133 $this->SVGPath($d, $obstyle);
24134 }
24135 $this->StopTransform();
24136 }
24137 }
24138 }
24139 break;
24140 }
24141 // shapes
24142 case 'rect': {
24143 if ($invisible) {
24144 break;
24145 }
24146 $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24147 $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24148 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24149 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24150 $rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
24151 $ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
24152 if ($clipping) {
24153 $this->SVGTransform($tm);
24154 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
24155 } else {
24156 $this->StartTransform();
24157 $this->SVGTransform($tm);
24158 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
24159 if (!empty($obstyle)) {
24160 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
24161 }
24162 $this->StopTransform();
24163 }
24164 break;
24165 }
24166 case 'circle': {
24167 if ($invisible) {
24168 break;
24169 }
24170 $r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
24171 $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24172 $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24173 $x = ($cx - $r);
24174 $y = ($cy - $r);
24175 $w = (2 * $r);
24176 $h = $w;
24177 if ($clipping) {
24178 $this->SVGTransform($tm);
24179 $this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
24180 } else {
24181 $this->StartTransform();
24182 $this->SVGTransform($tm);
24183 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
24184 if (!empty($obstyle)) {
24185 $this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
24186 }
24187 $this->StopTransform();
24188 }
24189 break;
24190 }
24191 case 'ellipse': {
24192 if ($invisible) {
24193 break;
24194 }
24195 $rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
24196 $ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
24197 $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24198 $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24199 $x = ($cx - $rx);
24200 $y = ($cy - $ry);
24201 $w = (2 * $rx);
24202 $h = (2 * $ry);
24203 if ($clipping) {
24204 $this->SVGTransform($tm);
24205 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
24206 } else {
24207 $this->StartTransform();
24208 $this->SVGTransform($tm);
24209 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
24210 if (!empty($obstyle)) {
24211 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
24212 }
24213 $this->StopTransform();
24214 }
24215 break;
24216 }
24217 case 'line': {
24218 if ($invisible) {
24219 break;
24220 }
24221 $x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
24222 $y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
24223 $x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
24224 $y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
24225 $x = $x1;
24226 $y = $y1;
24227 $w = abs($x2 - $x1);
24228 $h = abs($y2 - $y1);
24229 if (!$clipping) {
24230 $this->StartTransform();
24231 $this->SVGTransform($tm);
24232 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
24233 $this->Line($x1, $y1, $x2, $y2);
24234 $this->StopTransform();
24235 }
24236 break;
24237 }
24238 case 'polyline':
24239 case 'polygon': {
24240 if ($invisible) {
24241 break;
24242 }
24243 $points = (isset($attribs['points'])?$attribs['points']:'0 0');
24244 $points = trim($points);
24245 // note that point may use a complex syntax not covered here
24246 $points = preg_split('/[\,\s]+/si', $points);
24247 if (count($points) < 4) {
24248 break;
24249 }
24250 $p = array();
24251 $xmin = 2147483647;
24252 $xmax = 0;
24253 $ymin = 2147483647;
24254 $ymax = 0;
24255 foreach ($points as $key => $val) {
24256 $p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24257 if (($key % 2) == 0) {
24258 // X coordinate
24259 $xmin = min($xmin, $p[$key]);
24260 $xmax = max($xmax, $p[$key]);
24261 } else {
24262 // Y coordinate
24263 $ymin = min($ymin, $p[$key]);
24264 $ymax = max($ymax, $p[$key]);
24265 }
24266 }
24267 $x = $xmin;
24268 $y = $ymin;
24269 $w = ($xmax - $xmin);
24270 $h = ($ymax - $ymin);
24271 if ($name == 'polyline') {
24272 $this->StartTransform();
24273 $this->SVGTransform($tm);
24274 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
24275 if (!empty($obstyle)) {
24276 $this->PolyLine($p, $obstyle, array(), array());
24277 }
24278 $this->StopTransform();
24279 } else { // polygon
24280 if ($clipping) {
24281 $this->SVGTransform($tm);
24282 $this->Polygon($p, 'CNZ', array(), array(), true);
24283 } else {
24284 $this->StartTransform();
24285 $this->SVGTransform($tm);
24286 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
24287 if (!empty($obstyle)) {
24288 $this->Polygon($p, $obstyle, array(), array(), true);
24289 }
24290 $this->StopTransform();
24291 }
24292 }
24293 break;
24294 }
24295 // image
24296 case 'image': {
24297 if ($invisible) {
24298 break;
24299 }
24300 if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
24301 break;
24302 }
24303 $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24304 $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24305 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24306 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24307 $img = $attribs['xlink:href'];
24308 if (!$clipping) {
24309 $this->StartTransform();
24310 $this->SVGTransform($tm);
24311 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
24312 if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
24313 // embedded image encoded as base64
24314 $img = '@'.base64_decode(substr($img, strlen($m[0])));
24315 } else {
24316 // fix image path
24317 if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
24318 // replace relative path with full server path
24319 $img = $this->svgdir.'/'.$img;
24320 }
24321 if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
24322 $findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
24323 if (($findroot === false) OR ($findroot > 1)) {
24324 if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
24325 $img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
24326 } else {
24327 $img = $_SERVER['DOCUMENT_ROOT'].$img;
24328 }
24329 }
24330 }
24331 $img = urldecode($img);
24332 $testscrtype = @parse_url($img);
24333 if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
24334 // convert URL to server path
24335 $img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
24336 }
24337 }
24338 // get image type
24339 $imgtype = TCPDF_IMAGES::getImageFileType($img);
24340 if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
24341 $this->ImageEps($img, $x, $y, $w, $h);
24342 } elseif ($imgtype == 'svg') {
24343 $this->ImageSVG($img, $x, $y, $w, $h);
24344 } else {
24345 $this->Image($img, $x, $y, $w, $h);
24346 }
24347 $this->StopTransform();
24348 }
24349 break;
24350 }
24351 // text
24352 case 'text':
24353 case 'tspan': {
24354 // only basic support - advanced features must be implemented
24355 $this->svgtextmode['invisible'] = $invisible;
24356 if ($invisible) {
24357 break;
24358 }
24359 array_push($this->svgstyles, $svgstyle);
24360 if (isset($attribs['x'])) {
24361 $x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
24362 } elseif ($name == 'tspan') {
24363 $x = $this->x;
24364 } else {
24365 $x = 0;
24366 }
24367 if (isset($attribs['dx'])) {
24368 $x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
24369 }
24370 if (isset($attribs['y'])) {
24371 $y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
24372 } elseif ($name == 'tspan') {
24373 $y = $this->y;
24374 } else {
24375 $y = 0;
24376 }
24377 if (isset($attribs['dy'])) {
24378 $y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
24379 }
24380 $svgstyle['text-color'] = $svgstyle['fill'];
24381 $this->svgtext = '';
24382 if (isset($svgstyle['text-anchor'])) {
24383 $this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
24384 } else {
24385 $this->svgtextmode['text-anchor'] = 'start';
24386 }
24387 if (isset($svgstyle['direction'])) {
24388 if ($svgstyle['direction'] == 'rtl') {
24389 $this->svgtextmode['rtl'] = true;
24390 } else {
24391 $this->svgtextmode['rtl'] = false;
24392 }
24393 } else {
24394 $this->svgtextmode['rtl'] = false;
24395 }
24396 if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
24397 $this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
24398 } else {
24399 $this->svgtextmode['stroke'] = false;
24400 }
24401 $this->StartTransform();
24402 $this->SVGTransform($tm);
24403 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
24404 $this->x = $x;
24405 $this->y = $y;
24406 break;
24407 }
24408 // use
24409 case 'use': {
24410 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24411 $svgdefid = substr($attribs['xlink:href'], 1);
24412 if (isset($this->svgdefs[$svgdefid])) {
24413 $use = $this->svgdefs[$svgdefid];
24414 if (isset($attribs['xlink:href'])) {
24415 unset($attribs['xlink:href']);
24416 }
24417 if (isset($attribs['id'])) {
24418 unset($attribs['id']);
24419 }
24420 if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
24421 $attribs['x'] += $use['attribs']['x'];
24422 }
24423 if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
24424 $attribs['y'] += $use['attribs']['y'];
24425 }
24426 if (empty($attribs['style'])) {
24427 $attribs['style'] = '';
24428 }
24429 if (!empty($use['attribs']['style'])) {
24430 // merge styles
24431 $attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
24432 }
24433 $attribs = array_merge($use['attribs'], $attribs);
24434 $this->startSVGElementHandler($parser, $use['name'], $attribs);
24435 return;
24436 }
24437 }
24438 break;
24439 }
24440 default: {
24441 break;
24442 }
24443 } // end of switch
24444 // process child elements
24445 if (!empty($attribs['child_elements'])) {
24446 $child_elements = $attribs['child_elements'];
24447 unset($attribs['child_elements']);
24448 foreach($child_elements as $child_element) {
24449 if (empty($child_element['attribs']['closing_tag'])) {
24450 $this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
24451 } else {
24452 if (isset($child_element['attribs']['content'])) {
24453 $this->svgtext = $child_element['attribs']['content'];
24454 }
24455 $this->endSVGElementHandler('child-tag', $child_element['name']);
24456 }
24457 }
24458 }
24459 }
24460
24461 /**
24462 * Sets the closing SVG element handler function for the XML parser.
24463 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24464 * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
24465 * @author Nicola Asuni
24466 * @since 5.0.000 (2010-05-02)
24467 * @protected
24468 */
24469 protected function endSVGElementHandler($parser, $name) {
24470 if ($this->svgdefsmode AND !in_array($name, array('defs', 'clipPath', 'linearGradient', 'radialGradient', 'stop'))) {;
24471 if (end($this->svgdefs) !== FALSE) {
24472 $last_svgdefs_id = key($this->svgdefs);
24473 if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
24474 foreach($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'] as $child_element) {
24475 if (isset($child_element['attribs']['id']) AND ($child_element['name'] == $name)) {
24476 $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$child_element['attribs']['id'].'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24477 return;
24478 }
24479 }
24480 if ($this->svgdefs[$last_svgdefs_id]['name'] == $name) {
24481 $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$last_svgdefs_id.'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24482 return;
24483 }
24484 }
24485 }
24486 return;
24487 }
24488 switch($name) {
24489 case 'defs': {
24490 $this->svgdefsmode = false;
24491 break;
24492 }
24493 // clipPath
24494 case 'clipPath': {
24495 $this->svgclipmode = false;
24496 break;
24497 }
24498 case 'g': {
24499 // ungroup: remove last style from array
24500 array_pop($this->svgstyles);
24501 $this->StopTransform();
24502 break;
24503 }
24504 case 'text':
24505 case 'tspan': {
24506 if ($this->svgtextmode['invisible']) {
24507 // This implementation must be fixed to following the rule:
24508 // 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.
24509 break;
24510 }
24511 // print text
24512 $text = $this->svgtext;
24513 //$text = $this->stringTrim($text);
24514 $textlen = $this->GetStringWidth($text);
24515 if ($this->svgtextmode['text-anchor'] != 'start') {
24516 // check if string is RTL text
24517 if ($this->svgtextmode['text-anchor'] == 'end') {
24518 if ($this->svgtextmode['rtl']) {
24519 $this->x += $textlen;
24520 } else {
24521 $this->x -= $textlen;
24522 }
24523 } elseif ($this->svgtextmode['text-anchor'] == 'middle') {
24524 if ($this->svgtextmode['rtl']) {
24525 $this->x += ($textlen / 2);
24526 } else {
24527 $this->x -= ($textlen / 2);
24528 }
24529 }
24530 }
24531 $textrendermode = $this->textrendermode;
24532 $textstrokewidth = $this->textstrokewidth;
24533 $this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
24534 if ($name == 'text') {
24535 // store current coordinates
24536 $tmpx = $this->x;
24537 $tmpy = $this->y;
24538 }
24539 $this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
24540 if ($name == 'text') {
24541 // restore coordinates
24542 $this->x = $tmpx;
24543 $this->y = $tmpy;
24544 }
24545 // restore previous rendering mode
24546 $this->textrendermode = $textrendermode;
24547 $this->textstrokewidth = $textstrokewidth;
24548 $this->svgtext = '';
24549 $this->StopTransform();
24550 if (!$this->svgdefsmode) {
24551 array_pop($this->svgstyles);
24552 }
24553 break;
24554 }
24555 default: {
24556 break;
24557 }
24558 }
24559 }
24560
24561 /**
24562 * Sets the character data handler function for the XML parser.
24563 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24564 * @param $data (string) The second parameter, data, contains the character data as a string.
24565 * @author Nicola Asuni
24566 * @since 5.0.000 (2010-05-02)
24567 * @protected
24568 */
24569 protected function segSVGContentHandler($parser, $data) {
24570 $this->svgtext .= $data;
24571 }
24572
24573 // --- END SVG METHODS -----------------------------------------------------
24574
24575} // END OF TCPDF CLASS
24576
24577//============================================================+
24578// END OF FILE
24579//============================================================+