PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 2.3.1
GiveWP – Donation Plugin and Fundraising Platform v2.3.1
4.18.0 4.17.0 4.16.9 4.16.8.1 4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 All 257 releases
give / includes / libraries / tcpdf / tcpdf.php

tcpdf.php in GiveWP – Donation Plugin and Fundraising Platform 2.3.1, at includes/libraries/tcpdf/tcpdf.php

24,488 lines 876.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 //============================================================+
3 // File name : tcpdf.php
4 // Version : 6.2.13
5 // Begin : 2002-08-03
6 // Last Update : 2015-06-18
7 // Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - [email protected]
8 // License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
9 // -------------------------------------------------------------------
10 // Copyright (C) 2002-2015 Nicola Asuni - Tecnick.com LTD
11 //
12 // This file is part of TCPDF software library.
13 //
14 // TCPDF is free software: you can redistribute it and/or modify it
15 // under the terms of the GNU Lesser General Public License as
16 // published by the Free Software Foundation, either version 3 of the
17 // License, or (at your option) any later version.
18 //
19 // TCPDF is distributed in the hope that it will be useful, but
20 // WITHOUT ANY WARRANTY; without even the implied warranty of
21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22 // See the GNU Lesser General Public License for more details.
23 //
24 // You should have received a copy of the License
25 // along with TCPDF. If not, see
26 // <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
27 //
28 // See LICENSE.TXT file for more information.
29 // -------------------------------------------------------------------
30 //
31 // Description :
32 // This is a PHP class for generating PDF documents without requiring external extensions.
33 //
34 // NOTE:
35 // This class was originally derived in 2002 from the Public
36 // Domain FPDF class by Olivier Plathey (http://www.fpdf.org),
37 // but now is almost entirely rewritten and contains thousands of
38 // new lines of code and hundreds new features.
39 //
40 // Main features:
41 // * no external libraries are required for the basic functions;
42 // * all standard page formats, custom page formats, custom margins and units of measure;
43 // * UTF-8 Unicode and Right-To-Left languages;
44 // * TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;
45 // * font subsetting;
46 // * methods to publish some XHTML + CSS code, Javascript and Forms;
47 // * images, graphic (geometric figures) and transformation methods;
48 // * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via 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 Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;
50 // * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;
51 // * automatic page header and footer management;
52 // * document encryption up to 256 bit and digital signature certifications;
53 // * transactions to UNDO commands;
54 // * PDF annotations, including links, text and file attachments;
55 // * text rendering modes (fill, stroke and clipping);
56 // * multiple columns mode;
57 // * no-write page regions;
58 // * bookmarks, named destinations and table of content;
59 // * text hyphenation;
60 // * text stretching and spacing (tracking);
61 // * automatic page break, line break and text alignments including justification;
62 // * automatic page numbering and page groups;
63 // * move and delete pages;
64 // * page compression (requires php-zlib extension);
65 // * XOBject Templates;
66 // * Layers and object visibility.
67 // * PDF/A-1b support
68 //============================================================+
69
70 /**
71 * @file
72 * This is a PHP class for generating PDF documents without requiring external extensions.<br>
73 * TCPDF project (http://www.tcpdf.org) was originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
74 * <h3>TCPDF main features are:</h3>
75 * <ul>
76 * <li>no external libraries are required for the basic functions;</li>
77 * <li>all standard page formats, custom page formats, custom margins and units of measure;</li>
78 * <li>UTF-8 Unicode and Right-To-Left languages;</li>
79 * <li>TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;</li>
80 * <li>font subsetting;</li>
81 * <li>methods to publish some XHTML + CSS code, Javascript and Forms;</li>
82 * <li>images, graphic (geometric figures) and transformation methods;
83 * <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via 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 Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;</li>
85 * <li>JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;</li>
86 * <li>automatic page header and footer management;</li>
87 * <li>document encryption up to 256 bit and digital signature certifications;</li>
88 * <li>transactions to UNDO commands;</li>
89 * <li>PDF annotations, including links, text and file attachments;</li>
90 * <li>text rendering modes (fill, stroke and clipping);</li>
91 * <li>multiple columns mode;</li>
92 * <li>no-write page regions;</li>
93 * <li>bookmarks, named destinations and table of content;</li>
94 * <li>text hyphenation;</li>
95 * <li>text stretching and spacing (tracking);</li>
96 * <li>automatic page break, line break and text alignments including justification;</li>
97 * <li>automatic page numbering and page groups;</li>
98 * <li>move and delete pages;</li>
99 * <li>page compression (requires php-zlib extension);</li>
100 * <li>XOBject Templates;</li>
101 * <li>Layers and object visibility;</li>
102 * <li>PDF/A-1b support.</li>
103 * </ul>
104 * Tools to encode your unicode fonts are on fonts/utils directory.</p>
105 * @package com.tecnick.tcpdf
106 * @author Nicola Asuni
107 * @version 6.2.8
108 */
109
110 // TCPDF configuration
111 require_once(dirname(__FILE__).'/tcpdf_autoconfig.php');
112 // TCPDF static font methods and data
113 require_once(dirname(__FILE__).'/include/tcpdf_font_data.php');
114 // TCPDF static font methods and data
115 require_once(dirname(__FILE__).'/include/tcpdf_fonts.php');
116 // TCPDF static color methods and data
117 require_once(dirname(__FILE__).'/include/tcpdf_colors.php');
118 // TCPDF static image methods and data
119 require_once(dirname(__FILE__).'/include/tcpdf_images.php');
120 // TCPDF static methods and data
121 require_once(dirname(__FILE__).'/include/tcpdf_static.php');
122
123 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
124
125 /**
126 * @class TCPDF
127 * PHP class for generating PDF documents without requiring external extensions.
128 * TCPDF project (http://www.tcpdf.org) has been originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
129 * @package com.tecnick.tcpdf
130 * @brief PHP class for generating PDF documents without requiring external extensions.
131 * @version 6.2.8
132 * @author Nicola Asuni - [email protected]
133 */
134 class 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 * Depth of the svg tag, to keep track if the svg tag is a subtag or the root tag.
346 * @protected
347 */
348 protected $svg_tag_depth = 0;
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 * Counts the number of fonts.
1188 * @protected
1189 * @since 4.5.000 (2009-01-02)
1190 */
1191 protected $numfonts = 0;
1192
1193 /**
1194 * Store the font keys.
1195 * @protected
1196 * @since 4.5.000 (2009-01-02)
1197 */
1198 protected $fontkeys = array();
1199
1200 /**
1201 * Store the font object IDs.
1202 * @protected
1203 * @since 4.8.001 (2009-09-09)
1204 */
1205 protected $font_obj_ids = array();
1206
1207 /**
1208 * Store the fage status (true when opened, false when closed).
1209 * @protected
1210 * @since 4.5.000 (2009-01-02)
1211 */
1212 protected $pageopen = array();
1213
1214 /**
1215 * Default monospace font.
1216 * @protected
1217 * @since 4.5.025 (2009-03-10)
1218 */
1219 protected $default_monospaced_font = 'courier';
1220
1221 /**
1222 * Cloned copy of the current class object.
1223 * @protected
1224 * @since 4.5.029 (2009-03-19)
1225 */
1226 protected $objcopy;
1227
1228 /**
1229 * Array used to store the lengths of cache files.
1230 * @protected
1231 * @since 4.5.029 (2009-03-19)
1232 */
1233 protected $cache_file_length = array();
1234
1235 /**
1236 * Table header content to be repeated on each new page.
1237 * @protected
1238 * @since 4.5.030 (2009-03-20)
1239 */
1240 protected $thead = '';
1241
1242 /**
1243 * Margins used for table header.
1244 * @protected
1245 * @since 4.5.030 (2009-03-20)
1246 */
1247 protected $theadMargins = array();
1248
1249 /**
1250 * Boolean flag to enable document digital signature.
1251 * @protected
1252 * @since 4.6.005 (2009-04-24)
1253 */
1254 protected $sign = false;
1255
1256 /**
1257 * Digital signature data.
1258 * @protected
1259 * @since 4.6.005 (2009-04-24)
1260 */
1261 protected $signature_data = array();
1262
1263 /**
1264 * Digital signature max length.
1265 * @protected
1266 * @since 4.6.005 (2009-04-24)
1267 */
1268 protected $signature_max_length = 11742;
1269
1270 /**
1271 * Data for digital signature appearance.
1272 * @protected
1273 * @since 5.3.011 (2010-06-16)
1274 */
1275 protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1276
1277 /**
1278 * Array of empty digital signature appearances.
1279 * @protected
1280 * @since 5.9.101 (2011-07-06)
1281 */
1282 protected $empty_signature_appearance = array();
1283
1284 /**
1285 * Boolean flag to enable document timestamping with TSA.
1286 * @protected
1287 * @since 6.0.085 (2014-06-19)
1288 */
1289 protected $tsa_timestamp = false;
1290
1291 /**
1292 * Timestamping data.
1293 * @protected
1294 * @since 6.0.085 (2014-06-19)
1295 */
1296 protected $tsa_data = array();
1297
1298 /**
1299 * Regular expression used to find blank characters (required for word-wrapping).
1300 * @protected
1301 * @since 4.6.006 (2009-04-28)
1302 */
1303 protected $re_spaces = '/[^\S\xa0]/';
1304
1305 /**
1306 * Array of $re_spaces parts.
1307 * @protected
1308 * @since 5.5.011 (2010-07-09)
1309 */
1310 protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1311
1312 /**
1313 * Digital signature object ID.
1314 * @protected
1315 * @since 4.6.022 (2009-06-23)
1316 */
1317 protected $sig_obj_id = 0;
1318
1319 /**
1320 * ID of page objects.
1321 * @protected
1322 * @since 4.7.000 (2009-08-29)
1323 */
1324 protected $page_obj_id = array();
1325
1326 /**
1327 * List of form annotations IDs.
1328 * @protected
1329 * @since 4.8.000 (2009-09-07)
1330 */
1331 protected $form_obj_id = array();
1332
1333 /**
1334 * 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.
1335 * @protected
1336 * @since 4.8.000 (2009-09-07)
1337 */
1338 protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1339
1340 /**
1341 * Javascript objects array.
1342 * @protected
1343 * @since 4.8.000 (2009-09-07)
1344 */
1345 protected $js_objects = array();
1346
1347 /**
1348 * Current form action (used during XHTML rendering).
1349 * @protected
1350 * @since 4.8.000 (2009-09-07)
1351 */
1352 protected $form_action = '';
1353
1354 /**
1355 * Current form encryption type (used during XHTML rendering).
1356 * @protected
1357 * @since 4.8.000 (2009-09-07)
1358 */
1359 protected $form_enctype = 'application/x-www-form-urlencoded';
1360
1361 /**
1362 * Current method to submit forms.
1363 * @protected
1364 * @since 4.8.000 (2009-09-07)
1365 */
1366 protected $form_mode = 'post';
1367
1368 /**
1369 * List of fonts used on form fields (fontname => fontkey).
1370 * @protected
1371 * @since 4.8.001 (2009-09-09)
1372 */
1373 protected $annotation_fonts = array();
1374
1375 /**
1376 * List of radio buttons parent objects.
1377 * @protected
1378 * @since 4.8.001 (2009-09-09)
1379 */
1380 protected $radiobutton_groups = array();
1381
1382 /**
1383 * List of radio group objects IDs.
1384 * @protected
1385 * @since 4.8.001 (2009-09-09)
1386 */
1387 protected $radio_groups = array();
1388
1389 /**
1390 * Text indentation value (used for text-indent CSS attribute).
1391 * @protected
1392 * @since 4.8.006 (2009-09-23)
1393 */
1394 protected $textindent = 0;
1395
1396 /**
1397 * Store page number when startTransaction() is called.
1398 * @protected
1399 * @since 4.8.006 (2009-09-23)
1400 */
1401 protected $start_transaction_page = 0;
1402
1403 /**
1404 * Store Y position when startTransaction() is called.
1405 * @protected
1406 * @since 4.9.001 (2010-03-28)
1407 */
1408 protected $start_transaction_y = 0;
1409
1410 /**
1411 * True when we are printing the thead section on a new page.
1412 * @protected
1413 * @since 4.8.027 (2010-01-25)
1414 */
1415 protected $inthead = false;
1416
1417 /**
1418 * Array of column measures (width, space, starting Y position).
1419 * @protected
1420 * @since 4.9.001 (2010-03-28)
1421 */
1422 protected $columns = array();
1423
1424 /**
1425 * Number of colums.
1426 * @protected
1427 * @since 4.9.001 (2010-03-28)
1428 */
1429 protected $num_columns = 1;
1430
1431 /**
1432 * Current column number.
1433 * @protected
1434 * @since 4.9.001 (2010-03-28)
1435 */
1436 protected $current_column = 0;
1437
1438 /**
1439 * Starting page for columns.
1440 * @protected
1441 * @since 4.9.001 (2010-03-28)
1442 */
1443 protected $column_start_page = 0;
1444
1445 /**
1446 * Maximum page and column selected.
1447 * @protected
1448 * @since 5.8.000 (2010-08-11)
1449 */
1450 protected $maxselcol = array('page' => 0, 'column' => 0);
1451
1452 /**
1453 * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
1454 * @protected
1455 * @since 5.8.000 (2010-08-11)
1456 */
1457 protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1458
1459 /**
1460 * 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.
1461 * @protected
1462 * @since 4.9.008 (2010-04-03)
1463 */
1464 protected $textrendermode = 0;
1465
1466 /**
1467 * Text stroke width in doc units.
1468 * @protected
1469 * @since 4.9.008 (2010-04-03)
1470 */
1471 protected $textstrokewidth = 0;
1472
1473 /**
1474 * Current stroke color.
1475 * @protected
1476 * @since 4.9.008 (2010-04-03)
1477 */
1478 protected $strokecolor;
1479
1480 /**
1481 * Default unit of measure for document.
1482 * @protected
1483 * @since 5.0.000 (2010-04-22)
1484 */
1485 protected $pdfunit = 'mm';
1486
1487 /**
1488 * Boolean flag true when we are on TOC (Table Of Content) page.
1489 * @protected
1490 */
1491 protected $tocpage = false;
1492
1493 /**
1494 * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1495 * @protected
1496 * @since 5.0.000 (2010-04-26)
1497 */
1498 protected $rasterize_vector_images = false;
1499
1500 /**
1501 * Boolean flag: if true enables font subsetting by default.
1502 * @protected
1503 * @since 5.3.002 (2010-06-07)
1504 */
1505 protected $font_subsetting = true;
1506
1507 /**
1508 * Array of default graphic settings.
1509 * @protected
1510 * @since 5.5.008 (2010-07-02)
1511 */
1512 protected $default_graphic_vars = array();
1513
1514 /**
1515 * Array of XObjects.
1516 * @protected
1517 * @since 5.8.014 (2010-08-23)
1518 */
1519 protected $xobjects = array();
1520
1521 /**
1522 * Boolean value true when we are inside an XObject.
1523 * @protected
1524 * @since 5.8.017 (2010-08-24)
1525 */
1526 protected $inxobj = false;
1527
1528 /**
1529 * Current XObject ID.
1530 * @protected
1531 * @since 5.8.017 (2010-08-24)
1532 */
1533 protected $xobjid = '';
1534
1535 /**
1536 * Percentage of character stretching.
1537 * @protected
1538 * @since 5.9.000 (2010-09-29)
1539 */
1540 protected $font_stretching = 100;
1541
1542 /**
1543 * Increases or decreases the space between characters in a text by the specified amount (tracking).
1544 * @protected
1545 * @since 5.9.000 (2010-09-29)
1546 */
1547 protected $font_spacing = 0;
1548
1549 /**
1550 * Array of no-write regions.
1551 * ('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)
1552 * @protected
1553 * @since 5.9.003 (2010-10-14)
1554 */
1555 protected $page_regions = array();
1556
1557 /**
1558 * Boolean value true when page region check is active.
1559 * @protected
1560 */
1561 protected $check_page_regions = true;
1562
1563 /**
1564 * Array of PDF layers data.
1565 * @protected
1566 * @since 5.9.102 (2011-07-13)
1567 */
1568 protected $pdflayers = array();
1569
1570 /**
1571 * A dictionary of names and corresponding destinations (Dests key on document Catalog).
1572 * @protected
1573 * @since 5.9.097 (2011-06-23)
1574 */
1575 protected $dests = array();
1576
1577 /**
1578 * Object ID for Named Destinations
1579 * @protected
1580 * @since 5.9.097 (2011-06-23)
1581 */
1582 protected $n_dests;
1583
1584 /**
1585 * Embedded Files Names
1586 * @protected
1587 * @since 5.9.204 (2013-01-23)
1588 */
1589 protected $efnames = array();
1590
1591 /**
1592 * Directory used for the last SVG image.
1593 * @protected
1594 * @since 5.0.000 (2010-05-05)
1595 */
1596 protected $svgdir = '';
1597
1598 /**
1599 * Deafult unit of measure for SVG.
1600 * @protected
1601 * @since 5.0.000 (2010-05-02)
1602 */
1603 protected $svgunit = 'px';
1604
1605 /**
1606 * Array of SVG gradients.
1607 * @protected
1608 * @since 5.0.000 (2010-05-02)
1609 */
1610 protected $svggradients = array();
1611
1612 /**
1613 * ID of last SVG gradient.
1614 * @protected
1615 * @since 5.0.000 (2010-05-02)
1616 */
1617 protected $svggradientid = 0;
1618
1619 /**
1620 * Boolean value true when in SVG defs group.
1621 * @protected
1622 * @since 5.0.000 (2010-05-02)
1623 */
1624 protected $svgdefsmode = false;
1625
1626 /**
1627 * Array of SVG defs.
1628 * @protected
1629 * @since 5.0.000 (2010-05-02)
1630 */
1631 protected $svgdefs = array();
1632
1633 /**
1634 * Boolean value true when in SVG clipPath tag.
1635 * @protected
1636 * @since 5.0.000 (2010-04-26)
1637 */
1638 protected $svgclipmode = false;
1639
1640 /**
1641 * Array of SVG clipPath commands.
1642 * @protected
1643 * @since 5.0.000 (2010-05-02)
1644 */
1645 protected $svgclippaths = array();
1646
1647 /**
1648 * Array of SVG clipPath tranformation matrix.
1649 * @protected
1650 * @since 5.8.022 (2010-08-31)
1651 */
1652 protected $svgcliptm = array();
1653
1654 /**
1655 * ID of last SVG clipPath.
1656 * @protected
1657 * @since 5.0.000 (2010-05-02)
1658 */
1659 protected $svgclipid = 0;
1660
1661 /**
1662 * SVG text.
1663 * @protected
1664 * @since 5.0.000 (2010-05-02)
1665 */
1666 protected $svgtext = '';
1667
1668 /**
1669 * SVG text properties.
1670 * @protected
1671 * @since 5.8.013 (2010-08-23)
1672 */
1673 protected $svgtextmode = array();
1674
1675 /**
1676 * Array of SVG properties.
1677 * @protected
1678 * @since 5.0.000 (2010-05-02)
1679 */
1680 protected $svgstyles = array(array(
1681 'alignment-baseline' => 'auto',
1682 'baseline-shift' => 'baseline',
1683 'clip' => 'auto',
1684 'clip-path' => 'none',
1685 'clip-rule' => 'nonzero',
1686 'color' => 'black',
1687 'color-interpolation' => 'sRGB',
1688 'color-interpolation-filters' => 'linearRGB',
1689 'color-profile' => 'auto',
1690 'color-rendering' => 'auto',
1691 'cursor' => 'auto',
1692 'direction' => 'ltr',
1693 'display' => 'inline',
1694 'dominant-baseline' => 'auto',
1695 'enable-background' => 'accumulate',
1696 'fill' => 'black',
1697 'fill-opacity' => 1,
1698 'fill-rule' => 'nonzero',
1699 'filter' => 'none',
1700 'flood-color' => 'black',
1701 'flood-opacity' => 1,
1702 'font' => '',
1703 'font-family' => 'helvetica',
1704 'font-size' => 'medium',
1705 'font-size-adjust' => 'none',
1706 'font-stretch' => 'normal',
1707 'font-style' => 'normal',
1708 'font-variant' => 'normal',
1709 'font-weight' => 'normal',
1710 'glyph-orientation-horizontal' => '0deg',
1711 'glyph-orientation-vertical' => 'auto',
1712 'image-rendering' => 'auto',
1713 'kerning' => 'auto',
1714 'letter-spacing' => 'normal',
1715 'lighting-color' => 'white',
1716 'marker' => '',
1717 'marker-end' => 'none',
1718 'marker-mid' => 'none',
1719 'marker-start' => 'none',
1720 'mask' => 'none',
1721 'opacity' => 1,
1722 'overflow' => 'auto',
1723 'pointer-events' => 'visiblePainted',
1724 'shape-rendering' => 'auto',
1725 'stop-color' => 'black',
1726 'stop-opacity' => 1,
1727 'stroke' => 'none',
1728 'stroke-dasharray' => 'none',
1729 'stroke-dashoffset' => 0,
1730 'stroke-linecap' => 'butt',
1731 'stroke-linejoin' => 'miter',
1732 'stroke-miterlimit' => 4,
1733 'stroke-opacity' => 1,
1734 'stroke-width' => 1,
1735 'text-anchor' => 'start',
1736 'text-decoration' => 'none',
1737 'text-rendering' => 'auto',
1738 'unicode-bidi' => 'normal',
1739 'visibility' => 'visible',
1740 'word-spacing' => 'normal',
1741 'writing-mode' => 'lr-tb',
1742 'text-color' => 'black',
1743 'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1744 ));
1745
1746 /**
1747 * If true force sRGB color profile for all document.
1748 * @protected
1749 * @since 5.9.121 (2011-09-28)
1750 */
1751 protected $force_srgb = false;
1752
1753 /**
1754 * If true set the document to PDF/A mode.
1755 * @protected
1756 * @since 5.9.121 (2011-09-27)
1757 */
1758 protected $pdfa_mode = false;
1759
1760 /**
1761 * Document creation date-time
1762 * @protected
1763 * @since 5.9.152 (2012-03-22)
1764 */
1765 protected $doc_creation_timestamp;
1766
1767 /**
1768 * Document modification date-time
1769 * @protected
1770 * @since 5.9.152 (2012-03-22)
1771 */
1772 protected $doc_modification_timestamp;
1773
1774 /**
1775 * Custom XMP data.
1776 * @protected
1777 * @since 5.9.128 (2011-10-06)
1778 */
1779 protected $custom_xmp = '';
1780
1781 /**
1782 * Overprint mode array.
1783 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1784 * @protected
1785 * @since 5.9.152 (2012-03-23)
1786 */
1787 protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1788
1789 /**
1790 * Alpha mode array.
1791 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1792 * @protected
1793 * @since 5.9.152 (2012-03-23)
1794 */
1795 protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1796
1797 /**
1798 * Define the page boundaries boxes to be set on document.
1799 * @protected
1800 * @since 5.9.152 (2012-03-23)
1801 */
1802 protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1803
1804 /**
1805 * If true print TCPDF meta link.
1806 * @protected
1807 * @since 5.9.152 (2012-03-23)
1808 */
1809 protected $tcpdflink = true;
1810
1811 /**
1812 * Cache array for computed GD gamma values.
1813 * @protected
1814 * @since 5.9.1632 (2012-06-05)
1815 */
1816 protected $gdgammacache = array();
1817
1818 //------------------------------------------------------------
1819 // METHODS
1820 //------------------------------------------------------------
1821
1822 /**
1823 * This is the class constructor.
1824 * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1825 *
1826 * 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.
1827 *
1828 * @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>
1829 * @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.
1830 * @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().
1831 * @param $unicode (boolean) TRUE means that the input text is unicode (default = true)
1832 * @param $encoding (string) Charset encoding (used only when converting back html entities); default is UTF-8.
1833 * @param $diskcache (boolean) DEPRECATED FEATURE
1834 * @param $pdfa (boolean) If TRUE set the document to PDF/A mode.
1835 * @public
1836 * @see getPageSizeFromFormat(), setPageFormat()
1837 */
1838 public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
1839 /* Set internal character encoding to ASCII */
1840 if (function_exists('mb_internal_encoding') AND mb_internal_encoding()) {
1841 $this->internal_encoding = mb_internal_encoding();
1842 mb_internal_encoding('ASCII');
1843 }
1844 // set file ID for trailer
1845 $serformat = (is_array($format) ? json_encode($format) : $format);
1846 $this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
1847 $this->font_obj_ids = array();
1848 $this->page_obj_id = array();
1849 $this->form_obj_id = array();
1850 // set pdf/a mode
1851 $this->pdfa_mode = $pdfa;
1852 $this->force_srgb = false;
1853 // set language direction
1854 $this->rtl = false;
1855 $this->tmprtl = false;
1856 // some checks
1857 $this->_dochecks();
1858 // initialization of properties
1859 $this->isunicode = $unicode;
1860 $this->page = 0;
1861 $this->transfmrk[0] = array();
1862 $this->pagedim = array();
1863 $this->n = 2;
1864 $this->buffer = '';
1865 $this->pages = array();
1866 $this->state = 0;
1867 $this->fonts = array();
1868 $this->FontFiles = array();
1869 $this->diffs = array();
1870 $this->images = array();
1871 $this->links = array();
1872 $this->gradients = array();
1873 $this->InFooter = false;
1874 $this->lasth = 0;
1875 $this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
1876 $this->FontStyle = '';
1877 $this->FontSizePt = 12;
1878 $this->underline = false;
1879 $this->overline = false;
1880 $this->linethrough = false;
1881 $this->DrawColor = '0 G';
1882 $this->FillColor = '0 g';
1883 $this->TextColor = '0 g';
1884 $this->ColorFlag = false;
1885 $this->pdflayers = array();
1886 // encryption values
1887 $this->encrypted = false;
1888 $this->last_enc_key = '';
1889 // standard Unicode fonts
1890 $this->CoreFonts = array(
1891 'courier'=>'Courier',
1892 'courierB'=>'Courier-Bold',
1893 'courierI'=>'Courier-Oblique',
1894 'courierBI'=>'Courier-BoldOblique',
1895 'helvetica'=>'Helvetica',
1896 'helveticaB'=>'Helvetica-Bold',
1897 'helveticaI'=>'Helvetica-Oblique',
1898 'helveticaBI'=>'Helvetica-BoldOblique',
1899 'times'=>'Times-Roman',
1900 'timesB'=>'Times-Bold',
1901 'timesI'=>'Times-Italic',
1902 'timesBI'=>'Times-BoldItalic',
1903 'symbol'=>'Symbol',
1904 'zapfdingbats'=>'ZapfDingbats'
1905 );
1906 // set scale factor
1907 $this->setPageUnit($unit);
1908 // set page format and orientation
1909 $this->setPageFormat($format, $orientation);
1910 // page margins (1 cm)
1911 $margin = 28.35 / $this->k;
1912 $this->SetMargins($margin, $margin);
1913 $this->clMargin = $this->lMargin;
1914 $this->crMargin = $this->rMargin;
1915 // internal cell padding
1916 $cpadding = $margin / 10;
1917 $this->setCellPaddings($cpadding, 0, $cpadding, 0);
1918 // cell margins
1919 $this->setCellMargins(0, 0, 0, 0);
1920 // line width (0.2 mm)
1921 $this->LineWidth = 0.57 / $this->k;
1922 $this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
1923 $this->linestyleCap = '0 J';
1924 $this->linestyleJoin = '0 j';
1925 $this->linestyleDash = '[] 0 d';
1926 // automatic page break
1927 $this->SetAutoPageBreak(true, (2 * $margin));
1928 // full width display mode
1929 $this->SetDisplayMode('fullwidth');
1930 // compression
1931 $this->SetCompression();
1932 // set default PDF version number
1933 $this->setPDFVersion();
1934 $this->tcpdflink = true;
1935 $this->encoding = $encoding;
1936 $this->HREF = array();
1937 $this->getFontsList();
1938 $this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
1939 $this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
1940 $this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
1941 $this->extgstates = array();
1942 $this->setTextShadow();
1943 // signature
1944 $this->sign = false;
1945 $this->tsa_timestamp = false;
1946 $this->tsa_data = array();
1947 $this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
1948 $this->empty_signature_appearance = array();
1949 // user's rights
1950 $this->ur['enabled'] = false;
1951 $this->ur['document'] = '/FullSave';
1952 $this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
1953 $this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
1954 $this->ur['signature'] = '/Modify';
1955 $this->ur['ef'] = '/Create/Delete/Modify/Import';
1956 $this->ur['formex'] = '';
1957 // set default JPEG quality
1958 $this->jpeg_quality = 75;
1959 // initialize some settings
1960 TCPDF_FONTS::utf8Bidi(array(''), '', false, $this->isunicode, $this->CurrentFont);
1961 // set default font
1962 $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
1963 $this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1964 $this->setFooterFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1965 // check if PCRE Unicode support is enabled
1966 if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
1967 // PCRE unicode support is turned ON
1968 // \s : any whitespace character
1969 // \p{Z} : any separator
1970 // \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
1971 // \xa0 : Unicode Character 'NO-BREAK SPACE' (U+00A0)
1972 //$this->setSpacesRE('/(?!\xa0)[\s\p{Z}\p{Lo}]/u');
1973 $this->setSpacesRE('/(?!\xa0)[\s\p{Z}]/u');
1974 } else {
1975 // PCRE unicode support is turned OFF
1976 $this->setSpacesRE('/[^\S\xa0]/');
1977 }
1978 $this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1979 // set document creation and modification timestamp
1980 $this->doc_creation_timestamp = time();
1981 $this->doc_modification_timestamp = $this->doc_creation_timestamp;
1982 // get default graphic vars
1983 $this->default_graphic_vars = $this->getGraphicVars();
1984 $this->header_xobj_autoreset = false;
1985 $this->custom_xmp = '';
1986 // Call cleanup method after script execution finishes or exit() is called.
1987 // NOTE: This will not be executed if the process is killed with a SIGTERM or SIGKILL signal.
1988 register_shutdown_function(array($this, '_destroy'), true);
1989 }
1990
1991 /**
1992 * Default destructor.
1993 * @public
1994 * @since 1.53.0.TC016
1995 */
1996 public function __destruct() {
1997 // restore internal encoding
1998 if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) {
1999 mb_internal_encoding($this->internal_encoding);
2000 }
2001 // cleanup
2002 $this->_destroy(true);
2003 }
2004
2005 /**
2006 * Set the units of measure for the document.
2007 * @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.
2008 * @public
2009 * @since 3.0.015 (2008-06-06)
2010 */
2011 public function setPageUnit($unit) {
2012 $unit = strtolower($unit);
2013 //Set scale factor
2014 switch ($unit) {
2015 // points
2016 case 'px':
2017 case 'pt': {
2018 $this->k = 1;
2019 break;
2020 }
2021 // millimeters
2022 case 'mm': {
2023 $this->k = $this->dpi / 25.4;
2024 break;
2025 }
2026 // centimeters
2027 case 'cm': {
2028 $this->k = $this->dpi / 2.54;
2029 break;
2030 }
2031 // inches
2032 case 'in': {
2033 $this->k = $this->dpi;
2034 break;
2035 }
2036 // unsupported unit
2037 default : {
2038 $this->Error('Incorrect unit: '.$unit);
2039 break;
2040 }
2041 }
2042 $this->pdfunit = $unit;
2043 if (isset($this->CurOrientation)) {
2044 $this->setPageOrientation($this->CurOrientation);
2045 }
2046 }
2047
2048 /**
2049 * Change the format of the current page
2050 * @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>
2051 * <li>['format'] = page format name (one of the above);</li>
2052 * <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>
2053 * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2054 * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2055 * <li>['MediaBox']['llx'] : lower-left x coordinate</li>
2056 * <li>['MediaBox']['lly'] : lower-left y coordinate</li>
2057 * <li>['MediaBox']['urx'] : upper-right x coordinate</li>
2058 * <li>['MediaBox']['ury'] : upper-right y coordinate</li>
2059 * <li>['CropBox'] : the visible region of default user space:</li>
2060 * <li>['CropBox']['llx'] : lower-left x coordinate</li>
2061 * <li>['CropBox']['lly'] : lower-left y coordinate</li>
2062 * <li>['CropBox']['urx'] : upper-right x coordinate</li>
2063 * <li>['CropBox']['ury'] : upper-right y coordinate</li>
2064 * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2065 * <li>['BleedBox']['llx'] : lower-left x coordinate</li>
2066 * <li>['BleedBox']['lly'] : lower-left y coordinate</li>
2067 * <li>['BleedBox']['urx'] : upper-right x coordinate</li>
2068 * <li>['BleedBox']['ury'] : upper-right y coordinate</li>
2069 * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2070 * <li>['TrimBox']['llx'] : lower-left x coordinate</li>
2071 * <li>['TrimBox']['lly'] : lower-left y coordinate</li>
2072 * <li>['TrimBox']['urx'] : upper-right x coordinate</li>
2073 * <li>['TrimBox']['ury'] : upper-right y coordinate</li>
2074 * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2075 * <li>['ArtBox']['llx'] : lower-left x coordinate</li>
2076 * <li>['ArtBox']['lly'] : lower-left y coordinate</li>
2077 * <li>['ArtBox']['urx'] : upper-right x coordinate</li>
2078 * <li>['ArtBox']['ury'] : upper-right y coordinate</li>
2079 * <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>
2080 * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2081 * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2082 * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2083 * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2084 * <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>
2085 * <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>
2086 * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2087 * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2088 * <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>
2089 * <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>
2090 * <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>
2091 * <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>
2092 * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2093 * </ul>
2094 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul>
2095 * <li>P or Portrait (default)</li>
2096 * <li>L or Landscape</li>
2097 * <li>'' (empty string) for automatic orientation</li>
2098 * </ul>
2099 * @protected
2100 * @since 3.0.015 (2008-06-06)
2101 * @see getPageSizeFromFormat()
2102 */
2103 protected function setPageFormat($format, $orientation='P') {
2104 if (!empty($format) AND isset($this->pagedim[$this->page])) {
2105 // remove inherited values
2106 unset($this->pagedim[$this->page]);
2107 }
2108 if (is_string($format)) {
2109 // get page measures from format name
2110 $pf = TCPDF_STATIC::getPageSizeFromFormat($format);
2111 $this->fwPt = $pf[0];
2112 $this->fhPt = $pf[1];
2113 } else {
2114 // the boundaries of the physical medium on which the page shall be displayed or printed
2115 if (isset($format['MediaBox'])) {
2116 $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);
2117 $this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2118 $this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2119 } else {
2120 if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2121 $pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2122 } else {
2123 if (!isset($format['format'])) {
2124 // default value
2125 $format['format'] = 'A4';
2126 }
2127 $pf = TCPDF_STATIC::getPageSizeFromFormat($format['format']);
2128 }
2129 $this->fwPt = $pf[0];
2130 $this->fhPt = $pf[1];
2131 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2132 }
2133 // the visible region of default user space
2134 if (isset($format['CropBox'])) {
2135 $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);
2136 }
2137 // the region to which the contents of the page shall be clipped when output in a production environment
2138 if (isset($format['BleedBox'])) {
2139 $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);
2140 }
2141 // the intended dimensions of the finished page after trimming
2142 if (isset($format['TrimBox'])) {
2143 $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);
2144 }
2145 // the page's meaningful content (including potential white space)
2146 if (isset($format['ArtBox'])) {
2147 $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);
2148 }
2149 // specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2150 if (isset($format['BoxColorInfo'])) {
2151 $this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2152 }
2153 if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2154 // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2155 $this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2156 }
2157 if (isset($format['PZ'])) {
2158 // The page's preferred zoom (magnification) factor
2159 $this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2160 }
2161 if (isset($format['trans'])) {
2162 // The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2163 if (isset($format['trans']['Dur'])) {
2164 // The page's display duration
2165 $this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2166 }
2167 $stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2168 if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2169 // The transition style that shall be used when moving to this page from another during a presentation
2170 $this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2171 $valid_effect = array('Split', 'Blinds');
2172 $valid_vals = array('H', 'V');
2173 if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2174 $this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2175 }
2176 $valid_effect = array('Split', 'Box', 'Fly');
2177 $valid_vals = array('I', 'O');
2178 if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2179 $this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2180 }
2181 $valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2182 if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2183 if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2184 OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2185 OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2186 $this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2187 }
2188 }
2189 if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2190 $this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2191 }
2192 if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2193 $this->pagedim[$this->page]['trans']['B'] = 'true';
2194 }
2195 } else {
2196 $this->pagedim[$this->page]['trans']['S'] = 'R';
2197 }
2198 if (isset($format['trans']['D'])) {
2199 // The duration of the transition effect, in seconds
2200 $this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2201 } else {
2202 $this->pagedim[$this->page]['trans']['D'] = 1;
2203 }
2204 }
2205 }
2206 $this->setPageOrientation($orientation);
2207 }
2208
2209 /**
2210 * Set page orientation.
2211 * @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>
2212 * @param $autopagebreak (boolean) Boolean indicating if auto-page-break mode should be on or off.
2213 * @param $bottommargin (float) bottom margin of the page.
2214 * @public
2215 * @since 3.0.015 (2008-06-06)
2216 */
2217 public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
2218 if (!isset($this->pagedim[$this->page]['MediaBox'])) {
2219 // the boundaries of the physical medium on which the page shall be displayed or printed
2220 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2221 }
2222 if (!isset($this->pagedim[$this->page]['CropBox'])) {
2223 // the visible region of default user space
2224 $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);
2225 }
2226 if (!isset($this->pagedim[$this->page]['BleedBox'])) {
2227 // the region to which the contents of the page shall be clipped when output in a production environment
2228 $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);
2229 }
2230 if (!isset($this->pagedim[$this->page]['TrimBox'])) {
2231 // the intended dimensions of the finished page after trimming
2232 $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);
2233 }
2234 if (!isset($this->pagedim[$this->page]['ArtBox'])) {
2235 // the page's meaningful content (including potential white space)
2236 $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);
2237 }
2238 if (!isset($this->pagedim[$this->page]['Rotate'])) {
2239 // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2240 $this->pagedim[$this->page]['Rotate'] = 0;
2241 }
2242 if (!isset($this->pagedim[$this->page]['PZ'])) {
2243 // The page's preferred zoom (magnification) factor
2244 $this->pagedim[$this->page]['PZ'] = 1;
2245 }
2246 if ($this->fwPt > $this->fhPt) {
2247 // landscape
2248 $default_orientation = 'L';
2249 } else {
2250 // portrait
2251 $default_orientation = 'P';
2252 }
2253 $valid_orientations = array('P', 'L');
2254 if (empty($orientation)) {
2255 $orientation = $default_orientation;
2256 } else {
2257 $orientation = strtoupper($orientation[0]);
2258 }
2259 if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2260 $this->CurOrientation = $orientation;
2261 $this->wPt = $this->fhPt;
2262 $this->hPt = $this->fwPt;
2263 } else {
2264 $this->CurOrientation = $default_orientation;
2265 $this->wPt = $this->fwPt;
2266 $this->hPt = $this->fhPt;
2267 }
2268 if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
2269 // swap X and Y coordinates (change page orientation)
2270 $this->pagedim = TCPDF_STATIC::swapPageBoxCoordinates($this->page, $this->pagedim);
2271 }
2272 $this->w = ($this->wPt / $this->k);
2273 $this->h = ($this->hPt / $this->k);
2274 if (TCPDF_STATIC::empty_string($autopagebreak)) {
2275 if (isset($this->AutoPageBreak)) {
2276 $autopagebreak = $this->AutoPageBreak;
2277 } else {
2278 $autopagebreak = true;
2279 }
2280 }
2281 if (TCPDF_STATIC::empty_string($bottommargin)) {
2282 if (isset($this->bMargin)) {
2283 $bottommargin = $this->bMargin;
2284 } else {
2285 // default value = 2 cm
2286 $bottommargin = 2 * 28.35 / $this->k;
2287 }
2288 }
2289 $this->SetAutoPageBreak($autopagebreak, $bottommargin);
2290 // store page dimensions
2291 $this->pagedim[$this->page]['w'] = $this->wPt;
2292 $this->pagedim[$this->page]['h'] = $this->hPt;
2293 $this->pagedim[$this->page]['wk'] = $this->w;
2294 $this->pagedim[$this->page]['hk'] = $this->h;
2295 $this->pagedim[$this->page]['tm'] = $this->tMargin;
2296 $this->pagedim[$this->page]['bm'] = $bottommargin;
2297 $this->pagedim[$this->page]['lm'] = $this->lMargin;
2298 $this->pagedim[$this->page]['rm'] = $this->rMargin;
2299 $this->pagedim[$this->page]['pb'] = $autopagebreak;
2300 $this->pagedim[$this->page]['or'] = $this->CurOrientation;
2301 $this->pagedim[$this->page]['olm'] = $this->original_lMargin;
2302 $this->pagedim[$this->page]['orm'] = $this->original_rMargin;
2303 }
2304
2305 /**
2306 * Set regular expression to detect withespaces or word separators.
2307 * The pattern delimiter must be the forward-slash character "/".
2308 * Some example patterns are:
2309 * <pre>
2310 * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
2311 * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
2312 * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
2313 * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
2314 * \s : any whitespace character
2315 * \p{Z} : any separator
2316 * \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2317 * \xa0 : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2318 * </pre>
2319 * @param $re (string) regular expression (leave empty for default).
2320 * @public
2321 * @since 4.6.016 (2009-06-15)
2322 */
2323 public function setSpacesRE($re='/[^\S\xa0]/') {
2324 $this->re_spaces = $re;
2325 $re_parts = explode('/', $re);
2326 // get pattern parts
2327 $this->re_space = array();
2328 if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2329 $this->re_space['p'] = $re_parts[1];
2330 } else {
2331 $this->re_space['p'] = '[\s]';
2332 }
2333 // set pattern modifiers
2334 if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2335 $this->re_space['m'] = $re_parts[2];
2336 } else {
2337 $this->re_space['m'] = '';
2338 }
2339 }
2340
2341 /**
2342 * Enable or disable Right-To-Left language mode
2343 * @param $enable (Boolean) if true enable Right-To-Left language mode.
2344 * @param $resetx (Boolean) if true reset the X position on direction change.
2345 * @public
2346 * @since 2.0.000 (2008-01-03)
2347 */
2348 public function setRTL($enable, $resetx=true) {
2349 $enable = $enable ? true : false;
2350 $resetx = ($resetx AND ($enable != $this->rtl));
2351 $this->rtl = $enable;
2352 $this->tmprtl = false;
2353 if ($resetx) {
2354 $this->Ln(0);
2355 }
2356 }
2357
2358 /**
2359 * Return the RTL status
2360 * @return boolean
2361 * @public
2362 * @since 4.0.012 (2008-07-24)
2363 */
2364 public function getRTL() {
2365 return $this->rtl;
2366 }
2367
2368 /**
2369 * Force temporary RTL language direction
2370 * @param $mode (mixed) can be false, 'L' for LTR or 'R' for RTL
2371 * @public
2372 * @since 2.1.000 (2008-01-09)
2373 */
2374 public function setTempRTL($mode) {
2375 $newmode = false;
2376 switch (strtoupper($mode)) {
2377 case 'LTR':
2378 case 'L': {
2379 if ($this->rtl) {
2380 $newmode = 'L';
2381 }
2382 break;
2383 }
2384 case 'RTL':
2385 case 'R': {
2386 if (!$this->rtl) {
2387 $newmode = 'R';
2388 }
2389 break;
2390 }
2391 case false:
2392 default: {
2393 $newmode = false;
2394 break;
2395 }
2396 }
2397 $this->tmprtl = $newmode;
2398 }
2399
2400 /**
2401 * Return the current temporary RTL status
2402 * @return boolean
2403 * @public
2404 * @since 4.8.014 (2009-11-04)
2405 */
2406 public function isRTLTextDir() {
2407 return ($this->rtl OR ($this->tmprtl == 'R'));
2408 }
2409
2410 /**
2411 * Set the last cell height.
2412 * @param $h (float) cell height.
2413 * @author Nicola Asuni
2414 * @public
2415 * @since 1.53.0.TC034
2416 */
2417 public function setLastH($h) {
2418 $this->lasth = $h;
2419 }
2420
2421 /**
2422 * Return the cell height
2423 * @param $fontsize (int) Font size in internal units
2424 * @param $padding (boolean) If true add cell padding
2425 * @public
2426 */
2427 public function getCellHeight($fontsize, $padding=TRUE) {
2428 $height = ($fontsize * $this->cell_height_ratio);
2429 if ($padding) {
2430 $height += ($this->cell_padding['T'] + $this->cell_padding['B']);
2431 }
2432 return round($height, 6);
2433 }
2434
2435 /**
2436 * Reset the last cell height.
2437 * @public
2438 * @since 5.9.000 (2010-10-03)
2439 */
2440 public function resetLastH() {
2441 $this->lasth = $this->getCellHeight($this->FontSize);
2442 }
2443
2444 /**
2445 * Get the last cell height.
2446 * @return last cell height
2447 * @public
2448 * @since 4.0.017 (2008-08-05)
2449 */
2450 public function getLastH() {
2451 return $this->lasth;
2452 }
2453
2454 /**
2455 * Set the adjusting factor to convert pixels to user units.
2456 * @param $scale (float) adjusting factor to convert pixels to user units.
2457 * @author Nicola Asuni
2458 * @public
2459 * @since 1.5.2
2460 */
2461 public function setImageScale($scale) {
2462 $this->imgscale = $scale;
2463 }
2464
2465 /**
2466 * Returns the adjusting factor to convert pixels to user units.
2467 * @return float adjusting factor to convert pixels to user units.
2468 * @author Nicola Asuni
2469 * @public
2470 * @since 1.5.2
2471 */
2472 public function getImageScale() {
2473 return $this->imgscale;
2474 }
2475
2476 /**
2477 * Returns an array of page dimensions:
2478 * <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>
2479 * @param $pagenum (int) page number (empty = current page)
2480 * @return array of page dimensions.
2481 * @author Nicola Asuni
2482 * @public
2483 * @since 4.5.027 (2009-03-16)
2484 */
2485 public function getPageDimensions($pagenum='') {
2486 if (empty($pagenum)) {
2487 $pagenum = $this->page;
2488 }
2489 return $this->pagedim[$pagenum];
2490 }
2491
2492 /**
2493 * Returns the page width in units.
2494 * @param $pagenum (int) page number (empty = current page)
2495 * @return int page width.
2496 * @author Nicola Asuni
2497 * @public
2498 * @since 1.5.2
2499 * @see getPageDimensions()
2500 */
2501 public function getPageWidth($pagenum='') {
2502 if (empty($pagenum)) {
2503 return $this->w;
2504 }
2505 return $this->pagedim[$pagenum]['w'];
2506 }
2507
2508 /**
2509 * Returns the page height in units.
2510 * @param $pagenum (int) page number (empty = current page)
2511 * @return int page height.
2512 * @author Nicola Asuni
2513 * @public
2514 * @since 1.5.2
2515 * @see getPageDimensions()
2516 */
2517 public function getPageHeight($pagenum='') {
2518 if (empty($pagenum)) {
2519 return $this->h;
2520 }
2521 return $this->pagedim[$pagenum]['h'];
2522 }
2523
2524 /**
2525 * Returns the page break margin.
2526 * @param $pagenum (int) page number (empty = current page)
2527 * @return int page break margin.
2528 * @author Nicola Asuni
2529 * @public
2530 * @since 1.5.2
2531 * @see getPageDimensions()
2532 */
2533 public function getBreakMargin($pagenum='') {
2534 if (empty($pagenum)) {
2535 return $this->bMargin;
2536 }
2537 return $this->pagedim[$pagenum]['bm'];
2538 }
2539
2540 /**
2541 * Returns the scale factor (number of points in user unit).
2542 * @return int scale factor.
2543 * @author Nicola Asuni
2544 * @public
2545 * @since 1.5.2
2546 */
2547 public function getScaleFactor() {
2548 return $this->k;
2549 }
2550
2551 /**
2552 * Defines the left, top and right margins.
2553 * @param $left (float) Left margin.
2554 * @param $top (float) Top margin.
2555 * @param $right (float) Right margin. Default value is the left one.
2556 * @param $keepmargins (boolean) if true overwrites the default page margins
2557 * @public
2558 * @since 1.0
2559 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
2560 */
2561 public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
2562 //Set left, top and right margins
2563 $this->lMargin = $left;
2564 $this->tMargin = $top;
2565 if ($right == -1) {
2566 $right = $left;
2567 }
2568 $this->rMargin = $right;
2569 if ($keepmargins) {
2570 // overwrite original values
2571 $this->original_lMargin = $this->lMargin;
2572 $this->original_rMargin = $this->rMargin;
2573 }
2574 }
2575
2576 /**
2577 * 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.
2578 * @param $margin (float) The margin.
2579 * @public
2580 * @since 1.4
2581 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2582 */
2583 public function SetLeftMargin($margin) {
2584 //Set left margin
2585 $this->lMargin = $margin;
2586 if (($this->page > 0) AND ($this->x < $margin)) {
2587 $this->x = $margin;
2588 }
2589 }
2590
2591 /**
2592 * Defines the top margin. The method can be called before creating the first page.
2593 * @param $margin (float) The margin.
2594 * @public
2595 * @since 1.5
2596 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2597 */
2598 public function SetTopMargin($margin) {
2599 //Set top margin
2600 $this->tMargin = $margin;
2601 if (($this->page > 0) AND ($this->y < $margin)) {
2602 $this->y = $margin;
2603 }
2604 }
2605
2606 /**
2607 * Defines the right margin. The method can be called before creating the first page.
2608 * @param $margin (float) The margin.
2609 * @public
2610 * @since 1.5
2611 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
2612 */
2613 public function SetRightMargin($margin) {
2614 $this->rMargin = $margin;
2615 if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
2616 $this->x = $this->w - $margin;
2617 }
2618 }
2619
2620 /**
2621 * Set the same internal Cell padding for top, right, bottom, left-
2622 * @param $pad (float) internal padding.
2623 * @public
2624 * @since 2.1.000 (2008-01-09)
2625 * @see getCellPaddings(), setCellPaddings()
2626 */
2627 public function SetCellPadding($pad) {
2628 if ($pad >= 0) {
2629 $this->cell_padding['L'] = $pad;
2630 $this->cell_padding['T'] = $pad;
2631 $this->cell_padding['R'] = $pad;
2632 $this->cell_padding['B'] = $pad;
2633 }
2634 }
2635
2636 /**
2637 * Set the internal Cell paddings.
2638 * @param $left (float) left padding
2639 * @param $top (float) top padding
2640 * @param $right (float) right padding
2641 * @param $bottom (float) bottom padding
2642 * @public
2643 * @since 5.9.000 (2010-10-03)
2644 * @see getCellPaddings(), SetCellPadding()
2645 */
2646 public function setCellPaddings($left='', $top='', $right='', $bottom='') {
2647 if (($left !== '') AND ($left >= 0)) {
2648 $this->cell_padding['L'] = $left;
2649 }
2650 if (($top !== '') AND ($top >= 0)) {
2651 $this->cell_padding['T'] = $top;
2652 }
2653 if (($right !== '') AND ($right >= 0)) {
2654 $this->cell_padding['R'] = $right;
2655 }
2656 if (($bottom !== '') AND ($bottom >= 0)) {
2657 $this->cell_padding['B'] = $bottom;
2658 }
2659 }
2660
2661 /**
2662 * Get the internal Cell padding array.
2663 * @return array of padding values
2664 * @public
2665 * @since 5.9.000 (2010-10-03)
2666 * @see setCellPaddings(), SetCellPadding()
2667 */
2668 public function getCellPaddings() {
2669 return $this->cell_padding;
2670 }
2671
2672 /**
2673 * Set the internal Cell margins.
2674 * @param $left (float) left margin
2675 * @param $top (float) top margin
2676 * @param $right (float) right margin
2677 * @param $bottom (float) bottom margin
2678 * @public
2679 * @since 5.9.000 (2010-10-03)
2680 * @see getCellMargins()
2681 */
2682 public function setCellMargins($left='', $top='', $right='', $bottom='') {
2683 if (($left !== '') AND ($left >= 0)) {
2684 $this->cell_margin['L'] = $left;
2685 }
2686 if (($top !== '') AND ($top >= 0)) {
2687 $this->cell_margin['T'] = $top;
2688 }
2689 if (($right !== '') AND ($right >= 0)) {
2690 $this->cell_margin['R'] = $right;
2691 }
2692 if (($bottom !== '') AND ($bottom >= 0)) {
2693 $this->cell_margin['B'] = $bottom;
2694 }
2695 }
2696
2697 /**
2698 * Get the internal Cell margin array.
2699 * @return array of margin values
2700 * @public
2701 * @since 5.9.000 (2010-10-03)
2702 * @see setCellMargins()
2703 */
2704 public function getCellMargins() {
2705 return $this->cell_margin;
2706 }
2707
2708 /**
2709 * Adjust the internal Cell padding array to take account of the line width.
2710 * @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)))
2711 * @return array of adjustments
2712 * @public
2713 * @since 5.9.000 (2010-10-03)
2714 */
2715 protected function adjustCellPadding($brd=0) {
2716 if (empty($brd)) {
2717 return;
2718 }
2719 if (is_string($brd)) {
2720 // convert string to array
2721 $slen = strlen($brd);
2722 $newbrd = array();
2723 for ($i = 0; $i < $slen; ++$i) {
2724 $newbrd[$brd[$i]] = true;
2725 }
2726 $brd = $newbrd;
2727 } elseif (($brd === 1) OR ($brd === true) OR (is_numeric($brd) AND (intval($brd) > 0))) {
2728 $brd = array('LRTB' => true);
2729 }
2730 if (!is_array($brd)) {
2731 return;
2732 }
2733 // store current cell padding
2734 $cp = $this->cell_padding;
2735 // select border mode
2736 if (isset($brd['mode'])) {
2737 $mode = $brd['mode'];
2738 unset($brd['mode']);
2739 } else {
2740 $mode = 'normal';
2741 }
2742 // process borders
2743 foreach ($brd as $border => $style) {
2744 $line_width = $this->LineWidth;
2745 if (is_array($style) AND isset($style['width'])) {
2746 // get border width
2747 $line_width = $style['width'];
2748 }
2749 $adj = 0; // line width inside the cell
2750 switch ($mode) {
2751 case 'ext': {
2752 $adj = 0;
2753 break;
2754 }
2755 case 'int': {
2756 $adj = $line_width;
2757 break;
2758 }
2759 case 'normal':
2760 default: {
2761 $adj = ($line_width / 2);
2762 break;
2763 }
2764 }
2765 // correct internal cell padding if required to avoid overlap between text and lines
2766 if ((strpos($border,'T') !== false) AND ($this->cell_padding['T'] < $adj)) {
2767 $this->cell_padding['T'] = $adj;
2768 }
2769 if ((strpos($border,'R') !== false) AND ($this->cell_padding['R'] < $adj)) {
2770 $this->cell_padding['R'] = $adj;
2771 }
2772 if ((strpos($border,'B') !== false) AND ($this->cell_padding['B'] < $adj)) {
2773 $this->cell_padding['B'] = $adj;
2774 }
2775 if ((strpos($border,'L') !== false) AND ($this->cell_padding['L'] < $adj)) {
2776 $this->cell_padding['L'] = $adj;
2777 }
2778 }
2779 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']));
2780 }
2781
2782 /**
2783 * 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.
2784 * @param $auto (boolean) Boolean indicating if mode should be on or off.
2785 * @param $margin (float) Distance from the bottom of the page.
2786 * @public
2787 * @since 1.0
2788 * @see Cell(), MultiCell(), AcceptPageBreak()
2789 */
2790 public function SetAutoPageBreak($auto, $margin=0) {
2791 $this->AutoPageBreak = $auto ? true : false;
2792 $this->bMargin = $margin;
2793 $this->PageBreakTrigger = $this->h - $margin;
2794 }
2795
2796 /**
2797 * Return the auto-page-break mode (true or false).
2798 * @return boolean auto-page-break mode
2799 * @public
2800 * @since 5.9.088
2801 */
2802 public function getAutoPageBreak() {
2803 return $this->AutoPageBreak;
2804 }
2805
2806 /**
2807 * Defines the way the document is to be displayed by the viewer.
2808 * @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>
2809 * @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>
2810 * @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>
2811 * @public
2812 * @since 1.2
2813 */
2814 public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
2815 if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
2816 $this->ZoomMode = $zoom;
2817 } else {
2818 $this->Error('Incorrect zoom display mode: '.$zoom);
2819 }
2820 $this->LayoutMode = TCPDF_STATIC::getPageLayoutMode($layout);
2821 $this->PageMode = TCPDF_STATIC::getPageMode($mode);
2822 }
2823
2824 /**
2825 * 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.
2826 * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
2827 * @param $compress (boolean) Boolean indicating if compression must be enabled.
2828 * @public
2829 * @since 1.4
2830 */
2831 public function SetCompression($compress=true) {
2832 if (function_exists('gzcompress')) {
2833 $this->compress = $compress ? true : false;
2834 } else {
2835 $this->compress = false;
2836 }
2837 }
2838
2839 /**
2840 * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
2841 * @param $mode (boolean) If true force sRGB output intent.
2842 * @public
2843 * @since 5.9.121 (2011-09-28)
2844 */
2845 public function setSRGBmode($mode=false) {
2846 $this->force_srgb = $mode ? true : false;
2847 }
2848
2849 /**
2850 * Turn on/off Unicode mode for document information dictionary (meta tags).
2851 * This has effect only when unicode mode is set to false.
2852 * @param $unicode (boolean) if true set the meta information in Unicode
2853 * @since 5.9.027 (2010-12-01)
2854 * @public
2855 */
2856 public function SetDocInfoUnicode($unicode=true) {
2857 $this->docinfounicode = $unicode ? true : false;
2858 }
2859
2860 /**
2861 * Defines the title of the document.
2862 * @param $title (string) The title.
2863 * @public
2864 * @since 1.2
2865 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
2866 */
2867 public function SetTitle($title) {
2868 $this->title = $title;
2869 }
2870
2871 /**
2872 * Defines the subject of the document.
2873 * @param $subject (string) The subject.
2874 * @public
2875 * @since 1.2
2876 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
2877 */
2878 public function SetSubject($subject) {
2879 $this->subject = $subject;
2880 }
2881
2882 /**
2883 * Defines the author of the document.
2884 * @param $author (string) The name of the author.
2885 * @public
2886 * @since 1.2
2887 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
2888 */
2889 public function SetAuthor($author) {
2890 $this->author = $author;
2891 }
2892
2893 /**
2894 * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
2895 * @param $keywords (string) The list of keywords.
2896 * @public
2897 * @since 1.2
2898 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
2899 */
2900 public function SetKeywords($keywords) {
2901 $this->keywords = $keywords;
2902 }
2903
2904 /**
2905 * Defines the creator of the document. This is typically the name of the application that generates the PDF.
2906 * @param $creator (string) The name of the creator.
2907 * @public
2908 * @since 1.2
2909 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
2910 */
2911 public function SetCreator($creator) {
2912 $this->creator = $creator;
2913 }
2914
2915 /**
2916 * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
2917 * @param $msg (string) The error message
2918 * @public
2919 * @since 1.0
2920 */
2921 public function Error($msg) {
2922 // unset all class variables
2923 $this->_destroy(true);
2924 if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
2925 die('<strong>TCPDF ERROR: </strong>'.$msg);
2926 } else {
2927 throw new Exception('TCPDF ERROR: '.$msg);
2928 }
2929 }
2930
2931 /**
2932 * This method begins the generation of the PDF document.
2933 * It is not necessary to call it explicitly because AddPage() does it automatically.
2934 * Note: no page is created by this method
2935 * @public
2936 * @since 1.0
2937 * @see AddPage(), Close()
2938 */
2939 public function Open() {
2940 $this->state = 1;
2941 }
2942
2943 /**
2944 * Terminates the PDF document.
2945 * It is not necessary to call this method explicitly because Output() does it automatically.
2946 * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
2947 * @public
2948 * @since 1.0
2949 * @see Open(), Output()
2950 */
2951 public function Close() {
2952 if ($this->state == 3) {
2953 return;
2954 }
2955 if ($this->page == 0) {
2956 $this->AddPage();
2957 }
2958 $this->endLayer();
2959 if ($this->tcpdflink) {
2960 // save current graphic settings
2961 $gvars = $this->getGraphicVars();
2962 $this->setEqualColumns();
2963 $this->lastpage(true);
2964 $this->SetAutoPageBreak(false);
2965 $this->x = 0;
2966 $this->y = $this->h - (1 / $this->k);
2967 $this->lMargin = 0;
2968 $this->_outSaveGraphicsState();
2969 $font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
2970 $this->SetFont($font, '', 1);
2971 $this->setTextRenderingMode(0, false, false);
2972 $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";
2973 $lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
2974 $this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
2975 $this->_outRestoreGraphicsState();
2976 // restore graphic settings
2977 $this->setGraphicVars($gvars);
2978 }
2979 // close page
2980 $this->endPage();
2981 // close document
2982 $this->_enddoc();
2983 // unset all class variables (except critical ones)
2984 $this->_destroy(false);
2985 }
2986
2987 /**
2988 * Move pointer at the specified document page and update page dimensions.
2989 * @param $pnum (int) page number (1 ... numpages)
2990 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
2991 * @public
2992 * @since 2.1.000 (2008-01-07)
2993 * @see getPage(), lastpage(), getNumPages()
2994 */
2995 public function setPage($pnum, $resetmargins=false) {
2996 if (($pnum == $this->page) AND ($this->state == 2)) {
2997 return;
2998 }
2999 if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3000 $this->state = 2;
3001 // save current graphic settings
3002 //$gvars = $this->getGraphicVars();
3003 $oldpage = $this->page;
3004 $this->page = $pnum;
3005 $this->wPt = $this->pagedim[$this->page]['w'];
3006 $this->hPt = $this->pagedim[$this->page]['h'];
3007 $this->w = $this->pagedim[$this->page]['wk'];
3008 $this->h = $this->pagedim[$this->page]['hk'];
3009 $this->tMargin = $this->pagedim[$this->page]['tm'];
3010 $this->bMargin = $this->pagedim[$this->page]['bm'];
3011 $this->original_lMargin = $this->pagedim[$this->page]['olm'];
3012 $this->original_rMargin = $this->pagedim[$this->page]['orm'];
3013 $this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3014 $this->CurOrientation = $this->pagedim[$this->page]['or'];
3015 $this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3016 // restore graphic settings
3017 //$this->setGraphicVars($gvars);
3018 if ($resetmargins) {
3019 $this->lMargin = $this->pagedim[$this->page]['olm'];
3020 $this->rMargin = $this->pagedim[$this->page]['orm'];
3021 $this->SetY($this->tMargin);
3022 } else {
3023 // account for booklet mode
3024 if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3025 $deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3026 $this->lMargin += $deltam;
3027 $this->rMargin -= $deltam;
3028 }
3029 }
3030 } else {
3031 $this->Error('Wrong page number on setPage() function: '.$pnum);
3032 }
3033 }
3034
3035 /**
3036 * Reset pointer to the last document page.
3037 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
3038 * @public
3039 * @since 2.0.000 (2008-01-04)
3040 * @see setPage(), getPage(), getNumPages()
3041 */
3042 public function lastPage($resetmargins=false) {
3043 $this->setPage($this->getNumPages(), $resetmargins);
3044 }
3045
3046 /**
3047 * Get current document page number.
3048 * @return int page number
3049 * @public
3050 * @since 2.1.000 (2008-01-07)
3051 * @see setPage(), lastpage(), getNumPages()
3052 */
3053 public function getPage() {
3054 return $this->page;
3055 }
3056
3057 /**
3058 * Get the total number of insered pages.
3059 * @return int number of pages
3060 * @public
3061 * @since 2.1.000 (2008-01-07)
3062 * @see setPage(), getPage(), lastpage()
3063 */
3064 public function getNumPages() {
3065 return $this->numpages;
3066 }
3067
3068 /**
3069 * Adds a new TOC (Table Of Content) page to the document.
3070 * @param $orientation (string) page orientation.
3071 * @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().
3072 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3073 * @public
3074 * @since 5.0.001 (2010-05-06)
3075 * @see AddPage(), startPage(), endPage(), endTOCPage()
3076 */
3077 public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3078 $this->AddPage($orientation, $format, $keepmargins, true);
3079 }
3080
3081 /**
3082 * Terminate the current TOC (Table Of Content) page
3083 * @public
3084 * @since 5.0.001 (2010-05-06)
3085 * @see AddPage(), startPage(), endPage(), addTOCPage()
3086 */
3087 public function endTOCPage() {
3088 $this->endPage(true);
3089 }
3090
3091 /**
3092 * 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).
3093 * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3094 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3095 * @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().
3096 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3097 * @param $tocpage (boolean) if true set the tocpage state to true (the added page will be used to display Table Of Content).
3098 * @public
3099 * @since 1.0
3100 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3101 */
3102 public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3103 if ($this->inxobj) {
3104 // we are inside an XObject template
3105 return;
3106 }
3107 if (!isset($this->original_lMargin) OR $keepmargins) {
3108 $this->original_lMargin = $this->lMargin;
3109 }
3110 if (!isset($this->original_rMargin) OR $keepmargins) {
3111 $this->original_rMargin = $this->rMargin;
3112 }
3113 // terminate previous page
3114 $this->endPage();
3115 // start new page
3116 $this->startPage($orientation, $format, $tocpage);
3117 }
3118
3119 /**
3120 * Terminate the current page
3121 * @param $tocpage (boolean) if true set the tocpage state to false (end the page used to display Table Of Content).
3122 * @public
3123 * @since 4.2.010 (2008-11-14)
3124 * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3125 */
3126 public function endPage($tocpage=false) {
3127 // check if page is already closed
3128 if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3129 return;
3130 }
3131 // print page footer
3132 $this->setFooter();
3133 // close page
3134 $this->_endpage();
3135 // mark page as closed
3136 $this->pageopen[$this->page] = false;
3137 if ($tocpage) {
3138 $this->tocpage = false;
3139 }
3140 }
3141
3142 /**
3143 * Starts a new page to the document. The page must be closed using the endPage() function.
3144 * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3145 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3146 * @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().
3147 * @param $tocpage (boolean) if true the page is designated to contain the Table-Of-Content.
3148 * @since 4.2.010 (2008-11-14)
3149 * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3150 * @public
3151 */
3152 public function startPage($orientation='', $format='', $tocpage=false) {
3153 if ($tocpage) {
3154 $this->tocpage = true;
3155 }
3156 // move page numbers of documents to be attached
3157 if ($this->tocpage) {
3158 // move reference to unexistent pages (used for page attachments)
3159 // adjust outlines
3160 $tmpoutlines = $this->outlines;
3161 foreach ($tmpoutlines as $key => $outline) {
3162 if (!$outline['f'] AND ($outline['p'] > $this->numpages)) {
3163 $this->outlines[$key]['p'] = ($outline['p'] + 1);
3164 }
3165 }
3166 // adjust dests
3167 $tmpdests = $this->dests;
3168 foreach ($tmpdests as $key => $dest) {
3169 if (!$dest['f'] AND ($dest['p'] > $this->numpages)) {
3170 $this->dests[$key]['p'] = ($dest['p'] + 1);
3171 }
3172 }
3173 // adjust links
3174 $tmplinks = $this->links;
3175 foreach ($tmplinks as $key => $link) {
3176 if (!$link['f'] AND ($link['p'] > $this->numpages)) {
3177 $this->links[$key]['p'] = ($link['p'] + 1);
3178 }
3179 }
3180 }
3181 if ($this->numpages > $this->page) {
3182 // this page has been already added
3183 $this->setPage($this->page + 1);
3184 $this->SetY($this->tMargin);
3185 return;
3186 }
3187 // start a new page
3188 if ($this->state == 0) {
3189 $this->Open();
3190 }
3191 ++$this->numpages;
3192 $this->swapMargins($this->booklet);
3193 // save current graphic settings
3194 $gvars = $this->getGraphicVars();
3195 // start new page
3196 $this->_beginpage($orientation, $format);
3197 // mark page as open
3198 $this->pageopen[$this->page] = true;
3199 // restore graphic settings
3200 $this->setGraphicVars($gvars);
3201 // mark this point
3202 $this->setPageMark();
3203 // print page header
3204 $this->setHeader();
3205 // restore graphic settings
3206 $this->setGraphicVars($gvars);
3207 // mark this point
3208 $this->setPageMark();
3209 // print table header (if any)
3210 $this->setTableHeader();
3211 // set mark for empty page check
3212 $this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
3213 }
3214
3215 /**
3216 * Set start-writing mark on current page stream used to put borders and fills.
3217 * Borders and fills are always created after content and inserted on the position marked by this method.
3218 * This function must be called after calling Image() function for a background image.
3219 * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
3220 * @public
3221 * @since 4.0.016 (2008-07-30)
3222 */
3223 public function setPageMark() {
3224 $this->intmrk[$this->page] = $this->pagelen[$this->page];
3225 $this->bordermrk[$this->page] = $this->intmrk[$this->page];
3226 $this->setContentMark();
3227 }
3228
3229 /**
3230 * Set start-writing mark on selected page.
3231 * Borders and fills are always created after content and inserted on the position marked by this method.
3232 * @param $page (int) page number (default is the current page)
3233 * @protected
3234 * @since 4.6.021 (2009-07-20)
3235 */
3236 protected function setContentMark($page=0) {
3237 if ($page <= 0) {
3238 $page = $this->page;
3239 }
3240 if (isset($this->footerlen[$page])) {
3241 $this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3242 } else {
3243 $this->cntmrk[$page] = $this->pagelen[$page];
3244 }
3245 }
3246
3247 /**
3248 * Set header data.
3249 * @param $ln (string) header image logo
3250 * @param $lw (string) header image logo width in mm
3251 * @param $ht (string) string to print as title on document header
3252 * @param $hs (string) string to print on document header
3253 * @param $tc (array) RGB array color for text.
3254 * @param $lc (array) RGB array color for line.
3255 * @public
3256 */
3257 public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
3258 $this->header_logo = $ln;
3259 $this->header_logo_width = $lw;
3260 $this->header_title = $ht;
3261 $this->header_string = $hs;
3262 $this->header_text_color = $tc;
3263 $this->header_line_color = $lc;
3264 }
3265
3266 /**
3267 * Set footer data.
3268 * @param $tc (array) RGB array color for text.
3269 * @param $lc (array) RGB array color for line.
3270 * @public
3271 */
3272 public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
3273 $this->footer_text_color = $tc;
3274 $this->footer_line_color = $lc;
3275 }
3276
3277 /**
3278 * Returns header data:
3279 * <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>
3280 * @return array()
3281 * @public
3282 * @since 4.0.012 (2008-07-24)
3283 */
3284 public function getHeaderData() {
3285 $ret = array();
3286 $ret['logo'] = $this->header_logo;
3287 $ret['logo_width'] = $this->header_logo_width;
3288 $ret['title'] = $this->header_title;
3289 $ret['string'] = $this->header_string;
3290 $ret['text_color'] = $this->header_text_color;
3291 $ret['line_color'] = $this->header_line_color;
3292 return $ret;
3293 }
3294
3295 /**
3296 * Set header margin.
3297 * (minimum distance between header and top page margin)
3298 * @param $hm (int) distance in user units
3299 * @public
3300 */
3301 public function setHeaderMargin($hm=10) {
3302 $this->header_margin = $hm;
3303 }
3304
3305 /**
3306 * Returns header margin in user units.
3307 * @return float
3308 * @since 4.0.012 (2008-07-24)
3309 * @public
3310 */
3311 public function getHeaderMargin() {
3312 return $this->header_margin;
3313 }
3314
3315 /**
3316 * Set footer margin.
3317 * (minimum distance between footer and bottom page margin)
3318 * @param $fm (int) distance in user units
3319 * @public
3320 */
3321 public function setFooterMargin($fm=10) {
3322 $this->footer_margin = $fm;
3323 }
3324
3325 /**
3326 * Returns footer margin in user units.
3327 * @return float
3328 * @since 4.0.012 (2008-07-24)
3329 * @public
3330 */
3331 public function getFooterMargin() {
3332 return $this->footer_margin;
3333 }
3334 /**
3335 * Set a flag to print page header.
3336 * @param $val (boolean) set to true to print the page header (default), false otherwise.
3337 * @public
3338 */
3339 public function setPrintHeader($val=true) {
3340 $this->print_header = $val ? true : false;
3341 }
3342
3343 /**
3344 * Set a flag to print page footer.
3345 * @param $val (boolean) set to true to print the page footer (default), false otherwise.
3346 * @public
3347 */
3348 public function setPrintFooter($val=true) {
3349 $this->print_footer = $val ? true : false;
3350 }
3351
3352 /**
3353 * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
3354 * @return float
3355 * @public
3356 */
3357 public function getImageRBX() {
3358 return $this->img_rb_x;
3359 }
3360
3361 /**
3362 * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
3363 * @return float
3364 * @public
3365 */
3366 public function getImageRBY() {
3367 return $this->img_rb_y;
3368 }
3369
3370 /**
3371 * Reset the xobject template used by Header() method.
3372 * @public
3373 */
3374 public function resetHeaderTemplate() {
3375 $this->header_xobjid = false;
3376 }
3377
3378 /**
3379 * Set a flag to automatically reset the xobject template used by Header() method at each page.
3380 * @param $val (boolean) set to true to reset Header xobject template at each page, false otherwise.
3381 * @public
3382 */
3383 public function setHeaderTemplateAutoreset($val=true) {
3384 $this->header_xobj_autoreset = $val ? true : false;
3385 }
3386
3387 /**
3388 * This method is used to render the page header.
3389 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3390 * @public
3391 */
3392 public function Header() {
3393 if ($this->header_xobjid === false) {
3394 // start a new XObject Template
3395 $this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
3396 $headerfont = $this->getHeaderFont();
3397 $headerdata = $this->getHeaderData();
3398 $this->y = $this->header_margin;
3399 if ($this->rtl) {
3400 $this->x = $this->w - $this->original_rMargin;
3401 } else {
3402 $this->x = $this->original_lMargin;
3403 }
3404 if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
3405 $imgtype = TCPDF_IMAGES::getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
3406 if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
3407 $this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3408 } elseif ($imgtype == 'svg') {
3409 $this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3410 } else {
3411 $this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3412 }
3413 $imgy = $this->getImageRBY();
3414 } else {
3415 $imgy = $this->y;
3416 }
3417 $cell_height = $this->getCellHeight($headerfont[2] / $this->k);
3418 // set starting margin for text data cell
3419 if ($this->getRTL()) {
3420 $header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
3421 } else {
3422 $header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
3423 }
3424 $cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
3425 $this->SetTextColorArray($this->header_text_color);
3426 // header title
3427 $this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
3428 $this->SetX($header_x);
3429 $this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
3430 // header string
3431 $this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
3432 $this->SetX($header_x);
3433 $this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
3434 // print an ending header line
3435 $this->SetLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
3436 $this->SetY((2.835 / $this->k) + max($imgy, $this->y));
3437 if ($this->rtl) {
3438 $this->SetX($this->original_rMargin);
3439 } else {
3440 $this->SetX($this->original_lMargin);
3441 }
3442 $this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
3443 $this->endTemplate();
3444 }
3445 // print header template
3446 $x = 0;
3447 $dx = 0;
3448 if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
3449 // adjust margins for booklet mode
3450 $dx = ($this->original_lMargin - $this->original_rMargin);
3451 }
3452 if ($this->rtl) {
3453 $x = $this->w + $dx;
3454 } else {
3455 $x = 0 + $dx;
3456 }
3457 $this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
3458 if ($this->header_xobj_autoreset) {
3459 // reset header xobject template at each page
3460 $this->header_xobjid = false;
3461 }
3462 }
3463
3464 /**
3465 * This method is used to render the page footer.
3466 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3467 * @public
3468 */
3469 public function Footer() {
3470 $cur_y = $this->y;
3471 $this->SetTextColorArray($this->footer_text_color);
3472 //set style for cell border
3473 $line_width = (0.85 / $this->k);
3474 $this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
3475 //print document barcode
3476 $barcode = $this->getBarcode();
3477 if (!empty($barcode)) {
3478 $this->Ln($line_width);
3479 $barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
3480 $style = array(
3481 'position' => $this->rtl?'R':'L',
3482 'align' => $this->rtl?'R':'L',
3483 'stretch' => false,
3484 'fitwidth' => true,
3485 'cellfitalign' => '',
3486 'border' => false,
3487 'padding' => 0,
3488 'fgcolor' => array(0,0,0),
3489 'bgcolor' => false,
3490 'text' => false
3491 );
3492 $this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
3493 }
3494 $w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
3495 if (empty($this->pagegroups)) {
3496 $pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
3497 } else {
3498 $pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
3499 }
3500 $this->SetY($cur_y);
3501 //Print page number
3502 if ($this->getRTL()) {
3503 $this->SetX($this->original_rMargin);
3504 $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
3505 } else {
3506 $this->SetX($this->original_lMargin);
3507 $this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
3508 }
3509 }
3510
3511 /**
3512 * This method is used to render the page header.
3513 * @protected
3514 * @since 4.0.012 (2008-07-24)
3515 */
3516 protected function setHeader() {
3517 if (!$this->print_header OR ($this->state != 2)) {
3518 return;
3519 }
3520 $this->InHeader = true;
3521 $this->setGraphicVars($this->default_graphic_vars);
3522 $temp_thead = $this->thead;
3523 $temp_theadMargins = $this->theadMargins;
3524 $lasth = $this->lasth;
3525 $newline = $this->newline;
3526 $this->_outSaveGraphicsState();
3527 $this->rMargin = $this->original_rMargin;
3528 $this->lMargin = $this->original_lMargin;
3529 $this->SetCellPadding(0);
3530 //set current position
3531 if ($this->rtl) {
3532 $this->SetXY($this->original_rMargin, $this->header_margin);
3533 } else {
3534 $this->SetXY($this->original_lMargin, $this->header_margin);
3535 }
3536 $this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
3537 $this->Header();
3538 //restore position
3539 if ($this->rtl) {
3540 $this->SetXY($this->original_rMargin, $this->tMargin);
3541 } else {
3542 $this->SetXY($this->original_lMargin, $this->tMargin);
3543 }
3544 $this->_outRestoreGraphicsState();
3545 $this->lasth = $lasth;
3546 $this->thead = $temp_thead;
3547 $this->theadMargins = $temp_theadMargins;
3548 $this->newline = $newline;
3549 $this->InHeader = false;
3550 }
3551
3552 /**
3553 * This method is used to render the page footer.
3554 * @protected
3555 * @since 4.0.012 (2008-07-24)
3556 */
3557 protected function setFooter() {
3558 if ($this->state != 2) {
3559 return;
3560 }
3561 $this->InFooter = true;
3562 // save current graphic settings
3563 $gvars = $this->getGraphicVars();
3564 // mark this point
3565 $this->footerpos[$this->page] = $this->pagelen[$this->page];
3566 $this->_out("\n");
3567 if ($this->print_footer) {
3568 $this->setGraphicVars($this->default_graphic_vars);
3569 $this->current_column = 0;
3570 $this->num_columns = 1;
3571 $temp_thead = $this->thead;
3572 $temp_theadMargins = $this->theadMargins;
3573 $lasth = $this->lasth;
3574 $this->_outSaveGraphicsState();
3575 $this->rMargin = $this->original_rMargin;
3576 $this->lMargin = $this->original_lMargin;
3577 $this->SetCellPadding(0);
3578 //set current position
3579 $footer_y = $this->h - $this->footer_margin;
3580 if ($this->rtl) {
3581 $this->SetXY($this->original_rMargin, $footer_y);
3582 } else {
3583 $this->SetXY($this->original_lMargin, $footer_y);
3584 }
3585 $this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
3586 $this->Footer();
3587 //restore position
3588 if ($this->rtl) {
3589 $this->SetXY($this->original_rMargin, $this->tMargin);
3590 } else {
3591 $this->SetXY($this->original_lMargin, $this->tMargin);
3592 }
3593 $this->_outRestoreGraphicsState();
3594 $this->lasth = $lasth;
3595 $this->thead = $temp_thead;
3596 $this->theadMargins = $temp_theadMargins;
3597 }
3598 // restore graphic settings
3599 $this->setGraphicVars($gvars);
3600 $this->current_column = $gvars['current_column'];
3601 $this->num_columns = $gvars['num_columns'];
3602 // calculate footer length
3603 $this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
3604 $this->InFooter = false;
3605 }
3606
3607 /**
3608 * Check if we are on the page body (excluding page header and footer).
3609 * @return true if we are not in page header nor in page footer, false otherwise.
3610 * @protected
3611 * @since 5.9.091 (2011-06-15)
3612 */
3613 protected function inPageBody() {
3614 return (($this->InHeader === false) AND ($this->InFooter === false));
3615 }
3616
3617 /**
3618 * This method is used to render the table header on new page (if any).
3619 * @protected
3620 * @since 4.5.030 (2009-03-25)
3621 */
3622 protected function setTableHeader() {
3623 if ($this->num_columns > 1) {
3624 // multi column mode
3625 return;
3626 }
3627 if (isset($this->theadMargins['top'])) {
3628 // restore the original top-margin
3629 $this->tMargin = $this->theadMargins['top'];
3630 $this->pagedim[$this->page]['tm'] = $this->tMargin;
3631 $this->y = $this->tMargin;
3632 }
3633 if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
3634 // set margins
3635 $prev_lMargin = $this->lMargin;
3636 $prev_rMargin = $this->rMargin;
3637 $prev_cell_padding = $this->cell_padding;
3638 $this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
3639 $this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
3640 $this->cell_padding = $this->theadMargins['cell_padding'];
3641 if ($this->rtl) {
3642 $this->x = $this->w - $this->rMargin;
3643 } else {
3644 $this->x = $this->lMargin;
3645 }
3646 // account for special "cell" mode
3647 if ($this->theadMargins['cell']) {
3648 if ($this->rtl) {
3649 $this->x -= $this->cell_padding['R'];
3650 } else {
3651 $this->x += $this->cell_padding['L'];
3652 }
3653 }
3654 $gvars = $this->getGraphicVars();
3655 if (!empty($this->theadMargins['gvars'])) {
3656 // set the correct graphic style
3657 $this->setGraphicVars($this->theadMargins['gvars']);
3658 $this->rMargin = $gvars['rMargin'];
3659 $this->lMargin = $gvars['lMargin'];
3660 }
3661 // print table header
3662 $this->writeHTML($this->thead, false, false, false, false, '');
3663 $this->setGraphicVars($gvars);
3664 // set new top margin to skip the table headers
3665 if (!isset($this->theadMargins['top'])) {
3666 $this->theadMargins['top'] = $this->tMargin;
3667 }
3668 // store end of header position
3669 if (!isset($this->columns[0]['th'])) {
3670 $this->columns[0]['th'] = array();
3671 }
3672 $this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
3673 $this->tMargin = $this->y;
3674 $this->pagedim[$this->page]['tm'] = $this->tMargin;
3675 $this->lasth = 0;
3676 $this->lMargin = $prev_lMargin;
3677 $this->rMargin = $prev_rMargin;
3678 $this->cell_padding = $prev_cell_padding;
3679 }
3680 }
3681
3682 /**
3683 * Returns the current page number.
3684 * @return int page number
3685 * @public
3686 * @since 1.0
3687 * @see getAliasNbPages()
3688 */
3689 public function PageNo() {
3690 return $this->page;
3691 }
3692
3693 /**
3694 * Returns the array of spot colors.
3695 * @return (array) Spot colors array.
3696 * @public
3697 * @since 6.0.038 (2013-09-30)
3698 */
3699 public function getAllSpotColors() {
3700 return $this->spot_colors;
3701 }
3702
3703 /**
3704 * Defines a new spot color.
3705 * It can be expressed in RGB components or gray scale.
3706 * The method can be called before the first page is created and the value is retained from page to page.
3707 * @param $name (string) Full name of the spot color.
3708 * @param $c (float) Cyan color for CMYK. Value between 0 and 100.
3709 * @param $m (float) Magenta color for CMYK. Value between 0 and 100.
3710 * @param $y (float) Yellow color for CMYK. Value between 0 and 100.
3711 * @param $k (float) Key (Black) color for CMYK. Value between 0 and 100.
3712 * @public
3713 * @since 4.0.024 (2008-09-12)
3714 * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3715 */
3716 public function AddSpotColor($name, $c, $m, $y, $k) {
3717 if (!isset($this->spot_colors[$name])) {
3718 $i = (1 + count($this->spot_colors));
3719 $this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
3720 }
3721 }
3722
3723 /**
3724 * Set the spot color for the specified type ('draw', 'fill', 'text').
3725 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3726 * @param $name (string) Name of the spot color.
3727 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3728 * @return (string) PDF color command.
3729 * @public
3730 * @since 5.9.125 (2011-10-03)
3731 */
3732 public function setSpotColor($type, $name, $tint=100) {
3733 $spotcolor = TCPDF_COLORS::getSpotColor($name, $this->spot_colors);
3734 if ($spotcolor === false) {
3735 $this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
3736 }
3737 $tint = (max(0, min(100, $tint)) / 100);
3738 $pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
3739 switch ($type) {
3740 case 'draw': {
3741 $pdfcolor .= sprintf('CS %F SCN', $tint);
3742 $this->DrawColor = $pdfcolor;
3743 $this->strokecolor = $spotcolor;
3744 break;
3745 }
3746 case 'fill': {
3747 $pdfcolor .= sprintf('cs %F scn', $tint);
3748 $this->FillColor = $pdfcolor;
3749 $this->bgcolor = $spotcolor;
3750 break;
3751 }
3752 case 'text': {
3753 $pdfcolor .= sprintf('cs %F scn', $tint);
3754 $this->TextColor = $pdfcolor;
3755 $this->fgcolor = $spotcolor;
3756 break;
3757 }
3758 }
3759 $this->ColorFlag = ($this->FillColor != $this->TextColor);
3760 if ($this->state == 2) {
3761 $this->_out($pdfcolor);
3762 }
3763 if ($this->inxobj) {
3764 // we are inside an XObject template
3765 $this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
3766 }
3767 return $pdfcolor;
3768 }
3769
3770 /**
3771 * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
3772 * @param $name (string) Name of the spot color.
3773 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3774 * @public
3775 * @since 4.0.024 (2008-09-12)
3776 * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3777 */
3778 public function SetDrawSpotColor($name, $tint=100) {
3779 $this->setSpotColor('draw', $name, $tint);
3780 }
3781
3782 /**
3783 * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
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(), SetDrawSpotColor(), SetTextSpotColor()
3789 */
3790 public function SetFillSpotColor($name, $tint=100) {
3791 $this->setSpotColor('fill', $name, $tint);
3792 }
3793
3794 /**
3795 * Defines the spot color used for text.
3796 * @param $name (string) Name of the spot color.
3797 * @param $tint (int) 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(), SetFillSpotColor()
3801 */
3802 public function SetTextSpotColor($name, $tint=100) {
3803 $this->setSpotColor('text', $name, $tint);
3804 }
3805
3806 /**
3807 * Set the color array for the specified type ('draw', 'fill', 'text').
3808 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3809 * The method can be called before the first page is created and the value is retained from page to page.
3810 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3811 * @param $color (array) Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
3812 * @param $ret (boolean) If true do not send the PDF command.
3813 * @return (string) The PDF command or empty string.
3814 * @public
3815 * @since 3.1.000 (2008-06-11)
3816 */
3817 public function setColorArray($type, $color, $ret=false) {
3818 if (is_array($color)) {
3819 $color = array_values($color);
3820 // component: grey, RGB red or CMYK cyan
3821 $c = isset($color[0]) ? $color[0] : -1;
3822 // component: RGB green or CMYK magenta
3823 $m = isset($color[1]) ? $color[1] : -1;
3824 // component: RGB blue or CMYK yellow
3825 $y = isset($color[2]) ? $color[2] : -1;
3826 // component: CMYK black
3827 $k = isset($color[3]) ? $color[3] : -1;
3828 // color name
3829 $name = isset($color[4]) ? $color[4] : '';
3830 if ($c >= 0) {
3831 return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
3832 }
3833 }
3834 return '';
3835 }
3836
3837 /**
3838 * Defines the color used for all drawing operations (lines, rectangles and cell borders).
3839 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3840 * The method can be called before the first page is created and the value is retained from page to page.
3841 * @param $color (array) Array of colors (1, 3 or 4 values).
3842 * @param $ret (boolean) If true do not send the PDF command.
3843 * @return string the PDF command
3844 * @public
3845 * @since 3.1.000 (2008-06-11)
3846 * @see SetDrawColor()
3847 */
3848 public function SetDrawColorArray($color, $ret=false) {
3849 return $this->setColorArray('draw', $color, $ret);
3850 }
3851
3852 /**
3853 * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
3854 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3855 * The method can be called before the first page is created and the value is retained from page to page.
3856 * @param $color (array) Array of colors (1, 3 or 4 values).
3857 * @param $ret (boolean) If true do not send the PDF command.
3858 * @public
3859 * @since 3.1.000 (2008-6-11)
3860 * @see SetFillColor()
3861 */
3862 public function SetFillColorArray($color, $ret=false) {
3863 return $this->setColorArray('fill', $color, $ret);
3864 }
3865
3866 /**
3867 * Defines the color used for text. It can be expressed in RGB components or gray scale.
3868 * The method can be called before the first page is created and the value is retained from page to page.
3869 * @param $color (array) Array of colors (1, 3 or 4 values).
3870 * @param $ret (boolean) If true do not send the PDF command.
3871 * @public
3872 * @since 3.1.000 (2008-6-11)
3873 * @see SetFillColor()
3874 */
3875 public function SetTextColorArray($color, $ret=false) {
3876 return $this->setColorArray('text', $color, $ret);
3877 }
3878
3879 /**
3880 * Defines the color used by the specified type ('draw', 'fill', 'text').
3881 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3882 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3883 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3884 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3885 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3886 * @param $ret (boolean) If true do not send the command.
3887 * @param $name (string) spot color name (if any)
3888 * @return (string) The PDF command or empty string.
3889 * @public
3890 * @since 5.9.125 (2011-10-03)
3891 */
3892 public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3893 // set default values
3894 if (!is_numeric($col1)) {
3895 $col1 = 0;
3896 }
3897 if (!is_numeric($col2)) {
3898 $col2 = -1;
3899 }
3900 if (!is_numeric($col3)) {
3901 $col3 = -1;
3902 }
3903 if (!is_numeric($col4)) {
3904 $col4 = -1;
3905 }
3906 // set color by case
3907 $suffix = '';
3908 if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
3909 // Grey scale
3910 $col1 = max(0, min(255, $col1));
3911 $intcolor = array('G' => $col1);
3912 $pdfcolor = sprintf('%F ', ($col1 / 255));
3913 $suffix = 'g';
3914 } elseif ($col4 == -1) {
3915 // RGB
3916 $col1 = max(0, min(255, $col1));
3917 $col2 = max(0, min(255, $col2));
3918 $col3 = max(0, min(255, $col3));
3919 $intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
3920 $pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
3921 $suffix = 'rg';
3922 } else {
3923 $col1 = max(0, min(100, $col1));
3924 $col2 = max(0, min(100, $col2));
3925 $col3 = max(0, min(100, $col3));
3926 $col4 = max(0, min(100, $col4));
3927 if (empty($name)) {
3928 // CMYK
3929 $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
3930 $pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
3931 $suffix = 'k';
3932 } else {
3933 // SPOT COLOR
3934 $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
3935 $this->AddSpotColor($name, $col1, $col2, $col3, $col4);
3936 $pdfcolor = $this->setSpotColor($type, $name, 100);
3937 }
3938 }
3939 switch ($type) {
3940 case 'draw': {
3941 $pdfcolor .= strtoupper($suffix);
3942 $this->DrawColor = $pdfcolor;
3943 $this->strokecolor = $intcolor;
3944 break;
3945 }
3946 case 'fill': {
3947 $pdfcolor .= $suffix;
3948 $this->FillColor = $pdfcolor;
3949 $this->bgcolor = $intcolor;
3950 break;
3951 }
3952 case 'text': {
3953 $pdfcolor .= $suffix;
3954 $this->TextColor = $pdfcolor;
3955 $this->fgcolor = $intcolor;
3956 break;
3957 }
3958 }
3959 $this->ColorFlag = ($this->FillColor != $this->TextColor);
3960 if (($type != 'text') AND ($this->state == 2)) {
3961 if (!$ret) {
3962 $this->_out($pdfcolor);
3963 }
3964 return $pdfcolor;
3965 }
3966 return '';
3967 }
3968
3969 /**
3970 * 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.
3971 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3972 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3973 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3974 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3975 * @param $ret (boolean) If true do not send the command.
3976 * @param $name (string) spot color name (if any)
3977 * @return string the PDF command
3978 * @public
3979 * @since 1.3
3980 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
3981 */
3982 public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3983 return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
3984 }
3985
3986 /**
3987 * 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.
3988 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3989 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3990 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3991 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3992 * @param $ret (boolean) If true do not send the command.
3993 * @param $name (string) Spot color name (if any).
3994 * @return (string) The PDF command.
3995 * @public
3996 * @since 1.3
3997 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
3998 */
3999 public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4000 return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
4001 }
4002
4003 /**
4004 * 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.
4005 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4006 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4007 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4008 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4009 * @param $ret (boolean) If true do not send the command.
4010 * @param $name (string) Spot color name (if any).
4011 * @return (string) Empty string.
4012 * @public
4013 * @since 1.3
4014 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4015 */
4016 public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4017 return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4018 }
4019
4020 /**
4021 * Returns the length of a string in user unit. A font must be selected.<br>
4022 * @param $s (string) The string whose length is to be computed
4023 * @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.
4024 * @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.
4025 * @param $fontsize (float) Font size in points. The default value is the current size.
4026 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4027 * @return mixed int total string length or array of characted widths
4028 * @author Nicola Asuni
4029 * @public
4030 * @since 1.2
4031 */
4032 public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4033 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);
4034 }
4035
4036 /**
4037 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4038 * @param $sa (string) The array of chars whose total length is to be computed
4039 * @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.
4040 * @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.
4041 * @param $fontsize (float) Font size in points. The default value is the current size.
4042 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4043 * @return mixed int total string length or array of characted widths
4044 * @author Nicola Asuni
4045 * @public
4046 * @since 2.4.000 (2008-03-06)
4047 */
4048 public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4049 // store current values
4050 if (!TCPDF_STATIC::empty_string($fontname)) {
4051 $prev_FontFamily = $this->FontFamily;
4052 $prev_FontStyle = $this->FontStyle;
4053 $prev_FontSizePt = $this->FontSizePt;
4054 $this->SetFont($fontname, $fontstyle, $fontsize, '', 'default', false);
4055 }
4056 // convert UTF-8 array to Latin1 if required
4057 if ($this->isunicode AND (!$this->isUnicodeFont())) {
4058 $sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
4059 }
4060 $w = 0; // total width
4061 $wa = array(); // array of characters widths
4062 foreach ($sa as $ck => $char) {
4063 // character width
4064 $cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4065 $wa[] = $cw;
4066 $w += $cw;
4067 }
4068 // restore previous values
4069 if (!TCPDF_STATIC::empty_string($fontname)) {
4070 $this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4071 }
4072 if ($getarray) {
4073 return $wa;
4074 }
4075 return $w;
4076 }
4077
4078 /**
4079 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
4080 * @param $char (int) The char code whose length is to be returned
4081 * @param $notlast (boolean) If false ignore the font-spacing.
4082 * @return float char width
4083 * @author Nicola Asuni
4084 * @public
4085 * @since 2.4.000 (2008-03-06)
4086 */
4087 public function GetCharWidth($char, $notlast=true) {
4088 // get raw width
4089 $chw = $this->getRawCharWidth($char);
4090 if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
4091 // increase/decrease font spacing
4092 $chw += $this->font_spacing;
4093 }
4094 if ($this->font_stretching != 100) {
4095 // fixed stretching mode
4096 $chw *= ($this->font_stretching / 100);
4097 }
4098 return $chw;
4099 }
4100
4101 /**
4102 * Returns the length of the char in user unit for the current font.
4103 * @param $char (int) The char code whose length is to be returned
4104 * @return float char width
4105 * @author Nicola Asuni
4106 * @public
4107 * @since 5.9.000 (2010-09-28)
4108 */
4109 public function getRawCharWidth($char) {
4110 if ($char == 173) {
4111 // SHY character will not be printed
4112 return (0);
4113 }
4114 if (isset($this->CurrentFont['cw'][$char])) {
4115 $w = $this->CurrentFont['cw'][$char];
4116 } elseif (isset($this->CurrentFont['dw'])) {
4117 // default width
4118 $w = $this->CurrentFont['dw'];
4119 } elseif (isset($this->CurrentFont['cw'][32])) {
4120 // default width
4121 $w = $this->CurrentFont['cw'][32];
4122 } else {
4123 $w = 600;
4124 }
4125 return $this->getAbsFontMeasure($w);
4126 }
4127
4128 /**
4129 * Returns the numbero of characters in a string.
4130 * @param $s (string) The input string.
4131 * @return int number of characters
4132 * @public
4133 * @since 2.0.0001 (2008-01-07)
4134 */
4135 public function GetNumChars($s) {
4136 if ($this->isUnicodeFont()) {
4137 return count(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont));
4138 }
4139 return strlen($s);
4140 }
4141
4142 /**
4143 * Fill the list of available fonts ($this->fontlist).
4144 * @protected
4145 * @since 4.0.013 (2008-07-28)
4146 */
4147 protected function getFontsList() {
4148 if (($fontsdir = opendir(TCPDF_FONTS::_getfontpath())) !== false) {
4149 while (($file = readdir($fontsdir)) !== false) {
4150 if (substr($file, -4) == '.php') {
4151 array_push($this->fontlist, strtolower(basename($file, '.php')));
4152 }
4153 }
4154 closedir($fontsdir);
4155 }
4156 }
4157
4158 /**
4159 * Imports a TrueType, Type1, core, or CID0 font and makes it available.
4160 * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
4161 * 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.
4162 * @param $family (string) Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
4163 * @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>
4164 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4165 * @return array containing the font data, or false in case of error.
4166 * @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.
4167 * @public
4168 * @since 1.5
4169 * @see SetFont(), setFontSubsetting()
4170 */
4171 public function AddFont($family, $style='', $fontfile='', $subset='default') {
4172 if ($subset === 'default') {
4173 $subset = $this->font_subsetting;
4174 }
4175 if ($this->pdfa_mode) {
4176 $subset = false;
4177 }
4178 if (TCPDF_STATIC::empty_string($family)) {
4179 if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
4180 $family = $this->FontFamily;
4181 } else {
4182 $this->Error('Empty font family');
4183 }
4184 }
4185 // move embedded styles on $style
4186 if (substr($family, -1) == 'I') {
4187 $style .= 'I';
4188 $family = substr($family, 0, -1);
4189 }
4190 if (substr($family, -1) == 'B') {
4191 $style .= 'B';
4192 $family = substr($family, 0, -1);
4193 }
4194 // normalize family name
4195 $family = strtolower($family);
4196 if ((!$this->isunicode) AND ($family == 'arial')) {
4197 $family = 'helvetica';
4198 }
4199 if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4200 $style = '';
4201 }
4202 if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
4203 // all fonts must be embedded
4204 $family = 'pdfa'.$family;
4205 }
4206 $tempstyle = strtoupper($style);
4207 $style = '';
4208 // underline
4209 if (strpos($tempstyle, 'U') !== false) {
4210 $this->underline = true;
4211 } else {
4212 $this->underline = false;
4213 }
4214 // line-through (deleted)
4215 if (strpos($tempstyle, 'D') !== false) {
4216 $this->linethrough = true;
4217 } else {
4218 $this->linethrough = false;
4219 }
4220 // overline
4221 if (strpos($tempstyle, 'O') !== false) {
4222 $this->overline = true;
4223 } else {
4224 $this->overline = false;
4225 }
4226 // bold
4227 if (strpos($tempstyle, 'B') !== false) {
4228 $style .= 'B';
4229 }
4230 // oblique
4231 if (strpos($tempstyle, 'I') !== false) {
4232 $style .= 'I';
4233 }
4234 $bistyle = $style;
4235 $fontkey = $family.$style;
4236 $font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
4237 $fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4238 // check if the font has been already added
4239 $fb = $this->getFontBuffer($fontkey);
4240 if ($fb !== false) {
4241 if ($this->inxobj) {
4242 // we are inside an XObject template
4243 $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
4244 }
4245 return $fontdata;
4246 }
4247 // get specified font directory (if any)
4248 $fontdir = false;
4249 if (!TCPDF_STATIC::empty_string($fontfile)) {
4250 $fontdir = dirname($fontfile);
4251 if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
4252 $fontdir = '';
4253 } else {
4254 $fontdir .= '/';
4255 }
4256 }
4257 // true when the font style variation is missing
4258 $missing_style = false;
4259 // search and include font file
4260 if (TCPDF_STATIC::empty_string($fontfile) OR (!@file_exists($fontfile))) {
4261 // build a standard filenames for specified font
4262 $tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
4263 $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4264 if (TCPDF_STATIC::empty_string($fontfile)) {
4265 $missing_style = true;
4266 // try to remove the style part
4267 $tmp_fontfile = str_replace(' ', '', $family).'.php';
4268 $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4269 }
4270 }
4271 // include font file
4272 if (!TCPDF_STATIC::empty_string($fontfile) AND (@file_exists($fontfile))) {
4273 include($fontfile);
4274 } else {
4275 $this->Error('Could not include font definition file: '.$family.'');
4276 }
4277 // check font parameters
4278 if ((!isset($type)) OR (!isset($cw))) {
4279 $this->Error('The font definition file has a bad format: '.$fontfile.'');
4280 }
4281 // SET default parameters
4282 if (!isset($file) OR TCPDF_STATIC::empty_string($file)) {
4283 $file = '';
4284 }
4285 if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
4286 $enc = '';
4287 }
4288 if (!isset($cidinfo) OR TCPDF_STATIC::empty_string($cidinfo)) {
4289 $cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
4290 $cidinfo['uni2cid'] = array();
4291 }
4292 if (!isset($ctg) OR TCPDF_STATIC::empty_string($ctg)) {
4293 $ctg = '';
4294 }
4295 if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
4296 $desc = array();
4297 }
4298 if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
4299 $up = -100;
4300 }
4301 if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
4302 $ut = 50;
4303 }
4304 if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
4305 $cw = array();
4306 }
4307 if (!isset($dw) OR TCPDF_STATIC::empty_string($dw)) {
4308 // set default width
4309 if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4310 $dw = $desc['MissingWidth'];
4311 } elseif (isset($cw[32])) {
4312 $dw = $cw[32];
4313 } else {
4314 $dw = 600;
4315 }
4316 }
4317 ++$this->numfonts;
4318 if ($type == 'core') {
4319 $name = $this->CoreFonts[$fontkey];
4320 $subset = false;
4321 } elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4322 $subset = false;
4323 } elseif ($type == 'TrueTypeUnicode') {
4324 $enc = 'Identity-H';
4325 } elseif ($type == 'cidfont0') {
4326 if ($this->pdfa_mode) {
4327 $this->Error('All fonts must be embedded in PDF/A mode!');
4328 }
4329 } else {
4330 $this->Error('Unknow font type: '.$type.'');
4331 }
4332 // set name if unset
4333 if (!isset($name) OR empty($name)) {
4334 $name = $fontkey;
4335 }
4336 // create artificial font style variations if missing (only works with non-embedded fonts)
4337 if (($type != 'core') AND $missing_style) {
4338 // style variations
4339 $styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4340 $name .= $styles[$bistyle];
4341 // artificial bold
4342 if (strpos($bistyle, 'B') !== false) {
4343 if (isset($desc['StemV'])) {
4344 // from normal to bold
4345 $desc['StemV'] = round($desc['StemV'] * 1.75);
4346 } else {
4347 // bold
4348 $desc['StemV'] = 123;
4349 }
4350 }
4351 // artificial italic
4352 if (strpos($bistyle, 'I') !== false) {
4353 if (isset($desc['ItalicAngle'])) {
4354 $desc['ItalicAngle'] -= 11;
4355 } else {
4356 $desc['ItalicAngle'] = -11;
4357 }
4358 if (isset($desc['Flags'])) {
4359 $desc['Flags'] |= 64; //bit 7
4360 } else {
4361 $desc['Flags'] = 64;
4362 }
4363 }
4364 }
4365 // check if the array of characters bounding boxes is defined
4366 if (!isset($cbbox)) {
4367 $cbbox = array();
4368 }
4369 // initialize subsetchars
4370 $subsetchars = array_fill(0, 255, true);
4371 $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));
4372 if ($this->inxobj) {
4373 // we are inside an XObject template
4374 $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
4375 }
4376 if (isset($diff) AND (!empty($diff))) {
4377 //Search existing encodings
4378 $d = 0;
4379 $nb = count($this->diffs);
4380 for ($i=1; $i <= $nb; ++$i) {
4381 if ($this->diffs[$i] == $diff) {
4382 $d = $i;
4383 break;
4384 }
4385 }
4386 if ($d == 0) {
4387 $d = $nb + 1;
4388 $this->diffs[$d] = $diff;
4389 }
4390 $this->setFontSubBuffer($fontkey, 'diff', $d);
4391 }
4392 if (!TCPDF_STATIC::empty_string($file)) {
4393 if (!isset($this->FontFiles[$file])) {
4394 if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4395 $this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4396 } elseif ($type != 'core') {
4397 $this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4398 }
4399 } else {
4400 // update fontkeys that are sharing this font file
4401 $this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
4402 if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
4403 $this->FontFiles[$file]['fontkeys'][] = $fontkey;
4404 }
4405 }
4406 }
4407 return $fontdata;
4408 }
4409
4410 /**
4411 * Sets the font used to print character strings.
4412 * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
4413 * The method can be called before the first page is created and the font is retained from page to page.
4414 * If you just wish to change the current font size, it is simpler to call SetFontSize().
4415 * 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 />
4416 * @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.
4417 * @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.
4418 * @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
4419 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4420 * @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.
4421 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4422 * @author Nicola Asuni
4423 * @public
4424 * @since 1.0
4425 * @see AddFont(), SetFontSize()
4426 */
4427 public function SetFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
4428 //Select a font; size given in points
4429 if ($size === null) {
4430 $size = $this->FontSizePt;
4431 }
4432 if ($size < 0) {
4433 $size = 0;
4434 }
4435 // try to add font (if not already added)
4436 $fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4437 $this->FontFamily = $fontdata['family'];
4438 $this->FontStyle = $fontdata['style'];
4439 if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
4440 // save subset chars of the previous font
4441 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
4442 }
4443 $this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
4444 $this->SetFontSize($size, $out);
4445 }
4446
4447 /**
4448 * Defines the size of the current font.
4449 * @param $size (float) The font size in points.
4450 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4451 * @public
4452 * @since 1.0
4453 * @see SetFont()
4454 */
4455 public function SetFontSize($size, $out=true) {
4456 // font size in points
4457 $this->FontSizePt = $size;
4458 // font size in user units
4459 $this->FontSize = $size / $this->k;
4460 // calculate some font metrics
4461 if (isset($this->CurrentFont['desc']['FontBBox'])) {
4462 $bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4463 $font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4464 } else {
4465 $font_height = $size * 1.219;
4466 }
4467 if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
4468 $font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
4469 }
4470 if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
4471 $font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
4472 }
4473 if (!isset($font_ascent) AND !isset($font_descent)) {
4474 // core font
4475 $font_ascent = 0.76 * $font_height;
4476 $font_descent = $font_height - $font_ascent;
4477 } elseif (!isset($font_descent)) {
4478 $font_descent = $font_height - $font_ascent;
4479 } elseif (!isset($font_ascent)) {
4480 $font_ascent = $font_height - $font_descent;
4481 }
4482 $this->FontAscent = ($font_ascent / $this->k);
4483 $this->FontDescent = ($font_descent / $this->k);
4484 if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
4485 $this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4486 }
4487 }
4488
4489 /**
4490 * Returns the bounding box of the current font in user units.
4491 * @return array
4492 * @public
4493 * @since 5.9.152 (2012-03-23)
4494 */
4495 public function getFontBBox() {
4496 $fbbox = array();
4497 if (isset($this->CurrentFont['desc']['FontBBox'])) {
4498 $tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4499 $fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
4500 } else {
4501 // Find max width
4502 if (isset($this->CurrentFont['desc']['MaxWidth'])) {
4503 $maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
4504 } else {
4505 $maxw = 0;
4506 if (isset($this->CurrentFont['desc']['MissingWidth'])) {
4507 $maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
4508 }
4509 if (isset($this->CurrentFont['desc']['AvgWidth'])) {
4510 $maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
4511 }
4512 if (isset($this->CurrentFont['dw'])) {
4513 $maxw = max($maxw, $this->CurrentFont['dw']);
4514 }
4515 foreach ($this->CurrentFont['cw'] as $char => $w) {
4516 $maxw = max($maxw, $w);
4517 }
4518 if ($maxw == 0) {
4519 $maxw = 600;
4520 }
4521 $maxw = $this->getAbsFontMeasure($maxw);
4522 }
4523 $fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
4524 }
4525 return $fbbox;
4526 }
4527
4528 /**
4529 * Convert a relative font measure into absolute value.
4530 * @param $s (int) Font measure.
4531 * @return float Absolute measure.
4532 * @since 5.9.186 (2012-09-13)
4533 */
4534 public function getAbsFontMeasure($s) {
4535 return ($s * $this->FontSize / 1000);
4536 }
4537
4538 /**
4539 * Returns the glyph bounding box of the specified character in the current font in user units.
4540 * @param $char (int) Input character code.
4541 * @return mixed array(xMin, yMin, xMax, yMax) or FALSE if not defined.
4542 * @since 5.9.186 (2012-09-13)
4543 */
4544 public function getCharBBox($char) {
4545 $c = intval($char);
4546 if (isset($this->CurrentFont['cw'][$c])) {
4547 // glyph is defined ... use zero width & height for glyphs without outlines
4548 $result = array(0,0,0,0);
4549 if (isset($this->CurrentFont['cbbox'][$c])) {
4550 $result = $this->CurrentFont['cbbox'][$c];
4551 }
4552 return array_map(array($this,'getAbsFontMeasure'), $result);
4553 }
4554 return false;
4555 }
4556
4557 /**
4558 * Return the font descent value
4559 * @param $font (string) font name
4560 * @param $style (string) font style
4561 * @param $size (float) The size (in points)
4562 * @return int font descent
4563 * @public
4564 * @author Nicola Asuni
4565 * @since 4.9.003 (2010-03-30)
4566 */
4567 public function getFontDescent($font, $style='', $size=0) {
4568 $fontdata = $this->AddFont($font, $style);
4569 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4570 if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
4571 $descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
4572 } else {
4573 $descent = (1.219 * 0.24 * $size);
4574 }
4575 return ($descent / $this->k);
4576 }
4577
4578 /**
4579 * Return the font ascent value.
4580 * @param $font (string) font name
4581 * @param $style (string) font style
4582 * @param $size (float) The size (in points)
4583 * @return int font ascent
4584 * @public
4585 * @author Nicola Asuni
4586 * @since 4.9.003 (2010-03-30)
4587 */
4588 public function getFontAscent($font, $style='', $size=0) {
4589 $fontdata = $this->AddFont($font, $style);
4590 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4591 if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
4592 $ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
4593 } else {
4594 $ascent = 1.219 * 0.76 * $size;
4595 }
4596 return ($ascent / $this->k);
4597 }
4598
4599 /**
4600 * Return true in the character is present in the specified font.
4601 * @param $char (mixed) Character to check (integer value or string)
4602 * @param $font (string) Font name (family name).
4603 * @param $style (string) Font style.
4604 * @return (boolean) true if the char is defined, false otherwise.
4605 * @public
4606 * @since 5.9.153 (2012-03-28)
4607 */
4608 public function isCharDefined($char, $font='', $style='') {
4609 if (is_string($char)) {
4610 // get character code
4611 $char = TCPDF_FONTS::UTF8StringToArray($char, $this->isunicode, $this->CurrentFont);
4612 $char = $char[0];
4613 }
4614 if (TCPDF_STATIC::empty_string($font)) {
4615 if (TCPDF_STATIC::empty_string($style)) {
4616 return (isset($this->CurrentFont['cw'][intval($char)]));
4617 }
4618 $font = $this->FontFamily;
4619 }
4620 $fontdata = $this->AddFont($font, $style);
4621 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4622 return (isset($fontinfo['cw'][intval($char)]));
4623 }
4624
4625 /**
4626 * Replace missing font characters on selected font with specified substitutions.
4627 * @param $text (string) Text to process.
4628 * @param $font (string) Font name (family name).
4629 * @param $style (string) Font style.
4630 * @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.
4631 * @return (string) Processed text.
4632 * @public
4633 * @since 5.9.153 (2012-03-28)
4634 */
4635 public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
4636 if (empty($subs)) {
4637 return $text;
4638 }
4639 if (TCPDF_STATIC::empty_string($font)) {
4640 $font = $this->FontFamily;
4641 }
4642 $fontdata = $this->AddFont($font, $style);
4643 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4644 $uniarr = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
4645 foreach ($uniarr as $k => $chr) {
4646 if (!isset($fontinfo['cw'][$chr])) {
4647 // this character is missing on the selected font
4648 if (isset($subs[$chr])) {
4649 // we have available substitutions
4650 if (is_array($subs[$chr])) {
4651 foreach($subs[$chr] as $s) {
4652 if (isset($fontinfo['cw'][$s])) {
4653 $uniarr[$k] = $s;
4654 break;
4655 }
4656 }
4657 } elseif (isset($fontinfo['cw'][$subs[$chr]])) {
4658 $uniarr[$k] = $subs[$chr];
4659 }
4660 }
4661 }
4662 }
4663 return TCPDF_FONTS::UniArrSubString(TCPDF_FONTS::UTF8ArrayToUniArray($uniarr, $this->isunicode));
4664 }
4665
4666 /**
4667 * Defines the default monospaced font.
4668 * @param $font (string) Font name.
4669 * @public
4670 * @since 4.5.025
4671 */
4672 public function SetDefaultMonospacedFont($font) {
4673 $this->default_monospaced_font = $font;
4674 }
4675
4676 /**
4677 * 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 />
4678 * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
4679 * @public
4680 * @since 1.5
4681 * @see Cell(), Write(), Image(), Link(), SetLink()
4682 */
4683 public function AddLink() {
4684 // create a new internal link
4685 $n = count($this->links) + 1;
4686 $this->links[$n] = array('p' => 0, 'y' => 0, 'f' => false);
4687 return $n;
4688 }
4689
4690 /**
4691 * Defines the page and position a link points to.
4692 * @param $link (int) The link identifier returned by AddLink()
4693 * @param $y (float) Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
4694 * @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.
4695 * @public
4696 * @since 1.5
4697 * @see AddLink()
4698 */
4699 public function SetLink($link, $y=0, $page=-1) {
4700 $fixed = false;
4701 if (!empty($page) AND ($page[0] == '*')) {
4702 $page = intval(substr($page, 1));
4703 // this page number will not be changed when moving/add/deleting pages
4704 $fixed = true;
4705 }
4706 if ($page < 0) {
4707 $page = $this->page;
4708 }
4709 if ($y == -1) {
4710 $y = $this->y;
4711 }
4712 $this->links[$link] = array('p' => $page, 'y' => $y, 'f' => $fixed);
4713 }
4714
4715 /**
4716 * Puts a link on a rectangular area of the page.
4717 * 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.
4718 * @param $x (float) Abscissa of the upper-left corner of the rectangle
4719 * @param $y (float) Ordinate of the upper-left corner of the rectangle
4720 * @param $w (float) Width of the rectangle
4721 * @param $h (float) Height of the rectangle
4722 * @param $link (mixed) URL or identifier returned by AddLink()
4723 * @param $spaces (int) number of spaces on the text to link
4724 * @public
4725 * @since 1.5
4726 * @see AddLink(), Annotation(), Cell(), Write(), Image()
4727 */
4728 public function Link($x, $y, $w, $h, $link, $spaces=0) {
4729 $this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
4730 }
4731
4732 /**
4733 * Puts a markup annotation on a rectangular area of the page.
4734 * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
4735 * @param $x (float) Abscissa of the upper-left corner of the rectangle
4736 * @param $y (float) Ordinate of the upper-left corner of the rectangle
4737 * @param $w (float) Width of the rectangle
4738 * @param $h (float) Height of the rectangle
4739 * @param $text (string) annotation text or alternate content
4740 * @param $opt (array) array of options (see section 8.4 of PDF reference 1.7).
4741 * @param $spaces (int) number of spaces on the text to link
4742 * @public
4743 * @since 4.0.018 (2008-08-06)
4744 */
4745 public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
4746 if ($this->inxobj) {
4747 // store parameters for later use on template
4748 $this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
4749 return;
4750 }
4751 if ($x === '') {
4752 $x = $this->x;
4753 }
4754 if ($y === '') {
4755 $y = $this->y;
4756 }
4757 // check page for no-write regions and adapt page margins if necessary
4758 list($x, $y) = $this->checkPageRegions($h, $x, $y);
4759 // recalculate coordinates to account for graphic transformations
4760 if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
4761 for ($i=$this->transfmatrix_key; $i > 0; --$i) {
4762 $maxid = count($this->transfmatrix[$i]) - 1;
4763 for ($j=$maxid; $j >= 0; --$j) {
4764 $ctm = $this->transfmatrix[$i][$j];
4765 if (isset($ctm['a'])) {
4766 $x = $x * $this->k;
4767 $y = ($this->h - $y) * $this->k;
4768 $w = $w * $this->k;
4769 $h = $h * $this->k;
4770 // top left
4771 $xt = $x;
4772 $yt = $y;
4773 $x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4774 $y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4775 // top right
4776 $xt = $x + $w;
4777 $yt = $y;
4778 $x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4779 $y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4780 // bottom left
4781 $xt = $x;
4782 $yt = $y - $h;
4783 $x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4784 $y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4785 // bottom right
4786 $xt = $x + $w;
4787 $yt = $y - $h;
4788 $x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4789 $y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4790 // new coordinates (rectangle area)
4791 $x = min($x1, $x2, $x3, $x4);
4792 $y = max($y1, $y2, $y3, $y4);
4793 $w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
4794 $h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
4795 $x = $x / $this->k;
4796 $y = $this->h - ($y / $this->k);
4797 }
4798 }
4799 }
4800 }
4801 if ($this->page <= 0) {
4802 $page = 1;
4803 } else {
4804 $page = $this->page;
4805 }
4806 if (!isset($this->PageAnnots[$page])) {
4807 $this->PageAnnots[$page] = array();
4808 }
4809 $this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
4810 if (!$this->pdfa_mode) {
4811 if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
4812 AND (@file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
4813 AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
4814 $this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
4815 }
4816 }
4817 // Add widgets annotation's icons
4818 if (isset($opt['mk']['i']) AND @file_exists($opt['mk']['i'])) {
4819 $this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
4820 }
4821 if (isset($opt['mk']['ri']) AND @file_exists($opt['mk']['ri'])) {
4822 $this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4823 }
4824 if (isset($opt['mk']['ix']) AND @file_exists($opt['mk']['ix'])) {
4825 $this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4826 }
4827 }
4828
4829 /**
4830 * Embedd the attached files.
4831 * @since 4.4.000 (2008-12-07)
4832 * @protected
4833 * @see Annotation()
4834 */
4835 protected function _putEmbeddedFiles() {
4836 if ($this->pdfa_mode) {
4837 // embedded files are not allowed in PDF/A mode
4838 return;
4839 }
4840 reset($this->embeddedfiles);
4841 foreach ($this->embeddedfiles as $filename => $filedata) {
4842 $data = TCPDF_STATIC::fileGetContents($filedata['file']);
4843 if ($data !== FALSE) {
4844 $rawsize = strlen($data);
4845 if ($rawsize > 0) {
4846 // update name tree
4847 $this->efnames[$filename] = $filedata['f'].' 0 R';
4848 // embedded file specification object
4849 $out = $this->_getobj($filedata['f'])."\n";
4850 $out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']).' /EF <</F '.$filedata['n'].' 0 R>> >>';
4851 $out .= "\n".'endobj';
4852 $this->_out($out);
4853 // embedded file object
4854 $filter = '';
4855 if ($this->compress) {
4856 $data = gzcompress($data);
4857 $filter = ' /Filter /FlateDecode';
4858 }
4859 $stream = $this->_getrawstream($data, $filedata['n']);
4860 $out = $this->_getobj($filedata['n'])."\n";
4861 $out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
4862 $out .= ' stream'."\n".$stream."\n".'endstream';
4863 $out .= "\n".'endobj';
4864 $this->_out($out);
4865 }
4866 }
4867 }
4868 }
4869
4870 /**
4871 * Prints a text cell at the specified position.
4872 * This method allows to place a string precisely on the page.
4873 * @param $x (float) Abscissa of the cell origin
4874 * @param $y (float) Ordinate of the cell origin
4875 * @param $txt (string) String to print
4876 * @param $fstroke (int) outline size in user units (false = disable)
4877 * @param $fclip (boolean) if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
4878 * @param $ffill (boolean) if true fills the text
4879 * @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)))
4880 * @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.
4881 * @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>
4882 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
4883 * @param $link (mixed) URL or identifier returned by AddLink().
4884 * @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.
4885 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
4886 * @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>
4887 * @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>
4888 * @param $rtloff (boolean) if true uses the page top-left corner as origin of axis for $x and $y initial position.
4889 * @public
4890 * @since 1.0
4891 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
4892 */
4893 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) {
4894 $textrendermode = $this->textrendermode;
4895 $textstrokewidth = $this->textstrokewidth;
4896 $this->setTextRenderingMode($fstroke, $ffill, $fclip);
4897 $this->SetXY($x, $y, $rtloff);
4898 $this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
4899 // restore previous rendering mode
4900 $this->textrendermode = $textrendermode;
4901 $this->textstrokewidth = $textstrokewidth;
4902 }
4903
4904 /**
4905 * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
4906 * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
4907 * This method is called automatically and should not be called directly by the application.
4908 * @return boolean
4909 * @public
4910 * @since 1.4
4911 * @see SetAutoPageBreak()
4912 */
4913 public function AcceptPageBreak() {
4914 if ($this->num_columns > 1) {
4915 // multi column mode
4916 if ($this->current_column < ($this->num_columns - 1)) {
4917 // go to next column
4918 $this->selectColumn($this->current_column + 1);
4919 } elseif ($this->AutoPageBreak) {
4920 // add a new page
4921 $this->AddPage();
4922 // set first column
4923 $this->selectColumn(0);
4924 }
4925 // avoid page breaking from checkPageBreak()
4926 return false;
4927 }
4928 return $this->AutoPageBreak;
4929 }
4930
4931 /**
4932 * Add page if needed.
4933 * @param $h (float) Cell height. Default value: 0.
4934 * @param $y (mixed) starting y position, leave empty for current position.
4935 * @param $addpage (boolean) if true add a page, otherwise only return the true/false state
4936 * @return boolean true in case of page break, false otherwise.
4937 * @since 3.2.000 (2008-07-01)
4938 * @protected
4939 */
4940 protected function checkPageBreak($h=0, $y='', $addpage=true) {
4941 if (TCPDF_STATIC::empty_string($y)) {
4942 $y = $this->y;
4943 }
4944 $current_page = $this->page;
4945 if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
4946 if ($addpage) {
4947 //Automatic page break
4948 $x = $this->x;
4949 $this->AddPage($this->CurOrientation);
4950 $this->y = $this->tMargin;
4951 $oldpage = $this->page - 1;
4952 if ($this->rtl) {
4953 if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
4954 $this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
4955 } else {
4956 $this->x = $x;
4957 }
4958 } else {
4959 if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
4960 $this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
4961 } else {
4962 $this->x = $x;
4963 }
4964 }
4965 }
4966 return true;
4967 }
4968 if ($current_page != $this->page) {
4969 // account for columns mode
4970 return true;
4971 }
4972 return false;
4973 }
4974
4975 /**
4976 * 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 />
4977 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
4978 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
4979 * @param $h (float) Cell height. Default value: 0.
4980 * @param $txt (string) String to print. Default value: empty string.
4981 * @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)))
4982 * @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.
4983 * @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>
4984 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
4985 * @param $link (mixed) URL or identifier returned by AddLink().
4986 * @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.
4987 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
4988 * @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>
4989 * @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>
4990 * @public
4991 * @since 1.0
4992 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
4993 */
4994 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') {
4995 $prev_cell_margin = $this->cell_margin;
4996 $prev_cell_padding = $this->cell_padding;
4997 $this->adjustCellPadding($border);
4998 if (!$ignore_min_height) {
4999 $min_cell_height = $this->getCellHeight($this->FontSize);
5000 if ($h < $min_cell_height) {
5001 $h = $min_cell_height;
5002 }
5003 }
5004 $this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5005 // apply text shadow if enabled
5006 if ($this->txtshadow['enabled']) {
5007 // save data
5008 $x = $this->x;
5009 $y = $this->y;
5010 $bc = $this->bgcolor;
5011 $fc = $this->fgcolor;
5012 $sc = $this->strokecolor;
5013 $alpha = $this->alpha;
5014 // print shadow
5015 $this->x += $this->txtshadow['depth_w'];
5016 $this->y += $this->txtshadow['depth_h'];
5017 $this->SetFillColorArray($this->txtshadow['color']);
5018 $this->SetTextColorArray($this->txtshadow['color']);
5019 $this->SetDrawColorArray($this->txtshadow['color']);
5020 if ($this->txtshadow['opacity'] != $alpha['CA']) {
5021 $this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
5022 }
5023 if ($this->state == 2) {
5024 $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5025 }
5026 //restore data
5027 $this->x = $x;
5028 $this->y = $y;
5029 $this->SetFillColorArray($bc);
5030 $this->SetTextColorArray($fc);
5031 $this->SetDrawColorArray($sc);
5032 if ($this->txtshadow['opacity'] != $alpha['CA']) {
5033 $this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
5034 }
5035 }
5036 if ($this->state == 2) {
5037 $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5038 }
5039 $this->cell_padding = $prev_cell_padding;
5040 $this->cell_margin = $prev_cell_margin;
5041 }
5042
5043 /**
5044 * 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 />
5045 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5046 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
5047 * @param $h (float) Cell height. Default value: 0.
5048 * @param $txt (string) String to print. Default value: empty string.
5049 * @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)))
5050 * @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.
5051 * @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>
5052 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5053 * @param $link (mixed) URL or identifier returned by AddLink().
5054 * @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.
5055 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5056 * @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>
5057 * @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>
5058 * @return string containing cell code
5059 * @protected
5060 * @since 1.0
5061 * @see Cell()
5062 */
5063 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') {
5064 // replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
5065 $txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
5066 $prev_cell_margin = $this->cell_margin;
5067 $prev_cell_padding = $this->cell_padding;
5068 $txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
5069 $rs = ''; //string to be returned
5070 $this->adjustCellPadding($border);
5071 if (!$ignore_min_height) {
5072 $min_cell_height = $this->getCellHeight($this->FontSize);
5073 if ($h < $min_cell_height) {
5074 $h = $min_cell_height;
5075 }
5076 }
5077 $k = $this->k;
5078 // check page for no-write regions and adapt page margins if necessary
5079 list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
5080 if ($this->rtl) {
5081 $x = $this->x - $this->cell_margin['R'];
5082 } else {
5083 $x = $this->x + $this->cell_margin['L'];
5084 }
5085 $y = $this->y + $this->cell_margin['T'];
5086 $prev_font_stretching = $this->font_stretching;
5087 $prev_font_spacing = $this->font_spacing;
5088 // cell vertical alignment
5089 switch ($calign) {
5090 case 'A': {
5091 // font top
5092 switch ($valign) {
5093 case 'T': {
5094 // top
5095 $y -= $this->cell_padding['T'];
5096 break;
5097 }
5098 case 'B': {
5099 // bottom
5100 $y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5101 break;
5102 }
5103 default:
5104 case 'C':
5105 case 'M': {
5106 // center
5107 $y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5108 break;
5109 }
5110 }
5111 break;
5112 }
5113 case 'L': {
5114 // font baseline
5115 switch ($valign) {
5116 case 'T': {
5117 // top
5118 $y -= ($this->cell_padding['T'] + $this->FontAscent);
5119 break;
5120 }
5121 case 'B': {
5122 // bottom
5123 $y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5124 break;
5125 }
5126 default:
5127 case 'C':
5128 case 'M': {
5129 // center
5130 $y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5131 break;
5132 }
5133 }
5134 break;
5135 }
5136 case 'D': {
5137 // font bottom
5138 switch ($valign) {
5139 case 'T': {
5140 // top
5141 $y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5142 break;
5143 }
5144 case 'B': {
5145 // bottom
5146 $y -= ($h - $this->cell_padding['B']);
5147 break;
5148 }
5149 default:
5150 case 'C':
5151 case 'M': {
5152 // center
5153 $y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5154 break;
5155 }
5156 }
5157 break;
5158 }
5159 case 'B': {
5160 // cell bottom
5161 $y -= $h;
5162 break;
5163 }
5164 case 'C':
5165 case 'M': {
5166 // cell center
5167 $y -= ($h / 2);
5168 break;
5169 }
5170 default:
5171 case 'T': {
5172 // cell top
5173 break;
5174 }
5175 }
5176 // text vertical alignment
5177 switch ($valign) {
5178 case 'T': {
5179 // top
5180 $yt = $y + $this->cell_padding['T'];
5181 break;
5182 }
5183 case 'B': {
5184 // bottom
5185 $yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5186 break;
5187 }
5188 default:
5189 case 'C':
5190 case 'M': {
5191 // center
5192 $yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5193 break;
5194 }
5195 }
5196 $basefonty = $yt + $this->FontAscent;
5197 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5198 if ($this->rtl) {
5199 $w = $x - $this->lMargin;
5200 } else {
5201 $w = $this->w - $this->rMargin - $x;
5202 }
5203 }
5204 $s = '';
5205 // fill and borders
5206 if (is_string($border) AND (strlen($border) == 4)) {
5207 // full border
5208 $border = 1;
5209 }
5210 if ($fill OR ($border == 1)) {
5211 if ($fill) {
5212 $op = ($border == 1) ? 'B' : 'f';
5213 } else {
5214 $op = 'S';
5215 }
5216 if ($this->rtl) {
5217 $xk = (($x - $w) * $k);
5218 } else {
5219 $xk = ($x * $k);
5220 }
5221 $s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5222 }
5223 // draw borders
5224 $s .= $this->getCellBorder($x, $y, $w, $h, $border);
5225 if ($txt != '') {
5226 $txt2 = $txt;
5227 if ($this->isunicode) {
5228 if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
5229 $txt2 = TCPDF_FONTS::UTF8ToLatin1($txt2, $this->isunicode, $this->CurrentFont);
5230 } else {
5231 $unicode = TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont); // array of UTF-8 unicode values
5232 $unicode = TCPDF_FONTS::utf8Bidi($unicode, '', $this->tmprtl, $this->isunicode, $this->CurrentFont);
5233 // replace thai chars (if any)
5234 if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
5235 // number of chars
5236 $numchars = count($unicode);
5237 // po pla, for far, for fan
5238 $longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
5239 // do chada, to patak
5240 $lowtail = array(0x0e0e, 0x0e0f);
5241 // mai hun arkad, sara i, sara ii, sara ue, sara uee
5242 $upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
5243 // mai ek, mai tho, mai tri, mai chattawa, karan
5244 $tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
5245 // sara u, sara uu, pinthu
5246 $lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
5247 $output = array();
5248 for ($i = 0; $i < $numchars; $i++) {
5249 if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
5250 $ch0 = $unicode[$i];
5251 $ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
5252 $ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
5253 $chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
5254 if (in_array($ch0, $tonemark)) {
5255 if ($chn == 0x0e33) {
5256 // sara um
5257 if (in_array($ch1, $longtail)) {
5258 // tonemark at upper left
5259 $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5260 } else {
5261 // tonemark at upper right (normal position)
5262 $output[] = $ch0;
5263 }
5264 } elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
5265 // tonemark at lower left
5266 $output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
5267 } elseif (in_array($ch1, $upvowel)) {
5268 if (in_array($ch2, $longtail)) {
5269 // tonemark at upper left
5270 $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5271 } else {
5272 // tonemark at upper right (normal position)
5273 $output[] = $ch0;
5274 }
5275 } else {
5276 // tonemark at lower right
5277 $output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
5278 }
5279 } elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
5280 // add lower left nikhahit and sara aa
5281 if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
5282 $output[] = 0xf711;
5283 $this->CurrentFont['subsetchars'][0xf711] = true;
5284 $output[] = 0x0e32;
5285 $this->CurrentFont['subsetchars'][0x0e32] = true;
5286 } else {
5287 $output[] = $ch0;
5288 }
5289 } elseif (in_array($ch1, $longtail)) {
5290 if ($ch0 == 0x0e31) {
5291 // lower left mai hun arkad
5292 $output[] = $this->replaceChar($ch0, 0xf710);
5293 } elseif (in_array($ch0, $upvowel)) {
5294 // lower left
5295 $output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
5296 } elseif ($ch0 == 0x0e47) {
5297 // lower left mai tai koo
5298 $output[] = $this->replaceChar($ch0, 0xf712);
5299 } else {
5300 // normal character
5301 $output[] = $ch0;
5302 }
5303 } elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
5304 // lower vowel
5305 $output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
5306 } elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
5307 // yo ying without lower part
5308 $output[] = $this->replaceChar($ch0, 0xf70f);
5309 } elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
5310 // tho santan without lower part
5311 $output[] = $this->replaceChar($ch0, 0xf700);
5312 } else {
5313 $output[] = $ch0;
5314 }
5315 } else {
5316 // non-thai character
5317 $output[] = $unicode[$i];
5318 }
5319 }
5320 $unicode = $output;
5321 // update font subsetchars
5322 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
5323 } // end of K_THAI_TOPCHARS
5324 $txt2 = TCPDF_FONTS::arrUTF8ToUTF16BE($unicode, false);
5325 }
5326 }
5327 $txt2 = TCPDF_STATIC::_escape($txt2);
5328 // get current text width (considering general font stretching and spacing)
5329 $txwidth = $this->GetStringWidth($txt);
5330 $width = $txwidth;
5331 // check for stretch mode
5332 if ($stretch > 0) {
5333 // calculate ratio between cell width and text width
5334 if ($width <= 0) {
5335 $ratio = 1;
5336 } else {
5337 $ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
5338 }
5339 // check if stretching is required
5340 if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
5341 // the text will be stretched to fit cell width
5342 if ($stretch > 2) {
5343 // set new character spacing
5344 $this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
5345 } else {
5346 // set new horizontal stretching
5347 $this->font_stretching *= $ratio;
5348 }
5349 // recalculate text width (the text fills the entire cell)
5350 $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5351 // reset alignment
5352 $align = '';
5353 }
5354 }
5355 if ($this->font_stretching != 100) {
5356 // apply font stretching
5357 $rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
5358 }
5359 if ($this->font_spacing != 0) {
5360 // increase/decrease font spacing
5361 $rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
5362 }
5363 if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5364 $s .= 'q '.$this->TextColor.' ';
5365 }
5366 // rendering mode
5367 $s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
5368 // count number of spaces
5369 $ns = substr_count($txt, chr(32));
5370 // Justification
5371 $spacewidth = 0;
5372 if (($align == 'J') AND ($ns > 0)) {
5373 if ($this->isUnicodeFont()) {
5374 // get string width without spaces
5375 $width = $this->GetStringWidth(str_replace(' ', '', $txt));
5376 // calculate average space width
5377 $spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / ($this->FontSize?$this->FontSize:1);
5378 if ($this->font_stretching != 100) {
5379 // word spacing is affected by stretching
5380 $spacewidth /= ($this->font_stretching / 100);
5381 }
5382 // set word position to be used with TJ operator
5383 $txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
5384 $unicode_justification = true;
5385 } else {
5386 // get string width
5387 $width = $txwidth;
5388 // new space width
5389 $spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
5390 if ($this->font_stretching != 100) {
5391 // word spacing (Tw) is affected by stretching
5392 $spacewidth /= ($this->font_stretching / 100);
5393 }
5394 // set word spacing
5395 $rs .= sprintf('BT %F Tw ET ', $spacewidth);
5396 }
5397 $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5398 }
5399 // replace carriage return characters
5400 $txt2 = str_replace("\r", ' ', $txt2);
5401 switch ($align) {
5402 case 'C': {
5403 $dx = ($w - $width) / 2;
5404 break;
5405 }
5406 case 'R': {
5407 if ($this->rtl) {
5408 $dx = $this->cell_padding['R'];
5409 } else {
5410 $dx = $w - $width - $this->cell_padding['R'];
5411 }
5412 break;
5413 }
5414 case 'L': {
5415 if ($this->rtl) {
5416 $dx = $w - $width - $this->cell_padding['L'];
5417 } else {
5418 $dx = $this->cell_padding['L'];
5419 }
5420 break;
5421 }
5422 case 'J':
5423 default: {
5424 if ($this->rtl) {
5425 $dx = $this->cell_padding['R'];
5426 } else {
5427 $dx = $this->cell_padding['L'];
5428 }
5429 break;
5430 }
5431 }
5432 if ($this->rtl) {
5433 $xdx = $x - $dx - $width;
5434 } else {
5435 $xdx = $x + $dx;
5436 }
5437 $xdk = $xdx * $k;
5438 // print text
5439 $s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
5440 if (isset($uniblock)) {
5441 // print overlapping characters as separate string
5442 $xshift = 0; // horizontal shift
5443 $ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
5444 $spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
5445 foreach ($uniblock as $uk => $uniarr) {
5446 if (($uk % 2) == 0) {
5447 // x space to skip
5448 if ($spacewidth != 0) {
5449 // justification shift
5450 $xshift += (count(array_keys($uniarr, 32)) * $spw);
5451 }
5452 $xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5453 } else {
5454 // character to print
5455 $topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
5456 $topchr = TCPDF_STATIC::_escape($topchr);
5457 $s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
5458 }
5459 }
5460 }
5461 if ($this->underline) {
5462 $s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5463 }
5464 if ($this->linethrough) {
5465 $s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5466 }
5467 if ($this->overline) {
5468 $s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5469 }
5470 if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5471 $s .= ' Q';
5472 }
5473 if ($link) {
5474 $this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5475 }
5476 }
5477 // output cell
5478 if ($s) {
5479 // output cell
5480 $rs .= $s;
5481 if ($this->font_spacing != 0) {
5482 // reset font spacing mode
5483 $rs .= ' BT 0 Tc ET';
5484 }
5485 if ($this->font_stretching != 100) {
5486 // reset font stretching mode
5487 $rs .= ' BT 100 Tz ET';
5488 }
5489 }
5490 // reset word spacing
5491 if (!$this->isUnicodeFont() AND ($align == 'J')) {
5492 $rs .= ' BT 0 Tw ET';
5493 }
5494 // reset stretching and spacing
5495 $this->font_stretching = $prev_font_stretching;
5496 $this->font_spacing = $prev_font_spacing;
5497 $this->lasth = $h;
5498 if ($ln > 0) {
5499 //Go to the beginning of the next line
5500 $this->y = $y + $h + $this->cell_margin['B'];
5501 if ($ln == 1) {
5502 if ($this->rtl) {
5503 $this->x = $this->w - $this->rMargin;
5504 } else {
5505 $this->x = $this->lMargin;
5506 }
5507 }
5508 } else {
5509 // go left or right by case
5510 if ($this->rtl) {
5511 $this->x = $x - $w - $this->cell_margin['L'];
5512 } else {
5513 $this->x = $x + $w + $this->cell_margin['R'];
5514 }
5515 }
5516 $gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5517 $rs = $gstyles.$rs;
5518 $this->cell_padding = $prev_cell_padding;
5519 $this->cell_margin = $prev_cell_margin;
5520 return $rs;
5521 }
5522
5523 /**
5524 * Replace a char if is defined on the current font.
5525 * @param $oldchar (int) Integer code (unicode) of the character to replace.
5526 * @param $newchar (int) Integer code (unicode) of the new character.
5527 * @return int the replaced char or the old char in case the new char i not defined
5528 * @protected
5529 * @since 5.9.167 (2012-06-22)
5530 */
5531 protected function replaceChar($oldchar, $newchar) {
5532 if ($this->isCharDefined($newchar)) {
5533 // add the new char on the subset list
5534 $this->CurrentFont['subsetchars'][$newchar] = true;
5535 // return the new character
5536 return $newchar;
5537 }
5538 // return the old char
5539 return $oldchar;
5540 }
5541
5542 /**
5543 * Returns the code to draw the cell border
5544 * @param $x (float) X coordinate.
5545 * @param $y (float) Y coordinate.
5546 * @param $w (float) Cell width.
5547 * @param $h (float) Cell height.
5548 * @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)))
5549 * @return string containing cell border code
5550 * @protected
5551 * @see SetLineStyle()
5552 * @since 5.7.000 (2010-08-02)
5553 */
5554 protected function getCellBorder($x, $y, $w, $h, $brd) {
5555 $s = ''; // string to be returned
5556 if (empty($brd)) {
5557 return $s;
5558 }
5559 if ($brd == 1) {
5560 $brd = array('LRTB' => true);
5561 }
5562 // calculate coordinates for border
5563 $k = $this->k;
5564 if ($this->rtl) {
5565 $xeL = ($x - $w) * $k;
5566 $xeR = $x * $k;
5567 } else {
5568 $xeL = $x * $k;
5569 $xeR = ($x + $w) * $k;
5570 }
5571 $yeL = (($this->h - ($y + $h)) * $k);
5572 $yeT = (($this->h - $y) * $k);
5573 $xeT = $xeL;
5574 $xeB = $xeR;
5575 $yeR = $yeT;
5576 $yeB = $yeL;
5577 if (is_string($brd)) {
5578 // convert string to array
5579 $slen = strlen($brd);
5580 $newbrd = array();
5581 for ($i = 0; $i < $slen; ++$i) {
5582 $newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
5583 }
5584 $brd = $newbrd;
5585 }
5586 if (isset($brd['mode'])) {
5587 $mode = $brd['mode'];
5588 unset($brd['mode']);
5589 } else {
5590 $mode = 'normal';
5591 }
5592 foreach ($brd as $border => $style) {
5593 if (is_array($style) AND !empty($style)) {
5594 // apply border style
5595 $prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
5596 $s .= $this->SetLineStyle($style, true)."\n";
5597 }
5598 switch ($mode) {
5599 case 'ext': {
5600 $off = (($this->LineWidth / 2) * $k);
5601 $xL = $xeL - $off;
5602 $xR = $xeR + $off;
5603 $yT = $yeT + $off;
5604 $yL = $yeL - $off;
5605 $xT = $xL;
5606 $xB = $xR;
5607 $yR = $yT;
5608 $yB = $yL;
5609 $w += $this->LineWidth;
5610 $h += $this->LineWidth;
5611 break;
5612 }
5613 case 'int': {
5614 $off = ($this->LineWidth / 2) * $k;
5615 $xL = $xeL + $off;
5616 $xR = $xeR - $off;
5617 $yT = $yeT - $off;
5618 $yL = $yeL + $off;
5619 $xT = $xL;
5620 $xB = $xR;
5621 $yR = $yT;
5622 $yB = $yL;
5623 $w -= $this->LineWidth;
5624 $h -= $this->LineWidth;
5625 break;
5626 }
5627 case 'normal':
5628 default: {
5629 $xL = $xeL;
5630 $xT = $xeT;
5631 $xB = $xeB;
5632 $xR = $xeR;
5633 $yL = $yeL;
5634 $yT = $yeT;
5635 $yB = $yeB;
5636 $yR = $yeR;
5637 break;
5638 }
5639 }
5640 // draw borders by case
5641 if (strlen($border) == 4) {
5642 $s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5643 } elseif (strlen($border) == 3) {
5644 if (strpos($border,'B') === false) { // LTR
5645 $s .= sprintf('%F %F m ', $xL, $yL);
5646 $s .= sprintf('%F %F l ', $xT, $yT);
5647 $s .= sprintf('%F %F l ', $xR, $yR);
5648 $s .= sprintf('%F %F l ', $xB, $yB);
5649 $s .= 'S ';
5650 } elseif (strpos($border,'L') === false) { // TRB
5651 $s .= sprintf('%F %F m ', $xT, $yT);
5652 $s .= sprintf('%F %F l ', $xR, $yR);
5653 $s .= sprintf('%F %F l ', $xB, $yB);
5654 $s .= sprintf('%F %F l ', $xL, $yL);
5655 $s .= 'S ';
5656 } elseif (strpos($border,'T') === false) { // RBL
5657 $s .= sprintf('%F %F m ', $xR, $yR);
5658 $s .= sprintf('%F %F l ', $xB, $yB);
5659 $s .= sprintf('%F %F l ', $xL, $yL);
5660 $s .= sprintf('%F %F l ', $xT, $yT);
5661 $s .= 'S ';
5662 } elseif (strpos($border,'R') === false) { // BLT
5663 $s .= sprintf('%F %F m ', $xB, $yB);
5664 $s .= sprintf('%F %F l ', $xL, $yL);
5665 $s .= sprintf('%F %F l ', $xT, $yT);
5666 $s .= sprintf('%F %F l ', $xR, $yR);
5667 $s .= 'S ';
5668 }
5669 } elseif (strlen($border) == 2) {
5670 if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5671 $s .= sprintf('%F %F m ', $xL, $yL);
5672 $s .= sprintf('%F %F l ', $xT, $yT);
5673 $s .= sprintf('%F %F l ', $xR, $yR);
5674 $s .= 'S ';
5675 } elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5676 $s .= sprintf('%F %F m ', $xT, $yT);
5677 $s .= sprintf('%F %F l ', $xR, $yR);
5678 $s .= sprintf('%F %F l ', $xB, $yB);
5679 $s .= 'S ';
5680 } elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5681 $s .= sprintf('%F %F m ', $xR, $yR);
5682 $s .= sprintf('%F %F l ', $xB, $yB);
5683 $s .= sprintf('%F %F l ', $xL, $yL);
5684 $s .= 'S ';
5685 } elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5686 $s .= sprintf('%F %F m ', $xB, $yB);
5687 $s .= sprintf('%F %F l ', $xL, $yL);
5688 $s .= sprintf('%F %F l ', $xT, $yT);
5689 $s .= 'S ';
5690 } elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5691 $s .= sprintf('%F %F m ', $xL, $yL);
5692 $s .= sprintf('%F %F l ', $xT, $yT);
5693 $s .= 'S ';
5694 $s .= sprintf('%F %F m ', $xR, $yR);
5695 $s .= sprintf('%F %F l ', $xB, $yB);
5696 $s .= 'S ';
5697 } elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5698 $s .= sprintf('%F %F m ', $xT, $yT);
5699 $s .= sprintf('%F %F l ', $xR, $yR);
5700 $s .= 'S ';
5701 $s .= sprintf('%F %F m ', $xB, $yB);
5702 $s .= sprintf('%F %F l ', $xL, $yL);
5703 $s .= 'S ';
5704 }
5705 } else { // strlen($border) == 1
5706 if (strpos($border,'L') !== false) { // L
5707 $s .= sprintf('%F %F m ', $xL, $yL);
5708 $s .= sprintf('%F %F l ', $xT, $yT);
5709 $s .= 'S ';
5710 } elseif (strpos($border,'T') !== false) { // T
5711 $s .= sprintf('%F %F m ', $xT, $yT);
5712 $s .= sprintf('%F %F l ', $xR, $yR);
5713 $s .= 'S ';
5714 } elseif (strpos($border,'R') !== false) { // R
5715 $s .= sprintf('%F %F m ', $xR, $yR);
5716 $s .= sprintf('%F %F l ', $xB, $yB);
5717 $s .= 'S ';
5718 } elseif (strpos($border,'B') !== false) { // B
5719 $s .= sprintf('%F %F m ', $xB, $yB);
5720 $s .= sprintf('%F %F l ', $xL, $yL);
5721 $s .= 'S ';
5722 }
5723 }
5724 if (is_array($style) AND !empty($style)) {
5725 // reset border style to previous value
5726 $s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
5727 }
5728 }
5729 return $s;
5730 }
5731
5732 /**
5733 * This method allows printing text with line breaks.
5734 * 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 />
5735 * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
5736 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
5737 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
5738 * @param $txt (string) String to print
5739 * @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)))
5740 * @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>
5741 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5742 * @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>
5743 * @param $x (float) x position in user units
5744 * @param $y (float) y position in user units
5745 * @param $reseth (boolean) if true reset the last cell height (default true).
5746 * @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.
5747 * @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.
5748 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
5749 * @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.
5750 * @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.
5751 * @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 equal to $h.
5752 * @return int Return the number of cells or 1 for html mode.
5753 * @public
5754 * @since 1.3
5755 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
5756 */
5757 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) {
5758 $prev_cell_margin = $this->cell_margin;
5759 $prev_cell_padding = $this->cell_padding;
5760 // adjust internal padding
5761 $this->adjustCellPadding($border);
5762 $mc_padding = $this->cell_padding;
5763 $mc_margin = $this->cell_margin;
5764 $this->cell_padding['T'] = 0;
5765 $this->cell_padding['B'] = 0;
5766 $this->setCellMargins(0, 0, 0, 0);
5767 if (TCPDF_STATIC::empty_string($this->lasth) OR $reseth) {
5768 // reset row height
5769 $this->resetLastH();
5770 }
5771 if (!TCPDF_STATIC::empty_string($y)) {
5772 $this->SetY($y);
5773 } else {
5774 $y = $this->GetY();
5775 }
5776 $resth = 0;
5777 if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
5778 // spit cell in more pages/columns
5779 $newh = ($this->PageBreakTrigger - $y);
5780 $resth = ($h - $newh); // cell to be printed on the next page/column
5781 $h = $newh;
5782 }
5783 // get current page number
5784 $startpage = $this->page;
5785 // get current column
5786 $startcolumn = $this->current_column;
5787 if (!TCPDF_STATIC::empty_string($x)) {
5788 $this->SetX($x);
5789 } else {
5790 $x = $this->GetX();
5791 }
5792 // check page for no-write regions and adapt page margins if necessary
5793 list($x, $y) = $this->checkPageRegions(0, $x, $y);
5794 // apply margins
5795 $oy = $y + $mc_margin['T'];
5796 if ($this->rtl) {
5797 $ox = ($this->w - $x - $mc_margin['R']);
5798 } else {
5799 $ox = ($x + $mc_margin['L']);
5800 }
5801 $this->x = $ox;
5802 $this->y = $oy;
5803 // set width
5804 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5805 if ($this->rtl) {
5806 $w = ($this->x - $this->lMargin - $mc_margin['L']);
5807 } else {
5808 $w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
5809 }
5810 }
5811 // store original margin values
5812 $lMargin = $this->lMargin;
5813 $rMargin = $this->rMargin;
5814 if ($this->rtl) {
5815 $this->rMargin = ($this->w - $this->x);
5816 $this->lMargin = ($this->x - $w);
5817 } else {
5818 $this->lMargin = ($this->x);
5819 $this->rMargin = ($this->w - $this->x - $w);
5820 }
5821 $this->clMargin = $this->lMargin;
5822 $this->crMargin = $this->rMargin;
5823 if ($autopadding) {
5824 // add top padding
5825 $this->y += $mc_padding['T'];
5826 }
5827 if ($ishtml) { // ******* Write HTML text
5828 $this->writeHTML($txt, true, false, $reseth, true, $align);
5829 $nl = 1;
5830 } else { // ******* Write simple text
5831 $prev_FontSizePt = $this->FontSizePt;
5832 if ($fitcell) {
5833 // ajust height values
5834 $tobottom = ($this->h - $this->y - $this->bMargin - $this->cell_padding['T'] - $this->cell_padding['B']);
5835 $h = $maxh = max(min($h, $tobottom), min($maxh, $tobottom));
5836 }
5837 // vertical alignment
5838 if ($maxh > 0) {
5839 // get text height
5840 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5841 if ($fitcell AND ($text_height > $maxh) AND ($this->FontSizePt > 1)) {
5842 // try to reduce font size to fit text on cell (use a quick search algorithm)
5843 $fmin = 1;
5844 $fmax = $this->FontSizePt;
5845 $diff_epsilon = (1 / $this->k); // one point (min resolution)
5846 $maxit = (2 * min(100, max(10, intval($fmax)))); // max number of iterations
5847 while ($maxit >= 0) {
5848 $fmid = (($fmax + $fmin) / 2);
5849 $this->SetFontSize($fmid, false);
5850 $this->resetLastH();
5851 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5852 $diff = ($maxh - $text_height);
5853 if ($diff >= 0) {
5854 if ($diff <= $diff_epsilon) {
5855 break;
5856 }
5857 $fmin = $fmid;
5858 } else {
5859 $fmax = $fmid;
5860 }
5861 --$maxit;
5862 }
5863 if ($maxit < 0) {
5864 // premature exit, we get the minimum font value to fit the cell
5865 $this->SetFontSize($fmin);
5866 $this->resetLastH();
5867 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5868 } else {
5869 $this->SetFontSize($fmid);
5870 $this->resetLastH();
5871 }
5872 }
5873 if ($text_height < $maxh) {
5874 if ($valign == 'M') {
5875 // text vertically centered
5876 $this->y += (($maxh - $text_height) / 2);
5877 } elseif ($valign == 'B') {
5878 // text vertically aligned on bottom
5879 $this->y += ($maxh - $text_height);
5880 }
5881 }
5882 }
5883 $nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
5884 if ($fitcell) {
5885 // restore font size
5886 $this->SetFontSize($prev_FontSizePt);
5887 }
5888 }
5889 if ($autopadding) {
5890 // add bottom padding
5891 $this->y += $mc_padding['B'];
5892 }
5893 // Get end-of-text Y position
5894 $currentY = $this->y;
5895 // get latest page number
5896 $endpage = $this->page;
5897 if ($resth > 0) {
5898 $skip = ($endpage - $startpage);
5899 $tmpresth = $resth;
5900 while ($tmpresth > 0) {
5901 if ($skip <= 0) {
5902 // add a page (or trig AcceptPageBreak() for multicolumn mode)
5903 $this->checkPageBreak($this->PageBreakTrigger + 1);
5904 }
5905 if ($this->num_columns > 1) {
5906 $tmpresth -= ($this->h - $this->y - $this->bMargin);
5907 } else {
5908 $tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
5909 }
5910 --$skip;
5911 }
5912 $currentY = $this->y;
5913 $endpage = $this->page;
5914 }
5915 // get latest column
5916 $endcolumn = $this->current_column;
5917 if ($this->num_columns == 0) {
5918 $this->num_columns = 1;
5919 }
5920 // disable page regions check
5921 $check_page_regions = $this->check_page_regions;
5922 $this->check_page_regions = false;
5923 // get border modes
5924 $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
5925 $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
5926 $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
5927 // design borders around HTML cells.
5928 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
5929 $ccode = '';
5930 $this->setPage($page);
5931 if ($this->num_columns < 2) {
5932 // single-column mode
5933 $this->SetX($x);
5934 $this->y = $this->tMargin;
5935 }
5936 // account for margin changes
5937 if ($page > $startpage) {
5938 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
5939 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
5940 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
5941 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
5942 }
5943 }
5944 if ($startpage == $endpage) {
5945 // single page
5946 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
5947 if ($column != $this->current_column) {
5948 $this->selectColumn($column);
5949 }
5950 if ($this->rtl) {
5951 $this->x -= $mc_margin['R'];
5952 } else {
5953 $this->x += $mc_margin['L'];
5954 }
5955 if ($startcolumn == $endcolumn) { // single column
5956 $cborder = $border;
5957 $h = max($h, ($currentY - $oy));
5958 $this->y = $oy;
5959 } elseif ($column == $startcolumn) { // first column
5960 $cborder = $border_start;
5961 $this->y = $oy;
5962 $h = $this->h - $this->y - $this->bMargin;
5963 } elseif ($column == $endcolumn) { // end column
5964 $cborder = $border_end;
5965 $h = $currentY - $this->y;
5966 if ($resth > $h) {
5967 $h = $resth;
5968 }
5969 } else { // middle column
5970 $cborder = $border_middle;
5971 $h = $this->h - $this->y - $this->bMargin;
5972 $resth -= $h;
5973 }
5974 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
5975 } // end for each column
5976 } elseif ($page == $startpage) { // first page
5977 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
5978 if ($column != $this->current_column) {
5979 $this->selectColumn($column);
5980 }
5981 if ($this->rtl) {
5982 $this->x -= $mc_margin['R'];
5983 } else {
5984 $this->x += $mc_margin['L'];
5985 }
5986 if ($column == $startcolumn) { // first column
5987 $cborder = $border_start;
5988 $this->y = $oy;
5989 $h = $this->h - $this->y - $this->bMargin;
5990 } else { // middle column
5991 $cborder = $border_middle;
5992 $h = $this->h - $this->y - $this->bMargin;
5993 $resth -= $h;
5994 }
5995 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
5996 } // end for each column
5997 } elseif ($page == $endpage) { // last page
5998 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
5999 if ($column != $this->current_column) {
6000 $this->selectColumn($column);
6001 }
6002 if ($this->rtl) {
6003 $this->x -= $mc_margin['R'];
6004 } else {
6005 $this->x += $mc_margin['L'];
6006 }
6007 if ($column == $endcolumn) {
6008 // end column
6009 $cborder = $border_end;
6010 $h = $currentY - $this->y;
6011 if ($resth > $h) {
6012 $h = $resth;
6013 }
6014 } else {
6015 // middle column
6016 $cborder = $border_middle;
6017 $h = $this->h - $this->y - $this->bMargin;
6018 $resth -= $h;
6019 }
6020 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6021 } // end for each column
6022 } else { // middle page
6023 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6024 $this->selectColumn($column);
6025 if ($this->rtl) {
6026 $this->x -= $mc_margin['R'];
6027 } else {
6028 $this->x += $mc_margin['L'];
6029 }
6030 $cborder = $border_middle;
6031 $h = $this->h - $this->y - $this->bMargin;
6032 $resth -= $h;
6033 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6034 } // end for each column
6035 }
6036 if ($cborder OR $fill) {
6037 $offsetlen = strlen($ccode);
6038 // draw border and fill
6039 if ($this->inxobj) {
6040 // we are inside an XObject template
6041 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6042 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6043 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6044 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
6045 } else {
6046 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
6047 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
6048 }
6049 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6050 $pstart = substr($pagebuff, 0, $pagemark);
6051 $pend = substr($pagebuff, $pagemark);
6052 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6053 } else {
6054 if (end($this->transfmrk[$this->page]) !== false) {
6055 $pagemarkkey = key($this->transfmrk[$this->page]);
6056 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
6057 $this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
6058 } elseif ($this->InFooter) {
6059 $pagemark = $this->footerpos[$this->page];
6060 $this->footerpos[$this->page] += $offsetlen;
6061 } else {
6062 $pagemark = $this->intmrk[$this->page];
6063 $this->intmrk[$this->page] += $offsetlen;
6064 }
6065 $pagebuff = $this->getPageBuffer($this->page);
6066 $pstart = substr($pagebuff, 0, $pagemark);
6067 $pend = substr($pagebuff, $pagemark);
6068 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6069 }
6070 }
6071 } // end for each page
6072 // restore page regions check
6073 $this->check_page_regions = $check_page_regions;
6074 // Get end-of-cell Y position
6075 $currentY = $this->GetY();
6076 // restore previous values
6077 if ($this->num_columns > 1) {
6078 $this->selectColumn();
6079 } else {
6080 // restore original margins
6081 $this->lMargin = $lMargin;
6082 $this->rMargin = $rMargin;
6083 if ($this->page > $startpage) {
6084 // check for margin variations between pages (i.e. booklet mode)
6085 $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
6086 $dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
6087 if (($dl != 0) OR ($dr != 0)) {
6088 $this->lMargin += $dl;
6089 $this->rMargin += $dr;
6090 }
6091 }
6092 }
6093 if ($ln > 0) {
6094 //Go to the beginning of the next line
6095 $this->SetY($currentY + $mc_margin['B']);
6096 if ($ln == 2) {
6097 $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6098 }
6099 } else {
6100 // go left or right by case
6101 $this->setPage($startpage);
6102 $this->y = $y;
6103 $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6104 }
6105 $this->setContentMark();
6106 $this->cell_padding = $prev_cell_padding;
6107 $this->cell_margin = $prev_cell_margin;
6108 $this->clMargin = $this->lMargin;
6109 $this->crMargin = $this->rMargin;
6110 return $nl;
6111 }
6112
6113 /**
6114 * This method return the estimated number of lines for print a simple text string using Multicell() method.
6115 * @param $txt (string) String for calculating his height
6116 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6117 * @param $reseth (boolean) if true reset the last cell height (default false).
6118 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6119 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6120 * @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)))
6121 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6122 * @author Alexander Escalona Fern\E1ndez, Nicola Asuni
6123 * @public
6124 * @since 4.5.011
6125 */
6126 public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6127 if ($txt === NULL) {
6128 return 0;
6129 }
6130 if ($txt === '') {
6131 // empty string
6132 return 1;
6133 }
6134 // adjust internal padding
6135 $prev_cell_padding = $this->cell_padding;
6136 $prev_lasth = $this->lasth;
6137 if (is_array($cellpadding)) {
6138 $this->cell_padding = $cellpadding;
6139 }
6140 $this->adjustCellPadding($border);
6141 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
6142 if ($this->rtl) {
6143 $w = $this->x - $this->lMargin;
6144 } else {
6145 $w = $this->w - $this->rMargin - $this->x;
6146 }
6147 }
6148 $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6149 if ($reseth) {
6150 // reset row height
6151 $this->resetLastH();
6152 }
6153 $lines = 1;
6154 $sum = 0;
6155 $chars = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont), $txt, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6156 $charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6157 $length = count($chars);
6158 $lastSeparator = -1;
6159 for ($i = 0; $i < $length; ++$i) {
6160 $c = $chars[$i];
6161 $charWidth = $charsWidth[$i];
6162 if (($c != 160)
6163 AND (($c == 173)
6164 OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6165 OR (($c == 45)
6166 AND ($i > 0) AND ($i < ($length - 1))
6167 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i - 1)], $this->isunicode))
6168 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6169 )
6170 )
6171 ) {
6172 $lastSeparator = $i;
6173 }
6174 if ((($sum + $charWidth) > $wmax) OR ($c == 10)) {
6175 ++$lines;
6176 if ($c == 10) {
6177 $lastSeparator = -1;
6178 $sum = 0;
6179 } elseif ($lastSeparator != -1) {
6180 $i = $lastSeparator;
6181 $lastSeparator = -1;
6182 $sum = 0;
6183 } else {
6184 $sum = $charWidth;
6185 }
6186 } else {
6187 $sum += $charWidth;
6188 }
6189 }
6190 if ($chars[($length - 1)] == 10) {
6191 --$lines;
6192 }
6193 $this->cell_padding = $prev_cell_padding;
6194 $this->lasth = $prev_lasth;
6195 return $lines;
6196 }
6197
6198 /**
6199 * This method return the estimated height needed for printing a simple text string using the Multicell() method.
6200 * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6201 * @pre
6202 * // store current object
6203 * $pdf->startTransaction();
6204 * // store starting values
6205 * $start_y = $pdf->GetY();
6206 * $start_page = $pdf->getPage();
6207 * // call your printing functions with your parameters
6208 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6209 * $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);
6210 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6211 * // get the new Y
6212 * $end_y = $pdf->GetY();
6213 * $end_page = $pdf->getPage();
6214 * // calculate height
6215 * $height = 0;
6216 * if ($end_page == $start_page) {
6217 * $height = $end_y - $start_y;
6218 * } else {
6219 * for ($page=$start_page; $page <= $end_page; ++$page) {
6220 * $this->setPage($page);
6221 * if ($page == $start_page) {
6222 * // first page
6223 * $height = $this->h - $start_y - $this->bMargin;
6224 * } elseif ($page == $end_page) {
6225 * // last page
6226 * $height = $end_y - $this->tMargin;
6227 * } else {
6228 * $height = $this->h - $this->tMargin - $this->bMargin;
6229 * }
6230 * }
6231 * }
6232 * // restore previous object
6233 * $pdf = $pdf->rollbackTransaction();
6234 *
6235 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6236 * @param $txt (string) String for calculating his height
6237 * @param $reseth (boolean) if true reset the last cell height (default false).
6238 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6239 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6240 * @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)))
6241 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6242 * @author Nicola Asuni, Alexander Escalona Fern\E1ndez
6243 * @public
6244 */
6245 public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6246 // adjust internal padding
6247 $prev_cell_padding = $this->cell_padding;
6248 $prev_lasth = $this->lasth;
6249 if (is_array($cellpadding)) {
6250 $this->cell_padding = $cellpadding;
6251 }
6252 $this->adjustCellPadding($border);
6253 $lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6254 $height = $this->getCellHeight(($lines * $this->FontSize), $autopadding);
6255 $this->cell_padding = $prev_cell_padding;
6256 $this->lasth = $prev_lasth;
6257 return $height;
6258 }
6259
6260 /**
6261 * This method prints text from the current position.<br />
6262 * @param $h (float) Line height
6263 * @param $txt (string) String to print
6264 * @param $link (mixed) URL or identifier returned by AddLink()
6265 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
6266 * @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>
6267 * @param $ln (boolean) if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
6268 * @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.
6269 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
6270 * @param $firstblock (boolean) if true the string is the starting of a line.
6271 * @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.
6272 * @param $wadj (float) first line width will be reduced by this amount (used in HTML mode).
6273 * @param $margin (array) margin array of the parent container
6274 * @return mixed Return the number of cells or the remaining string if $firstline = true.
6275 * @public
6276 * @since 1.5
6277 */
6278 public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
6279 // check page for no-write regions and adapt page margins if necessary
6280 list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
6281 if (strlen($txt) == 0) {
6282 // fix empty text
6283 $txt = ' ';
6284 }
6285 if ($margin === '') {
6286 // set default margins
6287 $margin = $this->cell_margin;
6288 }
6289 // remove carriage returns
6290 $s = str_replace("\r", '', $txt);
6291 // check if string contains arabic text
6292 if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $s)) {
6293 $arabic = true;
6294 } else {
6295 $arabic = false;
6296 }
6297 // check if string contains RTL text
6298 if ($arabic OR ($this->tmprtl == 'R') OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $s)) {
6299 $rtlmode = true;
6300 } else {
6301 $rtlmode = false;
6302 }
6303 // get a char width
6304 $chrwidth = $this->GetCharWidth(46); // dot character
6305 // get array of unicode values
6306 $chars = TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont);
6307 // calculate maximum width for a single character on string
6308 $chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
6309 array_walk($chrw, array($this, 'getRawCharWidth'));
6310 $maxchwidth = max($chrw);
6311 // get array of chars
6312 $uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
6313 // get the number of characters
6314 $nb = count($chars);
6315 // replacement for SHY character (minus symbol)
6316 $shy_replacement = 45;
6317 $shy_replacement_char = TCPDF_FONTS::unichr($shy_replacement, $this->isunicode);
6318 // widht for SHY replacement
6319 $shy_replacement_width = $this->GetCharWidth($shy_replacement);
6320 // page width
6321 $pw = $w = $this->w - $this->lMargin - $this->rMargin;
6322 // calculate remaining line width ($w)
6323 if ($this->rtl) {
6324 $w = $this->x - $this->lMargin;
6325 } else {
6326 $w = $this->w - $this->rMargin - $this->x;
6327 }
6328 // max column width
6329 $wmax = ($w - $wadj);
6330 if (!$firstline) {
6331 $wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
6332 }
6333 if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
6334 // the maximum width character do not fit on column
6335 return '';
6336 }
6337 // minimum row height
6338 $row_height = max($h, $this->getCellHeight($this->FontSize));
6339 // max Y
6340 $maxy = $this->y + $maxh - max($row_height, $h);
6341 $start_page = $this->page;
6342 $i = 0; // character position
6343 $j = 0; // current starting position
6344 $sep = -1; // position of the last blank space
6345 $prevsep = $sep; // previous separator
6346 $shy = false; // true if the last blank is a soft hypen (SHY)
6347 $prevshy = $shy; // previous shy mode
6348 $l = 0; // current string length
6349 $nl = 0; //number of lines
6350 $linebreak = false;
6351 $pc = 0; // previous character
6352 // for each character
6353 while ($i < $nb) {
6354 if (($maxh > 0) AND ($this->y > $maxy) ) {
6355 break;
6356 }
6357 //Get the current character
6358 $c = $chars[$i];
6359 if ($c == 10) { // 10 = "\n" = new line
6360 //Explicit line break
6361 if ($align == 'J') {
6362 if ($this->rtl) {
6363 $talign = 'R';
6364 } else {
6365 $talign = 'L';
6366 }
6367 } else {
6368 $talign = $align;
6369 }
6370 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6371 if ($firstline) {
6372 $startx = $this->x;
6373 $tmparr = array_slice($chars, $j, ($i - $j));
6374 if ($rtlmode) {
6375 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6376 }
6377 $linew = $this->GetArrStringWidth($tmparr);
6378 unset($tmparr);
6379 if ($this->rtl) {
6380 $this->endlinex = $startx - $linew;
6381 } else {
6382 $this->endlinex = $startx + $linew;
6383 }
6384 $w = $linew;
6385 $tmpcellpadding = $this->cell_padding;
6386 if ($maxh == 0) {
6387 $this->SetCellPadding(0);
6388 }
6389 }
6390 if ($firstblock AND $this->isRTLTextDir()) {
6391 $tmpstr = $this->stringRightTrim($tmpstr);
6392 }
6393 // Skip newlines at the beginning of a page or column
6394 if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
6395 $this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6396 }
6397 unset($tmpstr);
6398 if ($firstline) {
6399 $this->cell_padding = $tmpcellpadding;
6400 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6401 }
6402 ++$nl;
6403 $j = $i + 1;
6404 $l = 0;
6405 $sep = -1;
6406 $prevsep = $sep;
6407 $shy = false;
6408 // account for margin changes
6409 if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6410 $this->AcceptPageBreak();
6411 if ($this->rtl) {
6412 $this->x -= $margin['R'];
6413 } else {
6414 $this->x += $margin['L'];
6415 }
6416 $this->lMargin += $margin['L'];
6417 $this->rMargin += $margin['R'];
6418 }
6419 $w = $this->getRemainingWidth();
6420 $wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
6421 } else {
6422 // 160 is the non-breaking space.
6423 // 173 is SHY (Soft Hypen).
6424 // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6425 // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6426 // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6427 if (($c != 160)
6428 AND (($c == 173)
6429 OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6430 OR (($c == 45)
6431 AND ($i < ($nb - 1))
6432 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($pc, $this->isunicode))
6433 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6434 )
6435 )
6436 ) {
6437 // update last blank space position
6438 $prevsep = $sep;
6439 $sep = $i;
6440 // check if is a SHY
6441 if (($c == 173) OR ($c == 45)) {
6442 $prevshy = $shy;
6443 $shy = true;
6444 if ($pc == 45) {
6445 $tmp_shy_replacement_width = 0;
6446 $tmp_shy_replacement_char = '';
6447 } else {
6448 $tmp_shy_replacement_width = $shy_replacement_width;
6449 $tmp_shy_replacement_char = $shy_replacement_char;
6450 }
6451 } else {
6452 $shy = false;
6453 }
6454 }
6455 // update string length
6456 if ($this->isUnicodeFont() AND ($arabic)) {
6457 // with bidirectional algorithm some chars may be changed affecting the line length
6458 // *** very slow ***
6459 $l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont));
6460 } else {
6461 $l += $this->GetCharWidth($c);
6462 }
6463 if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) >= $wmax))) {
6464 if (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) {
6465 $sep = $prevsep;
6466 $shy = $prevshy;
6467 }
6468 // we have reached the end of column
6469 if ($sep == -1) {
6470 // check if the line was already started
6471 if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
6472 OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
6473 // print a void cell and go to next line
6474 $this->Cell($w, $h, '', 0, 1);
6475 $linebreak = true;
6476 if ($firstline) {
6477 return (TCPDF_FONTS::UniArrSubString($uchars, $j));
6478 }
6479 } else {
6480 // truncate the word because do not fit on column
6481 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6482 if ($firstline) {
6483 $startx = $this->x;
6484 $tmparr = array_slice($chars, $j, ($i - $j));
6485 if ($rtlmode) {
6486 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6487 }
6488 $linew = $this->GetArrStringWidth($tmparr);
6489 unset($tmparr);
6490 if ($this->rtl) {
6491 $this->endlinex = $startx - $linew;
6492 } else {
6493 $this->endlinex = $startx + $linew;
6494 }
6495 $w = $linew;
6496 $tmpcellpadding = $this->cell_padding;
6497 if ($maxh == 0) {
6498 $this->SetCellPadding(0);
6499 }
6500 }
6501 if ($firstblock AND $this->isRTLTextDir()) {
6502 $tmpstr = $this->stringRightTrim($tmpstr);
6503 }
6504 $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6505 unset($tmpstr);
6506 if ($firstline) {
6507 $this->cell_padding = $tmpcellpadding;
6508 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6509 }
6510 $j = $i;
6511 --$i;
6512 }
6513 } else {
6514 // word wrapping
6515 if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6516 $endspace = 1;
6517 } else {
6518 $endspace = 0;
6519 }
6520 // check the length of the next string
6521 $strrest = TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace));
6522 $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $this->stringTrim($strrest));
6523 if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
6524 // truncate the word because do not fit on a full page width
6525 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6526 if ($firstline) {
6527 $startx = $this->x;
6528 $tmparr = array_slice($chars, $j, ($i - $j));
6529 if ($rtlmode) {
6530 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6531 }
6532 $linew = $this->GetArrStringWidth($tmparr);
6533 unset($tmparr);
6534 if ($this->rtl) {
6535 $this->endlinex = ($startx - $linew);
6536 } else {
6537 $this->endlinex = ($startx + $linew);
6538 }
6539 $w = $linew;
6540 $tmpcellpadding = $this->cell_padding;
6541 if ($maxh == 0) {
6542 $this->SetCellPadding(0);
6543 }
6544 }
6545 if ($firstblock AND $this->isRTLTextDir()) {
6546 $tmpstr = $this->stringRightTrim($tmpstr);
6547 }
6548 $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6549 unset($tmpstr);
6550 if ($firstline) {
6551 $this->cell_padding = $tmpcellpadding;
6552 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6553 }
6554 $j = $i;
6555 --$i;
6556 } else {
6557 // word wrapping
6558 if ($shy) {
6559 // add hypen (minus symbol) at the end of the line
6560 $shy_width = $tmp_shy_replacement_width;
6561 if ($this->rtl) {
6562 $shy_char_left = $tmp_shy_replacement_char;
6563 $shy_char_right = '';
6564 } else {
6565 $shy_char_left = '';
6566 $shy_char_right = $tmp_shy_replacement_char;
6567 }
6568 } else {
6569 $shy_width = 0;
6570 $shy_char_left = '';
6571 $shy_char_right = '';
6572 }
6573 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
6574 if ($firstline) {
6575 $startx = $this->x;
6576 $tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6577 if ($rtlmode) {
6578 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6579 }
6580 $linew = $this->GetArrStringWidth($tmparr);
6581 unset($tmparr);
6582 if ($this->rtl) {
6583 $this->endlinex = $startx - $linew - $shy_width;
6584 } else {
6585 $this->endlinex = $startx + $linew + $shy_width;
6586 }
6587 $w = $linew;
6588 $tmpcellpadding = $this->cell_padding;
6589 if ($maxh == 0) {
6590 $this->SetCellPadding(0);
6591 }
6592 }
6593 // print the line
6594 if ($firstblock AND $this->isRTLTextDir()) {
6595 $tmpstr = $this->stringRightTrim($tmpstr);
6596 }
6597 $this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6598 unset($tmpstr);
6599 if ($firstline) {
6600 if ($chars[$sep] == 45) {
6601 $endspace += 1;
6602 }
6603 // return the remaining text
6604 $this->cell_padding = $tmpcellpadding;
6605 return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
6606 }
6607 $i = $sep;
6608 $sep = -1;
6609 $shy = false;
6610 $j = ($i + 1);
6611 }
6612 }
6613 // account for margin changes
6614 if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6615 $this->AcceptPageBreak();
6616 if ($this->rtl) {
6617 $this->x -= $margin['R'];
6618 } else {
6619 $this->x += $margin['L'];
6620 }
6621 $this->lMargin += $margin['L'];
6622 $this->rMargin += $margin['R'];
6623 }
6624 $w = $this->getRemainingWidth();
6625 $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6626 if ($linebreak) {
6627 $linebreak = false;
6628 } else {
6629 ++$nl;
6630 $l = 0;
6631 }
6632 }
6633 }
6634 // save last character
6635 $pc = $c;
6636 ++$i;
6637 } // end while i < nb
6638 // print last substring (if any)
6639 if ($l > 0) {
6640 switch ($align) {
6641 case 'J':
6642 case 'C': {
6643 $w = $w;
6644 break;
6645 }
6646 case 'L': {
6647 if ($this->rtl) {
6648 $w = $w;
6649 } else {
6650 $w = $l;
6651 }
6652 break;
6653 }
6654 case 'R': {
6655 if ($this->rtl) {
6656 $w = $l;
6657 } else {
6658 $w = $w;
6659 }
6660 break;
6661 }
6662 default: {
6663 $w = $l;
6664 break;
6665 }
6666 }
6667 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
6668 if ($firstline) {
6669 $startx = $this->x;
6670 $tmparr = array_slice($chars, $j, ($nb - $j));
6671 if ($rtlmode) {
6672 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6673 }
6674 $linew = $this->GetArrStringWidth($tmparr);
6675 unset($tmparr);
6676 if ($this->rtl) {
6677 $this->endlinex = $startx - $linew;
6678 } else {
6679 $this->endlinex = $startx + $linew;
6680 }
6681 $w = $linew;
6682 $tmpcellpadding = $this->cell_padding;
6683 if ($maxh == 0) {
6684 $this->SetCellPadding(0);
6685 }
6686 }
6687 if ($firstblock AND $this->isRTLTextDir()) {
6688 $tmpstr = $this->stringRightTrim($tmpstr);
6689 }
6690 $this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6691 unset($tmpstr);
6692 if ($firstline) {
6693 $this->cell_padding = $tmpcellpadding;
6694 return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
6695 }
6696 ++$nl;
6697 }
6698 if ($firstline) {
6699 return '';
6700 }
6701 return $nl;
6702 }
6703
6704 /**
6705 * Returns the remaining width between the current position and margins.
6706 * @return int Return the remaining width
6707 * @protected
6708 */
6709 protected function getRemainingWidth() {
6710 list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
6711 if ($this->rtl) {
6712 return ($this->x - $this->lMargin);
6713 } else {
6714 return ($this->w - $this->rMargin - $this->x);
6715 }
6716 }
6717
6718 /**
6719 * Set the block dimensions accounting for page breaks and page/column fitting
6720 * @param $w (float) width
6721 * @param $h (float) height
6722 * @param $x (float) X coordinate
6723 * @param $y (float) Y coodiante
6724 * @param $fitonpage (boolean) if true the block is resized to not exceed page dimensions.
6725 * @return array($w, $h, $x, $y)
6726 * @protected
6727 * @since 5.5.009 (2010-07-05)
6728 */
6729 protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
6730 if ($w <= 0) {
6731 // set maximum width
6732 $w = ($this->w - $this->lMargin - $this->rMargin);
6733 if ($w <= 0) {
6734 $w = 1;
6735 }
6736 }
6737 if ($h <= 0) {
6738 // set maximum height
6739 $h = ($this->PageBreakTrigger - $this->tMargin);
6740 if ($h <= 0) {
6741 $h = 1;
6742 }
6743 }
6744 // resize the block to be vertically contained on a single page or single column
6745 if ($fitonpage OR $this->AutoPageBreak) {
6746 $ratio_wh = ($w / $h);
6747 if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
6748 $h = $this->PageBreakTrigger - $this->tMargin;
6749 $w = ($h * $ratio_wh);
6750 }
6751 // resize the block to be horizontally contained on a single page or single column
6752 if ($fitonpage) {
6753 $maxw = ($this->w - $this->lMargin - $this->rMargin);
6754 if ($w > $maxw) {
6755 $w = $maxw;
6756 $h = ($w / $ratio_wh);
6757 }
6758 }
6759 }
6760 // Check whether we need a new page or new column first as this does not fit
6761 $prev_x = $this->x;
6762 $prev_y = $this->y;
6763 if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
6764 $y = $this->y;
6765 if ($this->rtl) {
6766 $x += ($prev_x - $this->x);
6767 } else {
6768 $x += ($this->x - $prev_x);
6769 }
6770 $this->newline = true;
6771 }
6772 // resize the block to be contained on the remaining available page or column space
6773 if ($fitonpage) {
6774 $ratio_wh = ($w / $h);
6775 if (($y + $h) > $this->PageBreakTrigger) {
6776 $h = $this->PageBreakTrigger - $y;
6777 $w = ($h * $ratio_wh);
6778 }
6779 if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
6780 $w = $this->w - $this->rMargin - $x;
6781 $h = ($w / $ratio_wh);
6782 } elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
6783 $w = $x - $this->lMargin;
6784 $h = ($w / $ratio_wh);
6785 }
6786 }
6787 return array($w, $h, $x, $y);
6788 }
6789
6790 /**
6791 * Puts an image in the page.
6792 * The upper-left corner must be given.
6793 * The dimensions can be specified in different ways:<ul>
6794 * <li>explicit width and height (expressed in user unit)</li>
6795 * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
6796 * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
6797 * 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;
6798 * The format can be specified explicitly or inferred from the file extension.<br />
6799 * It is possible to put a link on the image.<br />
6800 * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
6801 * @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').
6802 * @param $x (float) Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
6803 * @param $y (float) Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
6804 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
6805 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
6806 * @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.
6807 * @param $link (mixed) URL or identifier returned by AddLink().
6808 * @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>
6809 * @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).
6810 * @param $dpi (int) dot-per-inch resolution used on resize
6811 * @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>
6812 * @param $ismask (boolean) true if this image is a mask, false otherwise
6813 * @param $imgmask (mixed) image object returned by this function or false
6814 * @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)))
6815 * @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).
6816 * @param $hidden (boolean) If true do not display the image.
6817 * @param $fitonpage (boolean) If true the image is resized to not exceed page dimensions.
6818 * @param $alt (boolean) If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
6819 * @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.
6820 * @return image information
6821 * @public
6822 * @since 1.1
6823 */
6824 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()) {
6825 if ($this->state != 2) {
6826 return;
6827 }
6828 if (strcmp($x, '') === 0) {
6829 $x = $this->x;
6830 }
6831 if (strcmp($y, '') === 0) {
6832 $y = $this->y;
6833 }
6834 // check page for no-write regions and adapt page margins if necessary
6835 list($x, $y) = $this->checkPageRegions($h, $x, $y);
6836 $exurl = ''; // external streams
6837 $imsize = FALSE;
6838 // check if we are passing an image as file or string
6839 if ($file[0] === '@') {
6840 // image from string
6841 $imgdata = substr($file, 1);
6842 } else { // image file
6843 if ($file[0] === '*') {
6844 // image as external stream
6845 $file = substr($file, 1);
6846 $exurl = $file;
6847 }
6848 // check if is a local file
6849 if (!@file_exists($file)) {
6850 // try to encode spaces on filename
6851 $tfile = str_replace(' ', '%20', $file);
6852 if (@file_exists($tfile)) {
6853 $file = $tfile;
6854 }
6855 }
6856 if (($imsize = @getimagesize($file)) === FALSE) {
6857 if (in_array($file, $this->imagekeys)) {
6858 // get existing image data
6859 $info = $this->getImageBuffer($file);
6860 $imsize = array($info['w'], $info['h']);
6861 } elseif (strpos($file, '__tcpdf_'.$this->file_id.'_img') === FALSE) {
6862 $imgdata = TCPDF_STATIC::fileGetContents($file);
6863 }
6864 }
6865 }
6866 if (!empty($imgdata)) {
6867 // copy image to cache
6868 $original_file = $file;
6869 $file = TCPDF_STATIC::getObjFilename('img', $this->file_id);
6870 $fp = TCPDF_STATIC::fopenLocal($file, 'w');
6871 if (!$fp) {
6872 $this->Error('Unable to write file: '.$file);
6873 }
6874 fwrite($fp, $imgdata);
6875 fclose($fp);
6876 unset($imgdata);
6877 $imsize = @getimagesize($file);
6878 if ($imsize === FALSE) {
6879 unlink($file);
6880 $file = $original_file;
6881 }
6882 }
6883 if ($imsize === FALSE) {
6884 if (($w > 0) AND ($h > 0)) {
6885 // get measures from specified data
6886 $pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6887 $ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6888 $imsize = array($pw, $ph);
6889 } else {
6890 $this->Error('[Image] Unable to get the size of the image: '.$file);
6891 }
6892 }
6893 // file hash
6894 $filehash = md5($file);
6895 // get original image width and height in pixels
6896 list($pixw, $pixh) = $imsize;
6897 // calculate image width and height on document
6898 if (($w <= 0) AND ($h <= 0)) {
6899 // convert image size to document unit
6900 $w = $this->pixelsToUnits($pixw);
6901 $h = $this->pixelsToUnits($pixh);
6902 } elseif ($w <= 0) {
6903 $w = $h * $pixw / $pixh;
6904 } elseif ($h <= 0) {
6905 $h = $w * $pixh / $pixw;
6906 } elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
6907 if (strlen($fitbox) !== 2) {
6908 // set default alignment
6909 $fitbox = '--';
6910 }
6911 // scale image dimensions proportionally to fit within the ($w, $h) box
6912 if ((($w * $pixh) / ($h * $pixw)) < 1) {
6913 // store current height
6914 $oldh = $h;
6915 // calculate new height
6916 $h = $w * $pixh / $pixw;
6917 // height difference
6918 $hdiff = ($oldh - $h);
6919 // vertical alignment
6920 switch (strtoupper($fitbox[1])) {
6921 case 'T': {
6922 break;
6923 }
6924 case 'M': {
6925 $y += ($hdiff / 2);
6926 break;
6927 }
6928 case 'B': {
6929 $y += $hdiff;
6930 break;
6931 }
6932 }
6933 } else {
6934 // store current width
6935 $oldw = $w;
6936 // calculate new width
6937 $w = $h * $pixw / $pixh;
6938 // width difference
6939 $wdiff = ($oldw - $w);
6940 // horizontal alignment
6941 switch (strtoupper($fitbox[0])) {
6942 case 'L': {
6943 if ($this->rtl) {
6944 $x -= $wdiff;
6945 }
6946 break;
6947 }
6948 case 'C': {
6949 if ($this->rtl) {
6950 $x -= ($wdiff / 2);
6951 } else {
6952 $x += ($wdiff / 2);
6953 }
6954 break;
6955 }
6956 case 'R': {
6957 if (!$this->rtl) {
6958 $x += $wdiff;
6959 }
6960 break;
6961 }
6962 }
6963 }
6964 }
6965 // fit the image on available space
6966 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
6967 // calculate new minimum dimensions in pixels
6968 $neww = round($w * $this->k * $dpi / $this->dpi);
6969 $newh = round($h * $this->k * $dpi / $this->dpi);
6970 // check if resize is necessary (resize is used only to reduce the image)
6971 $newsize = ($neww * $newh);
6972 $pixsize = ($pixw * $pixh);
6973 if (intval($resize) == 2) {
6974 $resize = true;
6975 } elseif ($newsize >= $pixsize) {
6976 $resize = false;
6977 }
6978 // check if image has been already added on document
6979 $newimage = true;
6980 if (in_array($file, $this->imagekeys)) {
6981 $newimage = false;
6982 // get existing image data
6983 $info = $this->getImageBuffer($file);
6984 if (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE) {
6985 // check if the newer image is larger
6986 $oldsize = ($info['w'] * $info['h']);
6987 if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
6988 $newimage = true;
6989 }
6990 }
6991 } elseif (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)) {
6992 // create temp image file (without alpha channel)
6993 $tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
6994 // create temp alpha file
6995 $tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
6996 // check for cached images
6997 if (in_array($tempfile_plain, $this->imagekeys)) {
6998 // get existing image data
6999 $info = $this->getImageBuffer($tempfile_plain);
7000 // check if the newer image is larger
7001 $oldsize = ($info['w'] * $info['h']);
7002 if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7003 $newimage = true;
7004 } else {
7005 $newimage = false;
7006 // embed mask image
7007 $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7008 // embed image, masked with previously embedded mask
7009 return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7010 }
7011 }
7012 }
7013 if ($newimage) {
7014 //First use of image, get info
7015 $type = strtolower($type);
7016 if ($type == '') {
7017 $type = TCPDF_IMAGES::getImageFileType($file, $imsize);
7018 } elseif ($type == 'jpg') {
7019 $type = 'jpeg';
7020 }
7021 $mqr = TCPDF_STATIC::get_mqr();
7022 TCPDF_STATIC::set_mqr(false);
7023 // Specific image handlers (defined on TCPDF_IMAGES CLASS)
7024 $mtd = '_parse'.$type;
7025 // GD image handler function
7026 $gdfunction = 'imagecreatefrom'.$type;
7027 $info = false;
7028 if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
7029 // TCPDF image functions
7030 $info = TCPDF_IMAGES::$mtd($file);
7031 if (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)
7032 AND (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns'])))) {
7033 return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
7034 }
7035 }
7036 if (($info === false) AND function_exists($gdfunction)) {
7037 try {
7038 // GD library
7039 $img = $gdfunction($file);
7040 if ($img !== false) {
7041 if ($resize) {
7042 $imgr = imagecreatetruecolor($neww, $newh);
7043 if (($type == 'gif') OR ($type == 'png')) {
7044 $imgr = TCPDF_IMAGES::setGDImageTransparency($imgr, $img);
7045 }
7046 imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7047 $img = $imgr;
7048 }
7049 if (($type == 'gif') OR ($type == 'png')) {
7050 $info = TCPDF_IMAGES::_toPNG($img, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7051 } else {
7052 $info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7053 }
7054 }
7055 } catch(Exception $e) {
7056 $info = false;
7057 }
7058 }
7059 if (($info === false) AND extension_loaded('imagick')) {
7060 try {
7061 // ImageMagick library
7062 $img = new Imagick();
7063 if ($type == 'svg') {
7064 if ($file[0] === '@') {
7065 // image from string
7066 $svgimg = substr($file, 1);
7067 } else {
7068 // get SVG file content
7069 $svgimg = TCPDF_STATIC::fileGetContents($file);
7070 }
7071 if ($svgimg !== FALSE) {
7072 // get width and height
7073 $regs = array();
7074 if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7075 $svgtag = $regs[1];
7076 $tmp = array();
7077 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7078 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7079 $owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
7080 $svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7081 } else {
7082 $ow = $w;
7083 }
7084 $tmp = array();
7085 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7086 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7087 $ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
7088 $svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7089 } else {
7090 $oh = $h;
7091 }
7092 $tmp = array();
7093 if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7094 $vbw = ($ow * $this->imgscale * $this->k);
7095 $vbh = ($oh * $this->imgscale * $this->k);
7096 $vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
7097 $svgtag = $vbox.$svgtag;
7098 }
7099 $svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7100 }
7101 $img->readImageBlob($svgimg);
7102 }
7103 } else {
7104 $img->readImage($file);
7105 }
7106 if ($resize) {
7107 $img->resizeImage($neww, $newh, 10, 1, false);
7108 }
7109 $img->setCompressionQuality($this->jpeg_quality);
7110 $img->setImageFormat('jpeg');
7111 $tempname = TCPDF_STATIC::getObjFilename('img', $this->file_id);
7112 $img->writeImage($tempname);
7113 $info = TCPDF_IMAGES::_parsejpeg($tempname);
7114 unlink($tempname);
7115 $img->destroy();
7116 } catch(Exception $e) {
7117 $info = false;
7118 }
7119 }
7120 if ($info === false) {
7121 // unable to process image
7122 return;
7123 }
7124 TCPDF_STATIC::set_mqr($mqr);
7125 if ($ismask) {
7126 // force grayscale
7127 $info['cs'] = 'DeviceGray';
7128 }
7129 if ($imgmask !== false) {
7130 $info['masked'] = $imgmask;
7131 }
7132 if (!empty($exurl)) {
7133 $info['exurl'] = $exurl;
7134 }
7135 // array of alternative images
7136 $info['altimgs'] = $altimgs;
7137 // add image to document
7138 $info['i'] = $this->setImageBuffer($file, $info);
7139 }
7140 // set alignment
7141 $this->img_rb_y = $y + $h;
7142 // set alignment
7143 if ($this->rtl) {
7144 if ($palign == 'L') {
7145 $ximg = $this->lMargin;
7146 } elseif ($palign == 'C') {
7147 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7148 } elseif ($palign == 'R') {
7149 $ximg = $this->w - $this->rMargin - $w;
7150 } else {
7151 $ximg = $x - $w;
7152 }
7153 $this->img_rb_x = $ximg;
7154 } else {
7155 if ($palign == 'L') {
7156 $ximg = $this->lMargin;
7157 } elseif ($palign == 'C') {
7158 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7159 } elseif ($palign == 'R') {
7160 $ximg = $this->w - $this->rMargin - $w;
7161 } else {
7162 $ximg = $x;
7163 }
7164 $this->img_rb_x = $ximg + $w;
7165 }
7166 if ($ismask OR $hidden) {
7167 // image is not displayed
7168 return $info['i'];
7169 }
7170 $xkimg = $ximg * $this->k;
7171 if (!$alt) {
7172 // only non-alternative immages will be set
7173 $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']));
7174 }
7175 if (!empty($border)) {
7176 $bx = $this->x;
7177 $by = $this->y;
7178 $this->x = $ximg;
7179 if ($this->rtl) {
7180 $this->x += $w;
7181 }
7182 $this->y = $y;
7183 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7184 $this->x = $bx;
7185 $this->y = $by;
7186 }
7187 if ($link) {
7188 $this->Link($ximg, $y, $w, $h, $link, 0);
7189 }
7190 // set pointer to align the next text/objects
7191 switch($align) {
7192 case 'T': {
7193 $this->y = $y;
7194 $this->x = $this->img_rb_x;
7195 break;
7196 }
7197 case 'M': {
7198 $this->y = $y + round($h/2);
7199 $this->x = $this->img_rb_x;
7200 break;
7201 }
7202 case 'B': {
7203 $this->y = $this->img_rb_y;
7204 $this->x = $this->img_rb_x;
7205 break;
7206 }
7207 case 'N': {
7208 $this->SetY($this->img_rb_y);
7209 break;
7210 }
7211 default:{
7212 break;
7213 }
7214 }
7215 $this->endlinex = $this->img_rb_x;
7216 if ($this->inxobj) {
7217 // we are inside an XObject template
7218 $this->xobjects[$this->xobjid]['images'][] = $info['i'];
7219 }
7220 return $info['i'];
7221 }
7222
7223 /**
7224 * Extract info from a PNG image with alpha channel using the Imagick or GD library.
7225 * @param $file (string) Name of the file containing the image.
7226 * @param $x (float) Abscissa of the upper-left corner.
7227 * @param $y (float) Ordinate of the upper-left corner.
7228 * @param $wpx (float) Original width of the image in pixels.
7229 * @param $hpx (float) original height of the image in pixels.
7230 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7231 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7232 * @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.
7233 * @param $link (mixed) URL or identifier returned by AddLink().
7234 * @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>
7235 * @param $resize (boolean) If true resize (reduce) the image to fit $w and $h (requires GD library).
7236 * @param $dpi (int) dot-per-inch resolution used on resize
7237 * @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>
7238 * @param $filehash (string) File hash used to build unique file names.
7239 * @author Nicola Asuni
7240 * @protected
7241 * @since 4.3.007 (2008-12-04)
7242 * @see Image()
7243 */
7244 protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
7245 // create temp images
7246 if (empty($filehash)) {
7247 $filehash = md5($file);
7248 }
7249 // create temp image file (without alpha channel)
7250 $tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7251 // create temp alpha file
7252 $tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7253 $parsed = false;
7254 $parse_error = '';
7255 // ImageMagick extension
7256 if (($parsed === false) AND extension_loaded('imagick')) {
7257 try {
7258 // ImageMagick library
7259 $img = new Imagick();
7260 $img->readImage($file);
7261 // clone image object
7262 $imga = TCPDF_STATIC::objclone($img);
7263 // extract alpha channel
7264 if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
7265 $img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
7266 } else {
7267 $img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7268 $img->negateImage(true);
7269 }
7270 $img->setImageFormat('png');
7271 $img->writeImage($tempfile_alpha);
7272 // remove alpha channel
7273 if (method_exists($imga, 'setImageMatte')) {
7274 $imga->setImageMatte(false);
7275 } else {
7276 $imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7277 }
7278 $imga->setImageFormat('png');
7279 $imga->writeImage($tempfile_plain);
7280 $parsed = true;
7281 } catch (Exception $e) {
7282 // Imagemagick fails, try with GD
7283 $parse_error = 'Imagick library error: '.$e->getMessage();
7284 }
7285 }
7286 // GD extension
7287 if (($parsed === false) AND function_exists('imagecreatefrompng')) {
7288 try {
7289 // generate images
7290 $img = imagecreatefrompng($file);
7291 $imgalpha = imagecreate($wpx, $hpx);
7292 // generate gray scale palette (0 -> 255)
7293 for ($c = 0; $c < 256; ++$c) {
7294 ImageColorAllocate($imgalpha, $c, $c, $c);
7295 }
7296 // extract alpha channel
7297 for ($xpx = 0; $xpx < $wpx; ++$xpx) {
7298 for ($ypx = 0; $ypx < $hpx; ++$ypx) {
7299 $color = imagecolorat($img, $xpx, $ypx);
7300 // get and correct gamma color
7301 $alpha = $this->getGDgamma($img, $color);
7302 imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
7303 }
7304 }
7305 imagepng($imgalpha, $tempfile_alpha);
7306 imagedestroy($imgalpha);
7307 // extract image without alpha channel
7308 $imgplain = imagecreatetruecolor($wpx, $hpx);
7309 imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7310 imagepng($imgplain, $tempfile_plain);
7311 imagedestroy($imgplain);
7312 $parsed = true;
7313 } catch (Exception $e) {
7314 // GD fails
7315 $parse_error = 'GD library error: '.$e->getMessage();
7316 }
7317 }
7318 if ($parsed === false) {
7319 if (empty($parse_error)) {
7320 $this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
7321 } else {
7322 $this->Error($parse_error);
7323 }
7324 }
7325 // embed mask image
7326 $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7327 // embed image, masked with previously embedded mask
7328 $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7329 }
7330
7331 /**
7332 * Get the GD-corrected PNG gamma value from alpha color
7333 * @param $img (int) GD image Resource ID.
7334 * @param $c (int) alpha color
7335 * @protected
7336 * @since 4.3.007 (2008-12-04)
7337 */
7338 protected function getGDgamma($img, $c) {
7339 if (!isset($this->gdgammacache['#'.$c])) {
7340 $colors = imagecolorsforindex($img, $c);
7341 // GD alpha is only 7 bit (0 -> 127)
7342 $this->gdgammacache['#'.$c] = (((127 - $colors['alpha']) / 127) * 255);
7343 // correct gamma
7344 $this->gdgammacache['#'.$c] = (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255);
7345 // store the latest values on cache to improve performances
7346 if (count($this->gdgammacache) > 8) {
7347 // remove one element from the cache array
7348 array_shift($this->gdgammacache);
7349 }
7350 }
7351 return $this->gdgammacache['#'.$c];
7352 }
7353
7354 /**
7355 * Performs a line break.
7356 * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
7357 * @param $h (float) The height of the break. By default, the value equals the height of the last printed cell.
7358 * @param $cell (boolean) if true add the current left (or right o for RTL) padding to the X coordinate
7359 * @public
7360 * @since 1.0
7361 * @see Cell()
7362 */
7363 public function Ln($h='', $cell=false) {
7364 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'])) {
7365 // revove vertical space from the top of the column
7366 return;
7367 }
7368 if ($cell) {
7369 if ($this->rtl) {
7370 $cellpadding = $this->cell_padding['R'];
7371 } else {
7372 $cellpadding = $this->cell_padding['L'];
7373 }
7374 } else {
7375 $cellpadding = 0;
7376 }
7377 if ($this->rtl) {
7378 $this->x = $this->w - $this->rMargin - $cellpadding;
7379 } else {
7380 $this->x = $this->lMargin + $cellpadding;
7381 }
7382 if (is_string($h)) {
7383 $h = $this->lasth;
7384 }
7385 $this->y += $h;
7386 $this->newline = true;
7387 }
7388
7389 /**
7390 * Returns the relative X value of current position.
7391 * The value is relative to the left border for LTR languages and to the right border for RTL languages.
7392 * @return float
7393 * @public
7394 * @since 1.2
7395 * @see SetX(), GetY(), SetY()
7396 */
7397 public function GetX() {
7398 //Get x position
7399 if ($this->rtl) {
7400 return ($this->w - $this->x);
7401 } else {
7402 return $this->x;
7403 }
7404 }
7405
7406 /**
7407 * Returns the absolute X value of current position.
7408 * @return float
7409 * @public
7410 * @since 1.2
7411 * @see SetX(), GetY(), SetY()
7412 */
7413 public function GetAbsX() {
7414 return $this->x;
7415 }
7416
7417 /**
7418 * Returns the ordinate of the current position.
7419 * @return float
7420 * @public
7421 * @since 1.0
7422 * @see SetY(), GetX(), SetX()
7423 */
7424 public function GetY() {
7425 return $this->y;
7426 }
7427
7428 /**
7429 * Defines the abscissa of the current position.
7430 * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
7431 * @param $x (float) The value of the abscissa in user units.
7432 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7433 * @public
7434 * @since 1.2
7435 * @see GetX(), GetY(), SetY(), SetXY()
7436 */
7437 public function SetX($x, $rtloff=false) {
7438 $x = floatval($x);
7439 if (!$rtloff AND $this->rtl) {
7440 if ($x >= 0) {
7441 $this->x = $this->w - $x;
7442 } else {
7443 $this->x = abs($x);
7444 }
7445 } else {
7446 if ($x >= 0) {
7447 $this->x = $x;
7448 } else {
7449 $this->x = $this->w + $x;
7450 }
7451 }
7452 if ($this->x < 0) {
7453 $this->x = 0;
7454 }
7455 if ($this->x > $this->w) {
7456 $this->x = $this->w;
7457 }
7458 }
7459
7460 /**
7461 * Moves the current abscissa back to the left margin and sets the ordinate.
7462 * If the passed value is negative, it is relative to the bottom of the page.
7463 * @param $y (float) The value of the ordinate in user units.
7464 * @param $resetx (bool) if true (default) reset the X position.
7465 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7466 * @public
7467 * @since 1.0
7468 * @see GetX(), GetY(), SetY(), SetXY()
7469 */
7470 public function SetY($y, $resetx=true, $rtloff=false) {
7471 $y = floatval($y);
7472 if ($resetx) {
7473 //reset x
7474 if (!$rtloff AND $this->rtl) {
7475 $this->x = $this->w - $this->rMargin;
7476 } else {
7477 $this->x = $this->lMargin;
7478 }
7479 }
7480 if ($y >= 0) {
7481 $this->y = $y;
7482 } else {
7483 $this->y = $this->h + $y;
7484 }
7485 if ($this->y < 0) {
7486 $this->y = 0;
7487 }
7488 if ($this->y > $this->h) {
7489 $this->y = $this->h;
7490 }
7491 }
7492
7493 /**
7494 * Defines the abscissa and ordinate of the current position.
7495 * If the passed values are negative, they are relative respectively to the right and bottom of the page.
7496 * @param $x (float) The value of the abscissa.
7497 * @param $y (float) The value of the ordinate.
7498 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7499 * @public
7500 * @since 1.2
7501 * @see SetX(), SetY()
7502 */
7503 public function SetXY($x, $y, $rtloff=false) {
7504 $this->SetY($y, false, $rtloff);
7505 $this->SetX($x, $rtloff);
7506 }
7507
7508 /**
7509 * Set the absolute X coordinate of the current pointer.
7510 * @param $x (float) The value of the abscissa in user units.
7511 * @public
7512 * @since 5.9.186 (2012-09-13)
7513 * @see setAbsX(), setAbsY(), SetAbsXY()
7514 */
7515 public function SetAbsX($x) {
7516 $this->x = floatval($x);
7517 }
7518
7519 /**
7520 * Set the absolute Y coordinate of the current pointer.
7521 * @param $y (float) (float) The value of the ordinate in user units.
7522 * @public
7523 * @since 5.9.186 (2012-09-13)
7524 * @see setAbsX(), setAbsY(), SetAbsXY()
7525 */
7526 public function SetAbsY($y) {
7527 $this->y = floatval($y);
7528 }
7529
7530 /**
7531 * Set the absolute X and Y coordinates of the current pointer.
7532 * @param $x (float) The value of the abscissa in user units.
7533 * @param $y (float) (float) The value of the ordinate in user units.
7534 * @public
7535 * @since 5.9.186 (2012-09-13)
7536 * @see setAbsX(), setAbsY(), SetAbsXY()
7537 */
7538 public function SetAbsXY($x, $y) {
7539 $this->SetAbsX($x);
7540 $this->SetAbsY($y);
7541 }
7542
7543 /**
7544 * Send the document to a given destination: string, local file or browser.
7545 * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
7546 * The method first calls Close() if necessary to terminate the document.
7547 * @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.
7548 * @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>
7549 * @return string
7550 * @public
7551 * @since 1.0
7552 * @see Close()
7553 */
7554 public function Output($name='doc.pdf', $dest='I') {
7555 //Output PDF to some destination
7556 //Finish document if necessary
7557 if ($this->state < 3) {
7558 $this->Close();
7559 }
7560 //Normalize parameters
7561 if (is_bool($dest)) {
7562 $dest = $dest ? 'D' : 'F';
7563 }
7564 $dest = strtoupper($dest);
7565 if ($dest[0] != 'F') {
7566 $name = preg_replace('/[\s]+/', '_', $name);
7567 $name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
7568 }
7569 if ($this->sign) {
7570 // *** apply digital signature to the document ***
7571 // get the document content
7572 $pdfdoc = $this->getBuffer();
7573 // remove last newline
7574 $pdfdoc = substr($pdfdoc, 0, -1);
7575 // remove filler space
7576 $byterange_string_len = strlen(TCPDF_STATIC::$byterange_string);
7577 // define the ByteRange
7578 $byte_range = array();
7579 $byte_range[0] = 0;
7580 $byte_range[1] = strpos($pdfdoc, TCPDF_STATIC::$byterange_string) + $byterange_string_len + 10;
7581 $byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
7582 $byte_range[3] = strlen($pdfdoc) - $byte_range[2];
7583 $pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
7584 // replace the ByteRange
7585 $byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
7586 $byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
7587 $pdfdoc = str_replace(TCPDF_STATIC::$byterange_string, $byterange, $pdfdoc);
7588 // write the document to a temporary folder
7589 $tempdoc = TCPDF_STATIC::getObjFilename('doc', $this->file_id);
7590 $f = TCPDF_STATIC::fopenLocal($tempdoc, 'wb');
7591 if (!$f) {
7592 $this->Error('Unable to create temporary file: '.$tempdoc);
7593 }
7594 $pdfdoc_length = strlen($pdfdoc);
7595 fwrite($f, $pdfdoc, $pdfdoc_length);
7596 fclose($f);
7597 // get digital signature via openssl library
7598 $tempsign = TCPDF_STATIC::getObjFilename('sig', $this->file_id);
7599 if (empty($this->signature_data['extracerts'])) {
7600 openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
7601 } else {
7602 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']);
7603 }
7604 // read signature
7605 $signature = file_get_contents($tempsign);
7606 // extract signature
7607 $signature = substr($signature, $pdfdoc_length);
7608 $signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
7609 $tmparr = explode("\n\n", $signature);
7610 $signature = $tmparr[1];
7611 // decode signature
7612 $signature = base64_decode(trim($signature));
7613 // add TSA timestamp to signature
7614 $signature = $this->applyTSA($signature);
7615 // convert signature to hex
7616 $signature = current(unpack('H*', $signature));
7617 $signature = str_pad($signature, $this->signature_max_length, '0');
7618 // Add signature to the document
7619 $this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
7620 $this->bufferlen = strlen($this->buffer);
7621 }
7622 switch($dest) {
7623 case 'I': {
7624 // Send PDF to the standard output
7625 if (ob_get_contents()) {
7626 $this->Error('Some data has already been output, can\'t send PDF file');
7627 }
7628 if (php_sapi_name() != 'cli') {
7629 // send output to a browser
7630 header('Content-Type: application/pdf');
7631 if (headers_sent()) {
7632 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7633 }
7634 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7635 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7636 header('Pragma: public');
7637 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7638 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7639 header('Content-Disposition: inline; filename="'.basename($name).'"');
7640 TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7641 } else {
7642 echo $this->getBuffer();
7643 }
7644 break;
7645 }
7646 case 'D': {
7647 // download PDF as file
7648 if (ob_get_contents()) {
7649 $this->Error('Some data has already been output, can\'t send PDF file');
7650 }
7651 header('Content-Description: File Transfer');
7652 if (headers_sent()) {
7653 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7654 }
7655 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7656 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7657 header('Pragma: public');
7658 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7659 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7660 // force download dialog
7661 if (strpos(php_sapi_name(), 'cgi') === false) {
7662 header('Content-Type: application/force-download');
7663 header('Content-Type: application/octet-stream', false);
7664 header('Content-Type: application/download', false);
7665 header('Content-Type: application/pdf', false);
7666 } else {
7667 header('Content-Type: application/pdf');
7668 }
7669 // use the Content-Disposition header to supply a recommended filename
7670 header('Content-Disposition: attachment; filename="'.basename($name).'"');
7671 header('Content-Transfer-Encoding: binary');
7672 TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7673 break;
7674 }
7675 case 'F':
7676 case 'FI':
7677 case 'FD': {
7678 // save PDF to a local file
7679 $f = TCPDF_STATIC::fopenLocal($name, 'wb');
7680 if (!$f) {
7681 $this->Error('Unable to create output file: '.$name);
7682 }
7683 fwrite($f, $this->getBuffer(), $this->bufferlen);
7684 fclose($f);
7685 if ($dest == 'FI') {
7686 // send headers to browser
7687 header('Content-Type: application/pdf');
7688 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7689 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7690 header('Pragma: public');
7691 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7692 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7693 header('Content-Disposition: inline; filename="'.basename($name).'"');
7694 TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7695 } elseif ($dest == 'FD') {
7696 // send headers to browser
7697 if (ob_get_contents()) {
7698 $this->Error('Some data has already been output, can\'t send PDF file');
7699 }
7700 header('Content-Description: File Transfer');
7701 if (headers_sent()) {
7702 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7703 }
7704 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=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(file_get_contents($name), filesize($name));
7721 }
7722 break;
7723 }
7724 case 'E': {
7725 // return PDF as base64 mime multi-part email attachment (RFC 2045)
7726 $retval = 'Content-Type: application/pdf;'."\r\n";
7727 $retval .= ' name="'.$name.'"'."\r\n";
7728 $retval .= 'Content-Transfer-Encoding: base64'."\r\n";
7729 $retval .= 'Content-Disposition: attachment;'."\r\n";
7730 $retval .= ' filename="'.$name.'"'."\r\n\r\n";
7731 $retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
7732 return $retval;
7733 }
7734 case 'S': {
7735 // returns PDF as a string
7736 return $this->getBuffer();
7737 }
7738 default: {
7739 $this->Error('Incorrect output destination: '.$dest);
7740 }
7741 }
7742 return '';
7743 }
7744
7745 /**
7746 * Unset all class variables except the following critical variables.
7747 * @param $destroyall (boolean) if true destroys all class variables, otherwise preserves critical variables.
7748 * @param $preserve_objcopy (boolean) if true preserves the objcopy variable
7749 * @public
7750 * @since 4.5.016 (2009-02-24)
7751 */
7752 public function _destroy($destroyall=false, $preserve_objcopy=false) {
7753 if ($destroyall AND !$preserve_objcopy) {
7754 // remove all temporary files
7755 $tmpfiles = glob(K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_*');
7756 if (!empty($tmpfiles)) {
7757 array_map('unlink', $tmpfiles);
7758 }
7759 }
7760 $preserve = array(
7761 'file_id',
7762 'internal_encoding',
7763 'state',
7764 'bufferlen',
7765 'buffer',
7766 'cached_files',
7767 'sign',
7768 'signature_data',
7769 'signature_max_length',
7770 'byterange_string',
7771 'tsa_timestamp',
7772 'tsa_data'
7773 );
7774 foreach (array_keys(get_object_vars($this)) as $val) {
7775 if ($destroyall OR !in_array($val, $preserve)) {
7776 if ((!$preserve_objcopy OR ($val != 'objcopy')) AND ($val != 'file_id') AND isset($this->$val)) {
7777 unset($this->$val);
7778 }
7779 }
7780 }
7781 }
7782
7783 /**
7784 * Check for locale-related bug
7785 * @protected
7786 */
7787 protected function _dochecks() {
7788 //Check for locale-related bug
7789 if (1.1 == 1) {
7790 $this->Error('Don\'t alter the locale before including class file');
7791 }
7792 //Check for decimal separator
7793 if (sprintf('%.1F', 1.0) != '1.0') {
7794 setlocale(LC_NUMERIC, 'C');
7795 }
7796 }
7797
7798 /**
7799 * Return an array containing variations for the basic page number alias.
7800 * @param $a (string) Base alias.
7801 * @return array of page number aliases
7802 * @protected
7803 */
7804 protected function getInternalPageNumberAliases($a= '') {
7805 $alias = array();
7806 // build array of Unicode + ASCII variants (the order is important)
7807 $alias = array('u' => array(), 'a' => array());
7808 $u = '{'.$a.'}';
7809 $alias['u'][] = TCPDF_STATIC::_escape($u);
7810 if ($this->isunicode) {
7811 $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($u, $this->isunicode, $this->CurrentFont));
7812 $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($u, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7813 $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($a, $this->isunicode, $this->CurrentFont));
7814 $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($a, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7815 }
7816 $alias['a'][] = TCPDF_STATIC::_escape($a);
7817 return $alias;
7818 }
7819
7820 /**
7821 * Return an array containing all internal page aliases.
7822 * @return array of page number aliases
7823 * @protected
7824 */
7825 protected function getAllInternalPageNumberAliases() {
7826 $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);
7827 $pnalias = array();
7828 foreach($basic_alias as $k => $a) {
7829 $pnalias[$k] = $this->getInternalPageNumberAliases($a);
7830 }
7831 return $pnalias;
7832 }
7833
7834 /**
7835 * Replace right shift page number aliases with spaces to correct right alignment.
7836 * This works perfectly only when using monospaced fonts.
7837 * @param $page (string) Page content.
7838 * @param $aliases (array) Array of page aliases.
7839 * @param $diff (int) initial difference to add.
7840 * @return replaced page content.
7841 * @protected
7842 */
7843 protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
7844 foreach ($aliases as $type => $alias) {
7845 foreach ($alias as $a) {
7846 // find position of compensation factor
7847 $startnum = (strpos($a, ':') + 1);
7848 $a = substr($a, 0, $startnum);
7849 if (($pos = strpos($page, $a)) !== false) {
7850 // end of alias
7851 $endnum = strpos($page, '}', $pos);
7852 // string to be replaced
7853 $aa = substr($page, $pos, ($endnum - $pos + 1));
7854 // get compensation factor
7855 $ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
7856 $ratio = preg_replace('/[^0-9\.]/', '', $ratio);
7857 $ratio = floatval($ratio);
7858 if ($type == 'u') {
7859 $chrdiff = floor(($diff + 12) * $ratio);
7860 $shift = str_repeat(' ', $chrdiff);
7861 $shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
7862 } else {
7863 $chrdiff = floor(($diff + 11) * $ratio);
7864 $shift = str_repeat(' ', $chrdiff);
7865 }
7866 $page = str_replace($aa, $shift, $page);
7867 }
7868 }
7869 }
7870 return $page;
7871 }
7872
7873 /**
7874 * Set page boxes to be included on page descriptions.
7875 * @param $boxes (array) Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
7876 * @protected
7877 */
7878 protected function setPageBoxTypes($boxes) {
7879 $this->page_boxes = array();
7880 foreach ($boxes as $box) {
7881 if (in_array($box, TCPDF_STATIC::$pageboxes)) {
7882 $this->page_boxes[] = $box;
7883 }
7884 }
7885 }
7886
7887 /**
7888 * Output pages (and replace page number aliases).
7889 * @protected
7890 */
7891 protected function _putpages() {
7892 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
7893 // get internal aliases for page numbers
7894 $pnalias = $this->getAllInternalPageNumberAliases();
7895 $num_pages = $this->numpages;
7896 $ptpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $num_pages - 1));
7897 $ptpu = TCPDF_FONTS::UTF8ToUTF16BE($ptpa, false, $this->isunicode, $this->CurrentFont);
7898 $ptp_num_chars = $this->GetNumChars($ptpa);
7899 $pagegroupnum = 0;
7900 $groupnum = 0;
7901 $ptgu = 1;
7902 $ptga = 1;
7903 $ptg_num_chars = 1;
7904 for ($n = 1; $n <= $num_pages; ++$n) {
7905 // get current page
7906 $temppage = $this->getPageBuffer($n);
7907 $pagelen = strlen($temppage);
7908 // set replacements for total pages number
7909 $pnpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $n - 1));
7910 $pnpu = TCPDF_FONTS::UTF8ToUTF16BE($pnpa, false, $this->isunicode, $this->CurrentFont);
7911 $pnp_num_chars = $this->GetNumChars($pnpa);
7912 $pdiff = 0; // difference used for right shift alignment of page numbers
7913 $gdiff = 0; // difference used for right shift alignment of page group numbers
7914 if (!empty($this->pagegroups)) {
7915 if (isset($this->newpagegroup[$n])) {
7916 $pagegroupnum = 0;
7917 ++$groupnum;
7918 $ptga = TCPDF_STATIC::formatPageNumber($this->pagegroups[$groupnum]);
7919 $ptgu = TCPDF_FONTS::UTF8ToUTF16BE($ptga, false, $this->isunicode, $this->CurrentFont);
7920 $ptg_num_chars = $this->GetNumChars($ptga);
7921 }
7922 ++$pagegroupnum;
7923 $pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
7924 $pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
7925 $png_num_chars = $this->GetNumChars($pnga);
7926 // replace page numbers
7927 $replace = array();
7928 $replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
7929 $replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
7930 $replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
7931 $replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
7932 list($temppage, $gdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $gdiff);
7933 }
7934 // replace page numbers
7935 $replace = array();
7936 $replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
7937 $replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
7938 $replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
7939 $replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
7940 list($temppage, $pdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $pdiff);
7941 // replace right shift alias
7942 $temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
7943 // replace EPS marker
7944 $temppage = str_replace($this->epsmarker, '', $temppage);
7945 //Page
7946 $this->page_obj_id[$n] = $this->_newobj();
7947 $out = '<<';
7948 $out .= ' /Type /Page';
7949 $out .= ' /Parent 1 0 R';
7950 if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
7951 $out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
7952 }
7953 $out .= ' /Resources 2 0 R';
7954 foreach ($this->page_boxes as $box) {
7955 $out .= ' /'.$box;
7956 $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']);
7957 }
7958 if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
7959 $out .= ' /BoxColorInfo <<';
7960 foreach ($this->page_boxes as $box) {
7961 if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
7962 $out .= ' /'.$box.' <<';
7963 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
7964 $color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
7965 $out .= ' /C [';
7966 $out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
7967 $out .= ' ]';
7968 }
7969 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
7970 $out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
7971 }
7972 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
7973 $out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
7974 }
7975 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
7976 $dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
7977 $out .= ' /D [';
7978 foreach ($dashes as $dash) {
7979 $out .= sprintf(' %F', ($dash * $this->k));
7980 }
7981 $out .= ' ]';
7982 }
7983 $out .= ' >>';
7984 }
7985 }
7986 $out .= ' >>';
7987 }
7988 $out .= ' /Contents '.($this->n + 1).' 0 R';
7989 $out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
7990 if (!$this->pdfa_mode) {
7991 $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
7992 }
7993 if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
7994 // page transitions
7995 if (isset($this->pagedim[$n]['trans']['Dur'])) {
7996 $out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
7997 }
7998 $out .= ' /Trans <<';
7999 $out .= ' /Type /Trans';
8000 if (isset($this->pagedim[$n]['trans']['S'])) {
8001 $out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
8002 }
8003 if (isset($this->pagedim[$n]['trans']['D'])) {
8004 $out .= ' /D '.$this->pagedim[$n]['trans']['D'];
8005 }
8006 if (isset($this->pagedim[$n]['trans']['Dm'])) {
8007 $out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
8008 }
8009 if (isset($this->pagedim[$n]['trans']['M'])) {
8010 $out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
8011 }
8012 if (isset($this->pagedim[$n]['trans']['Di'])) {
8013 $out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
8014 }
8015 if (isset($this->pagedim[$n]['trans']['SS'])) {
8016 $out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
8017 }
8018 if (isset($this->pagedim[$n]['trans']['B'])) {
8019 $out .= ' /B '.$this->pagedim[$n]['trans']['B'];
8020 }
8021 $out .= ' >>';
8022 }
8023 $out .= $this->_getannotsrefs($n);
8024 $out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8025 $out .= ' >>';
8026 $out .= "\n".'endobj';
8027 $this->_out($out);
8028 //Page content
8029 $p = ($this->compress) ? gzcompress($temppage) : $temppage;
8030 $this->_newobj();
8031 $p = $this->_getrawstream($p);
8032 $this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8033 }
8034 //Pages root
8035 $out = $this->_getobj(1)."\n";
8036 $out .= '<< /Type /Pages /Kids [';
8037 foreach($this->page_obj_id as $page_obj) {
8038 $out .= ' '.$page_obj.' 0 R';
8039 }
8040 $out .= ' ] /Count '.$num_pages.' >>';
8041 $out .= "\n".'endobj';
8042 $this->_out($out);
8043 }
8044
8045 /**
8046 * Get references to page annotations.
8047 * @param $n (int) page number
8048 * @return string
8049 * @protected
8050 * @author Nicola Asuni
8051 * @since 5.0.010 (2010-05-17)
8052 */
8053 protected function _getannotsrefs($n) {
8054 if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
8055 return '';
8056 }
8057 $out = ' /Annots [';
8058 if (isset($this->PageAnnots[$n])) {
8059 foreach ($this->PageAnnots[$n] as $key => $val) {
8060 if (!in_array($val['n'], $this->radio_groups)) {
8061 $out .= ' '.$val['n'].' 0 R';
8062 }
8063 }
8064 // add radiobutton groups
8065 if (isset($this->radiobutton_groups[$n])) {
8066 foreach ($this->radiobutton_groups[$n] as $key => $data) {
8067 if (isset($data['n'])) {
8068 $out .= ' '.$data['n'].' 0 R';
8069 }
8070 }
8071 }
8072 }
8073 if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
8074 // set reference for signature object
8075 $out .= ' '.$this->sig_obj_id.' 0 R';
8076 }
8077 if (!empty($this->empty_signature_appearance)) {
8078 foreach ($this->empty_signature_appearance as $esa) {
8079 if ($esa['page'] == $n) {
8080 // set reference for empty signature objects
8081 $out .= ' '.$esa['objid'].' 0 R';
8082 }
8083 }
8084 }
8085 $out .= ' ]';
8086 return $out;
8087 }
8088
8089 /**
8090 * Output annotations objects for all pages.
8091 * !!! THIS METHOD IS NOT YET COMPLETED !!!
8092 * See section 12.5 of PDF 32000_2008 reference.
8093 * @protected
8094 * @author Nicola Asuni
8095 * @since 4.0.018 (2008-08-06)
8096 */
8097 protected function _putannotsobjs() {
8098 // reset object counter
8099 for ($n=1; $n <= $this->numpages; ++$n) {
8100 if (isset($this->PageAnnots[$n])) {
8101 // set page annotations
8102 foreach ($this->PageAnnots[$n] as $key => $pl) {
8103 $annot_obj_id = $this->PageAnnots[$n][$key]['n'];
8104 // create annotation object for grouping radiobuttons
8105 if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
8106 $radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
8107 $annots = '<<';
8108 $annots .= ' /Type /Annot';
8109 $annots .= ' /Subtype /Widget';
8110 $annots .= ' /Rect [0 0 0 0]';
8111 if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
8112 // read only
8113 $annots .= ' /F 68';
8114 $annots .= ' /Ff 49153';
8115 } else {
8116 $annots .= ' /F 4'; // default print for PDF/A
8117 $annots .= ' /Ff 49152';
8118 }
8119 $annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8120 if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8121 $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
8122 }
8123 $annots .= ' /FT /Btn';
8124 $annots .= ' /Kids [';
8125 $defval = '';
8126 foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
8127 if (isset($data['kid'])) {
8128 $annots .= ' '.$data['kid'].' 0 R';
8129 if ($data['def'] !== 'Off') {
8130 $defval = $data['def'];
8131 }
8132 }
8133 }
8134 $annots .= ' ]';
8135 if (!empty($defval)) {
8136 $annots .= ' /V /'.$defval;
8137 }
8138 $annots .= ' >>';
8139 $this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8140 $this->form_obj_id[] = $radio_button_obj_id;
8141 // store object id to be used on Parent entry of Kids
8142 $this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
8143 }
8144 $formfield = false;
8145 $pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
8146 $a = $pl['x'] * $this->k;
8147 $b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
8148 $c = $pl['w'] * $this->k;
8149 $d = $pl['h'] * $this->k;
8150 $rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
8151 // create new annotation object
8152 $annots = '<</Type /Annot';
8153 $annots .= ' /Subtype /'.$pl['opt']['subtype'];
8154 $annots .= ' /Rect ['.$rect.']';
8155 $ft = array('Btn', 'Tx', 'Ch', 'Sig');
8156 if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8157 $annots .= ' /FT /'.$pl['opt']['ft'];
8158 $formfield = true;
8159 }
8160 if ($pl['opt']['subtype'] !== 'Link') {
8161 $annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8162 }
8163 $annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
8164 $annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8165 $annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
8166 if (isset($pl['opt']['f'])) {
8167 $fval = 0;
8168 if (is_array($pl['opt']['f'])) {
8169 foreach ($pl['opt']['f'] as $f) {
8170 switch (strtolower($f)) {
8171 case 'invisible': {
8172 $fval += 1 << 0;
8173 break;
8174 }
8175 case 'hidden': {
8176 $fval += 1 << 1;
8177 break;
8178 }
8179 case 'print': {
8180 $fval += 1 << 2;
8181 break;
8182 }
8183 case 'nozoom': {
8184 $fval += 1 << 3;
8185 break;
8186 }
8187 case 'norotate': {
8188 $fval += 1 << 4;
8189 break;
8190 }
8191 case 'noview': {
8192 $fval += 1 << 5;
8193 break;
8194 }
8195 case 'readonly': {
8196 $fval += 1 << 6;
8197 break;
8198 }
8199 case 'locked': {
8200 $fval += 1 << 8;
8201 break;
8202 }
8203 case 'togglenoview': {
8204 $fval += 1 << 9;
8205 break;
8206 }
8207 case 'lockedcontents': {
8208 $fval += 1 << 10;
8209 break;
8210 }
8211 default: {
8212 break;
8213 }
8214 }
8215 }
8216 } else {
8217 $fval = intval($pl['opt']['f']);
8218 }
8219 } else {
8220 $fval = 4;
8221 }
8222 if ($this->pdfa_mode) {
8223 // force print flag for PDF/A mode
8224 $fval |= 4;
8225 }
8226 $annots .= ' /F '.intval($fval);
8227 if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8228 $annots .= ' /AS /'.$pl['opt']['as'];
8229 }
8230 if (isset($pl['opt']['ap'])) {
8231 // appearance stream
8232 $annots .= ' /AP <<';
8233 if (is_array($pl['opt']['ap'])) {
8234 foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8235 // $apmode can be: n = normal; r = rollover; d = down;
8236 $annots .= ' /'.strtoupper($apmode);
8237 if (is_array($apdef)) {
8238 $annots .= ' <<';
8239 foreach ($apdef as $apstate => $stream) {
8240 // reference to XObject that define the appearance for this mode-state
8241 $apsobjid = $this->_putAPXObject($c, $d, $stream);
8242 $annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8243 }
8244 $annots .= ' >>';
8245 } else {
8246 // reference to XObject that define the appearance for this mode
8247 $apsobjid = $this->_putAPXObject($c, $d, $apdef);
8248 $annots .= ' '.$apsobjid.' 0 R';
8249 }
8250 }
8251 } else {
8252 $annots .= $pl['opt']['ap'];
8253 }
8254 $annots .= ' >>';
8255 }
8256 if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8257 $annots .= ' /BS <<';
8258 $annots .= ' /Type /Border';
8259 if (isset($pl['opt']['bs']['w'])) {
8260 $annots .= ' /W '.intval($pl['opt']['bs']['w']);
8261 }
8262 $bstyles = array('S', 'D', 'B', 'I', 'U');
8263 if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8264 $annots .= ' /S /'.$pl['opt']['bs']['s'];
8265 }
8266 if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8267 $annots .= ' /D [';
8268 foreach ($pl['opt']['bs']['d'] as $cord) {
8269 $annots .= ' '.intval($cord);
8270 }
8271 $annots .= ']';
8272 }
8273 $annots .= ' >>';
8274 } else {
8275 $annots .= ' /Border [';
8276 if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8277 $annots .= intval($pl['opt']['border'][0]).' ';
8278 $annots .= intval($pl['opt']['border'][1]).' ';
8279 $annots .= intval($pl['opt']['border'][2]);
8280 if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8281 $annots .= ' [';
8282 foreach ($pl['opt']['border'][3] as $dash) {
8283 $annots .= intval($dash).' ';
8284 }
8285 $annots .= ']';
8286 }
8287 } else {
8288 $annots .= '0 0 0';
8289 }
8290 $annots .= ']';
8291 }
8292 if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8293 $annots .= ' /BE <<';
8294 $bstyles = array('S', 'C');
8295 if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
8296 $annots .= ' /S /'.$pl['opt']['bs']['s'];
8297 } else {
8298 $annots .= ' /S /S';
8299 }
8300 if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8301 $annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
8302 }
8303 $annots .= '>>';
8304 }
8305 if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8306 $annots .= ' /C '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['c']);
8307 }
8308 //$annots .= ' /StructParent ';
8309 //$annots .= ' /OC ';
8310 $markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8311 if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8312 // this is a markup type
8313 if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8314 $annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8315 }
8316 //$annots .= ' /Popup ';
8317 if (isset($pl['opt']['ca'])) {
8318 $annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
8319 }
8320 if (isset($pl['opt']['rc'])) {
8321 $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8322 }
8323 $annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
8324 //$annots .= ' /IRT ';
8325 if (isset($pl['opt']['subj'])) {
8326 $annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8327 }
8328 //$annots .= ' /RT ';
8329 //$annots .= ' /IT ';
8330 //$annots .= ' /ExData ';
8331 }
8332 $lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8333 // Annotation types
8334 switch (strtolower($pl['opt']['subtype'])) {
8335 case 'text': {
8336 if (isset($pl['opt']['open'])) {
8337 $annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
8338 }
8339 $iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8340 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8341 $annots .= ' /Name /'.$pl['opt']['name'];
8342 } else {
8343 $annots .= ' /Name /Note';
8344 }
8345 $statemodels = array('Marked', 'Review');
8346 if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
8347 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8348 } else {
8349 $pl['opt']['statemodel'] = 'Marked';
8350 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8351 }
8352 if ($pl['opt']['statemodel'] == 'Marked') {
8353 $states = array('Accepted', 'Unmarked');
8354 } else {
8355 $states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8356 }
8357 if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
8358 $annots .= ' /State /'.$pl['opt']['state'];
8359 } else {
8360 if ($pl['opt']['statemodel'] == 'Marked') {
8361 $annots .= ' /State /Unmarked';
8362 } else {
8363 $annots .= ' /State /None';
8364 }
8365 }
8366 break;
8367 }
8368 case 'link': {
8369 if (is_string($pl['txt']) && !empty($pl['txt'])) {
8370 if ($pl['txt'][0] == '#') {
8371 // internal destination
8372 $annots .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1));
8373 } elseif ($pl['txt'][0] == '%') {
8374 // embedded PDF file
8375 $filename = basename(substr($pl['txt'], 1));
8376 $annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
8377 } elseif ($pl['txt'][0] == '*') {
8378 // embedded generic file
8379 $filename = basename(substr($pl['txt'], 1));
8380 $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});';
8381 $annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
8382 } else {
8383 $parsedUrl = parse_url($pl['txt']);
8384 if (empty($parsedUrl['scheme']) AND (strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
8385 // relative link to a PDF file
8386 $dest = '[0 /Fit]'; // default page 0
8387 if (!empty($parsedUrl['fragment'])) {
8388 // check for named destination
8389 $tmp = explode('=', $parsedUrl['fragment']);
8390 $dest = '('.((count($tmp) == 2) ? $tmp[1] : $tmp[0]).')';
8391 }
8392 $annots .= ' /A <</S /GoToR /D '.$dest.' /F '.$this->_datastring($this->unhtmlentities($parsedUrl['path']), $annot_obj_id).' /NewWindow true>>';
8393 } else {
8394 // external URI link
8395 $annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8396 }
8397 }
8398 } elseif (isset($this->links[$pl['txt']])) {
8399 // internal link ID
8400 $l = $this->links[$pl['txt']];
8401 if (isset($this->page_obj_id[($l['p'])])) {
8402 $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)));
8403 }
8404 }
8405 $hmodes = array('N', 'I', 'O', 'P');
8406 if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8407 $annots .= ' /H /'.$pl['opt']['h'];
8408 } else {
8409 $annots .= ' /H /I';
8410 }
8411 //$annots .= ' /PA ';
8412 //$annots .= ' /Quadpoints ';
8413 break;
8414 }
8415 case 'freetext': {
8416 if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8417 $annots .= ' /DA ('.$pl['opt']['da'].')';
8418 }
8419 if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8420 $annots .= ' /Q '.intval($pl['opt']['q']);
8421 }
8422 if (isset($pl['opt']['rc'])) {
8423 $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8424 }
8425 if (isset($pl['opt']['ds'])) {
8426 $annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8427 }
8428 if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8429 $annots .= ' /CL [';
8430 foreach ($pl['opt']['cl'] as $cl) {
8431 $annots .= sprintf('%F ', $cl * $this->k);
8432 }
8433 $annots .= ']';
8434 }
8435 $tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8436 if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8437 $annots .= ' /IT /'.$pl['opt']['it'];
8438 }
8439 if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8440 $l = $pl['opt']['rd'][0] * $this->k;
8441 $r = $pl['opt']['rd'][1] * $this->k;
8442 $t = $pl['opt']['rd'][2] * $this->k;
8443 $b = $pl['opt']['rd'][3] * $this->k;
8444 $annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
8445 }
8446 if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8447 $annots .= ' /LE /'.$pl['opt']['le'];
8448 }
8449 break;
8450 }
8451 case 'line': {
8452 break;
8453 }
8454 case 'square': {
8455 break;
8456 }
8457 case 'circle': {
8458 break;
8459 }
8460 case 'polygon': {
8461 break;
8462 }
8463 case 'polyline': {
8464 break;
8465 }
8466 case 'highlight': {
8467 break;
8468 }
8469 case 'underline': {
8470 break;
8471 }
8472 case 'squiggly': {
8473 break;
8474 }
8475 case 'strikeout': {
8476 break;
8477 }
8478 case 'stamp': {
8479 break;
8480 }
8481 case 'caret': {
8482 break;
8483 }
8484 case 'ink': {
8485 break;
8486 }
8487 case 'popup': {
8488 break;
8489 }
8490 case 'fileattachment': {
8491 if ($this->pdfa_mode) {
8492 // embedded files are not allowed in PDF/A mode
8493 break;
8494 }
8495 if (!isset($pl['opt']['fs'])) {
8496 break;
8497 }
8498 $filename = basename($pl['opt']['fs']);
8499 if (isset($this->embeddedfiles[$filename]['f'])) {
8500 $annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
8501 $iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8502 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8503 $annots .= ' /Name /'.$pl['opt']['name'];
8504 } else {
8505 $annots .= ' /Name /PushPin';
8506 }
8507 // index (zero-based) of the annotation in the Annots array of this page
8508 $this->embeddedfiles[$filename]['a'] = $key;
8509 }
8510 break;
8511 }
8512 case 'sound': {
8513 if (!isset($pl['opt']['fs'])) {
8514 break;
8515 }
8516 $filename = basename($pl['opt']['fs']);
8517 if (isset($this->embeddedfiles[$filename]['f'])) {
8518 // ... TO BE COMPLETED ...
8519 // /R /C /B /E /CO /CP
8520 $annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
8521 $iconsapp = array('Speaker', 'Mic');
8522 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8523 $annots .= ' /Name /'.$pl['opt']['name'];
8524 } else {
8525 $annots .= ' /Name /Speaker';
8526 }
8527 }
8528 break;
8529 }
8530 case 'movie': {
8531 break;
8532 }
8533 case 'widget': {
8534 $hmode = array('N', 'I', 'O', 'P', 'T');
8535 if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8536 $annots .= ' /H /'.$pl['opt']['h'];
8537 }
8538 if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8539 $annots .= ' /MK <<';
8540 if (isset($pl['opt']['mk']['r'])) {
8541 $annots .= ' /R '.$pl['opt']['mk']['r'];
8542 }
8543 if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8544 $annots .= ' /BC '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bc']);
8545 }
8546 if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8547 $annots .= ' /BG '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bg']);
8548 }
8549 if (isset($pl['opt']['mk']['ca'])) {
8550 $annots .= ' /CA '.$pl['opt']['mk']['ca'];
8551 }
8552 if (isset($pl['opt']['mk']['rc'])) {
8553 $annots .= ' /RC '.$pl['opt']['mk']['rc'];
8554 }
8555 if (isset($pl['opt']['mk']['ac'])) {
8556 $annots .= ' /AC '.$pl['opt']['mk']['ac'];
8557 }
8558 if (isset($pl['opt']['mk']['i'])) {
8559 $info = $this->getImageBuffer($pl['opt']['mk']['i']);
8560 if ($info !== false) {
8561 $annots .= ' /I '.$info['n'].' 0 R';
8562 }
8563 }
8564 if (isset($pl['opt']['mk']['ri'])) {
8565 $info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8566 if ($info !== false) {
8567 $annots .= ' /RI '.$info['n'].' 0 R';
8568 }
8569 }
8570 if (isset($pl['opt']['mk']['ix'])) {
8571 $info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8572 if ($info !== false) {
8573 $annots .= ' /IX '.$info['n'].' 0 R';
8574 }
8575 }
8576 if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8577 $annots .= ' /IF <<';
8578 $if_sw = array('A', 'B', 'S', 'N');
8579 if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8580 $annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8581 }
8582 $if_s = array('A', 'P');
8583 if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8584 $annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8585 }
8586 if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8587 $annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8588 }
8589 if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8590 $annots .= ' /FB true';
8591 }
8592 $annots .= '>>';
8593 }
8594 if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8595 $annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8596 }
8597 $annots .= '>>';
8598 } // end MK
8599 // --- Entries for field dictionaries ---
8600 if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8601 // set parent
8602 $annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
8603 }
8604 if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8605 $annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8606 }
8607 if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8608 $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8609 }
8610 if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8611 $annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8612 }
8613 if (isset($pl['opt']['ff'])) {
8614 if (is_array($pl['opt']['ff'])) {
8615 // array of bit settings
8616 $flag = 0;
8617 foreach($pl['opt']['ff'] as $val) {
8618 $flag += 1 << ($val - 1);
8619 }
8620 } else {
8621 $flag = intval($pl['opt']['ff']);
8622 }
8623 $annots .= ' /Ff '.$flag;
8624 }
8625 if (isset($pl['opt']['maxlen'])) {
8626 $annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
8627 }
8628 if (isset($pl['opt']['v'])) {
8629 $annots .= ' /V';
8630 if (is_array($pl['opt']['v'])) {
8631 foreach ($pl['opt']['v'] AS $optval) {
8632 if (is_float($optval)) {
8633 $optval = sprintf('%F', $optval);
8634 }
8635 $annots .= ' '.$optval;
8636 }
8637 } else {
8638 $annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8639 }
8640 }
8641 if (isset($pl['opt']['dv'])) {
8642 $annots .= ' /DV';
8643 if (is_array($pl['opt']['dv'])) {
8644 foreach ($pl['opt']['dv'] AS $optval) {
8645 if (is_float($optval)) {
8646 $optval = sprintf('%F', $optval);
8647 }
8648 $annots .= ' '.$optval;
8649 }
8650 } else {
8651 $annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8652 }
8653 }
8654 if (isset($pl['opt']['rv'])) {
8655 $annots .= ' /RV';
8656 if (is_array($pl['opt']['rv'])) {
8657 foreach ($pl['opt']['rv'] AS $optval) {
8658 if (is_float($optval)) {
8659 $optval = sprintf('%F', $optval);
8660 }
8661 $annots .= ' '.$optval;
8662 }
8663 } else {
8664 $annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
8665 }
8666 }
8667 if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
8668 $annots .= ' /A << '.$pl['opt']['a'].' >>';
8669 }
8670 if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
8671 $annots .= ' /AA << '.$pl['opt']['aa'].' >>';
8672 }
8673 if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8674 $annots .= ' /DA ('.$pl['opt']['da'].')';
8675 }
8676 if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8677 $annots .= ' /Q '.intval($pl['opt']['q']);
8678 }
8679 if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
8680 $annots .= ' /Opt [';
8681 foreach($pl['opt']['opt'] AS $copt) {
8682 if (is_array($copt)) {
8683 $annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
8684 } else {
8685 $annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8686 }
8687 }
8688 $annots .= ']';
8689 }
8690 if (isset($pl['opt']['ti'])) {
8691 $annots .= ' /TI '.intval($pl['opt']['ti']);
8692 }
8693 if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
8694 $annots .= ' /I [';
8695 foreach($pl['opt']['i'] AS $copt) {
8696 $annots .= intval($copt).' ';
8697 }
8698 $annots .= ']';
8699 }
8700 break;
8701 }
8702 case 'screen': {
8703 break;
8704 }
8705 case 'printermark': {
8706 break;
8707 }
8708 case 'trapnet': {
8709 break;
8710 }
8711 case 'watermark': {
8712 break;
8713 }
8714 case '3d': {
8715 break;
8716 }
8717 default: {
8718 break;
8719 }
8720 }
8721 $annots .= '>>';
8722 // create new annotation object
8723 $this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
8724 if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
8725 // store reference of form object
8726 $this->form_obj_id[] = $annot_obj_id;
8727 }
8728 }
8729 }
8730 } // end for each page
8731 }
8732
8733 /**
8734 * Put appearance streams XObject used to define annotation's appearance states.
8735 * @param $w (int) annotation width
8736 * @param $h (int) annotation height
8737 * @param $stream (string) appearance stream
8738 * @return int object ID
8739 * @protected
8740 * @since 4.8.001 (2009-09-09)
8741 */
8742 protected function _putAPXObject($w=0, $h=0, $stream='') {
8743 $stream = trim($stream);
8744 $out = $this->_getobj()."\n";
8745 $this->xobjects['AX'.$this->n] = array('n' => $this->n);
8746 $out .= '<<';
8747 $out .= ' /Type /XObject';
8748 $out .= ' /Subtype /Form';
8749 $out .= ' /FormType 1';
8750 if ($this->compress) {
8751 $stream = gzcompress($stream);
8752 $out .= ' /Filter /FlateDecode';
8753 }
8754 $rect = sprintf('%F %F', $w, $h);
8755 $out .= ' /BBox [0 0 '.$rect.']';
8756 $out .= ' /Matrix [1 0 0 1 0 0]';
8757 $out .= ' /Resources 2 0 R';
8758 $stream = $this->_getrawstream($stream);
8759 $out .= ' /Length '.strlen($stream);
8760 $out .= ' >>';
8761 $out .= ' stream'."\n".$stream."\n".'endstream';
8762 $out .= "\n".'endobj';
8763 $this->_out($out);
8764 return $this->n;
8765 }
8766
8767 /**
8768 * Output fonts.
8769 * @author Nicola Asuni
8770 * @protected
8771 */
8772 protected function _putfonts() {
8773 $nf = $this->n;
8774 foreach ($this->diffs as $diff) {
8775 //Encodings
8776 $this->_newobj();
8777 $this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
8778 }
8779 $mqr = TCPDF_STATIC::get_mqr();
8780 TCPDF_STATIC::set_mqr(false);
8781 foreach ($this->FontFiles as $file => $info) {
8782 // search and get font file to embedd
8783 $fontfile = TCPDF_FONTS::getFontFullPath($file, $info['fontdir']);
8784 if (!TCPDF_STATIC::empty_string($fontfile)) {
8785 $font = file_get_contents($fontfile);
8786 $compressed = (substr($file, -2) == '.z');
8787 if ((!$compressed) AND (isset($info['length2']))) {
8788 $header = (ord($font[0]) == 128);
8789 if ($header) {
8790 // strip first binary header
8791 $font = substr($font, 6);
8792 }
8793 if ($header AND (ord($font[$info['length1']]) == 128)) {
8794 // strip second binary header
8795 $font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
8796 }
8797 } elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
8798 if ($compressed) {
8799 // uncompress font
8800 $font = gzuncompress($font);
8801 }
8802 // merge subset characters
8803 $subsetchars = array(); // used chars
8804 foreach ($info['fontkeys'] as $fontkey) {
8805 $fontinfo = $this->getFontBuffer($fontkey);
8806 $subsetchars += $fontinfo['subsetchars'];
8807 }
8808 // rebuild a font subset
8809 $font = TCPDF_FONTS::_getTrueTypeFontSubset($font, $subsetchars);
8810 // calculate new font length
8811 $info['length1'] = strlen($font);
8812 if ($compressed) {
8813 // recompress font
8814 $font = gzcompress($font);
8815 }
8816 }
8817 $this->_newobj();
8818 $this->FontFiles[$file]['n'] = $this->n;
8819 $stream = $this->_getrawstream($font);
8820 $out = '<< /Length '.strlen($stream);
8821 if ($compressed) {
8822 $out .= ' /Filter /FlateDecode';
8823 }
8824 $out .= ' /Length1 '.$info['length1'];
8825 if (isset($info['length2'])) {
8826 $out .= ' /Length2 '.$info['length2'].' /Length3 0';
8827 }
8828 $out .= ' >>';
8829 $out .= ' stream'."\n".$stream."\n".'endstream';
8830 $out .= "\n".'endobj';
8831 $this->_out($out);
8832 }
8833 }
8834 TCPDF_STATIC::set_mqr($mqr);
8835 foreach ($this->fontkeys as $k) {
8836 //Font objects
8837 $font = $this->getFontBuffer($k);
8838 $type = $font['type'];
8839 $name = $font['name'];
8840 if ($type == 'core') {
8841 // standard core font
8842 $out = $this->_getobj($this->font_obj_ids[$k])."\n";
8843 $out .= '<</Type /Font';
8844 $out .= ' /Subtype /Type1';
8845 $out .= ' /BaseFont /'.$name;
8846 $out .= ' /Name /F'.$font['i'];
8847 if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
8848 $out .= ' /Encoding /WinAnsiEncoding';
8849 }
8850 if ($k == 'helvetica') {
8851 // add default font for annotations
8852 $this->annotation_fonts[$k] = $font['i'];
8853 }
8854 $out .= ' >>';
8855 $out .= "\n".'endobj';
8856 $this->_out($out);
8857 } elseif (($type == 'Type1') OR ($type == 'TrueType')) {
8858 // additional Type1 or TrueType font
8859 $out = $this->_getobj($this->font_obj_ids[$k])."\n";
8860 $out .= '<</Type /Font';
8861 $out .= ' /Subtype /'.$type;
8862 $out .= ' /BaseFont /'.$name;
8863 $out .= ' /Name /F'.$font['i'];
8864 $out .= ' /FirstChar 32 /LastChar 255';
8865 $out .= ' /Widths '.($this->n + 1).' 0 R';
8866 $out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
8867 if ($font['enc']) {
8868 if (isset($font['diff'])) {
8869 $out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
8870 } else {
8871 $out .= ' /Encoding /WinAnsiEncoding';
8872 }
8873 }
8874 $out .= ' >>';
8875 $out .= "\n".'endobj';
8876 $this->_out($out);
8877 // Widths
8878 $this->_newobj();
8879 $s = '[';
8880 for ($i = 32; $i < 256; ++$i) {
8881 if (isset($font['cw'][$i])) {
8882 $s .= $font['cw'][$i].' ';
8883 } else {
8884 $s .= $font['dw'].' ';
8885 }
8886 }
8887 $s .= ']';
8888 $s .= "\n".'endobj';
8889 $this->_out($s);
8890 //Descriptor
8891 $this->_newobj();
8892 $s = '<</Type /FontDescriptor /FontName /'.$name;
8893 foreach ($font['desc'] as $fdk => $fdv) {
8894 if (is_float($fdv)) {
8895 $fdv = sprintf('%F', $fdv);
8896 }
8897 $s .= ' /'.$fdk.' '.$fdv.'';
8898 }
8899 if (!TCPDF_STATIC::empty_string($font['file'])) {
8900 $s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
8901 }
8902 $s .= '>>';
8903 $s .= "\n".'endobj';
8904 $this->_out($s);
8905 } else {
8906 // additional types
8907 $mtd = '_put'.strtolower($type);
8908 if (!method_exists($this, $mtd)) {
8909 $this->Error('Unsupported font type: '.$type);
8910 }
8911 $this->$mtd($font);
8912 }
8913 }
8914 }
8915
8916 /**
8917 * Adds unicode fonts.<br>
8918 * Based on PDF Reference 1.3 (section 5)
8919 * @param $font (array) font data
8920 * @protected
8921 * @author Nicola Asuni
8922 * @since 1.52.0.TC005 (2005-01-05)
8923 */
8924 protected function _puttruetypeunicode($font) {
8925 $fontname = '';
8926 if ($font['subset']) {
8927 // change name for font subsetting
8928 $subtag = sprintf('%06u', $font['i']);
8929 $subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
8930 $fontname .= $subtag.'+';
8931 }
8932 $fontname .= $font['name'];
8933 // Type0 Font
8934 // A composite font composed of other fonts, organized hierarchically
8935 $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
8936 $out .= '<< /Type /Font';
8937 $out .= ' /Subtype /Type0';
8938 $out .= ' /BaseFont /'.$fontname;
8939 $out .= ' /Name /F'.$font['i'];
8940 $out .= ' /Encoding /'.$font['enc'];
8941 $out .= ' /ToUnicode '.($this->n + 1).' 0 R';
8942 $out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
8943 $out .= ' >>';
8944 $out .= "\n".'endobj';
8945 $this->_out($out);
8946 // ToUnicode map for Identity-H
8947 $stream = TCPDF_FONT_DATA::$uni_identity_h;
8948 // ToUnicode Object
8949 $this->_newobj();
8950 $stream = ($this->compress) ? gzcompress($stream) : $stream;
8951 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
8952 $stream = $this->_getrawstream($stream);
8953 $this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
8954 // CIDFontType2
8955 // A CIDFont whose glyph descriptions are based on TrueType font technology
8956 $oid = $this->_newobj();
8957 $out = '<< /Type /Font';
8958 $out .= ' /Subtype /CIDFontType2';
8959 $out .= ' /BaseFont /'.$fontname;
8960 // A dictionary containing entries that define the character collection of the CIDFont.
8961 $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
8962 $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
8963 $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
8964 $out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
8965 $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
8966 $out .= ' /DW '.$font['dw']; // default width
8967 $out .= "\n".TCPDF_FONTS::_putfontwidths($font, 0);
8968 if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
8969 $out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
8970 }
8971 $out .= ' >>';
8972 $out .= "\n".'endobj';
8973 $this->_out($out);
8974 // Font descriptor
8975 // A font descriptor describing the CIDFont default metrics other than its glyph widths
8976 $this->_newobj();
8977 $out = '<< /Type /FontDescriptor';
8978 $out .= ' /FontName /'.$fontname;
8979 foreach ($font['desc'] as $key => $value) {
8980 if (is_float($value)) {
8981 $value = sprintf('%F', $value);
8982 }
8983 $out .= ' /'.$key.' '.$value;
8984 }
8985 $fontdir = false;
8986 if (!TCPDF_STATIC::empty_string($font['file'])) {
8987 // A stream containing a TrueType font
8988 $out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
8989 $fontdir = $this->FontFiles[$font['file']]['fontdir'];
8990 }
8991 $out .= ' >>';
8992 $out .= "\n".'endobj';
8993 $this->_out($out);
8994 if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
8995 $this->_newobj();
8996 // Embed CIDToGIDMap
8997 // A specification of the mapping from CIDs to glyph indices
8998 // search and get CTG font file to embedd
8999 $ctgfile = strtolower($font['ctg']);
9000 // search and get ctg font file to embedd
9001 $fontfile = TCPDF_FONTS::getFontFullPath($ctgfile, $fontdir);
9002 if (TCPDF_STATIC::empty_string($fontfile)) {
9003 $this->Error('Font file not found: '.$ctgfile);
9004 }
9005 $stream = $this->_getrawstream(file_get_contents($fontfile));
9006 $out = '<< /Length '.strlen($stream).'';
9007 if (substr($fontfile, -2) == '.z') { // check file extension
9008 // Decompresses data encoded using the public-domain
9009 // zlib/deflate compression method, reproducing the
9010 // original text or binary data
9011 $out .= ' /Filter /FlateDecode';
9012 }
9013 $out .= ' >>';
9014 $out .= ' stream'."\n".$stream."\n".'endstream';
9015 $out .= "\n".'endobj';
9016 $this->_out($out);
9017 }
9018 }
9019
9020 /**
9021 * Output CID-0 fonts.
9022 * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
9023 * @param $font (array) font data
9024 * @protected
9025 * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
9026 * @since 3.2.000 (2008-06-23)
9027 */
9028 protected function _putcidfont0($font) {
9029 $cidoffset = 0;
9030 if (!isset($font['cw'][1])) {
9031 $cidoffset = 31;
9032 }
9033 if (isset($font['cidinfo']['uni2cid'])) {
9034 // convert unicode to cid.
9035 $uni2cid = $font['cidinfo']['uni2cid'];
9036 $cw = array();
9037 foreach ($font['cw'] as $uni => $width) {
9038 if (isset($uni2cid[$uni])) {
9039 $cw[($uni2cid[$uni] + $cidoffset)] = $width;
9040 } elseif ($uni < 256) {
9041 $cw[$uni] = $width;
9042 } // else unknown character
9043 }
9044 $font = array_merge($font, array('cw' => $cw));
9045 }
9046 $name = $font['name'];
9047 $enc = $font['enc'];
9048 if ($enc) {
9049 $longname = $name.'-'.$enc;
9050 } else {
9051 $longname = $name;
9052 }
9053 $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9054 $out .= '<</Type /Font';
9055 $out .= ' /Subtype /Type0';
9056 $out .= ' /BaseFont /'.$longname;
9057 $out .= ' /Name /F'.$font['i'];
9058 if ($enc) {
9059 $out .= ' /Encoding /'.$enc;
9060 }
9061 $out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
9062 $out .= ' >>';
9063 $out .= "\n".'endobj';
9064 $this->_out($out);
9065 $oid = $this->_newobj();
9066 $out = '<</Type /Font';
9067 $out .= ' /Subtype /CIDFontType0';
9068 $out .= ' /BaseFont /'.$name;
9069 $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9070 $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9071 $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9072 $out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
9073 $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9074 $out .= ' /DW '.$font['dw'];
9075 $out .= "\n".TCPDF_FONTS::_putfontwidths($font, $cidoffset);
9076 $out .= ' >>';
9077 $out .= "\n".'endobj';
9078 $this->_out($out);
9079 $this->_newobj();
9080 $s = '<</Type /FontDescriptor /FontName /'.$name;
9081 foreach ($font['desc'] as $k => $v) {
9082 if ($k != 'Style') {
9083 if (is_float($v)) {
9084 $v = sprintf('%F', $v);
9085 }
9086 $s .= ' /'.$k.' '.$v.'';
9087 }
9088 }
9089 $s .= '>>';
9090 $s .= "\n".'endobj';
9091 $this->_out($s);
9092 }
9093
9094 /**
9095 * Output images.
9096 * @protected
9097 */
9098 protected function _putimages() {
9099 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9100 foreach ($this->imagekeys as $file) {
9101 $info = $this->getImageBuffer($file);
9102 // set object for alternate images array
9103 if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
9104 $altoid = $this->_newobj();
9105 $out = '[';
9106 foreach ($info['altimgs'] as $altimage) {
9107 if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
9108 $out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
9109 $out .= ' /DefaultForPrinting';
9110 if ($altimage[1] === true) {
9111 $out .= ' true';
9112 } else {
9113 $out .= ' false';
9114 }
9115 $out .= ' >>';
9116 }
9117 }
9118 $out .= ' ]';
9119 $out .= "\n".'endobj';
9120 $this->_out($out);
9121 }
9122 // set image object
9123 $oid = $this->_newobj();
9124 $this->xobjects['I'.$info['i']] = array('n' => $oid);
9125 $this->setImageSubBuffer($file, 'n', $this->n);
9126 $out = '<</Type /XObject';
9127 $out .= ' /Subtype /Image';
9128 $out .= ' /Width '.$info['w'];
9129 $out .= ' /Height '.$info['h'];
9130 if (array_key_exists('masked', $info)) {
9131 $out .= ' /SMask '.($this->n - 1).' 0 R';
9132 }
9133 // set color space
9134 $icc = false;
9135 if (isset($info['icc']) AND ($info['icc'] !== false)) {
9136 // ICC Colour Space
9137 $icc = true;
9138 $out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
9139 } elseif ($info['cs'] == 'Indexed') {
9140 // Indexed Colour Space
9141 $out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
9142 } else {
9143 // Device Colour Space
9144 $out .= ' /ColorSpace /'.$info['cs'];
9145 }
9146 if ($info['cs'] == 'DeviceCMYK') {
9147 $out .= ' /Decode [1 0 1 0 1 0 1 0]';
9148 }
9149 $out .= ' /BitsPerComponent '.$info['bpc'];
9150 if (isset($altoid) AND ($altoid > 0)) {
9151 // reference to alternate images dictionary
9152 $out .= ' /Alternates '.$altoid.' 0 R';
9153 }
9154 if (isset($info['exurl']) AND !empty($info['exurl'])) {
9155 // external stream
9156 $out .= ' /Length 0';
9157 $out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
9158 if (isset($info['f'])) {
9159 $out .= ' /FFilter /'.$info['f'];
9160 }
9161 $out .= ' >>';
9162 $out .= ' stream'."\n".'endstream';
9163 } else {
9164 if (isset($info['f'])) {
9165 $out .= ' /Filter /'.$info['f'];
9166 }
9167 if (isset($info['parms'])) {
9168 $out .= ' '.$info['parms'];
9169 }
9170 if (isset($info['trns']) AND is_array($info['trns'])) {
9171 $trns = '';
9172 $count_info = count($info['trns']);
9173 if ($info['cs'] == 'Indexed') {
9174 $maxval =(pow(2, $info['bpc']) - 1);
9175 for ($i = 0; $i < $count_info; ++$i) {
9176 if (($info['trns'][$i] != 0) AND ($info['trns'][$i] != $maxval)) {
9177 // this is not a binary type mask @TODO: create a SMask
9178 $trns = '';
9179 break;
9180 } elseif (empty($trns) AND ($info['trns'][$i] == 0)) {
9181 // store the first fully transparent value
9182 $trns .= $i.' '.$i.' ';
9183 }
9184 }
9185 } else {
9186 // grayscale or RGB
9187 for ($i = 0; $i < $count_info; ++$i) {
9188 if ($info['trns'][$i] == 0) {
9189 $trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
9190 }
9191 }
9192 }
9193 // Colour Key Masking
9194 if (!empty($trns)) {
9195 $out .= ' /Mask ['.$trns.']';
9196 }
9197 }
9198 $stream = $this->_getrawstream($info['data']);
9199 $out .= ' /Length '.strlen($stream).' >>';
9200 $out .= ' stream'."\n".$stream."\n".'endstream';
9201 }
9202 $out .= "\n".'endobj';
9203 $this->_out($out);
9204 if ($icc) {
9205 // ICC colour profile
9206 $this->_newobj();
9207 $icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
9208 $icc = $this->_getrawstream($icc);
9209 $this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9210 } elseif ($info['cs'] == 'Indexed') {
9211 // colour palette
9212 $this->_newobj();
9213 $pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
9214 $pal = $this->_getrawstream($pal);
9215 $this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
9216 }
9217 }
9218 }
9219
9220 /**
9221 * Output Form XObjects Templates.
9222 * @author Nicola Asuni
9223 * @since 5.8.017 (2010-08-24)
9224 * @protected
9225 * @see startTemplate(), endTemplate(), printTemplate()
9226 */
9227 protected function _putxobjects() {
9228 foreach ($this->xobjects as $key => $data) {
9229 if (isset($data['outdata'])) {
9230 $stream = str_replace($this->epsmarker, '', trim($data['outdata']));
9231 $out = $this->_getobj($data['n'])."\n";
9232 $out .= '<<';
9233 $out .= ' /Type /XObject';
9234 $out .= ' /Subtype /Form';
9235 $out .= ' /FormType 1';
9236 if ($this->compress) {
9237 $stream = gzcompress($stream);
9238 $out .= ' /Filter /FlateDecode';
9239 }
9240 $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));
9241 $out .= ' /Matrix [1 0 0 1 0 0]';
9242 $out .= ' /Resources <<';
9243 $out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9244 if (!$this->pdfa_mode) {
9245 // transparency
9246 if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
9247 $out .= ' /ExtGState <<';
9248 foreach ($data['extgstates'] as $k => $extgstate) {
9249 if (isset($this->extgstates[$k]['name'])) {
9250 $out .= ' /'.$this->extgstates[$k]['name'];
9251 } else {
9252 $out .= ' /GS'.$k;
9253 }
9254 $out .= ' '.$this->extgstates[$k]['n'].' 0 R';
9255 }
9256 $out .= ' >>';
9257 }
9258 if (isset($data['gradients']) AND !empty($data['gradients'])) {
9259 $gp = '';
9260 $gs = '';
9261 foreach ($data['gradients'] as $id => $grad) {
9262 // gradient patterns
9263 $gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
9264 // gradient shadings
9265 $gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
9266 }
9267 $out .= ' /Pattern <<'.$gp.' >>';
9268 $out .= ' /Shading <<'.$gs.' >>';
9269 }
9270 }
9271 // spot colors
9272 if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
9273 $out .= ' /ColorSpace <<';
9274 foreach ($data['spot_colors'] as $name => $color) {
9275 $out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
9276 }
9277 $out .= ' >>';
9278 }
9279 // fonts
9280 if (!empty($data['fonts'])) {
9281 $out .= ' /Font <<';
9282 foreach ($data['fonts'] as $fontkey => $fontid) {
9283 $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9284 }
9285 $out .= ' >>';
9286 }
9287 // images or nested xobjects
9288 if (!empty($data['images']) OR !empty($data['xobjects'])) {
9289 $out .= ' /XObject <<';
9290 foreach ($data['images'] as $imgid) {
9291 $out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
9292 }
9293 foreach ($data['xobjects'] as $sub_id => $sub_objid) {
9294 $out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
9295 }
9296 $out .= ' >>';
9297 }
9298 $out .= ' >>'; //end resources
9299 if (isset($data['group']) AND ($data['group'] !== false)) {
9300 // set transparency group
9301 $out .= ' /Group << /Type /Group /S /Transparency';
9302 if (is_array($data['group'])) {
9303 if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
9304 $out .= ' /CS /'.$data['group']['CS'];
9305 }
9306 if (isset($data['group']['I'])) {
9307 $out .= ' /I /'.($data['group']['I']===true?'true':'false');
9308 }
9309 if (isset($data['group']['K'])) {
9310 $out .= ' /K /'.($data['group']['K']===true?'true':'false');
9311 }
9312 }
9313 $out .= ' >>';
9314 }
9315 $stream = $this->_getrawstream($stream, $data['n']);
9316 $out .= ' /Length '.strlen($stream);
9317 $out .= ' >>';
9318 $out .= ' stream'."\n".$stream."\n".'endstream';
9319 $out .= "\n".'endobj';
9320 $this->_out($out);
9321 }
9322 }
9323 }
9324
9325 /**
9326 * Output Spot Colors Resources.
9327 * @protected
9328 * @since 4.0.024 (2008-09-12)
9329 */
9330 protected function _putspotcolors() {
9331 foreach ($this->spot_colors as $name => $color) {
9332 $this->_newobj();
9333 $this->spot_colors[$name]['n'] = $this->n;
9334 $out = '[/Separation /'.str_replace(' ', '#20', $name);
9335 $out .= ' /DeviceCMYK <<';
9336 $out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
9337 $out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
9338 $out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
9339 $out .= "\n".'endobj';
9340 $this->_out($out);
9341 }
9342 }
9343
9344 /**
9345 * Return XObjects Dictionary.
9346 * @return string XObjects dictionary
9347 * @protected
9348 * @since 5.8.014 (2010-08-23)
9349 */
9350 protected function _getxobjectdict() {
9351 $out = '';
9352 foreach ($this->xobjects as $id => $objid) {
9353 $out .= ' /'.$id.' '.$objid['n'].' 0 R';
9354 }
9355 return $out;
9356 }
9357
9358 /**
9359 * Output Resources Dictionary.
9360 * @protected
9361 */
9362 protected function _putresourcedict() {
9363 $out = $this->_getobj(2)."\n";
9364 $out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9365 $out .= ' /Font <<';
9366 foreach ($this->fontkeys as $fontkey) {
9367 $font = $this->getFontBuffer($fontkey);
9368 $out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
9369 }
9370 $out .= ' >>';
9371 $out .= ' /XObject <<';
9372 $out .= $this->_getxobjectdict();
9373 $out .= ' >>';
9374 // layers
9375 if (!empty($this->pdflayers)) {
9376 $out .= ' /Properties <<';
9377 foreach ($this->pdflayers as $layer) {
9378 $out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
9379 }
9380 $out .= ' >>';
9381 }
9382 if (!$this->pdfa_mode) {
9383 // transparency
9384 if (isset($this->extgstates) AND !empty($this->extgstates)) {
9385 $out .= ' /ExtGState <<';
9386 foreach ($this->extgstates as $k => $extgstate) {
9387 if (isset($extgstate['name'])) {
9388 $out .= ' /'.$extgstate['name'];
9389 } else {
9390 $out .= ' /GS'.$k;
9391 }
9392 $out .= ' '.$extgstate['n'].' 0 R';
9393 }
9394 $out .= ' >>';
9395 }
9396 if (isset($this->gradients) AND !empty($this->gradients)) {
9397 $gp = '';
9398 $gs = '';
9399 foreach ($this->gradients as $id => $grad) {
9400 // gradient patterns
9401 $gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
9402 // gradient shadings
9403 $gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
9404 }
9405 $out .= ' /Pattern <<'.$gp.' >>';
9406 $out .= ' /Shading <<'.$gs.' >>';
9407 }
9408 }
9409 // spot colors
9410 if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
9411 $out .= ' /ColorSpace <<';
9412 foreach ($this->spot_colors as $color) {
9413 $out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
9414 }
9415 $out .= ' >>';
9416 }
9417 $out .= ' >>';
9418 $out .= "\n".'endobj';
9419 $this->_out($out);
9420 }
9421
9422 /**
9423 * Output Resources.
9424 * @protected
9425 */
9426 protected function _putresources() {
9427 $this->_putextgstates();
9428 $this->_putocg();
9429 $this->_putfonts();
9430 $this->_putimages();
9431 $this->_putspotcolors();
9432 $this->_putshaders();
9433 $this->_putxobjects();
9434 $this->_putresourcedict();
9435 $this->_putdests();
9436 $this->_putEmbeddedFiles();
9437 $this->_putannotsobjs();
9438 $this->_putjavascript();
9439 $this->_putbookmarks();
9440 $this->_putencryption();
9441 }
9442
9443 /**
9444 * Adds some Metadata information (Document Information Dictionary)
9445 * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
9446 * @return int object id
9447 * @protected
9448 */
9449 protected function _putinfo() {
9450 $oid = $this->_newobj();
9451 $out = '<<';
9452 // store current isunicode value
9453 $prev_isunicode = $this->isunicode;
9454 if ($this->docinfounicode) {
9455 $this->isunicode = true;
9456 }
9457 if (!TCPDF_STATIC::empty_string($this->title)) {
9458 // The document's title.
9459 $out .= ' /Title '.$this->_textstring($this->title, $oid);
9460 }
9461 if (!TCPDF_STATIC::empty_string($this->author)) {
9462 // The name of the person who created the document.
9463 $out .= ' /Author '.$this->_textstring($this->author, $oid);
9464 }
9465 if (!TCPDF_STATIC::empty_string($this->subject)) {
9466 // The subject of the document.
9467 $out .= ' /Subject '.$this->_textstring($this->subject, $oid);
9468 }
9469 if (!TCPDF_STATIC::empty_string($this->keywords)) {
9470 // Keywords associated with the document.
9471 $out .= ' /Keywords '.$this->_textstring($this->keywords, $oid);
9472 }
9473 if (!TCPDF_STATIC::empty_string($this->creator)) {
9474 // 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.
9475 $out .= ' /Creator '.$this->_textstring($this->creator, $oid);
9476 }
9477 // restore previous isunicode value
9478 $this->isunicode = $prev_isunicode;
9479 // default producer
9480 $out .= ' /Producer '.$this->_textstring(TCPDF_STATIC::getTCPDFProducer(), $oid);
9481 // The date and time the document was created, in human-readable form
9482 $out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
9483 // The date and time the document was most recently modified, in human-readable form
9484 $out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
9485 // A name object indicating whether the document has been modified to include trapping information
9486 $out .= ' /Trapped /False';
9487 $out .= ' >>';
9488 $out .= "\n".'endobj';
9489 $this->_out($out);
9490 return $oid;
9491 }
9492
9493 /**
9494 * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
9495 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9496 * @param $xmp (string) Custom XMP data.
9497 * @since 5.9.128 (2011-10-06)
9498 * @public
9499 */
9500 public function setExtraXMP($xmp) {
9501 $this->custom_xmp = $xmp;
9502 }
9503
9504 /**
9505 * Put XMP data object and return ID.
9506 * @return (int) The object ID.
9507 * @since 5.9.121 (2011-09-28)
9508 * @protected
9509 */
9510 protected function _putXMP() {
9511 $oid = $this->_newobj();
9512 // store current isunicode value
9513 $prev_isunicode = $this->isunicode;
9514 $this->isunicode = true;
9515 $prev_encrypted = $this->encrypted;
9516 $this->encrypted = false;
9517 // set XMP data
9518 $xmp = '<?xpacket begin="'.TCPDF_FONTS::unichr(0xfeff, $this->isunicode).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
9519 $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";
9520 $xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
9521 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
9522 $xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
9523 $xmp .= "\t\t\t".'<dc:title>'."\n";
9524 $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9525 $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->title).'</rdf:li>'."\n";
9526 $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9527 $xmp .= "\t\t\t".'</dc:title>'."\n";
9528 $xmp .= "\t\t\t".'<dc:creator>'."\n";
9529 $xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
9530 $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->author).'</rdf:li>'."\n";
9531 $xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
9532 $xmp .= "\t\t\t".'</dc:creator>'."\n";
9533 $xmp .= "\t\t\t".'<dc:description>'."\n";
9534 $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9535 $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->subject).'</rdf:li>'."\n";
9536 $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9537 $xmp .= "\t\t\t".'</dc:description>'."\n";
9538 $xmp .= "\t\t\t".'<dc:subject>'."\n";
9539 $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9540 $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->keywords).'</rdf:li>'."\n";
9541 $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9542 $xmp .= "\t\t\t".'</dc:subject>'."\n";
9543 $xmp .= "\t\t".'</rdf:Description>'."\n";
9544 // convert doc creation date format
9545 $dcdate = TCPDF_STATIC::getFormattedDate($this->doc_creation_timestamp);
9546 $doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
9547 $doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
9548 $doccreationdate .= substr($dcdate, 14, 3).':'.substr($dcdate, 18, 2);
9549 $doccreationdate = TCPDF_STATIC::_escapeXML($doccreationdate);
9550 // convert doc modification date format
9551 $dmdate = TCPDF_STATIC::getFormattedDate($this->doc_modification_timestamp);
9552 $docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
9553 $docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
9554 $docmoddate .= substr($dmdate, 14, 3).':'.substr($dmdate, 18, 2);
9555 $docmoddate = TCPDF_STATIC::_escapeXML($docmoddate);
9556 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
9557 $xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
9558 $xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
9559 $xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
9560 $xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
9561 $xmp .= "\t\t".'</rdf:Description>'."\n";
9562 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
9563 $xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC::_escapeXML($this->keywords).'</pdf:Keywords>'."\n";
9564 $xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC::_escapeXML(TCPDF_STATIC::getTCPDFProducer()).'</pdf:Producer>'."\n";
9565 $xmp .= "\t\t".'</rdf:Description>'."\n";
9566 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
9567 $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);
9568 $xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
9569 $xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
9570 $xmp .= "\t\t".'</rdf:Description>'."\n";
9571 if ($this->pdfa_mode) {
9572 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
9573 $xmp .= "\t\t\t".'<pdfaid:part>1</pdfaid:part>'."\n";
9574 $xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
9575 $xmp .= "\t\t".'</rdf:Description>'."\n";
9576 }
9577 // XMP extension schemas
9578 $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";
9579 $xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
9580 $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9581 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9582 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
9583 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
9584 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
9585 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9586 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9587 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
9588 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
9589 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
9590 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9591 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9592 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9593 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9594 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
9595 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9596 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9597 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9598 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9599 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9600 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9601 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9602 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
9603 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
9604 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
9605 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9606 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9607 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9608 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9609 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
9610 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
9611 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
9612 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9613 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9614 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9615 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
9616 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
9617 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9618 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9619 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9620 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9621 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
9622 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
9623 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9624 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9625 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9626 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9627 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9628 $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9629 $xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
9630 $xmp .= "\t\t".'</rdf:Description>'."\n";
9631 $xmp .= "\t".'</rdf:RDF>'."\n";
9632 $xmp .= $this->custom_xmp;
9633 $xmp .= '</x:xmpmeta>'."\n";
9634 $xmp .= '<?xpacket end="w"?>';
9635 $out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
9636 // restore previous isunicode value
9637 $this->isunicode = $prev_isunicode;
9638 $this->encrypted = $prev_encrypted;
9639 $this->_out($out);
9640 return $oid;
9641 }
9642
9643 /**
9644 * Output Catalog.
9645 * @return int object id
9646 * @protected
9647 */
9648 protected function _putcatalog() {
9649 // put XMP
9650 $xmpobj = $this->_putXMP();
9651 // if required, add standard sRGB_IEC61966-2.1 blackscaled ICC colour profile
9652 if ($this->pdfa_mode OR $this->force_srgb) {
9653 $iccobj = $this->_newobj();
9654 $icc = file_get_contents(dirname(__FILE__).'/include/sRGB.icc');
9655 $filter = '';
9656 if ($this->compress) {
9657 $filter = ' /Filter /FlateDecode';
9658 $icc = gzcompress($icc);
9659 }
9660 $icc = $this->_getrawstream($icc);
9661 $this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9662 }
9663 // start catalog
9664 $oid = $this->_newobj();
9665 $out = '<< /Type /Catalog';
9666 $out .= ' /Version /'.$this->PDFVersion;
9667 //$out .= ' /Extensions <<>>';
9668 $out .= ' /Pages 1 0 R';
9669 //$out .= ' /PageLabels ' //...;
9670 $out .= ' /Names <<';
9671 if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
9672 $out .= ' /JavaScript '.$this->n_js;
9673 }
9674 if (!empty($this->efnames)) {
9675 $out .= ' /EmbeddedFiles <</Names [';
9676 foreach ($this->efnames AS $fn => $fref) {
9677 $out .= ' '.$this->_datastring($fn).' '.$fref;
9678 }
9679 $out .= ' ]>>';
9680 }
9681 $out .= ' >>';
9682 if (!empty($this->dests)) {
9683 $out .= ' /Dests '.($this->n_dests).' 0 R';
9684 }
9685 $out .= $this->_putviewerpreferences();
9686 if (isset($this->LayoutMode) AND (!TCPDF_STATIC::empty_string($this->LayoutMode))) {
9687 $out .= ' /PageLayout /'.$this->LayoutMode;
9688 }
9689 if (isset($this->PageMode) AND (!TCPDF_STATIC::empty_string($this->PageMode))) {
9690 $out .= ' /PageMode /'.$this->PageMode;
9691 }
9692 if (count($this->outlines) > 0) {
9693 $out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
9694 $out .= ' /PageMode /UseOutlines';
9695 }
9696 //$out .= ' /Threads []';
9697 if ($this->ZoomMode == 'fullpage') {
9698 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
9699 } elseif ($this->ZoomMode == 'fullwidth') {
9700 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
9701 } elseif ($this->ZoomMode == 'real') {
9702 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
9703 } elseif (!is_string($this->ZoomMode)) {
9704 $out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
9705 }
9706 //$out .= ' /AA <<>>';
9707 //$out .= ' /URI <<>>';
9708 $out .= ' /Metadata '.$xmpobj.' 0 R';
9709 //$out .= ' /StructTreeRoot <<>>';
9710 //$out .= ' /MarkInfo <<>>';
9711 if (isset($this->l['a_meta_language'])) {
9712 $out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
9713 }
9714 //$out .= ' /SpiderInfo <<>>';
9715 // set OutputIntent to sRGB IEC61966-2.1 if required
9716 if ($this->pdfa_mode OR $this->force_srgb) {
9717 $out .= ' /OutputIntents [<<';
9718 $out .= ' /Type /OutputIntent';
9719 $out .= ' /S /GTS_PDFA1';
9720 $out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9721 $out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9722 $out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
9723 $out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9724 $out .= ' /DestOutputProfile '.$iccobj.' 0 R';
9725 $out .= ' >>]';
9726 }
9727 //$out .= ' /PieceInfo <<>>';
9728 if (!empty($this->pdflayers)) {
9729 $lyrobjs = '';
9730 $lyrobjs_off = '';
9731 $lyrobjs_lock = '';
9732 foreach ($this->pdflayers as $layer) {
9733 $layer_obj_ref = ' '.$layer['objid'].' 0 R';
9734 $lyrobjs .= $layer_obj_ref;
9735 if ($layer['view'] === false) {
9736 $lyrobjs_off .= $layer_obj_ref;
9737 }
9738 if ($layer['lock']) {
9739 $lyrobjs_lock .= $layer_obj_ref;
9740 }
9741 }
9742 $out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
9743 $out .= ' /D <<';
9744 $out .= ' /Name '.$this->_textstring('Layers', $oid);
9745 $out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
9746 $out .= ' /BaseState /ON';
9747 $out .= ' /OFF ['.$lyrobjs_off.']';
9748 $out .= ' /Locked ['.$lyrobjs_lock.']';
9749 $out .= ' /Intent /View';
9750 $out .= ' /AS [';
9751 $out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
9752 $out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
9753 $out .= ' ]';
9754 $out .= ' /Order ['.$lyrobjs.']';
9755 $out .= ' /ListMode /AllPages';
9756 //$out .= ' /RBGroups ['..']';
9757 //$out .= ' /Locked ['..']';
9758 $out .= ' >>';
9759 $out .= ' >>';
9760 }
9761 // AcroForm
9762 if (!empty($this->form_obj_id)
9763 OR ($this->sign AND isset($this->signature_data['cert_type']))
9764 OR !empty($this->empty_signature_appearance)) {
9765 $out .= ' /AcroForm <<';
9766 $objrefs = '';
9767 if ($this->sign AND isset($this->signature_data['cert_type'])) {
9768 // set reference for signature object
9769 $objrefs .= $this->sig_obj_id.' 0 R';
9770 }
9771 if (!empty($this->empty_signature_appearance)) {
9772 foreach ($this->empty_signature_appearance as $esa) {
9773 // set reference for empty signature objects
9774 $objrefs .= ' '.$esa['objid'].' 0 R';
9775 }
9776 }
9777 if (!empty($this->form_obj_id)) {
9778 foreach($this->form_obj_id as $objid) {
9779 $objrefs .= ' '.$objid.' 0 R';
9780 }
9781 }
9782 $out .= ' /Fields ['.$objrefs.']';
9783 // It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
9784 if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
9785 $out .= ' /NeedAppearances false';
9786 }
9787 if ($this->sign AND isset($this->signature_data['cert_type'])) {
9788 if ($this->signature_data['cert_type'] > 0) {
9789 $out .= ' /SigFlags 3';
9790 } else {
9791 $out .= ' /SigFlags 1';
9792 }
9793 }
9794 //$out .= ' /CO ';
9795 if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
9796 $out .= ' /DR <<';
9797 $out .= ' /Font <<';
9798 foreach ($this->annotation_fonts as $fontkey => $fontid) {
9799 $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9800 }
9801 $out .= ' >> >>';
9802 }
9803 $font = $this->getFontBuffer('helvetica');
9804 $out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
9805 $out .= ' /Q '.(($this->rtl)?'2':'0');
9806 //$out .= ' /XFA ';
9807 $out .= ' >>';
9808 // signatures
9809 if ($this->sign AND isset($this->signature_data['cert_type'])
9810 AND (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A'))) {
9811 if ($this->signature_data['cert_type'] > 0) {
9812 $out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
9813 } else {
9814 $out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
9815 }
9816 }
9817 }
9818 //$out .= ' /Legal <<>>';
9819 //$out .= ' /Requirements []';
9820 //$out .= ' /Collection <<>>';
9821 //$out .= ' /NeedsRendering true';
9822 $out .= ' >>';
9823 $out .= "\n".'endobj';
9824 $this->_out($out);
9825 return $oid;
9826 }
9827
9828 /**
9829 * Output viewer preferences.
9830 * @return string for viewer preferences
9831 * @author Nicola asuni
9832 * @since 3.1.000 (2008-06-09)
9833 * @protected
9834 */
9835 protected function _putviewerpreferences() {
9836 $vp = $this->viewer_preferences;
9837 $out = ' /ViewerPreferences <<';
9838 if ($this->rtl) {
9839 $out .= ' /Direction /R2L';
9840 } else {
9841 $out .= ' /Direction /L2R';
9842 }
9843 if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
9844 $out .= ' /HideToolbar true';
9845 }
9846 if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
9847 $out .= ' /HideMenubar true';
9848 }
9849 if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
9850 $out .= ' /HideWindowUI true';
9851 }
9852 if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
9853 $out .= ' /FitWindow true';
9854 }
9855 if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
9856 $out .= ' /CenterWindow true';
9857 }
9858 if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
9859 $out .= ' /DisplayDocTitle true';
9860 }
9861 if (isset($vp['NonFullScreenPageMode'])) {
9862 $out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
9863 }
9864 if (isset($vp['ViewArea'])) {
9865 $out .= ' /ViewArea /'.$vp['ViewArea'];
9866 }
9867 if (isset($vp['ViewClip'])) {
9868 $out .= ' /ViewClip /'.$vp['ViewClip'];
9869 }
9870 if (isset($vp['PrintArea'])) {
9871 $out .= ' /PrintArea /'.$vp['PrintArea'];
9872 }
9873 if (isset($vp['PrintClip'])) {
9874 $out .= ' /PrintClip /'.$vp['PrintClip'];
9875 }
9876 if (isset($vp['PrintScaling'])) {
9877 $out .= ' /PrintScaling /'.$vp['PrintScaling'];
9878 }
9879 if (isset($vp['Duplex']) AND (!TCPDF_STATIC::empty_string($vp['Duplex']))) {
9880 $out .= ' /Duplex /'.$vp['Duplex'];
9881 }
9882 if (isset($vp['PickTrayByPDFSize'])) {
9883 if ($vp['PickTrayByPDFSize']) {
9884 $out .= ' /PickTrayByPDFSize true';
9885 } else {
9886 $out .= ' /PickTrayByPDFSize false';
9887 }
9888 }
9889 if (isset($vp['PrintPageRange'])) {
9890 $PrintPageRangeNum = '';
9891 foreach ($vp['PrintPageRange'] as $k => $v) {
9892 $PrintPageRangeNum .= ' '.($v - 1).'';
9893 }
9894 $out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
9895 }
9896 if (isset($vp['NumCopies'])) {
9897 $out .= ' /NumCopies '.intval($vp['NumCopies']);
9898 }
9899 $out .= ' >>';
9900 return $out;
9901 }
9902
9903 /**
9904 * Output PDF File Header (7.5.2).
9905 * @protected
9906 */
9907 protected function _putheader() {
9908 $this->_out('%PDF-'.$this->PDFVersion);
9909 $this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
9910 }
9911
9912 /**
9913 * Output end of document (EOF).
9914 * @protected
9915 */
9916 protected function _enddoc() {
9917 if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
9918 // save subset chars of the previous font
9919 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
9920 }
9921 $this->state = 1;
9922 $this->_putheader();
9923 $this->_putpages();
9924 $this->_putresources();
9925 // empty signature fields
9926 if (!empty($this->empty_signature_appearance)) {
9927 foreach ($this->empty_signature_appearance as $key => $esa) {
9928 // widget annotation for empty signature
9929 $out = $this->_getobj($esa['objid'])."\n";
9930 $out .= '<< /Type /Annot';
9931 $out .= ' /Subtype /Widget';
9932 $out .= ' /Rect ['.$esa['rect'].']';
9933 $out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
9934 $out .= ' /F 4';
9935 $out .= ' /FT /Sig';
9936 $signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
9937 $out .= ' /T '.$this->_textstring($signame, $esa['objid']);
9938 $out .= ' /Ff 0';
9939 $out .= ' >>';
9940 $out .= "\n".'endobj';
9941 $this->_out($out);
9942 }
9943 }
9944 // Signature
9945 if ($this->sign AND isset($this->signature_data['cert_type'])) {
9946 // widget annotation for signature
9947 $out = $this->_getobj($this->sig_obj_id)."\n";
9948 $out .= '<< /Type /Annot';
9949 $out .= ' /Subtype /Widget';
9950 $out .= ' /Rect ['.$this->signature_appearance['rect'].']';
9951 $out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
9952 $out .= ' /F 4';
9953 $out .= ' /FT /Sig';
9954 $out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
9955 $out .= ' /Ff 0';
9956 $out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
9957 $out .= ' >>';
9958 $out .= "\n".'endobj';
9959 $this->_out($out);
9960 // signature
9961 $this->_putsignature();
9962 }
9963 // Info
9964 $objid_info = $this->_putinfo();
9965 // Catalog
9966 $objid_catalog = $this->_putcatalog();
9967 // Cross-ref
9968 $o = $this->bufferlen;
9969 // XREF section
9970 $this->_out('xref');
9971 $this->_out('0 '.($this->n + 1));
9972 $this->_out('0000000000 65535 f ');
9973 $freegen = ($this->n + 2);
9974 for ($i=1; $i <= $this->n; ++$i) {
9975 if (!isset($this->offsets[$i]) AND ($i > 1)) {
9976 $this->_out(sprintf('0000000000 %05d f ', $freegen));
9977 ++$freegen;
9978 } else {
9979 $this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
9980 }
9981 }
9982 // TRAILER
9983 $out = 'trailer'."\n";
9984 $out .= '<<';
9985 $out .= ' /Size '.($this->n + 1);
9986 $out .= ' /Root '.$objid_catalog.' 0 R';
9987 $out .= ' /Info '.$objid_info.' 0 R';
9988 if ($this->encrypted) {
9989 $out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
9990 }
9991 $out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
9992 $out .= ' >>';
9993 $this->_out($out);
9994 $this->_out('startxref');
9995 $this->_out($o);
9996 $this->_out('%%EOF');
9997 $this->state = 3; // end-of-doc
9998 }
9999
10000 /**
10001 * Initialize a new page.
10002 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
10003 * @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().
10004 * @protected
10005 * @see getPageSizeFromFormat(), setPageFormat()
10006 */
10007 protected function _beginpage($orientation='', $format='') {
10008 ++$this->page;
10009 $this->pageobjects[$this->page] = array();
10010 $this->setPageBuffer($this->page, '');
10011 // initialize array for graphics tranformation positions inside a page buffer
10012 $this->transfmrk[$this->page] = array();
10013 $this->state = 2;
10014 if (TCPDF_STATIC::empty_string($orientation)) {
10015 if (isset($this->CurOrientation)) {
10016 $orientation = $this->CurOrientation;
10017 } elseif ($this->fwPt > $this->fhPt) {
10018 // landscape
10019 $orientation = 'L';
10020 } else {
10021 // portrait
10022 $orientation = 'P';
10023 }
10024 }
10025 if (TCPDF_STATIC::empty_string($format)) {
10026 $this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10027 $this->setPageOrientation($orientation);
10028 } else {
10029 $this->setPageFormat($format, $orientation);
10030 }
10031 if ($this->rtl) {
10032 $this->x = $this->w - $this->rMargin;
10033 } else {
10034 $this->x = $this->lMargin;
10035 }
10036 $this->y = $this->tMargin;
10037 if (isset($this->newpagegroup[$this->page])) {
10038 // start a new group
10039 $this->currpagegroup = $this->newpagegroup[$this->page];
10040 $this->pagegroups[$this->currpagegroup] = 1;
10041 } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
10042 ++$this->pagegroups[$this->currpagegroup];
10043 }
10044 }
10045
10046 /**
10047 * Mark end of page.
10048 * @protected
10049 */
10050 protected function _endpage() {
10051 $this->setVisibility('all');
10052 $this->state = 1;
10053 }
10054
10055 /**
10056 * Begin a new object and return the object number.
10057 * @return int object number
10058 * @protected
10059 */
10060 protected function _newobj() {
10061 $this->_out($this->_getobj());
10062 return $this->n;
10063 }
10064
10065 /**
10066 * Return the starting object string for the selected object ID.
10067 * @param $objid (int) Object ID (leave empty to get a new ID).
10068 * @return string the starting object string
10069 * @protected
10070 * @since 5.8.009 (2010-08-20)
10071 */
10072 protected function _getobj($objid='') {
10073 if ($objid === '') {
10074 ++$this->n;
10075 $objid = $this->n;
10076 }
10077 $this->offsets[$objid] = $this->bufferlen;
10078 $this->pageobjects[$this->page][] = $objid;
10079 return $objid.' 0 obj';
10080 }
10081
10082 /**
10083 * Underline text.
10084 * @param $x (int) X coordinate
10085 * @param $y (int) Y coordinate
10086 * @param $txt (string) text to underline
10087 * @protected
10088 */
10089 protected function _dounderline($x, $y, $txt) {
10090 $w = $this->GetStringWidth($txt);
10091 return $this->_dounderlinew($x, $y, $w);
10092 }
10093
10094 /**
10095 * Underline for rectangular text area.
10096 * @param $x (int) X coordinate
10097 * @param $y (int) Y coordinate
10098 * @param $w (int) width to underline
10099 * @protected
10100 * @since 4.8.008 (2009-09-29)
10101 */
10102 protected function _dounderlinew($x, $y, $w) {
10103 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10104 return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
10105 }
10106
10107 /**
10108 * Line through text.
10109 * @param $x (int) X coordinate
10110 * @param $y (int) Y coordinate
10111 * @param $txt (string) text to linethrough
10112 * @protected
10113 */
10114 protected function _dolinethrough($x, $y, $txt) {
10115 $w = $this->GetStringWidth($txt);
10116 return $this->_dolinethroughw($x, $y, $w);
10117 }
10118
10119 /**
10120 * Line through for rectangular text area.
10121 * @param $x (int) X coordinate
10122 * @param $y (int) Y coordinate
10123 * @param $w (int) line length (width)
10124 * @protected
10125 * @since 4.9.008 (2009-09-29)
10126 */
10127 protected function _dolinethroughw($x, $y, $w) {
10128 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10129 return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
10130 }
10131
10132 /**
10133 * Overline text.
10134 * @param $x (int) X coordinate
10135 * @param $y (int) Y coordinate
10136 * @param $txt (string) text to overline
10137 * @protected
10138 * @since 4.9.015 (2010-04-19)
10139 */
10140 protected function _dooverline($x, $y, $txt) {
10141 $w = $this->GetStringWidth($txt);
10142 return $this->_dooverlinew($x, $y, $w);
10143 }
10144
10145 /**
10146 * Overline for rectangular text area.
10147 * @param $x (int) X coordinate
10148 * @param $y (int) Y coordinate
10149 * @param $w (int) width to overline
10150 * @protected
10151 * @since 4.9.015 (2010-04-19)
10152 */
10153 protected function _dooverlinew($x, $y, $w) {
10154 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10155 return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
10156
10157 }
10158
10159 /**
10160 * Format a data string for meta information
10161 * @param $s (string) data string to escape.
10162 * @param $n (int) object ID
10163 * @return string escaped string.
10164 * @protected
10165 */
10166 protected function _datastring($s, $n=0) {
10167 if ($n == 0) {
10168 $n = $this->n;
10169 }
10170 $s = $this->_encrypt_data($n, $s);
10171 return '('. TCPDF_STATIC::_escape($s).')';
10172 }
10173
10174 /**
10175 * Set the document creation timestamp
10176 * @param $time (mixed) Document creation timestamp in seconds or date-time string.
10177 * @public
10178 * @since 5.9.152 (2012-03-23)
10179 */
10180 public function setDocCreationTimestamp($time) {
10181 if (is_string($time)) {
10182 $time = TCPDF_STATIC::getTimestamp($time);
10183 }
10184 $this->doc_creation_timestamp = intval($time);
10185 }
10186
10187 /**
10188 * Set the document modification timestamp
10189 * @param $time (mixed) Document modification timestamp in seconds or date-time string.
10190 * @public
10191 * @since 5.9.152 (2012-03-23)
10192 */
10193 public function setDocModificationTimestamp($time) {
10194 if (is_string($time)) {
10195 $time = TCPDF_STATIC::getTimestamp($time);
10196 }
10197 $this->doc_modification_timestamp = intval($time);
10198 }
10199
10200 /**
10201 * Returns document creation timestamp in seconds.
10202 * @return (int) Creation timestamp in seconds.
10203 * @public
10204 * @since 5.9.152 (2012-03-23)
10205 */
10206 public function getDocCreationTimestamp() {
10207 return $this->doc_creation_timestamp;
10208 }
10209
10210 /**
10211 * Returns document modification timestamp in seconds.
10212 * @return (int) Modfication timestamp in seconds.
10213 * @public
10214 * @since 5.9.152 (2012-03-23)
10215 */
10216 public function getDocModificationTimestamp() {
10217 return $this->doc_modification_timestamp;
10218 }
10219
10220 /**
10221 * Returns a formatted date for meta information
10222 * @param $n (int) Object ID.
10223 * @param $timestamp (int) Timestamp to convert.
10224 * @return string escaped date string.
10225 * @protected
10226 * @since 4.6.028 (2009-08-25)
10227 */
10228 protected function _datestring($n=0, $timestamp=0) {
10229 if ((empty($timestamp)) OR ($timestamp < 0)) {
10230 $timestamp = $this->doc_creation_timestamp;
10231 }
10232 return $this->_datastring('D:'.TCPDF_STATIC::getFormattedDate($timestamp), $n);
10233 }
10234
10235 /**
10236 * Format a text string for meta information
10237 * @param $s (string) string to escape.
10238 * @param $n (int) object ID
10239 * @return string escaped string.
10240 * @protected
10241 */
10242 protected function _textstring($s, $n=0) {
10243 if ($this->isunicode) {
10244 //Convert string to UTF-16BE
10245 $s = TCPDF_FONTS::UTF8ToUTF16BE($s, true, $this->isunicode, $this->CurrentFont);
10246 }
10247 return $this->_datastring($s, $n);
10248 }
10249
10250 /**
10251 * get raw output stream.
10252 * @param $s (string) string to output.
10253 * @param $n (int) object reference for encryption mode
10254 * @protected
10255 * @author Nicola Asuni
10256 * @since 5.5.000 (2010-06-22)
10257 */
10258 protected function _getrawstream($s, $n=0) {
10259 if ($n <= 0) {
10260 // default to current object
10261 $n = $this->n;
10262 }
10263 return $this->_encrypt_data($n, $s);
10264 }
10265
10266 /**
10267 * Output a string to the document.
10268 * @param $s (string) string to output.
10269 * @protected
10270 */
10271 protected function _out($s) {
10272 if ($this->state == 2) {
10273 if ($this->inxobj) {
10274 // we are inside an XObject template
10275 $this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
10276 } elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
10277 // puts data before page footer
10278 $pagebuff = $this->getPageBuffer($this->page);
10279 $page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
10280 $footer = substr($pagebuff, -$this->footerlen[$this->page]);
10281 $this->setPageBuffer($this->page, $page.$s."\n".$footer);
10282 // update footer position
10283 $this->footerpos[$this->page] += strlen($s."\n");
10284 } else {
10285 // set page data
10286 $this->setPageBuffer($this->page, $s."\n", true);
10287 }
10288 } elseif ($this->state > 0) {
10289 // set general data
10290 $this->setBuffer($s."\n");
10291 }
10292 }
10293
10294 /**
10295 * Set header font.
10296 * @param $font (array) Array describing the basic font parameters: (family, style, size).
10297 * @public
10298 * @since 1.1
10299 */
10300 public function setHeaderFont($font) {
10301 $this->header_font = $font;
10302 }
10303
10304 /**
10305 * Get header font.
10306 * @return array() Array describing the basic font parameters: (family, style, size).
10307 * @public
10308 * @since 4.0.012 (2008-07-24)
10309 */
10310 public function getHeaderFont() {
10311 return $this->header_font;
10312 }
10313
10314 /**
10315 * Set footer font.
10316 * @param $font (array) Array describing the basic font parameters: (family, style, size).
10317 * @public
10318 * @since 1.1
10319 */
10320 public function setFooterFont($font) {
10321 $this->footer_font = $font;
10322 }
10323
10324 /**
10325 * Get Footer font.
10326 * @return array() Array describing the basic font parameters: (family, style, size).
10327 * @public
10328 * @since 4.0.012 (2008-07-24)
10329 */
10330 public function getFooterFont() {
10331 return $this->footer_font;
10332 }
10333
10334 /**
10335 * Set language array.
10336 * @param $language (array)
10337 * @public
10338 * @since 1.1
10339 */
10340 public function setLanguageArray($language) {
10341 $this->l = $language;
10342 if (isset($this->l['a_meta_dir'])) {
10343 $this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
10344 } else {
10345 $this->rtl = false;
10346 }
10347 }
10348
10349 /**
10350 * Returns the PDF data.
10351 * @public
10352 */
10353 public function getPDFData() {
10354 if ($this->state < 3) {
10355 $this->Close();
10356 }
10357 return $this->buffer;
10358 }
10359
10360 /**
10361 * Output anchor link.
10362 * @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;)
10363 * @param $name (string) link name
10364 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
10365 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
10366 * @param $color (array) array of RGB text color
10367 * @param $style (string) font style (U, D, B, I)
10368 * @param $firstblock (boolean) if true the string is the starting of a line.
10369 * @return the number of cells used or the remaining text if $firstline = true;
10370 * @public
10371 */
10372 public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
10373 if (isset($url[1]) AND ($url[0] == '#') AND is_numeric($url[1])) {
10374 // convert url to internal link
10375 $lnkdata = explode(',', $url);
10376 if (isset($lnkdata[0]) ) {
10377 $page = substr($lnkdata[0], 1);
10378 if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
10379 $lnky = floatval($lnkdata[1]);
10380 } else {
10381 $lnky = 0;
10382 }
10383 $url = $this->AddLink();
10384 $this->SetLink($url, $lnky, $page);
10385 }
10386 }
10387 // store current settings
10388 $prevcolor = $this->fgcolor;
10389 $prevstyle = $this->FontStyle;
10390 if (empty($color)) {
10391 $this->SetTextColorArray($this->htmlLinkColorArray);
10392 } else {
10393 $this->SetTextColorArray($color);
10394 }
10395 if ($style == -1) {
10396 $this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
10397 } else {
10398 $this->SetFont('', $this->FontStyle.$style);
10399 }
10400 $ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
10401 // restore settings
10402 $this->SetFont('', $prevstyle);
10403 $this->SetTextColorArray($prevcolor);
10404 return $ret;
10405 }
10406
10407 /**
10408 * Converts pixels to User's Units.
10409 * @param $px (int) pixels
10410 * @return float value in user's unit
10411 * @public
10412 * @see setImageScale(), getImageScale()
10413 */
10414 public function pixelsToUnits($px) {
10415 return ($px / ($this->imgscale * $this->k));
10416 }
10417
10418 /**
10419 * Reverse function for htmlentities.
10420 * Convert entities in UTF-8.
10421 * @param $text_to_convert (string) Text to convert.
10422 * @return string converted text string
10423 * @public
10424 */
10425 public function unhtmlentities($text_to_convert) {
10426 return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
10427 }
10428
10429 // ENCRYPTION METHODS ----------------------------------
10430
10431 /**
10432 * Compute encryption key depending on object number where the encrypted data is stored.
10433 * This is used for all strings and streams without crypt filter specifier.
10434 * @param $n (int) object number
10435 * @return int object key
10436 * @protected
10437 * @author Nicola Asuni
10438 * @since 2.0.000 (2008-01-02)
10439 */
10440 protected function _objectkey($n) {
10441 $objkey = $this->encryptdata['key'].pack('VXxx', $n);
10442 if ($this->encryptdata['mode'] == 2) { // AES-128
10443 // AES padding
10444 $objkey .= "\x73\x41\x6C\x54"; // sAlT
10445 }
10446 $objkey = substr(TCPDF_STATIC::_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
10447 $objkey = substr($objkey, 0, 16);
10448 return $objkey;
10449 }
10450
10451 /**
10452 * Encrypt the input string.
10453 * @param $n (int) object number
10454 * @param $s (string) data string to encrypt
10455 * @return encrypted string
10456 * @protected
10457 * @author Nicola Asuni
10458 * @since 5.0.005 (2010-05-11)
10459 */
10460 protected function _encrypt_data($n, $s) {
10461 if (!$this->encrypted) {
10462 return $s;
10463 }
10464 switch ($this->encryptdata['mode']) {
10465 case 0: // RC4-40
10466 case 1: { // RC4-128
10467 $s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
10468 break;
10469 }
10470 case 2: { // AES-128
10471 $s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
10472 break;
10473 }
10474 case 3: { // AES-256
10475 $s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
10476 break;
10477 }
10478 }
10479 return $s;
10480 }
10481
10482 /**
10483 * Put encryption on PDF document.
10484 * @protected
10485 * @author Nicola Asuni
10486 * @since 2.0.000 (2008-01-02)
10487 */
10488 protected function _putencryption() {
10489 if (!$this->encrypted) {
10490 return;
10491 }
10492 $this->encryptdata['objid'] = $this->_newobj();
10493 $out = '<<';
10494 if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
10495 $this->encryptdata['Filter'] = 'Standard';
10496 }
10497 $out .= ' /Filter /'.$this->encryptdata['Filter'];
10498 if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
10499 $out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
10500 }
10501 if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
10502 $this->encryptdata['V'] = 1;
10503 }
10504 // V is a code specifying the algorithm to be used in encrypting and decrypting the document
10505 $out .= ' /V '.$this->encryptdata['V'];
10506 if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
10507 // The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
10508 $out .= ' /Length '.$this->encryptdata['Length'];
10509 } else {
10510 $out .= ' /Length 40';
10511 }
10512 if ($this->encryptdata['V'] >= 4) {
10513 if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
10514 $this->encryptdata['StmF'] = 'Identity';
10515 }
10516 if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
10517 // The name of the crypt filter that shall be used when decrypting all strings in the document.
10518 $this->encryptdata['StrF'] = 'Identity';
10519 }
10520 // A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
10521 if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
10522 $out .= ' /CF <<';
10523 $out .= ' /'.$this->encryptdata['StmF'].' <<';
10524 $out .= ' /Type /CryptFilter';
10525 if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
10526 // The method used
10527 $out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
10528 if ($this->encryptdata['pubkey']) {
10529 $out .= ' /Recipients [';
10530 foreach ($this->encryptdata['Recipients'] as $rec) {
10531 $out .= ' <'.$rec.'>';
10532 }
10533 $out .= ' ]';
10534 if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
10535 $out .= ' /EncryptMetadata false';
10536 } else {
10537 $out .= ' /EncryptMetadata true';
10538 }
10539 }
10540 } else {
10541 $out .= ' /CFM /None';
10542 }
10543 if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
10544 // The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
10545 $out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
10546 } else {
10547 $out .= ' /AuthEvent /DocOpen';
10548 }
10549 if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
10550 // The bit length of the encryption key.
10551 $out .= ' /Length '.$this->encryptdata['CF']['Length'];
10552 }
10553 $out .= ' >> >>';
10554 }
10555 // The name of the crypt filter that shall be used by default when decrypting streams.
10556 $out .= ' /StmF /'.$this->encryptdata['StmF'];
10557 // The name of the crypt filter that shall be used when decrypting all strings in the document.
10558 $out .= ' /StrF /'.$this->encryptdata['StrF'];
10559 if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
10560 // The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
10561 $out .= ' /EFF /'.$this->encryptdata[''];
10562 }
10563 }
10564 // Additional encryption dictionary entries for the standard security handler
10565 if ($this->encryptdata['pubkey']) {
10566 if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
10567 $out .= ' /Recipients [';
10568 foreach ($this->encryptdata['Recipients'] as $rec) {
10569 $out .= ' <'.$rec.'>';
10570 }
10571 $out .= ' ]';
10572 }
10573 } else {
10574 $out .= ' /R';
10575 if ($this->encryptdata['V'] == 5) { // AES-256
10576 $out .= ' 5';
10577 $out .= ' /OE ('.TCPDF_STATIC::_escape($this->encryptdata['OE']).')';
10578 $out .= ' /UE ('.TCPDF_STATIC::_escape($this->encryptdata['UE']).')';
10579 $out .= ' /Perms ('.TCPDF_STATIC::_escape($this->encryptdata['perms']).')';
10580 } elseif ($this->encryptdata['V'] == 4) { // AES-128
10581 $out .= ' 4';
10582 } elseif ($this->encryptdata['V'] < 2) { // RC-40
10583 $out .= ' 2';
10584 } else { // RC-128
10585 $out .= ' 3';
10586 }
10587 $out .= ' /O ('.TCPDF_STATIC::_escape($this->encryptdata['O']).')';
10588 $out .= ' /U ('.TCPDF_STATIC::_escape($this->encryptdata['U']).')';
10589 $out .= ' /P '.$this->encryptdata['P'];
10590 if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
10591 $out .= ' /EncryptMetadata false';
10592 } else {
10593 $out .= ' /EncryptMetadata true';
10594 }
10595 }
10596 $out .= ' >>';
10597 $out .= "\n".'endobj';
10598 $this->_out($out);
10599 }
10600
10601 /**
10602 * Compute U value (used for encryption)
10603 * @return string U value
10604 * @protected
10605 * @since 2.0.000 (2008-01-02)
10606 * @author Nicola Asuni
10607 */
10608 protected function _Uvalue() {
10609 if ($this->encryptdata['mode'] == 0) { // RC4-40
10610 return TCPDF_STATIC::_RC4($this->encryptdata['key'], TCPDF_STATIC::$enc_padding, $this->last_enc_key, $this->last_enc_key_c);
10611 } elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
10612 $tmp = TCPDF_STATIC::_md5_16(TCPDF_STATIC::$enc_padding.$this->encryptdata['fileid']);
10613 $enc = TCPDF_STATIC::_RC4($this->encryptdata['key'], $tmp, $this->last_enc_key, $this->last_enc_key_c);
10614 $len = strlen($tmp);
10615 for ($i = 1; $i <= 19; ++$i) {
10616 $ek = '';
10617 for ($j = 0; $j < $len; ++$j) {
10618 $ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
10619 }
10620 $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10621 }
10622 $enc .= str_repeat("\x00", 16);
10623 return substr($enc, 0, 32);
10624 } elseif ($this->encryptdata['mode'] == 3) { // AES-256
10625 $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10626 // User Validation Salt
10627 $this->encryptdata['UVS'] = substr($seed, 0, 8);
10628 // User Key Salt
10629 $this->encryptdata['UKS'] = substr($seed, 8, 16);
10630 return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
10631 }
10632 }
10633
10634 /**
10635 * Compute UE value (used for encryption)
10636 * @return string UE value
10637 * @protected
10638 * @since 5.9.006 (2010-10-19)
10639 * @author Nicola Asuni
10640 */
10641 protected function _UEvalue() {
10642 $hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
10643 return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10644 }
10645
10646 /**
10647 * Compute O value (used for encryption)
10648 * @return string O value
10649 * @protected
10650 * @since 2.0.000 (2008-01-02)
10651 * @author Nicola Asuni
10652 */
10653 protected function _Ovalue() {
10654 if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
10655 $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['owner_password']);
10656 if ($this->encryptdata['mode'] > 0) {
10657 for ($i = 0; $i < 50; ++$i) {
10658 $tmp = TCPDF_STATIC::_md5_16($tmp);
10659 }
10660 }
10661 $owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
10662 $enc = TCPDF_STATIC::_RC4($owner_key, $this->encryptdata['user_password'], $this->last_enc_key, $this->last_enc_key_c);
10663 if ($this->encryptdata['mode'] > 0) {
10664 $len = strlen($owner_key);
10665 for ($i = 1; $i <= 19; ++$i) {
10666 $ek = '';
10667 for ($j = 0; $j < $len; ++$j) {
10668 $ek .= chr(ord($owner_key[$j]) ^ $i);
10669 }
10670 $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10671 }
10672 }
10673 return $enc;
10674 } elseif ($this->encryptdata['mode'] == 3) { // AES-256
10675 $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10676 // Owner Validation Salt
10677 $this->encryptdata['OVS'] = substr($seed, 0, 8);
10678 // Owner Key Salt
10679 $this->encryptdata['OKS'] = substr($seed, 8, 16);
10680 return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
10681 }
10682 }
10683
10684 /**
10685 * Compute OE value (used for encryption)
10686 * @return string OE value
10687 * @protected
10688 * @since 5.9.006 (2010-10-19)
10689 * @author Nicola Asuni
10690 */
10691 protected function _OEvalue() {
10692 $hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
10693 return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10694 }
10695
10696 /**
10697 * Convert password for AES-256 encryption mode
10698 * @param $password (string) password
10699 * @return string password
10700 * @protected
10701 * @since 5.9.006 (2010-10-19)
10702 * @author Nicola Asuni
10703 */
10704 protected function _fixAES256Password($password) {
10705 $psw = ''; // password to be returned
10706 $psw_array = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($password, $this->isunicode, $this->CurrentFont), $password, $this->rtl, $this->isunicode, $this->CurrentFont);
10707 foreach ($psw_array as $c) {
10708 $psw .= TCPDF_FONTS::unichr($c, $this->isunicode);
10709 }
10710 return substr($psw, 0, 127);
10711 }
10712
10713 /**
10714 * Compute encryption key
10715 * @protected
10716 * @since 2.0.000 (2008-01-02)
10717 * @author Nicola Asuni
10718 */
10719 protected function _generateencryptionkey() {
10720 $keybytelen = ($this->encryptdata['Length'] / 8);
10721 if (!$this->encryptdata['pubkey']) { // standard mode
10722 if ($this->encryptdata['mode'] == 3) { // AES-256
10723 // generate 256 bit random key
10724 $this->encryptdata['key'] = substr(hash('sha256', TCPDF_STATIC::getRandomSeed(), true), 0, $keybytelen);
10725 // truncate passwords
10726 $this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
10727 $this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
10728 // Compute U value
10729 $this->encryptdata['U'] = $this->_Uvalue();
10730 // Compute UE value
10731 $this->encryptdata['UE'] = $this->_UEvalue();
10732 // Compute O value
10733 $this->encryptdata['O'] = $this->_Ovalue();
10734 // Compute OE value
10735 $this->encryptdata['OE'] = $this->_OEvalue();
10736 // Compute P value
10737 $this->encryptdata['P'] = $this->encryptdata['protection'];
10738 // Computing the encryption dictionary's Perms (permissions) value
10739 $perms = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
10740 $perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
10741 if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
10742 $perms .= 'F';
10743 } else {
10744 $perms .= 'T';
10745 }
10746 $perms .= 'adb'; // bytes 9-11
10747 $perms .= 'nick'; // bytes 12-15
10748 $this->encryptdata['perms'] = TCPDF_STATIC::_AESnopad($this->encryptdata['key'], $perms);
10749 } else { // RC4-40, RC4-128, AES-128
10750 // Pad passwords
10751 $this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10752 $this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10753 // Compute O value
10754 $this->encryptdata['O'] = $this->_Ovalue();
10755 // get default permissions (reverse byte order)
10756 $permissions = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']);
10757 // Compute encryption key
10758 $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
10759 if ($this->encryptdata['mode'] > 0) {
10760 for ($i = 0; $i < 50; ++$i) {
10761 $tmp = TCPDF_STATIC::_md5_16(substr($tmp, 0, $keybytelen));
10762 }
10763 }
10764 $this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
10765 // Compute U value
10766 $this->encryptdata['U'] = $this->_Uvalue();
10767 // Compute P value
10768 $this->encryptdata['P'] = $this->encryptdata['protection'];
10769 }
10770 } else { // Public-Key mode
10771 // random 20-byte seed
10772 $seed = sha1(TCPDF_STATIC::getRandomSeed(), true);
10773 $recipient_bytes = '';
10774 foreach ($this->encryptdata['pubkeys'] as $pubkey) {
10775 // for each public certificate
10776 if (isset($pubkey['p'])) {
10777 $pkprotection = TCPDF_STATIC::getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
10778 } else {
10779 $pkprotection = $this->encryptdata['protection'];
10780 }
10781 // get default permissions (reverse byte order)
10782 $pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
10783 // envelope data
10784 $envelope = $seed.$pkpermissions;
10785 // write the envelope data to a temporary file
10786 $tempkeyfile = TCPDF_STATIC::getObjFilename('key', $this->file_id);
10787 $f = TCPDF_STATIC::fopenLocal($tempkeyfile, 'wb');
10788 if (!$f) {
10789 $this->Error('Unable to create temporary key file: '.$tempkeyfile);
10790 }
10791 $envelope_length = strlen($envelope);
10792 fwrite($f, $envelope, $envelope_length);
10793 fclose($f);
10794 $tempencfile = TCPDF_STATIC::getObjFilename('enc', $this->file_id);
10795 if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
10796 $this->Error('Unable to encrypt the file: '.$tempkeyfile);
10797 }
10798 // read encryption signature
10799 $signature = file_get_contents($tempencfile, false, null, $envelope_length);
10800 // extract signature
10801 $signature = substr($signature, strpos($signature, 'Content-Disposition'));
10802 $tmparr = explode("\n\n", $signature);
10803 $signature = trim($tmparr[1]);
10804 unset($tmparr);
10805 // decode signature
10806 $signature = base64_decode($signature);
10807 // convert signature to hex
10808 $hexsignature = current(unpack('H*', $signature));
10809 // store signature on recipients array
10810 $this->encryptdata['Recipients'][] = $hexsignature;
10811 // The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
10812 $recipient_bytes .= $signature;
10813 }
10814 // calculate encryption key
10815 if ($this->encryptdata['mode'] == 3) { // AES-256
10816 $this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
10817 } else { // RC4-40, RC4-128, AES-128
10818 $this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
10819 }
10820 }
10821 }
10822
10823 /**
10824 * Set document protection
10825 * Remark: the protection against modification is for people who have the full Acrobat product.
10826 * 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.
10827 * 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.
10828 * @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>
10829 * @param $user_pass (String) user password. Empty by default.
10830 * @param $owner_pass (String) owner password. If not specified, a random value is used.
10831 * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
10832 * @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')))
10833 * @public
10834 * @since 2.0.000 (2008-01-02)
10835 * @author Nicola Asuni
10836 */
10837 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) {
10838 if ($this->pdfa_mode) {
10839 // encryption is not allowed in PDF/A mode
10840 return;
10841 }
10842 $this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
10843 if (($pubkeys !== null) AND (is_array($pubkeys))) {
10844 // public-key mode
10845 $this->encryptdata['pubkeys'] = $pubkeys;
10846 if ($mode == 0) {
10847 // public-Key Security requires at least 128 bit
10848 $mode = 1;
10849 }
10850 if (!function_exists('openssl_pkcs7_encrypt')) {
10851 $this->Error('Public-Key Security requires openssl library.');
10852 }
10853 // Set Public-Key filter (available are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
10854 $this->encryptdata['pubkey'] = true;
10855 $this->encryptdata['Filter'] = 'Adobe.PubSec';
10856 $this->encryptdata['StmF'] = 'DefaultCryptFilter';
10857 $this->encryptdata['StrF'] = 'DefaultCryptFilter';
10858 } else {
10859 // standard mode (password mode)
10860 $this->encryptdata['pubkey'] = false;
10861 $this->encryptdata['Filter'] = 'Standard';
10862 $this->encryptdata['StmF'] = 'StdCF';
10863 $this->encryptdata['StrF'] = 'StdCF';
10864 }
10865 if ($mode > 1) { // AES
10866 if (!extension_loaded('openssl') && !extension_loaded('mcrypt')) {
10867 $this->Error('AES encryption requires openssl or mcrypt extension (http://www.php.net/manual/en/mcrypt.requirements.php).');
10868 }
10869 if (extension_loaded('openssl') && !in_array('aes-256-cbc', openssl_get_cipher_methods())) {
10870 $this->Error('AES encryption requires openssl/aes-256-cbc cypher.');
10871 }
10872 if (extension_loaded('mcrypt') && mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
10873 $this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
10874 }
10875 if (($mode == 3) AND !function_exists('hash')) {
10876 // the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
10877 $this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
10878 }
10879 }
10880 if ($owner_pass === null) {
10881 $owner_pass = md5(TCPDF_STATIC::getRandomSeed());
10882 }
10883 $this->encryptdata['user_password'] = $user_pass;
10884 $this->encryptdata['owner_password'] = $owner_pass;
10885 $this->encryptdata['mode'] = $mode;
10886 switch ($mode) {
10887 case 0: { // RC4 40 bit
10888 $this->encryptdata['V'] = 1;
10889 $this->encryptdata['Length'] = 40;
10890 $this->encryptdata['CF']['CFM'] = 'V2';
10891 break;
10892 }
10893 case 1: { // RC4 128 bit
10894 $this->encryptdata['V'] = 2;
10895 $this->encryptdata['Length'] = 128;
10896 $this->encryptdata['CF']['CFM'] = 'V2';
10897 if ($this->encryptdata['pubkey']) {
10898 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
10899 $this->encryptdata['Recipients'] = array();
10900 }
10901 break;
10902 }
10903 case 2: { // AES 128 bit
10904 $this->encryptdata['V'] = 4;
10905 $this->encryptdata['Length'] = 128;
10906 $this->encryptdata['CF']['CFM'] = 'AESV2';
10907 $this->encryptdata['CF']['Length'] = 128;
10908 if ($this->encryptdata['pubkey']) {
10909 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
10910 $this->encryptdata['Recipients'] = array();
10911 }
10912 break;
10913 }
10914 case 3: { // AES 256 bit
10915 $this->encryptdata['V'] = 5;
10916 $this->encryptdata['Length'] = 256;
10917 $this->encryptdata['CF']['CFM'] = 'AESV3';
10918 $this->encryptdata['CF']['Length'] = 256;
10919 if ($this->encryptdata['pubkey']) {
10920 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
10921 $this->encryptdata['Recipients'] = array();
10922 }
10923 break;
10924 }
10925 }
10926 $this->encrypted = true;
10927 $this->encryptdata['fileid'] = TCPDF_STATIC::convertHexStringToString($this->file_id);
10928 $this->_generateencryptionkey();
10929 }
10930
10931 // END OF ENCRYPTION FUNCTIONS -------------------------
10932
10933 // START TRANSFORMATIONS SECTION -----------------------
10934
10935 /**
10936 * Starts a 2D tranformation saving current graphic state.
10937 * This function must be called before scaling, mirroring, translation, rotation and skewing.
10938 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
10939 * @public
10940 * @since 2.1.000 (2008-01-07)
10941 * @see StartTransform(), StopTransform()
10942 */
10943 public function StartTransform() {
10944 if ($this->state != 2) {
10945 return;
10946 }
10947 $this->_outSaveGraphicsState();
10948 if ($this->inxobj) {
10949 // we are inside an XObject template
10950 $this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
10951 } else {
10952 $this->transfmrk[$this->page][] = $this->pagelen[$this->page];
10953 }
10954 ++$this->transfmatrix_key;
10955 $this->transfmatrix[$this->transfmatrix_key] = array();
10956 }
10957
10958 /**
10959 * Stops a 2D tranformation restoring previous graphic state.
10960 * This function must be called after scaling, mirroring, translation, rotation and skewing.
10961 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
10962 * @public
10963 * @since 2.1.000 (2008-01-07)
10964 * @see StartTransform(), StopTransform()
10965 */
10966 public function StopTransform() {
10967 if ($this->state != 2) {
10968 return;
10969 }
10970 $this->_outRestoreGraphicsState();
10971 if (isset($this->transfmatrix[$this->transfmatrix_key])) {
10972 array_pop($this->transfmatrix[$this->transfmatrix_key]);
10973 --$this->transfmatrix_key;
10974 }
10975 if ($this->inxobj) {
10976 // we are inside an XObject template
10977 array_pop($this->xobjects[$this->xobjid]['transfmrk']);
10978 } else {
10979 array_pop($this->transfmrk[$this->page]);
10980 }
10981 }
10982 /**
10983 * Horizontal Scaling.
10984 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
10985 * @param $x (int) abscissa of the scaling center. Default is current x position
10986 * @param $y (int) ordinate of the scaling center. Default is current y position
10987 * @public
10988 * @since 2.1.000 (2008-01-07)
10989 * @see StartTransform(), StopTransform()
10990 */
10991 public function ScaleX($s_x, $x='', $y='') {
10992 $this->Scale($s_x, 100, $x, $y);
10993 }
10994
10995 /**
10996 * Vertical Scaling.
10997 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
10998 * @param $x (int) abscissa of the scaling center. Default is current x position
10999 * @param $y (int) ordinate of the scaling center. Default is current y position
11000 * @public
11001 * @since 2.1.000 (2008-01-07)
11002 * @see StartTransform(), StopTransform()
11003 */
11004 public function ScaleY($s_y, $x='', $y='') {
11005 $this->Scale(100, $s_y, $x, $y);
11006 }
11007
11008 /**
11009 * Vertical and horizontal proportional Scaling.
11010 * @param $s (float) scaling factor for width and height as percent. 0 is not allowed.
11011 * @param $x (int) abscissa of the scaling center. Default is current x position
11012 * @param $y (int) ordinate of the scaling center. Default is current y position
11013 * @public
11014 * @since 2.1.000 (2008-01-07)
11015 * @see StartTransform(), StopTransform()
11016 */
11017 public function ScaleXY($s, $x='', $y='') {
11018 $this->Scale($s, $s, $x, $y);
11019 }
11020
11021 /**
11022 * Vertical and horizontal non-proportional Scaling.
11023 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
11024 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
11025 * @param $x (int) abscissa of the scaling center. Default is current x position
11026 * @param $y (int) ordinate of the scaling center. Default is current y position
11027 * @public
11028 * @since 2.1.000 (2008-01-07)
11029 * @see StartTransform(), StopTransform()
11030 */
11031 public function Scale($s_x, $s_y, $x='', $y='') {
11032 if ($x === '') {
11033 $x = $this->x;
11034 }
11035 if ($y === '') {
11036 $y = $this->y;
11037 }
11038 if (($s_x == 0) OR ($s_y == 0)) {
11039 $this->Error('Please do not use values equal to zero for scaling');
11040 }
11041 $y = ($this->h - $y) * $this->k;
11042 $x *= $this->k;
11043 //calculate elements of transformation matrix
11044 $s_x /= 100;
11045 $s_y /= 100;
11046 $tm = array();
11047 $tm[0] = $s_x;
11048 $tm[1] = 0;
11049 $tm[2] = 0;
11050 $tm[3] = $s_y;
11051 $tm[4] = $x * (1 - $s_x);
11052 $tm[5] = $y * (1 - $s_y);
11053 //scale the coordinate system
11054 $this->Transform($tm);
11055 }
11056
11057 /**
11058 * Horizontal Mirroring.
11059 * @param $x (int) abscissa of the point. Default is current x position
11060 * @public
11061 * @since 2.1.000 (2008-01-07)
11062 * @see StartTransform(), StopTransform()
11063 */
11064 public function MirrorH($x='') {
11065 $this->Scale(-100, 100, $x);
11066 }
11067
11068 /**
11069 * Verical Mirroring.
11070 * @param $y (int) ordinate of the point. Default is current y position
11071 * @public
11072 * @since 2.1.000 (2008-01-07)
11073 * @see StartTransform(), StopTransform()
11074 */
11075 public function MirrorV($y='') {
11076 $this->Scale(100, -100, '', $y);
11077 }
11078
11079 /**
11080 * Point reflection mirroring.
11081 * @param $x (int) abscissa of the point. Default is current x position
11082 * @param $y (int) ordinate of the point. Default is current y position
11083 * @public
11084 * @since 2.1.000 (2008-01-07)
11085 * @see StartTransform(), StopTransform()
11086 */
11087 public function MirrorP($x='',$y='') {
11088 $this->Scale(-100, -100, $x, $y);
11089 }
11090
11091 /**
11092 * Reflection against a straight line through point (x, y) with the gradient angle (angle).
11093 * @param $angle (float) gradient angle of the straight line. Default is 0 (horizontal line).
11094 * @param $x (int) abscissa of the point. Default is current x position
11095 * @param $y (int) ordinate of the point. Default is current y position
11096 * @public
11097 * @since 2.1.000 (2008-01-07)
11098 * @see StartTransform(), StopTransform()
11099 */
11100 public function MirrorL($angle=0, $x='',$y='') {
11101 $this->Scale(-100, 100, $x, $y);
11102 $this->Rotate(-2*($angle-90), $x, $y);
11103 }
11104
11105 /**
11106 * Translate graphic object horizontally.
11107 * @param $t_x (int) movement to the right (or left for RTL)
11108 * @public
11109 * @since 2.1.000 (2008-01-07)
11110 * @see StartTransform(), StopTransform()
11111 */
11112 public function TranslateX($t_x) {
11113 $this->Translate($t_x, 0);
11114 }
11115
11116 /**
11117 * Translate graphic object vertically.
11118 * @param $t_y (int) movement to the bottom
11119 * @public
11120 * @since 2.1.000 (2008-01-07)
11121 * @see StartTransform(), StopTransform()
11122 */
11123 public function TranslateY($t_y) {
11124 $this->Translate(0, $t_y);
11125 }
11126
11127 /**
11128 * Translate graphic object horizontally and vertically.
11129 * @param $t_x (int) movement to the right
11130 * @param $t_y (int) movement to the bottom
11131 * @public
11132 * @since 2.1.000 (2008-01-07)
11133 * @see StartTransform(), StopTransform()
11134 */
11135 public function Translate($t_x, $t_y) {
11136 //calculate elements of transformation matrix
11137 $tm = array();
11138 $tm[0] = 1;
11139 $tm[1] = 0;
11140 $tm[2] = 0;
11141 $tm[3] = 1;
11142 $tm[4] = $t_x * $this->k;
11143 $tm[5] = -$t_y * $this->k;
11144 //translate the coordinate system
11145 $this->Transform($tm);
11146 }
11147
11148 /**
11149 * Rotate object.
11150 * @param $angle (float) angle in degrees for counter-clockwise rotation
11151 * @param $x (int) abscissa of the rotation center. Default is current x position
11152 * @param $y (int) ordinate of the rotation center. Default is current y position
11153 * @public
11154 * @since 2.1.000 (2008-01-07)
11155 * @see StartTransform(), StopTransform()
11156 */
11157 public function Rotate($angle, $x='', $y='') {
11158 if ($x === '') {
11159 $x = $this->x;
11160 }
11161 if ($y === '') {
11162 $y = $this->y;
11163 }
11164 $y = ($this->h - $y) * $this->k;
11165 $x *= $this->k;
11166 //calculate elements of transformation matrix
11167 $tm = array();
11168 $tm[0] = cos(deg2rad($angle));
11169 $tm[1] = sin(deg2rad($angle));
11170 $tm[2] = -$tm[1];
11171 $tm[3] = $tm[0];
11172 $tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
11173 $tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
11174 //rotate the coordinate system around ($x,$y)
11175 $this->Transform($tm);
11176 }
11177
11178 /**
11179 * Skew horizontally.
11180 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11181 * @param $x (int) abscissa of the skewing center. default is current x position
11182 * @param $y (int) ordinate of the skewing center. default is current y position
11183 * @public
11184 * @since 2.1.000 (2008-01-07)
11185 * @see StartTransform(), StopTransform()
11186 */
11187 public function SkewX($angle_x, $x='', $y='') {
11188 $this->Skew($angle_x, 0, $x, $y);
11189 }
11190
11191 /**
11192 * Skew vertically.
11193 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11194 * @param $x (int) abscissa of the skewing center. default is current x position
11195 * @param $y (int) ordinate of the skewing center. default is current y position
11196 * @public
11197 * @since 2.1.000 (2008-01-07)
11198 * @see StartTransform(), StopTransform()
11199 */
11200 public function SkewY($angle_y, $x='', $y='') {
11201 $this->Skew(0, $angle_y, $x, $y);
11202 }
11203
11204 /**
11205 * Skew.
11206 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11207 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11208 * @param $x (int) abscissa of the skewing center. default is current x position
11209 * @param $y (int) ordinate of the skewing center. default is current y position
11210 * @public
11211 * @since 2.1.000 (2008-01-07)
11212 * @see StartTransform(), StopTransform()
11213 */
11214 public function Skew($angle_x, $angle_y, $x='', $y='') {
11215 if ($x === '') {
11216 $x = $this->x;
11217 }
11218 if ($y === '') {
11219 $y = $this->y;
11220 }
11221 if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
11222 $this->Error('Please use values between -90 and +90 degrees for Skewing.');
11223 }
11224 $x *= $this->k;
11225 $y = ($this->h - $y) * $this->k;
11226 //calculate elements of transformation matrix
11227 $tm = array();
11228 $tm[0] = 1;
11229 $tm[1] = tan(deg2rad($angle_y));
11230 $tm[2] = tan(deg2rad($angle_x));
11231 $tm[3] = 1;
11232 $tm[4] = -$tm[2] * $y;
11233 $tm[5] = -$tm[1] * $x;
11234 //skew the coordinate system
11235 $this->Transform($tm);
11236 }
11237
11238 /**
11239 * Apply graphic transformations.
11240 * @param $tm (array) transformation matrix
11241 * @protected
11242 * @since 2.1.000 (2008-01-07)
11243 * @see StartTransform(), StopTransform()
11244 */
11245 protected function Transform($tm) {
11246 if ($this->state != 2) {
11247 return;
11248 }
11249 $this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
11250 // add tranformation matrix
11251 $this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
11252 // update transformation mark
11253 if ($this->inxobj) {
11254 // we are inside an XObject template
11255 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
11256 $key = key($this->xobjects[$this->xobjid]['transfmrk']);
11257 $this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
11258 }
11259 } elseif (end($this->transfmrk[$this->page]) !== false) {
11260 $key = key($this->transfmrk[$this->page]);
11261 $this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
11262 }
11263 }
11264
11265 // END TRANSFORMATIONS SECTION -------------------------
11266
11267 // START GRAPHIC FUNCTIONS SECTION ---------------------
11268 // The following section is based on the code provided by David Hernandez Sanz
11269
11270 /**
11271 * 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.
11272 * @param $width (float) The width.
11273 * @public
11274 * @since 1.0
11275 * @see Line(), Rect(), Cell(), MultiCell()
11276 */
11277 public function SetLineWidth($width) {
11278 //Set line width
11279 $this->LineWidth = $width;
11280 $this->linestyleWidth = sprintf('%F w', ($width * $this->k));
11281 if ($this->state == 2) {
11282 $this->_out($this->linestyleWidth);
11283 }
11284 }
11285
11286 /**
11287 * Returns the current the line width.
11288 * @return int Line width
11289 * @public
11290 * @since 2.1.000 (2008-01-07)
11291 * @see Line(), SetLineWidth()
11292 */
11293 public function GetLineWidth() {
11294 return $this->LineWidth;
11295 }
11296
11297 /**
11298 * Set line style.
11299 * @param $style (array) Line style. Array with keys among the following:
11300 * <ul>
11301 * <li>width (float): Width of the line in user units.</li>
11302 * <li>cap (string): Type of cap to put on the line. Possible values are:
11303 * butt, round, square. The difference between "square" and "butt" is that
11304 * "square" projects a flat end past the end of the line.</li>
11305 * <li>join (string): Type of join. Possible values are: miter, round,
11306 * bevel.</li>
11307 * <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
11308 * series of length values, which are the lengths of the on and off dashes.
11309 * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
11310 * 1 off, 2 on, 1 off, ...</li>
11311 * <li>phase (integer): Modifier on the dash pattern which is used to shift
11312 * the point at which the pattern starts.</li>
11313 * <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>
11314 * </ul>
11315 * @param $ret (boolean) if true do not send the command.
11316 * @return string the PDF command
11317 * @public
11318 * @since 2.1.000 (2008-01-08)
11319 */
11320 public function SetLineStyle($style, $ret=false) {
11321 $s = ''; // string to be returned
11322 if (!is_array($style)) {
11323 return;
11324 }
11325 if (isset($style['width'])) {
11326 $this->LineWidth = $style['width'];
11327 $this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
11328 $s .= $this->linestyleWidth.' ';
11329 }
11330 if (isset($style['cap'])) {
11331 $ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
11332 if (isset($ca[$style['cap']])) {
11333 $this->linestyleCap = $ca[$style['cap']].' J';
11334 $s .= $this->linestyleCap.' ';
11335 }
11336 }
11337 if (isset($style['join'])) {
11338 $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
11339 if (isset($ja[$style['join']])) {
11340 $this->linestyleJoin = $ja[$style['join']].' j';
11341 $s .= $this->linestyleJoin.' ';
11342 }
11343 }
11344 if (isset($style['dash'])) {
11345 $dash_string = '';
11346 if ($style['dash']) {
11347 if (preg_match('/^.+,/', $style['dash']) > 0) {
11348 $tab = explode(',', $style['dash']);
11349 } else {
11350 $tab = array($style['dash']);
11351 }
11352 $dash_string = '';
11353 foreach ($tab as $i => $v) {
11354 if ($i) {
11355 $dash_string .= ' ';
11356 }
11357 $dash_string .= sprintf('%F', $v);
11358 }
11359 }
11360 if (!isset($style['phase']) OR !$style['dash']) {
11361 $style['phase'] = 0;
11362 }
11363 $this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
11364 $s .= $this->linestyleDash.' ';
11365 }
11366 if (isset($style['color'])) {
11367 $s .= $this->SetDrawColorArray($style['color'], true).' ';
11368 }
11369 if (!$ret AND ($this->state == 2)) {
11370 $this->_out($s);
11371 }
11372 return $s;
11373 }
11374
11375 /**
11376 * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
11377 * @param $x (float) Abscissa of point.
11378 * @param $y (float) Ordinate of point.
11379 * @protected
11380 * @since 2.1.000 (2008-01-08)
11381 */
11382 protected function _outPoint($x, $y) {
11383 if ($this->state == 2) {
11384 $this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
11385 }
11386 }
11387
11388 /**
11389 * Append a straight line segment from the current point to the point (x, y).
11390 * The new current point shall be (x, y).
11391 * @param $x (float) Abscissa of end point.
11392 * @param $y (float) Ordinate of end point.
11393 * @protected
11394 * @since 2.1.000 (2008-01-08)
11395 */
11396 protected function _outLine($x, $y) {
11397 if ($this->state == 2) {
11398 $this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
11399 }
11400 }
11401
11402 /**
11403 * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
11404 * @param $x (float) Abscissa of upper-left corner.
11405 * @param $y (float) Ordinate of upper-left corner.
11406 * @param $w (float) Width.
11407 * @param $h (float) Height.
11408 * @param $op (string) options
11409 * @protected
11410 * @since 2.1.000 (2008-01-08)
11411 */
11412 protected function _outRect($x, $y, $w, $h, $op) {
11413 if ($this->state == 2) {
11414 $this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k), (($this->h - $y) * $this->k), ($w * $this->k), (-$h * $this->k), $op));
11415 }
11416 }
11417
11418 /**
11419 * 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.
11420 * The new current point shall be (x3, y3).
11421 * @param $x1 (float) Abscissa of control point 1.
11422 * @param $y1 (float) Ordinate of control point 1.
11423 * @param $x2 (float) Abscissa of control point 2.
11424 * @param $y2 (float) Ordinate of control point 2.
11425 * @param $x3 (float) Abscissa of end point.
11426 * @param $y3 (float) Ordinate of end point.
11427 * @protected
11428 * @since 2.1.000 (2008-01-08)
11429 */
11430 protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
11431 if ($this->state == 2) {
11432 $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)));
11433 }
11434 }
11435
11436 /**
11437 * 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.
11438 * The new current point shall be (x3, y3).
11439 * @param $x2 (float) Abscissa of control point 2.
11440 * @param $y2 (float) Ordinate of control point 2.
11441 * @param $x3 (float) Abscissa of end point.
11442 * @param $y3 (float) Ordinate of end point.
11443 * @protected
11444 * @since 4.9.019 (2010-04-26)
11445 */
11446 protected function _outCurveV($x2, $y2, $x3, $y3) {
11447 if ($this->state == 2) {
11448 $this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11449 }
11450 }
11451
11452 /**
11453 * 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.
11454 * The new current point shall be (x3, y3).
11455 * @param $x1 (float) Abscissa of control point 1.
11456 * @param $y1 (float) Ordinate of control point 1.
11457 * @param $x3 (float) Abscissa of end point.
11458 * @param $y3 (float) Ordinate of end point.
11459 * @protected
11460 * @since 2.1.000 (2008-01-08)
11461 */
11462 protected function _outCurveY($x1, $y1, $x3, $y3) {
11463 if ($this->state == 2) {
11464 $this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11465 }
11466 }
11467
11468 /**
11469 * Draws a line between two points.
11470 * @param $x1 (float) Abscissa of first point.
11471 * @param $y1 (float) Ordinate of first point.
11472 * @param $x2 (float) Abscissa of second point.
11473 * @param $y2 (float) Ordinate of second point.
11474 * @param $style (array) Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
11475 * @public
11476 * @since 1.0
11477 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
11478 */
11479 public function Line($x1, $y1, $x2, $y2, $style=array()) {
11480 if ($this->state != 2) {
11481 return;
11482 }
11483 if (is_array($style)) {
11484 $this->SetLineStyle($style);
11485 }
11486 $this->_outPoint($x1, $y1);
11487 $this->_outLine($x2, $y2);
11488 $this->_out('S');
11489 }
11490
11491 /**
11492 * Draws a rectangle.
11493 * @param $x (float) Abscissa of upper-left corner.
11494 * @param $y (float) Ordinate of upper-left corner.
11495 * @param $w (float) Width.
11496 * @param $h (float) Height.
11497 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11498 * @param $border_style (array) Border style of rectangle. Array with keys among the following:
11499 * <ul>
11500 * <li>all: Line style of all borders. Array like for SetLineStyle().</li>
11501 * <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
11502 * </ul>
11503 * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
11504 * @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).
11505 * @public
11506 * @since 1.0
11507 * @see SetLineStyle()
11508 */
11509 public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
11510 if ($this->state != 2) {
11511 return;
11512 }
11513 if (empty($style)) {
11514 $style = 'S';
11515 }
11516 if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
11517 // set background color
11518 $this->SetFillColorArray($fill_color);
11519 }
11520 if (!empty($border_style)) {
11521 if (isset($border_style['all']) AND !empty($border_style['all'])) {
11522 //set global style for border
11523 $this->SetLineStyle($border_style['all']);
11524 $border_style = array();
11525 } else {
11526 // remove stroke operator from style
11527 $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*' );
11528 if (isset($opnostroke[$style])) {
11529 $style = $opnostroke[$style];
11530 }
11531 }
11532 }
11533 if (!empty($style)) {
11534 $op = TCPDF_STATIC::getPathPaintOperator($style);
11535 $this->_outRect($x, $y, $w, $h, $op);
11536 }
11537 if (!empty($border_style)) {
11538 $border_style2 = array();
11539 foreach ($border_style as $line => $value) {
11540 $length = strlen($line);
11541 for ($i = 0; $i < $length; ++$i) {
11542 $border_style2[$line[$i]] = $value;
11543 }
11544 }
11545 $border_style = $border_style2;
11546 if (isset($border_style['L']) AND $border_style['L']) {
11547 $this->Line($x, $y, $x, $y + $h, $border_style['L']);
11548 }
11549 if (isset($border_style['T']) AND $border_style['T']) {
11550 $this->Line($x, $y, $x + $w, $y, $border_style['T']);
11551 }
11552 if (isset($border_style['R']) AND $border_style['R']) {
11553 $this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
11554 }
11555 if (isset($border_style['B']) AND $border_style['B']) {
11556 $this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
11557 }
11558 }
11559 }
11560
11561 /**
11562 * Draws a Bezier curve.
11563 * The Bezier curve is a tangent to the line between the control points at
11564 * either end of the curve.
11565 * @param $x0 (float) Abscissa of start point.
11566 * @param $y0 (float) Ordinate of start point.
11567 * @param $x1 (float) Abscissa of control point 1.
11568 * @param $y1 (float) Ordinate of control point 1.
11569 * @param $x2 (float) Abscissa of control point 2.
11570 * @param $y2 (float) Ordinate of control point 2.
11571 * @param $x3 (float) Abscissa of end point.
11572 * @param $y3 (float) Ordinate of end point.
11573 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11574 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11575 * @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).
11576 * @public
11577 * @see SetLineStyle()
11578 * @since 2.1.000 (2008-01-08)
11579 */
11580 public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
11581 if ($this->state != 2) {
11582 return;
11583 }
11584 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11585 $this->SetFillColorArray($fill_color);
11586 }
11587 $op = TCPDF_STATIC::getPathPaintOperator($style);
11588 if ($line_style) {
11589 $this->SetLineStyle($line_style);
11590 }
11591 $this->_outPoint($x0, $y0);
11592 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11593 $this->_out($op);
11594 }
11595
11596 /**
11597 * Draws a poly-Bezier curve.
11598 * Each Bezier curve segment is a tangent to the line between the control points at
11599 * either end of the curve.
11600 * @param $x0 (float) Abscissa of start point.
11601 * @param $y0 (float) Ordinate of start point.
11602 * @param $segments (float) An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
11603 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11604 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11605 * @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).
11606 * @public
11607 * @see SetLineStyle()
11608 * @since 3.0008 (2008-05-12)
11609 */
11610 public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
11611 if ($this->state != 2) {
11612 return;
11613 }
11614 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11615 $this->SetFillColorArray($fill_color);
11616 }
11617 $op = TCPDF_STATIC::getPathPaintOperator($style);
11618 if ($op == 'f') {
11619 $line_style = array();
11620 }
11621 if ($line_style) {
11622 $this->SetLineStyle($line_style);
11623 }
11624 $this->_outPoint($x0, $y0);
11625 foreach ($segments as $segment) {
11626 list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
11627 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11628 }
11629 $this->_out($op);
11630 }
11631
11632 /**
11633 * Draws an ellipse.
11634 * An ellipse is formed from n Bezier curves.
11635 * @param $x0 (float) Abscissa of center point.
11636 * @param $y0 (float) Ordinate of center point.
11637 * @param $rx (float) Horizontal radius.
11638 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11639 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
11640 * @param $astart: (float) Angle start of draw line. Default value: 0.
11641 * @param $afinish: (float) Angle finish of draw line. Default value: 360.
11642 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11643 * @param $line_style (array) Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
11644 * @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).
11645 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11646 * @author Nicola Asuni
11647 * @public
11648 * @since 2.1.000 (2008-01-08)
11649 */
11650 public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11651 if ($this->state != 2) {
11652 return;
11653 }
11654 if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
11655 $ry = $rx;
11656 }
11657 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11658 $this->SetFillColorArray($fill_color);
11659 }
11660 $op = TCPDF_STATIC::getPathPaintOperator($style);
11661 if ($op == 'f') {
11662 $line_style = array();
11663 }
11664 if ($line_style) {
11665 $this->SetLineStyle($line_style);
11666 }
11667 $this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
11668 $this->_out($op);
11669 }
11670
11671 /**
11672 * Append an elliptical arc to the current path.
11673 * An ellipse is formed from n Bezier curves.
11674 * @param $xc (float) Abscissa of center point.
11675 * @param $yc (float) Ordinate of center point.
11676 * @param $rx (float) Horizontal radius.
11677 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11678 * @param $xang: (float) Angle between the X-axis and the major axis of the ellipse. Default value: 0.
11679 * @param $angs: (float) Angle start of draw line. Default value: 0.
11680 * @param $angf: (float) Angle finish of draw line. Default value: 360.
11681 * @param $pie (boolean) if true do not mark the border point (used to draw pie sectors).
11682 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11683 * @param $startpoint (boolean) if true output a starting point.
11684 * @param $ccw (boolean) if true draws in counter-clockwise.
11685 * @param $svg (boolean) if true the angles are in svg mode (already calculated).
11686 * @return array bounding box coordinates (x min, y min, x max, y max)
11687 * @author Nicola Asuni
11688 * @protected
11689 * @since 4.9.019 (2010-04-26)
11690 */
11691 protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
11692 if (($rx <= 0) OR ($ry < 0)) {
11693 return;
11694 }
11695 $k = $this->k;
11696 if ($nc < 2) {
11697 $nc = 2;
11698 }
11699 $xmin = 2147483647;
11700 $ymin = 2147483647;
11701 $xmax = 0;
11702 $ymax = 0;
11703 if ($pie) {
11704 // center of the arc
11705 $this->_outPoint($xc, $yc);
11706 }
11707 $xang = deg2rad((float) $xang);
11708 $angs = deg2rad((float) $angs);
11709 $angf = deg2rad((float) $angf);
11710 if ($svg) {
11711 $as = $angs;
11712 $af = $angf;
11713 } else {
11714 $as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
11715 $af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
11716 }
11717 if ($as < 0) {
11718 $as += (2 * M_PI);
11719 }
11720 if ($af < 0) {
11721 $af += (2 * M_PI);
11722 }
11723 if ($ccw AND ($as > $af)) {
11724 // reverse rotation
11725 $as -= (2 * M_PI);
11726 } elseif (!$ccw AND ($as < $af)) {
11727 // reverse rotation
11728 $af -= (2 * M_PI);
11729 }
11730 $total_angle = ($af - $as);
11731 if ($nc < 2) {
11732 $nc = 2;
11733 }
11734 // total arcs to draw
11735 $nc *= (2 * abs($total_angle) / M_PI);
11736 $nc = round($nc) + 1;
11737 // angle of each arc
11738 $arcang = ($total_angle / $nc);
11739 // center point in PDF coordinates
11740 $x0 = $xc;
11741 $y0 = ($this->h - $yc);
11742 // starting angle
11743 $ang = $as;
11744 $alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
11745 $cos_xang = cos($xang);
11746 $sin_xang = sin($xang);
11747 $cos_ang = cos($ang);
11748 $sin_ang = sin($ang);
11749 // first arc point
11750 $px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11751 $py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11752 // first Bezier control point
11753 $qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11754 $qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11755 if ($pie) {
11756 // line from center to arc starting point
11757 $this->_outLine($px1, $this->h - $py1);
11758 } elseif ($startpoint) {
11759 // arc starting point
11760 $this->_outPoint($px1, $this->h - $py1);
11761 }
11762 // draw arcs
11763 for ($i = 1; $i <= $nc; ++$i) {
11764 // starting angle
11765 $ang = $as + ($i * $arcang);
11766 if ($i == $nc) {
11767 $ang = $af;
11768 }
11769 $cos_ang = cos($ang);
11770 $sin_ang = sin($ang);
11771 // second arc point
11772 $px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11773 $py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11774 // second Bezier control point
11775 $qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11776 $qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11777 // draw arc
11778 $cx1 = ($px1 + $qx1);
11779 $cy1 = ($this->h - ($py1 + $qy1));
11780 $cx2 = ($px2 - $qx2);
11781 $cy2 = ($this->h - ($py2 - $qy2));
11782 $cx3 = $px2;
11783 $cy3 = ($this->h - $py2);
11784 $this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
11785 // get bounding box coordinates
11786 $xmin = min($xmin, $cx1, $cx2, $cx3);
11787 $ymin = min($ymin, $cy1, $cy2, $cy3);
11788 $xmax = max($xmax, $cx1, $cx2, $cx3);
11789 $ymax = max($ymax, $cy1, $cy2, $cy3);
11790 // move to next point
11791 $px1 = $px2;
11792 $py1 = $py2;
11793 $qx1 = $qx2;
11794 $qy1 = $qy2;
11795 }
11796 if ($pie) {
11797 $this->_outLine($xc, $yc);
11798 // get bounding box coordinates
11799 $xmin = min($xmin, $xc);
11800 $ymin = min($ymin, $yc);
11801 $xmax = max($xmax, $xc);
11802 $ymax = max($ymax, $yc);
11803 }
11804 return array($xmin, $ymin, $xmax, $ymax);
11805 }
11806
11807 /**
11808 * Draws a circle.
11809 * A circle is formed from n Bezier curves.
11810 * @param $x0 (float) Abscissa of center point.
11811 * @param $y0 (float) Ordinate of center point.
11812 * @param $r (float) Radius.
11813 * @param $angstr: (float) Angle start of draw line. Default value: 0.
11814 * @param $angend: (float) Angle finish of draw line. Default value: 360.
11815 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11816 * @param $line_style (array) Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
11817 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11818 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of circle.
11819 * @public
11820 * @since 2.1.000 (2008-01-08)
11821 */
11822 public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11823 $this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
11824 }
11825
11826 /**
11827 * Draws a polygonal line
11828 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11829 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11830 * @param $line_style (array) Line style of polygon. Array with keys among the following:
11831 * <ul>
11832 * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11833 * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11834 * </ul>
11835 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11836 * @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).
11837 * @since 4.8.003 (2009-09-15)
11838 * @public
11839 */
11840 public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
11841 $this->Polygon($p, $style, $line_style, $fill_color, false);
11842 }
11843
11844 /**
11845 * Draws a polygon.
11846 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11847 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11848 * @param $line_style (array) Line style of polygon. Array with keys among the following:
11849 * <ul>
11850 * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11851 * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11852 * </ul>
11853 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11854 * @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).
11855 * @param $closed (boolean) if true the polygon is closes, otherwise will remain open
11856 * @public
11857 * @since 2.1.000 (2008-01-08)
11858 */
11859 public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
11860 if ($this->state != 2) {
11861 return;
11862 }
11863 $nc = count($p); // number of coordinates
11864 $np = $nc / 2; // number of points
11865 if ($closed) {
11866 // close polygon by adding the first 2 points at the end (one line)
11867 for ($i = 0; $i < 4; ++$i) {
11868 $p[$nc + $i] = $p[$i];
11869 }
11870 // copy style for the last added line
11871 if (isset($line_style[0])) {
11872 $line_style[$np] = $line_style[0];
11873 }
11874 $nc += 4;
11875 }
11876 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11877 $this->SetFillColorArray($fill_color);
11878 }
11879 $op = TCPDF_STATIC::getPathPaintOperator($style);
11880 if ($op == 'f') {
11881 $line_style = array();
11882 }
11883 $draw = true;
11884 if ($line_style) {
11885 if (isset($line_style['all'])) {
11886 $this->SetLineStyle($line_style['all']);
11887 } else {
11888 $draw = false;
11889 if ($op == 'B') {
11890 // draw fill
11891 $op = 'f';
11892 $this->_outPoint($p[0], $p[1]);
11893 for ($i = 2; $i < $nc; $i = $i + 2) {
11894 $this->_outLine($p[$i], $p[$i + 1]);
11895 }
11896 $this->_out($op);
11897 }
11898 // draw outline
11899 $this->_outPoint($p[0], $p[1]);
11900 for ($i = 2; $i < $nc; $i = $i + 2) {
11901 $line_num = ($i / 2) - 1;
11902 if (isset($line_style[$line_num])) {
11903 if ($line_style[$line_num] != 0) {
11904 if (is_array($line_style[$line_num])) {
11905 $this->_out('S');
11906 $this->SetLineStyle($line_style[$line_num]);
11907 $this->_outPoint($p[$i - 2], $p[$i - 1]);
11908 $this->_outLine($p[$i], $p[$i + 1]);
11909 $this->_out('S');
11910 $this->_outPoint($p[$i], $p[$i + 1]);
11911 } else {
11912 $this->_outLine($p[$i], $p[$i + 1]);
11913 }
11914 }
11915 } else {
11916 $this->_outLine($p[$i], $p[$i + 1]);
11917 }
11918 }
11919 $this->_out($op);
11920 }
11921 }
11922 if ($draw) {
11923 $this->_outPoint($p[0], $p[1]);
11924 for ($i = 2; $i < $nc; $i = $i + 2) {
11925 $this->_outLine($p[$i], $p[$i + 1]);
11926 }
11927 $this->_out($op);
11928 }
11929 }
11930
11931 /**
11932 * Draws a regular polygon.
11933 * @param $x0 (float) Abscissa of center point.
11934 * @param $y0 (float) Ordinate of center point.
11935 * @param $r: (float) Radius of inscribed circle.
11936 * @param $ns (integer) Number of sides.
11937 * @param $angle (float) Angle oriented (anti-clockwise). Default value: 0.
11938 * @param $draw_circle (boolean) Draw inscribed circle or not. Default value: false.
11939 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11940 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
11941 * <ul>
11942 * <li>all: Line style of all sides. Array like for SetLineStyle().</li>
11943 * <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
11944 * </ul>
11945 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
11946 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11947 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
11948 * <ul>
11949 * <li>D or empty string: Draw (default).</li>
11950 * <li>F: Fill.</li>
11951 * <li>DF or FD: Draw and fill.</li>
11952 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
11953 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
11954 * </ul>
11955 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
11956 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
11957 * @public
11958 * @since 2.1.000 (2008-01-08)
11959 */
11960 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()) {
11961 if (3 > $ns) {
11962 $ns = 3;
11963 }
11964 if ($draw_circle) {
11965 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
11966 }
11967 $p = array();
11968 for ($i = 0; $i < $ns; ++$i) {
11969 $a = $angle + ($i * 360 / $ns);
11970 $a_rad = deg2rad((float) $a);
11971 $p[] = $x0 + ($r * sin($a_rad));
11972 $p[] = $y0 + ($r * cos($a_rad));
11973 }
11974 $this->Polygon($p, $style, $line_style, $fill_color);
11975 }
11976
11977 /**
11978 * Draws a star polygon
11979 * @param $x0 (float) Abscissa of center point.
11980 * @param $y0 (float) Ordinate of center point.
11981 * @param $r (float) Radius of inscribed circle.
11982 * @param $nv (integer) Number of vertices.
11983 * @param $ng (integer) Number of gap (if ($ng % $nv = 1) then is a regular polygon).
11984 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
11985 * @param $draw_circle: (boolean) Draw inscribed circle or not. Default value is false.
11986 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11987 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
11988 * <ul>
11989 * <li>all: Line style of all sides. Array like for
11990 * SetLineStyle().</li>
11991 * <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
11992 * </ul>
11993 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
11994 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11995 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
11996 * <ul>
11997 * <li>D or empty string: Draw (default).</li>
11998 * <li>F: Fill.</li>
11999 * <li>DF or FD: Draw and fill.</li>
12000 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12001 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12002 * </ul>
12003 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12004 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12005 * @public
12006 * @since 2.1.000 (2008-01-08)
12007 */
12008 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()) {
12009 if ($nv < 2) {
12010 $nv = 2;
12011 }
12012 if ($draw_circle) {
12013 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12014 }
12015 $p2 = array();
12016 $visited = array();
12017 for ($i = 0; $i < $nv; ++$i) {
12018 $a = $angle + ($i * 360 / $nv);
12019 $a_rad = deg2rad((float) $a);
12020 $p2[] = $x0 + ($r * sin($a_rad));
12021 $p2[] = $y0 + ($r * cos($a_rad));
12022 $visited[] = false;
12023 }
12024 $p = array();
12025 $i = 0;
12026 do {
12027 $p[] = $p2[$i * 2];
12028 $p[] = $p2[($i * 2) + 1];
12029 $visited[$i] = true;
12030 $i += $ng;
12031 $i %= $nv;
12032 } while (!$visited[$i]);
12033 $this->Polygon($p, $style, $line_style, $fill_color);
12034 }
12035
12036 /**
12037 * Draws a rounded rectangle.
12038 * @param $x (float) Abscissa of upper-left corner.
12039 * @param $y (float) Ordinate of upper-left corner.
12040 * @param $w (float) Width.
12041 * @param $h (float) Height.
12042 * @param $r (float) the radius of the circle used to round off the corners of the rectangle.
12043 * @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").
12044 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12045 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12046 * @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).
12047 * @public
12048 * @since 2.1.000 (2008-01-08)
12049 */
12050 public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12051 $this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
12052 }
12053
12054 /**
12055 * Draws a rounded rectangle.
12056 * @param $x (float) Abscissa of upper-left corner.
12057 * @param $y (float) Ordinate of upper-left corner.
12058 * @param $w (float) Width.
12059 * @param $h (float) Height.
12060 * @param $rx (float) the x-axis radius of the ellipse used to round off the corners of the rectangle.
12061 * @param $ry (float) the y-axis radius of the ellipse used to round off the corners of the rectangle.
12062 * @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").
12063 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12064 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12065 * @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).
12066 * @public
12067 * @since 4.9.019 (2010-04-22)
12068 */
12069 public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12070 if ($this->state != 2) {
12071 return;
12072 }
12073 if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
12074 // Not rounded
12075 $this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
12076 return;
12077 }
12078 // Rounded
12079 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12080 $this->SetFillColorArray($fill_color);
12081 }
12082 $op = TCPDF_STATIC::getPathPaintOperator($style);
12083 if ($op == 'f') {
12084 $border_style = array();
12085 }
12086 if ($border_style) {
12087 $this->SetLineStyle($border_style);
12088 }
12089 $MyArc = 4 / 3 * (sqrt(2) - 1);
12090 $this->_outPoint($x + $rx, $y);
12091 $xc = $x + $w - $rx;
12092 $yc = $y + $ry;
12093 $this->_outLine($xc, $y);
12094 if ($round_corner[0]) {
12095 $this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
12096 } else {
12097 $this->_outLine($x + $w, $y);
12098 }
12099 $xc = $x + $w - $rx;
12100 $yc = $y + $h - $ry;
12101 $this->_outLine($x + $w, $yc);
12102 if ($round_corner[1]) {
12103 $this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
12104 } else {
12105 $this->_outLine($x + $w, $y + $h);
12106 }
12107 $xc = $x + $rx;
12108 $yc = $y + $h - $ry;
12109 $this->_outLine($xc, $y + $h);
12110 if ($round_corner[2]) {
12111 $this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
12112 } else {
12113 $this->_outLine($x, $y + $h);
12114 }
12115 $xc = $x + $rx;
12116 $yc = $y + $ry;
12117 $this->_outLine($x, $yc);
12118 if ($round_corner[3]) {
12119 $this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
12120 } else {
12121 $this->_outLine($x, $y);
12122 $this->_outLine($x + $rx, $y);
12123 }
12124 $this->_out($op);
12125 }
12126
12127 /**
12128 * Draws a grahic arrow.
12129 * @param $x0 (float) Abscissa of first point.
12130 * @param $y0 (float) Ordinate of first point.
12131 * @param $x1 (float) Abscissa of second point.
12132 * @param $y1 (float) Ordinate of second point.
12133 * @param $head_style (int) (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
12134 * @param $arm_size (float) length of arrowhead arms
12135 * @param $arm_angle (int) angle between an arm and the shaft
12136 * @author Piotr Galecki, Nicola Asuni, Andy Meier
12137 * @since 4.6.018 (2009-07-10)
12138 */
12139 public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
12140 // getting arrow direction angle
12141 // 0 deg angle is when both arms go along X axis. angle grows clockwise.
12142 $dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
12143 if ($dir_angle < 0) {
12144 $dir_angle += (2 * M_PI);
12145 }
12146 $arm_angle = deg2rad($arm_angle);
12147 $sx1 = $x1;
12148 $sy1 = $y1;
12149 if ($head_style > 0) {
12150 // calculate the stopping point for the arrow shaft
12151 $sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
12152 $sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
12153 }
12154 // main arrow line / shaft
12155 $this->Line($x0, $y0, $sx1, $sy1);
12156 // left arrowhead arm tip
12157 $x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
12158 $y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
12159 // right arrowhead arm tip
12160 $x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
12161 $y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
12162 $mode = 'D';
12163 $style = array();
12164 switch ($head_style) {
12165 case 0: {
12166 // draw only arrowhead arms
12167 $mode = 'D';
12168 $style = array(1, 1, 0);
12169 break;
12170 }
12171 case 1: {
12172 // draw closed arrowhead, but no fill
12173 $mode = 'D';
12174 break;
12175 }
12176 case 2: {
12177 // closed and filled arrowhead
12178 $mode = 'DF';
12179 break;
12180 }
12181 case 3: {
12182 // filled arrowhead
12183 $mode = 'F';
12184 break;
12185 }
12186 }
12187 $this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
12188 }
12189
12190 // END GRAPHIC FUNCTIONS SECTION -----------------------
12191
12192 /**
12193 * Add a Named Destination.
12194 * NOTE: destination names are unique, so only last entry will be saved.
12195 * @param $name (string) Destination name.
12196 * @param $y (float) Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
12197 * @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.
12198 * @param $x (float) X position in user units of the destiantion on the selected page (default = -1 = current position;).
12199 * @return (string) Stripped named destination identifier or false in case of error.
12200 * @public
12201 * @author Christian Deligant, Nicola Asuni
12202 * @since 5.9.097 (2011-06-23)
12203 */
12204 public function setDestination($name, $y=-1, $page='', $x=-1) {
12205 // remove unsupported characters
12206 $name = TCPDF_STATIC::encodeNameObject($name);
12207 if (TCPDF_STATIC::empty_string($name)) {
12208 return false;
12209 }
12210 if ($y == -1) {
12211 $y = $this->GetY();
12212 } elseif ($y < 0) {
12213 $y = 0;
12214 } elseif ($y > $this->h) {
12215 $y = $this->h;
12216 }
12217 if ($x == -1) {
12218 $x = $this->GetX();
12219 } elseif ($x < 0) {
12220 $x = 0;
12221 } elseif ($x > $this->w) {
12222 $x = $this->w;
12223 }
12224 $fixed = false;
12225 if (!empty($page) AND ($page[0] == '*')) {
12226 $page = intval(substr($page, 1));
12227 // this page number will not be changed when moving/add/deleting pages
12228 $fixed = true;
12229 }
12230 if (empty($page)) {
12231 $page = $this->PageNo();
12232 if (empty($page)) {
12233 return;
12234 }
12235 }
12236 $this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
12237 return $name;
12238 }
12239
12240 /**
12241 * Return the Named Destination array.
12242 * @return (array) Named Destination array.
12243 * @public
12244 * @author Nicola Asuni
12245 * @since 5.9.097 (2011-06-23)
12246 */
12247 public function getDestination() {
12248 return $this->dests;
12249 }
12250
12251 /**
12252 * Insert Named Destinations.
12253 * @protected
12254 * @author Johannes G\FCntert, Nicola Asuni
12255 * @since 5.9.098 (2011-06-23)
12256 */
12257 protected function _putdests() {
12258 if (empty($this->dests)) {
12259 return;
12260 }
12261 $this->n_dests = $this->_newobj();
12262 $out = ' <<';
12263 foreach($this->dests as $name => $o) {
12264 $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)));
12265 }
12266 $out .= ' >>';
12267 $out .= "\n".'endobj';
12268 $this->_out($out);
12269 }
12270
12271 /**
12272 * Adds a bookmark - alias for Bookmark().
12273 * @param $txt (string) Bookmark description.
12274 * @param $level (int) Bookmark level (minimum value is 0).
12275 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12276 * @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.
12277 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12278 * @param $color (array) RGB color array (values from 0 to 255).
12279 * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12280 * @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).
12281 * @public
12282 */
12283 public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12284 $this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
12285 }
12286
12287 /**
12288 * Adds a bookmark.
12289 * @param $txt (string) Bookmark description.
12290 * @param $level (int) Bookmark level (minimum value is 0).
12291 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12292 * @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.
12293 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12294 * @param $color (array) RGB color array (values from 0 to 255).
12295 * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12296 * @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).
12297 * @public
12298 * @since 2.1.002 (2008-02-12)
12299 */
12300 public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12301 if ($level < 0) {
12302 $level = 0;
12303 }
12304 if (isset($this->outlines[0])) {
12305 $lastoutline = end($this->outlines);
12306 $maxlevel = $lastoutline['l'] + 1;
12307 } else {
12308 $maxlevel = 0;
12309 }
12310 if ($level > $maxlevel) {
12311 $level = $maxlevel;
12312 }
12313 if ($y == -1) {
12314 $y = $this->GetY();
12315 } elseif ($y < 0) {
12316 $y = 0;
12317 } elseif ($y > $this->h) {
12318 $y = $this->h;
12319 }
12320 if ($x == -1) {
12321 $x = $this->GetX();
12322 } elseif ($x < 0) {
12323 $x = 0;
12324 } elseif ($x > $this->w) {
12325 $x = $this->w;
12326 }
12327 $fixed = false;
12328 if (!empty($page) AND ($page[0] == '*')) {
12329 $page = intval(substr($page, 1));
12330 // this page number will not be changed when moving/add/deleting pages
12331 $fixed = true;
12332 }
12333 if (empty($page)) {
12334 $page = $this->PageNo();
12335 if (empty($page)) {
12336 return;
12337 }
12338 }
12339 $this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
12340 }
12341
12342 /**
12343 * Sort bookmarks for page and key.
12344 * @protected
12345 * @since 5.9.119 (2011-09-19)
12346 */
12347 protected function sortBookmarks() {
12348 // get sorting columns
12349 $outline_p = array();
12350 $outline_y = array();
12351 foreach ($this->outlines as $key => $row) {
12352 $outline_p[$key] = $row['p'];
12353 $outline_k[$key] = $key;
12354 }
12355 // sort outlines by page and original position
12356 array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
12357 }
12358
12359 /**
12360 * Create a bookmark PDF string.
12361 * @protected
12362 * @author Olivier Plathey, Nicola Asuni
12363 * @since 2.1.002 (2008-02-12)
12364 */
12365 protected function _putbookmarks() {
12366 $nb = count($this->outlines);
12367 if ($nb == 0) {
12368 return;
12369 }
12370 // sort bookmarks
12371 $this->sortBookmarks();
12372 $lru = array();
12373 $level = 0;
12374 foreach ($this->outlines as $i => $o) {
12375 if ($o['l'] > 0) {
12376 $parent = $lru[($o['l'] - 1)];
12377 //Set parent and last pointers
12378 $this->outlines[$i]['parent'] = $parent;
12379 $this->outlines[$parent]['last'] = $i;
12380 if ($o['l'] > $level) {
12381 //Level increasing: set first pointer
12382 $this->outlines[$parent]['first'] = $i;
12383 }
12384 } else {
12385 $this->outlines[$i]['parent'] = $nb;
12386 }
12387 if (($o['l'] <= $level) AND ($i > 0)) {
12388 //Set prev and next pointers
12389 $prev = $lru[$o['l']];
12390 $this->outlines[$prev]['next'] = $i;
12391 $this->outlines[$i]['prev'] = $prev;
12392 }
12393 $lru[$o['l']] = $i;
12394 $level = $o['l'];
12395 }
12396 //Outline items
12397 $n = $this->n + 1;
12398 $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';
12399 foreach ($this->outlines as $i => $o) {
12400 $oid = $this->_newobj();
12401 // covert HTML title to string
12402 $title = preg_replace($nltags, "\n", $o['t']);
12403 $title = preg_replace("/[\r]+/si", '', $title);
12404 $title = preg_replace("/[\n]+/si", "\n", $title);
12405 $title = strip_tags($title);
12406 $title = $this->stringTrim($title);
12407 $out = '<</Title '.$this->_textstring($title, $oid);
12408 $out .= ' /Parent '.($n + $o['parent']).' 0 R';
12409 if (isset($o['prev'])) {
12410 $out .= ' /Prev '.($n + $o['prev']).' 0 R';
12411 }
12412 if (isset($o['next'])) {
12413 $out .= ' /Next '.($n + $o['next']).' 0 R';
12414 }
12415 if (isset($o['first'])) {
12416 $out .= ' /First '.($n + $o['first']).' 0 R';
12417 }
12418 if (isset($o['last'])) {
12419 $out .= ' /Last '.($n + $o['last']).' 0 R';
12420 }
12421 if (isset($o['u']) AND !empty($o['u'])) {
12422 // link
12423 if (is_string($o['u'])) {
12424 if ($o['u'][0] == '#') {
12425 // internal destination
12426 $out .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($o['u'], 1));
12427 } elseif ($o['u'][0] == '%') {
12428 // embedded PDF file
12429 $filename = basename(substr($o['u'], 1));
12430 $out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
12431 } elseif ($o['u'][0] == '*') {
12432 // embedded generic file
12433 $filename = basename(substr($o['u'], 1));
12434 $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});';
12435 $out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
12436 } else {
12437 // external URI link
12438 $out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
12439 }
12440 } elseif (isset($this->links[$o['u']])) {
12441 // internal link ID
12442 $l = $this->links[$o['u']];
12443 if (isset($this->page_obj_id[($l['p'])])) {
12444 $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)));
12445 }
12446 }
12447 } elseif (isset($this->page_obj_id[($o['p'])])) {
12448 // link to a page
12449 $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)));
12450 }
12451 // set font style
12452 $style = 0;
12453 if (!empty($o['s'])) {
12454 // bold
12455 if (strpos($o['s'], 'B') !== false) {
12456 $style |= 2;
12457 }
12458 // oblique
12459 if (strpos($o['s'], 'I') !== false) {
12460 $style |= 1;
12461 }
12462 }
12463 $out .= sprintf(' /F %d', $style);
12464 // set bookmark color
12465 if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
12466 $color = array_values($o['c']);
12467 $out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
12468 } else {
12469 // black
12470 $out .= ' /C [0.0 0.0 0.0]';
12471 }
12472 $out .= ' /Count 0'; // normally closed item
12473 $out .= ' >>';
12474 $out .= "\n".'endobj';
12475 $this->_out($out);
12476 }
12477 //Outline root
12478 $this->OutlineRoot = $this->_newobj();
12479 $this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
12480 }
12481
12482 // --- JAVASCRIPT ------------------------------------------------------
12483
12484 /**
12485 * Adds a javascript
12486 * @param $script (string) Javascript code
12487 * @public
12488 * @author Johannes G\FCntert, Nicola Asuni
12489 * @since 2.1.002 (2008-02-12)
12490 */
12491 public function IncludeJS($script) {
12492 $this->javascript .= $script;
12493 }
12494
12495 /**
12496 * Adds a javascript object and return object ID
12497 * @param $script (string) Javascript code
12498 * @param $onload (boolean) if true executes this object when opening the document
12499 * @return int internal object ID
12500 * @public
12501 * @author Nicola Asuni
12502 * @since 4.8.000 (2009-09-07)
12503 */
12504 public function addJavascriptObject($script, $onload=false) {
12505 if ($this->pdfa_mode) {
12506 // javascript is not allowed in PDF/A mode
12507 return false;
12508 }
12509 ++$this->n;
12510 $this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
12511 return $this->n;
12512 }
12513
12514 /**
12515 * Create a javascript PDF string.
12516 * @protected
12517 * @author Johannes G\FCntert, Nicola Asuni
12518 * @since 2.1.002 (2008-02-12)
12519 */
12520 protected function _putjavascript() {
12521 if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
12522 return;
12523 }
12524 if (strpos($this->javascript, 'this.addField') > 0) {
12525 if (!$this->ur['enabled']) {
12526 //$this->setUserRights();
12527 }
12528 // the following two lines are used to avoid form fields duplication after saving
12529 // The addField method only works when releasing user rights (UR3)
12530 $jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
12531 $jsb = "getField('tcpdfdocsaved').value='saved';";
12532 $this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
12533 }
12534 // name tree for javascript
12535 $this->n_js = '<< /Names [';
12536 if (!empty($this->javascript)) {
12537 $this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
12538 }
12539 if (!empty($this->js_objects)) {
12540 foreach ($this->js_objects as $key => $val) {
12541 if ($val['onload']) {
12542 $this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
12543 }
12544 }
12545 }
12546 $this->n_js .= ' ] >>';
12547 // default Javascript object
12548 if (!empty($this->javascript)) {
12549 $obj_id = $this->_newobj();
12550 $out = '<< /S /JavaScript';
12551 $out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
12552 $out .= ' >>';
12553 $out .= "\n".'endobj';
12554 $this->_out($out);
12555 }
12556 // additional Javascript objects
12557 if (!empty($this->js_objects)) {
12558 foreach ($this->js_objects as $key => $val) {
12559 $out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
12560 $this->_out($out);
12561 }
12562 }
12563 }
12564
12565 /**
12566 * Adds a javascript form field.
12567 * @param $type (string) field type
12568 * @param $name (string) field name
12569 * @param $x (int) horizontal position
12570 * @param $y (int) vertical position
12571 * @param $w (int) width
12572 * @param $h (int) height
12573 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12574 * @protected
12575 * @author Denis Van Nuffelen, Nicola Asuni
12576 * @since 2.1.002 (2008-02-12)
12577 */
12578 protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
12579 if ($this->rtl) {
12580 $x = $x - $w;
12581 }
12582 // the followind avoid fields duplication after saving the document
12583 $this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
12584 $k = $this->k;
12585 $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";
12586 $this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
12587 foreach($prop as $key => $val) {
12588 if (strcmp(substr($key, -5), 'Color') == 0) {
12589 $val = TCPDF_COLORS::_JScolor($val);
12590 } else {
12591 $val = "'".$val."'";
12592 }
12593 $this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
12594 }
12595 if ($this->rtl) {
12596 $this->x -= $w;
12597 } else {
12598 $this->x += $w;
12599 }
12600 $this->javascript .= '}';
12601 }
12602
12603 // --- FORM FIELDS -----------------------------------------------------
12604
12605
12606
12607 /**
12608 * Set default properties for form fields.
12609 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12610 * @public
12611 * @author Nicola Asuni
12612 * @since 4.8.000 (2009-09-06)
12613 */
12614 public function setFormDefaultProp($prop=array()) {
12615 $this->default_form_prop = $prop;
12616 }
12617
12618 /**
12619 * Return the default properties for form fields.
12620 * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12621 * @public
12622 * @author Nicola Asuni
12623 * @since 4.8.000 (2009-09-06)
12624 */
12625 public function getFormDefaultProp() {
12626 return $this->default_form_prop;
12627 }
12628
12629 /**
12630 * Creates a text field
12631 * @param $name (string) field name
12632 * @param $w (float) Width of the rectangle
12633 * @param $h (float) Height of the rectangle
12634 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12635 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12636 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12637 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12638 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12639 * @public
12640 * @author Nicola Asuni
12641 * @since 4.8.000 (2009-09-07)
12642 */
12643 public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12644 if ($x === '') {
12645 $x = $this->x;
12646 }
12647 if ($y === '') {
12648 $y = $this->y;
12649 }
12650 // check page for no-write regions and adapt page margins if necessary
12651 list($x, $y) = $this->checkPageRegions($h, $x, $y);
12652 if ($js) {
12653 $this->_addfield('text', $name, $x, $y, $w, $h, $prop);
12654 return;
12655 }
12656 // get default style
12657 $prop = array_merge($this->getFormDefaultProp(), $prop);
12658 // get annotation data
12659 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12660 // set default appearance stream
12661 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12662 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12663 $popt['da'] = $fontstyle;
12664 // build appearance stream
12665 $popt['ap'] = array();
12666 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12667 $text = '';
12668 if (isset($prop['value']) AND !empty($prop['value'])) {
12669 $text = $prop['value'];
12670 } elseif (isset($opt['v']) AND !empty($opt['v'])) {
12671 $text = $opt['v'];
12672 }
12673 $tmpid = $this->startTemplate($w, $h, false);
12674 $align = '';
12675 if (isset($popt['q'])) {
12676 switch ($popt['q']) {
12677 case 0: {
12678 $align = 'L';
12679 break;
12680 }
12681 case 1: {
12682 $align = 'C';
12683 break;
12684 }
12685 case 2: {
12686 $align = 'R';
12687 break;
12688 }
12689 default: {
12690 $align = '';
12691 break;
12692 }
12693 }
12694 }
12695 $this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12696 $this->endTemplate();
12697 --$this->n;
12698 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12699 unset($this->xobjects[$tmpid]);
12700 $popt['ap']['n'] .= 'Q EMC';
12701 // merge options
12702 $opt = array_merge($popt, $opt);
12703 // remove some conflicting options
12704 unset($opt['bs']);
12705 // set remaining annotation data
12706 $opt['Subtype'] = 'Widget';
12707 $opt['ft'] = 'Tx';
12708 $opt['t'] = $name;
12709 // Additional annotation's parameters (check _putannotsobj() method):
12710 //$opt['f']
12711 //$opt['as']
12712 //$opt['bs']
12713 //$opt['be']
12714 //$opt['c']
12715 //$opt['border']
12716 //$opt['h']
12717 //$opt['mk'];
12718 //$opt['mk']['r']
12719 //$opt['mk']['bc'];
12720 //$opt['mk']['bg'];
12721 unset($opt['mk']['ca']);
12722 unset($opt['mk']['rc']);
12723 unset($opt['mk']['ac']);
12724 unset($opt['mk']['i']);
12725 unset($opt['mk']['ri']);
12726 unset($opt['mk']['ix']);
12727 unset($opt['mk']['if']);
12728 //$opt['mk']['if']['sw'];
12729 //$opt['mk']['if']['s'];
12730 //$opt['mk']['if']['a'];
12731 //$opt['mk']['if']['fb'];
12732 unset($opt['mk']['tp']);
12733 //$opt['tu']
12734 //$opt['tm']
12735 //$opt['ff']
12736 //$opt['v']
12737 //$opt['dv']
12738 //$opt['a']
12739 //$opt['aa']
12740 //$opt['q']
12741 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12742 if ($this->rtl) {
12743 $this->x -= $w;
12744 } else {
12745 $this->x += $w;
12746 }
12747 }
12748
12749 /**
12750 * Creates a RadioButton field.
12751 * @param $name (string) Field name.
12752 * @param $w (int) Width of the radio button.
12753 * @param $prop (array) Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12754 * @param $opt (array) Annotation parameters. Possible values are described on official PDF32000_2008 reference.
12755 * @param $onvalue (string) Value to be returned if selected.
12756 * @param $checked (boolean) Define the initial state.
12757 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12758 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12759 * @param $js (boolean) If true put the field using JavaScript (requires Acrobat Writer to be rendered).
12760 * @public
12761 * @author Nicola Asuni
12762 * @since 4.8.000 (2009-09-07)
12763 */
12764 public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
12765 if ($x === '') {
12766 $x = $this->x;
12767 }
12768 if ($y === '') {
12769 $y = $this->y;
12770 }
12771 // check page for no-write regions and adapt page margins if necessary
12772 list($x, $y) = $this->checkPageRegions($w, $x, $y);
12773 if ($js) {
12774 $this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
12775 return;
12776 }
12777 if (TCPDF_STATIC::empty_string($onvalue)) {
12778 $onvalue = 'On';
12779 }
12780 if ($checked) {
12781 $defval = $onvalue;
12782 } else {
12783 $defval = 'Off';
12784 }
12785 // set font
12786 $font = 'zapfdingbats';
12787 if ($this->pdfa_mode) {
12788 // all fonts must be embedded
12789 $font = 'pdfa'.$font;
12790 }
12791 $this->AddFont($font);
12792 $tmpfont = $this->getFontBuffer($font);
12793 // set data for parent group
12794 if (!isset($this->radiobutton_groups[$this->page])) {
12795 $this->radiobutton_groups[$this->page] = array();
12796 }
12797 if (!isset($this->radiobutton_groups[$this->page][$name])) {
12798 $this->radiobutton_groups[$this->page][$name] = array();
12799 ++$this->n;
12800 $this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
12801 $this->radio_groups[] = $this->n;
12802 }
12803 $kid = ($this->n + 1);
12804 // save object ID to be added on Kids entry on parent object
12805 $this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
12806 // get default style
12807 $prop = array_merge($this->getFormDefaultProp(), $prop);
12808 $prop['NoToggleToOff'] = 'true';
12809 $prop['Radio'] = 'true';
12810 $prop['borderStyle'] = 'inset';
12811 // get annotation data
12812 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12813 // set additional default options
12814 $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
12815 $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
12816 $popt['da'] = $fontstyle;
12817 // build appearance stream
12818 $popt['ap'] = array();
12819 $popt['ap']['n'] = array();
12820 $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
12821 $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
12822 $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);
12823 $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);
12824 if (!isset($popt['mk'])) {
12825 $popt['mk'] = array();
12826 }
12827 $popt['mk']['ca'] = '(l)';
12828 // merge options
12829 $opt = array_merge($popt, $opt);
12830 // set remaining annotation data
12831 $opt['Subtype'] = 'Widget';
12832 $opt['ft'] = 'Btn';
12833 if ($checked) {
12834 $opt['v'] = array('/'.$onvalue);
12835 $opt['as'] = $onvalue;
12836 } else {
12837 $opt['as'] = 'Off';
12838 }
12839 // store readonly flag
12840 if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
12841 $this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
12842 }
12843 $this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
12844 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
12845 if ($this->rtl) {
12846 $this->x -= $w;
12847 } else {
12848 $this->x += $w;
12849 }
12850 }
12851
12852 /**
12853 * Creates a List-box field
12854 * @param $name (string) field name
12855 * @param $w (int) width
12856 * @param $h (int) height
12857 * @param $values (array) array containing the list of values.
12858 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12859 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12860 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12861 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12862 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12863 * @public
12864 * @author Nicola Asuni
12865 * @since 4.8.000 (2009-09-07)
12866 */
12867 public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12868 if ($x === '') {
12869 $x = $this->x;
12870 }
12871 if ($y === '') {
12872 $y = $this->y;
12873 }
12874 // check page for no-write regions and adapt page margins if necessary
12875 list($x, $y) = $this->checkPageRegions($h, $x, $y);
12876 if ($js) {
12877 $this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
12878 $s = '';
12879 foreach ($values as $value) {
12880 if (is_array($value)) {
12881 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
12882 } else {
12883 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
12884 }
12885 }
12886 $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
12887 return;
12888 }
12889 // get default style
12890 $prop = array_merge($this->getFormDefaultProp(), $prop);
12891 // get annotation data
12892 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12893 // set additional default values
12894 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12895 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12896 $popt['da'] = $fontstyle;
12897 // build appearance stream
12898 $popt['ap'] = array();
12899 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12900 $text = '';
12901 foreach($values as $item) {
12902 if (is_array($item)) {
12903 $text .= $item[1]."\n";
12904 } else {
12905 $text .= $item."\n";
12906 }
12907 }
12908 $tmpid = $this->startTemplate($w, $h, false);
12909 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12910 $this->endTemplate();
12911 --$this->n;
12912 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12913 unset($this->xobjects[$tmpid]);
12914 $popt['ap']['n'] .= 'Q EMC';
12915 // merge options
12916 $opt = array_merge($popt, $opt);
12917 // set remaining annotation data
12918 $opt['Subtype'] = 'Widget';
12919 $opt['ft'] = 'Ch';
12920 $opt['t'] = $name;
12921 $opt['opt'] = $values;
12922 unset($opt['mk']['ca']);
12923 unset($opt['mk']['rc']);
12924 unset($opt['mk']['ac']);
12925 unset($opt['mk']['i']);
12926 unset($opt['mk']['ri']);
12927 unset($opt['mk']['ix']);
12928 unset($opt['mk']['if']);
12929 unset($opt['mk']['tp']);
12930 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12931 if ($this->rtl) {
12932 $this->x -= $w;
12933 } else {
12934 $this->x += $w;
12935 }
12936 }
12937
12938 /**
12939 * Creates a Combo-box field
12940 * @param $name (string) field name
12941 * @param $w (int) width
12942 * @param $h (int) height
12943 * @param $values (array) array containing the list of values.
12944 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12945 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12946 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12947 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12948 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12949 * @public
12950 * @author Nicola Asuni
12951 * @since 4.8.000 (2009-09-07)
12952 */
12953 public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12954 if ($x === '') {
12955 $x = $this->x;
12956 }
12957 if ($y === '') {
12958 $y = $this->y;
12959 }
12960 // check page for no-write regions and adapt page margins if necessary
12961 list($x, $y) = $this->checkPageRegions($h, $x, $y);
12962 if ($js) {
12963 $this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
12964 $s = '';
12965 foreach ($values as $value) {
12966 if (is_array($value)) {
12967 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
12968 } else {
12969 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
12970 }
12971 }
12972 $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
12973 return;
12974 }
12975 // get default style
12976 $prop = array_merge($this->getFormDefaultProp(), $prop);
12977 $prop['Combo'] = true;
12978 // get annotation data
12979 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12980 // set additional default options
12981 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12982 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12983 $popt['da'] = $fontstyle;
12984 // build appearance stream
12985 $popt['ap'] = array();
12986 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12987 $text = '';
12988 foreach($values as $item) {
12989 if (is_array($item)) {
12990 $text .= $item[1]."\n";
12991 } else {
12992 $text .= $item."\n";
12993 }
12994 }
12995 $tmpid = $this->startTemplate($w, $h, false);
12996 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12997 $this->endTemplate();
12998 --$this->n;
12999 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13000 unset($this->xobjects[$tmpid]);
13001 $popt['ap']['n'] .= 'Q EMC';
13002 // merge options
13003 $opt = array_merge($popt, $opt);
13004 // set remaining annotation data
13005 $opt['Subtype'] = 'Widget';
13006 $opt['ft'] = 'Ch';
13007 $opt['t'] = $name;
13008 $opt['opt'] = $values;
13009 unset($opt['mk']['ca']);
13010 unset($opt['mk']['rc']);
13011 unset($opt['mk']['ac']);
13012 unset($opt['mk']['i']);
13013 unset($opt['mk']['ri']);
13014 unset($opt['mk']['ix']);
13015 unset($opt['mk']['if']);
13016 unset($opt['mk']['tp']);
13017 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13018 if ($this->rtl) {
13019 $this->x -= $w;
13020 } else {
13021 $this->x += $w;
13022 }
13023 }
13024
13025 /**
13026 * Creates a CheckBox field
13027 * @param $name (string) field name
13028 * @param $w (int) width
13029 * @param $checked (boolean) define the initial state.
13030 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13031 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13032 * @param $onvalue (string) value to be returned if selected.
13033 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13034 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13035 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13036 * @public
13037 * @author Nicola Asuni
13038 * @since 4.8.000 (2009-09-07)
13039 */
13040 public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
13041 if ($x === '') {
13042 $x = $this->x;
13043 }
13044 if ($y === '') {
13045 $y = $this->y;
13046 }
13047 // check page for no-write regions and adapt page margins if necessary
13048 list($x, $y) = $this->checkPageRegions($w, $x, $y);
13049 if ($js) {
13050 $this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
13051 return;
13052 }
13053 if (!isset($prop['value'])) {
13054 $prop['value'] = array('Yes');
13055 }
13056 // get default style
13057 $prop = array_merge($this->getFormDefaultProp(), $prop);
13058 $prop['borderStyle'] = 'inset';
13059 // get annotation data
13060 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13061 // set additional default options
13062 $font = 'zapfdingbats';
13063 if ($this->pdfa_mode) {
13064 // all fonts must be embedded
13065 $font = 'pdfa'.$font;
13066 }
13067 $this->AddFont($font);
13068 $tmpfont = $this->getFontBuffer($font);
13069 $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
13070 $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
13071 $popt['da'] = $fontstyle;
13072 // build appearance stream
13073 $popt['ap'] = array();
13074 $popt['ap']['n'] = array();
13075 $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
13076 $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
13077 $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);
13078 $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);
13079 // merge options
13080 $opt = array_merge($popt, $opt);
13081 // set remaining annotation data
13082 $opt['Subtype'] = 'Widget';
13083 $opt['ft'] = 'Btn';
13084 $opt['t'] = $name;
13085 if (TCPDF_STATIC::empty_string($onvalue)) {
13086 $onvalue = 'Yes';
13087 }
13088 $opt['opt'] = array($onvalue);
13089 if ($checked) {
13090 $opt['v'] = array('/Yes');
13091 $opt['as'] = 'Yes';
13092 } else {
13093 $opt['v'] = array('/Off');
13094 $opt['as'] = 'Off';
13095 }
13096 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13097 if ($this->rtl) {
13098 $this->x -= $w;
13099 } else {
13100 $this->x += $w;
13101 }
13102 }
13103
13104 /**
13105 * Creates a button field
13106 * @param $name (string) field name
13107 * @param $w (int) width
13108 * @param $h (int) height
13109 * @param $caption (string) caption.
13110 * @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.
13111 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13112 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13113 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13114 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13115 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13116 * @public
13117 * @author Nicola Asuni
13118 * @since 4.8.000 (2009-09-07)
13119 */
13120 public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
13121 if ($x === '') {
13122 $x = $this->x;
13123 }
13124 if ($y === '') {
13125 $y = $this->y;
13126 }
13127 // check page for no-write regions and adapt page margins if necessary
13128 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13129 if ($js) {
13130 $this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
13131 $this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
13132 $this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
13133 $this->javascript .= 'f'.$name.".highlight='push';\n";
13134 $this->javascript .= 'f'.$name.".print=false;\n";
13135 return;
13136 }
13137 // get default style
13138 $prop = array_merge($this->getFormDefaultProp(), $prop);
13139 $prop['Pushbutton'] = 'true';
13140 $prop['highlight'] = 'push';
13141 $prop['display'] = 'display.noPrint';
13142 // get annotation data
13143 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13144 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13145 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13146 $popt['da'] = $fontstyle;
13147 // build appearance stream
13148 $popt['ap'] = array();
13149 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13150 $tmpid = $this->startTemplate($w, $h, false);
13151 $bw = (2 / $this->k); // border width
13152 $border = array(
13153 'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13154 'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
13155 'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13156 'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
13157 $this->SetFillColor(204);
13158 $this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
13159 $this->endTemplate();
13160 --$this->n;
13161 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13162 unset($this->xobjects[$tmpid]);
13163 $popt['ap']['n'] .= 'Q EMC';
13164 // set additional default options
13165 if (!isset($popt['mk'])) {
13166 $popt['mk'] = array();
13167 }
13168 $ann_obj_id = ($this->n + 1);
13169 if (!empty($action) AND !is_array($action)) {
13170 $ann_obj_id = ($this->n + 2);
13171 }
13172 $popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
13173 $popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
13174 $popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
13175 // merge options
13176 $opt = array_merge($popt, $opt);
13177 // set remaining annotation data
13178 $opt['Subtype'] = 'Widget';
13179 $opt['ft'] = 'Btn';
13180 $opt['t'] = $caption;
13181 $opt['v'] = $name;
13182 if (!empty($action)) {
13183 if (is_array($action)) {
13184 // form action options as on section 12.7.5 of PDF32000_2008.
13185 $opt['aa'] = '/D <<';
13186 $bmode = array('SubmitForm', 'ResetForm', 'ImportData');
13187 foreach ($action AS $key => $val) {
13188 if (($key == 'S') AND in_array($val, $bmode)) {
13189 $opt['aa'] .= ' /S /'.$val;
13190 } elseif (($key == 'F') AND (!empty($val))) {
13191 $opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
13192 } elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
13193 $opt['aa'] .= ' /Fields [';
13194 foreach ($val AS $field) {
13195 $opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
13196 }
13197 $opt['aa'] .= ']';
13198 } elseif (($key == 'Flags')) {
13199 $ff = 0;
13200 if (is_array($val)) {
13201 foreach ($val AS $flag) {
13202 switch ($flag) {
13203 case 'Include/Exclude': {
13204 $ff += 1 << 0;
13205 break;
13206 }
13207 case 'IncludeNoValueFields': {
13208 $ff += 1 << 1;
13209 break;
13210 }
13211 case 'ExportFormat': {
13212 $ff += 1 << 2;
13213 break;
13214 }
13215 case 'GetMethod': {
13216 $ff += 1 << 3;
13217 break;
13218 }
13219 case 'SubmitCoordinates': {
13220 $ff += 1 << 4;
13221 break;
13222 }
13223 case 'XFDF': {
13224 $ff += 1 << 5;
13225 break;
13226 }
13227 case 'IncludeAppendSaves': {
13228 $ff += 1 << 6;
13229 break;
13230 }
13231 case 'IncludeAnnotations': {
13232 $ff += 1 << 7;
13233 break;
13234 }
13235 case 'SubmitPDF': {
13236 $ff += 1 << 8;
13237 break;
13238 }
13239 case 'CanonicalFormat': {
13240 $ff += 1 << 9;
13241 break;
13242 }
13243 case 'ExclNonUserAnnots': {
13244 $ff += 1 << 10;
13245 break;
13246 }
13247 case 'ExclFKey': {
13248 $ff += 1 << 11;
13249 break;
13250 }
13251 case 'EmbedForm': {
13252 $ff += 1 << 13;
13253 break;
13254 }
13255 }
13256 }
13257 } else {
13258 $ff = intval($val);
13259 }
13260 $opt['aa'] .= ' /Flags '.$ff;
13261 }
13262 }
13263 $opt['aa'] .= ' >>';
13264 } else {
13265 // Javascript action or raw action command
13266 $js_obj_id = $this->addJavascriptObject($action);
13267 $opt['aa'] = '/D '.$js_obj_id.' 0 R';
13268 }
13269 }
13270 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13271 if ($this->rtl) {
13272 $this->x -= $w;
13273 } else {
13274 $this->x += $w;
13275 }
13276 }
13277
13278 // --- END FORMS FIELDS ------------------------------------------------
13279
13280 /**
13281 * Add certification signature (DocMDP or UR3)
13282 * You can set only one signature type
13283 * @protected
13284 * @author Nicola Asuni
13285 * @since 4.6.008 (2009-05-07)
13286 */
13287 protected function _putsignature() {
13288 if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
13289 return;
13290 }
13291 $sigobjid = ($this->sig_obj_id + 1);
13292 $out = $this->_getobj($sigobjid)."\n";
13293 $out .= '<< /Type /Sig';
13294 $out .= ' /Filter /Adobe.PPKLite';
13295 $out .= ' /SubFilter /adbe.pkcs7.detached';
13296 $out .= ' '.TCPDF_STATIC::$byterange_string;
13297 $out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
13298 if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
13299 $out .= ' /Reference ['; // array of signature reference dictionaries
13300 $out .= ' << /Type /SigRef';
13301 if ($this->signature_data['cert_type'] > 0) {
13302 $out .= ' /TransformMethod /DocMDP';
13303 $out .= ' /TransformParams <<';
13304 $out .= ' /Type /TransformParams';
13305 $out .= ' /P '.$this->signature_data['cert_type'];
13306 $out .= ' /V /1.2';
13307 } else {
13308 $out .= ' /TransformMethod /UR3';
13309 $out .= ' /TransformParams <<';
13310 $out .= ' /Type /TransformParams';
13311 $out .= ' /V /2.2';
13312 if (!TCPDF_STATIC::empty_string($this->ur['document'])) {
13313 $out .= ' /Document['.$this->ur['document'].']';
13314 }
13315 if (!TCPDF_STATIC::empty_string($this->ur['form'])) {
13316 $out .= ' /Form['.$this->ur['form'].']';
13317 }
13318 if (!TCPDF_STATIC::empty_string($this->ur['signature'])) {
13319 $out .= ' /Signature['.$this->ur['signature'].']';
13320 }
13321 if (!TCPDF_STATIC::empty_string($this->ur['annots'])) {
13322 $out .= ' /Annots['.$this->ur['annots'].']';
13323 }
13324 if (!TCPDF_STATIC::empty_string($this->ur['ef'])) {
13325 $out .= ' /EF['.$this->ur['ef'].']';
13326 }
13327 if (!TCPDF_STATIC::empty_string($this->ur['formex'])) {
13328 $out .= ' /FormEX['.$this->ur['formex'].']';
13329 }
13330 }
13331 $out .= ' >>'; // close TransformParams
13332 // optional digest data (values must be calculated and replaced later)
13333 //$out .= ' /Data ********** 0 R';
13334 //$out .= ' /DigestMethod/MD5';
13335 //$out .= ' /DigestLocation[********** 34]';
13336 //$out .= ' /DigestValue<********************************>';
13337 $out .= ' >>';
13338 $out .= ' ]'; // end of reference
13339 }
13340 if (isset($this->signature_data['info']['Name']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Name'])) {
13341 $out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
13342 }
13343 if (isset($this->signature_data['info']['Location']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Location'])) {
13344 $out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
13345 }
13346 if (isset($this->signature_data['info']['Reason']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Reason'])) {
13347 $out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
13348 }
13349 if (isset($this->signature_data['info']['ContactInfo']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['ContactInfo'])) {
13350 $out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
13351 }
13352 $out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
13353 $out .= ' >>';
13354 $out .= "\n".'endobj';
13355 $this->_out($out);
13356 }
13357
13358 /**
13359 * Set User's Rights for PDF Reader
13360 * WARNING: This is experimental and currently do not work.
13361 * Check the PDF Reference 8.7.1 Transform Methods,
13362 * Table 8.105 Entries in the UR transform parameters dictionary
13363 * @param $enable (boolean) if true enable user's rights on PDF reader
13364 * @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.
13365 * @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.
13366 * @param $form (string) Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
13367 * @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.
13368 * @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
13369 Names specifying additional embedded-files-related usage rights for the document.
13370 * @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.
13371 * @public
13372 * @author Nicola Asuni
13373 * @since 2.9.000 (2008-03-26)
13374 */
13375 public function setUserRights(
13376 $enable=true,
13377 $document='/FullSave',
13378 $annots='/Create/Delete/Modify/Copy/Import/Export',
13379 $form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
13380 $signature='/Modify',
13381 $ef='/Create/Delete/Modify/Import',
13382 $formex='') {
13383 $this->ur['enabled'] = $enable;
13384 $this->ur['document'] = $document;
13385 $this->ur['annots'] = $annots;
13386 $this->ur['form'] = $form;
13387 $this->ur['signature'] = $signature;
13388 $this->ur['ef'] = $ef;
13389 $this->ur['formex'] = $formex;
13390 if (!$this->sign) {
13391 $this->setSignature('', '', '', '', 0, array());
13392 }
13393 }
13394
13395 /**
13396 * Enable document signature (requires the OpenSSL Library).
13397 * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
13398 * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13399 * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13400 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
13401 * @param $signing_cert (mixed) signing certificate (string or filename prefixed with 'file://')
13402 * @param $private_key (mixed) private key (string or filename prefixed with 'file://')
13403 * @param $private_key_password (string) password
13404 * @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.
13405 * @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.
13406 * @param $info (array) array of option information: Name, Location, Reason, ContactInfo.
13407 * @param $approval (string) Enable approval signature eg. for PDF incremental update
13408 * @public
13409 * @author Nicola Asuni
13410 * @since 4.6.005 (2009-04-24)
13411 */
13412 public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array(), $approval='') {
13413 // to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13414 // to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13415 // to convert pfx certificate to pem: openssl
13416 // OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
13417 $this->sign = true;
13418 ++$this->n;
13419 $this->sig_obj_id = $this->n; // signature widget
13420 ++$this->n; // signature object ($this->sig_obj_id + 1)
13421 $this->signature_data = array();
13422 if (strlen($signing_cert) == 0) {
13423 $this->Error('Please provide a certificate file and password!');
13424 }
13425 if (strlen($private_key) == 0) {
13426 $private_key = $signing_cert;
13427 }
13428 $this->signature_data['signcert'] = $signing_cert;
13429 $this->signature_data['privkey'] = $private_key;
13430 $this->signature_data['password'] = $private_key_password;
13431 $this->signature_data['extracerts'] = $extracerts;
13432 $this->signature_data['cert_type'] = $cert_type;
13433 $this->signature_data['info'] = $info;
13434 $this->signature_data['approval'] = $approval;
13435 }
13436
13437 /**
13438 * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
13439 * @param $x (float) Abscissa of the upper-left corner.
13440 * @param $y (float) Ordinate of the upper-left corner.
13441 * @param $w (float) Width of the signature area.
13442 * @param $h (float) Height of the signature area.
13443 * @param $page (int) option page number (if < 0 the current page is used).
13444 * @param $name (string) Name of the signature.
13445 * @public
13446 * @author Nicola Asuni
13447 * @since 5.3.011 (2010-06-17)
13448 */
13449 public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13450 $this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13451 }
13452
13453 /**
13454 * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
13455 * @param $x (float) Abscissa of the upper-left corner.
13456 * @param $y (float) Ordinate of the upper-left corner.
13457 * @param $w (float) Width of the signature area.
13458 * @param $h (float) Height of the signature area.
13459 * @param $page (int) option page number (if < 0 the current page is used).
13460 * @param $name (string) Name of the signature.
13461 * @public
13462 * @author Nicola Asuni
13463 * @since 5.9.101 (2011-07-06)
13464 */
13465 public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13466 ++$this->n;
13467 $this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13468 }
13469
13470 /**
13471 * Get the array that defines the signature appearance (page and rectangle coordinates).
13472 * @param $x (float) Abscissa of the upper-left corner.
13473 * @param $y (float) Ordinate of the upper-left corner.
13474 * @param $w (float) Width of the signature area.
13475 * @param $h (float) Height of the signature area.
13476 * @param $page (int) option page number (if < 0 the current page is used).
13477 * @param $name (string) Name of the signature.
13478 * @return (array) Array defining page and rectangle coordinates of signature appearance.
13479 * @protected
13480 * @author Nicola Asuni
13481 * @since 5.9.101 (2011-07-06)
13482 */
13483 protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13484 $sigapp = array();
13485 if (($page < 1) OR ($page > $this->numpages)) {
13486 $sigapp['page'] = $this->page;
13487 } else {
13488 $sigapp['page'] = intval($page);
13489 }
13490 if (empty($name)) {
13491 $sigapp['name'] = 'Signature';
13492 } else {
13493 $sigapp['name'] = $name;
13494 }
13495 $a = $x * $this->k;
13496 $b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
13497 $c = $w * $this->k;
13498 $d = $h * $this->k;
13499 $sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
13500 return $sigapp;
13501 }
13502
13503 /**
13504 * Enable document timestamping (requires the OpenSSL Library).
13505 * The trusted timestamping improve document security that means that no one should be able to change the document once it has been recorded.
13506 * Use with digital signature only!
13507 * @param $tsa_host (string) Time Stamping Authority (TSA) server (prefixed with 'https://')
13508 * @param $tsa_username (string) Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional)
13509 * @param $tsa_password (string) Specifies the password for TSA authorization (optional)
13510 * @param $tsa_cert (string) Specifies the location of TSA certificate for authorization (optional for cURL)
13511 * @public
13512 * @author Richard Stockinger
13513 * @since 6.0.090 (2014-06-16)
13514 */
13515 public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') {
13516 $this->tsa_data = array();
13517 if (!function_exists('curl_init')) {
13518 $this->Error('Please enable cURL PHP extension!');
13519 }
13520 if (strlen($tsa_host) == 0) {
13521 $this->Error('Please specify the host of Time Stamping Authority (TSA)!');
13522 }
13523 $this->tsa_data['tsa_host'] = $tsa_host;
13524 if (is_file($tsa_username)) {
13525 $this->tsa_data['tsa_auth'] = $tsa_username;
13526 } else {
13527 $this->tsa_data['tsa_username'] = $tsa_username;
13528 }
13529 $this->tsa_data['tsa_password'] = $tsa_password;
13530 $this->tsa_data['tsa_cert'] = $tsa_cert;
13531 $this->tsa_timestamp = true;
13532 }
13533
13534 /**
13535 * NOT YET IMPLEMENTED
13536 * Request TSA for a timestamp
13537 * @param $signature (string) Digital signature as binary string
13538 * @return (string) Timestamped digital signature
13539 * @protected
13540 * @author Richard Stockinger
13541 * @since 6.0.090 (2014-06-16)
13542 */
13543 protected function applyTSA($signature) {
13544 if (!$this->tsa_timestamp) {
13545 return $signature;
13546 }
13547 //@TODO: implement this feature
13548 return $signature;
13549 }
13550
13551 /**
13552 * Create a new page group.
13553 * NOTE: call this function before calling AddPage()
13554 * @param $page (int) starting group page (leave empty for next page).
13555 * @public
13556 * @since 3.0.000 (2008-03-27)
13557 */
13558 public function startPageGroup($page='') {
13559 if (empty($page)) {
13560 $page = $this->page + 1;
13561 }
13562 $this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
13563 }
13564
13565 /**
13566 * Set the starting page number.
13567 * @param $num (int) Starting page number.
13568 * @since 5.9.093 (2011-06-16)
13569 * @public
13570 */
13571 public function setStartingPageNumber($num=1) {
13572 $this->starting_page_number = max(0, intval($num));
13573 }
13574
13575 /**
13576 * Returns the string alias used right align page numbers.
13577 * If the current font is unicode type, the returned string wil contain an additional open curly brace.
13578 * @return string
13579 * @since 5.9.099 (2011-06-27)
13580 * @public
13581 */
13582 public function getAliasRightShift() {
13583 // calculate aproximatively the ratio between widths of aliases and replacements.
13584 $ref = '{'.TCPDF_STATIC::$alias_right_shift.'}{'.TCPDF_STATIC::$alias_tot_pages.'}{'.TCPDF_STATIC::$alias_num_page.'}';
13585 $rep = str_repeat(' ', $this->GetNumChars($ref));
13586 $wrep = $this->GetStringWidth($rep);
13587 if ($wrep > 0) {
13588 $wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
13589 } else {
13590 $wdiff = 1;
13591 }
13592 $sdiff = sprintf('%F', $wdiff);
13593 $alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
13594 if ($this->isUnicodeFont()) {
13595 $alias = '{'.$alias;
13596 }
13597 return $alias;
13598 }
13599
13600 /**
13601 * Returns the string alias used for the total number of pages.
13602 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13603 * This alias will be replaced by the total number of pages in the document.
13604 * @return string
13605 * @since 4.0.018 (2008-08-08)
13606 * @public
13607 */
13608 public function getAliasNbPages() {
13609 if ($this->isUnicodeFont()) {
13610 return '{'.TCPDF_STATIC::$alias_tot_pages.'}';
13611 }
13612 return TCPDF_STATIC::$alias_tot_pages;
13613 }
13614
13615 /**
13616 * Returns the string alias used for the page number.
13617 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13618 * This alias will be replaced by the page number.
13619 * @return string
13620 * @since 4.5.000 (2009-01-02)
13621 * @public
13622 */
13623 public function getAliasNumPage() {
13624 if ($this->isUnicodeFont()) {
13625 return '{'.TCPDF_STATIC::$alias_num_page.'}';
13626 }
13627 return TCPDF_STATIC::$alias_num_page;
13628 }
13629
13630 /**
13631 * Return the alias for the total number of pages in the current page group.
13632 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13633 * This alias will be replaced by the total number of pages in this group.
13634 * @return alias of the current page group
13635 * @public
13636 * @since 3.0.000 (2008-03-27)
13637 */
13638 public function getPageGroupAlias() {
13639 if ($this->isUnicodeFont()) {
13640 return '{'.TCPDF_STATIC::$alias_group_tot_pages.'}';
13641 }
13642 return TCPDF_STATIC::$alias_group_tot_pages;
13643 }
13644
13645 /**
13646 * Return the alias for the page number on the current page group.
13647 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13648 * This alias will be replaced by the page number (relative to the belonging group).
13649 * @return alias of the current page group
13650 * @public
13651 * @since 4.5.000 (2009-01-02)
13652 */
13653 public function getPageNumGroupAlias() {
13654 if ($this->isUnicodeFont()) {
13655 return '{'.TCPDF_STATIC::$alias_group_num_page.'}';
13656 }
13657 return TCPDF_STATIC::$alias_group_num_page;
13658 }
13659
13660 /**
13661 * Return the current page in the group.
13662 * @return current page in the group
13663 * @public
13664 * @since 3.0.000 (2008-03-27)
13665 */
13666 public function getGroupPageNo() {
13667 return $this->pagegroups[$this->currpagegroup];
13668 }
13669
13670 /**
13671 * Returns the current group page number formatted as a string.
13672 * @public
13673 * @since 4.3.003 (2008-11-18)
13674 * @see PaneNo(), formatPageNumber()
13675 */
13676 public function getGroupPageNoFormatted() {
13677 return TCPDF_STATIC::formatPageNumber($this->getGroupPageNo());
13678 }
13679
13680 /**
13681 * Returns the current page number formatted as a string.
13682 * @public
13683 * @since 4.2.005 (2008-11-06)
13684 * @see PaneNo(), formatPageNumber()
13685 */
13686 public function PageNoFormatted() {
13687 return TCPDF_STATIC::formatPageNumber($this->PageNo());
13688 }
13689
13690 /**
13691 * Put pdf layers.
13692 * @protected
13693 * @since 3.0.000 (2008-03-27)
13694 */
13695 protected function _putocg() {
13696 if (empty($this->pdflayers)) {
13697 return;
13698 }
13699 foreach ($this->pdflayers as $key => $layer) {
13700 $this->pdflayers[$key]['objid'] = $this->_newobj();
13701 $out = '<< /Type /OCG';
13702 $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
13703 $out .= ' /Usage <<';
13704 if (isset($layer['print']) AND ($layer['print'] !== NULL)) {
13705 $out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
13706 }
13707 $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
13708 $out .= ' >> >>';
13709 $out .= "\n".'endobj';
13710 $this->_out($out);
13711 }
13712 }
13713
13714 /**
13715 * Start a new pdf layer.
13716 * @param $name (string) Layer name (only a-z letters and numbers). Leave empty for automatic name.
13717 * @param $print (boolean|null) Set to TRUE to print this layer, FALSE to not print and NULL to not set this option
13718 * @param $view (boolean) Set to true to view this layer.
13719 * @param $lock (boolean) If true lock the layer
13720 * @public
13721 * @since 5.9.102 (2011-07-13)
13722 */
13723 public function startLayer($name='', $print=true, $view=true, $lock=true) {
13724 if ($this->state != 2) {
13725 return;
13726 }
13727 $layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
13728 if (empty($name)) {
13729 $name = $layer;
13730 } else {
13731 $name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
13732 }
13733 $this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view, 'lock' => $lock);
13734 $this->openMarkedContent = true;
13735 $this->_out('/OC /'.$layer.' BDC');
13736 }
13737
13738 /**
13739 * End the current PDF layer.
13740 * @public
13741 * @since 5.9.102 (2011-07-13)
13742 */
13743 public function endLayer() {
13744 if ($this->state != 2) {
13745 return;
13746 }
13747 if ($this->openMarkedContent) {
13748 // close existing open marked-content layer
13749 $this->_out('EMC');
13750 $this->openMarkedContent = false;
13751 }
13752 }
13753
13754 /**
13755 * Set the visibility of the successive elements.
13756 * This can be useful, for instance, to put a background
13757 * image or color that will show on screen but won't print.
13758 * @param $v (string) visibility mode. Legal values are: all, print, screen or view.
13759 * @public
13760 * @since 3.0.000 (2008-03-27)
13761 */
13762 public function setVisibility($v) {
13763 if ($this->state != 2) {
13764 return;
13765 }
13766 $this->endLayer();
13767 switch($v) {
13768 case 'print': {
13769 $this->startLayer('Print', true, false);
13770 break;
13771 }
13772 case 'view':
13773 case 'screen': {
13774 $this->startLayer('View', false, true);
13775 break;
13776 }
13777 case 'all': {
13778 $this->_out('');
13779 break;
13780 }
13781 default: {
13782 $this->Error('Incorrect visibility: '.$v);
13783 break;
13784 }
13785 }
13786 }
13787
13788 /**
13789 * Add transparency parameters to the current extgstate
13790 * @param $parms (array) parameters
13791 * @return the number of extgstates
13792 * @protected
13793 * @since 3.0.000 (2008-03-27)
13794 */
13795 protected function addExtGState($parms) {
13796 if ($this->pdfa_mode) {
13797 // transparencies are not allowed in PDF/A mode
13798 return;
13799 }
13800 // check if this ExtGState already exist
13801 foreach ($this->extgstates as $i => $ext) {
13802 if ($ext['parms'] == $parms) {
13803 if ($this->inxobj) {
13804 // we are inside an XObject template
13805 $this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
13806 }
13807 // return reference to existing ExtGState
13808 return $i;
13809 }
13810 }
13811 $n = (count($this->extgstates) + 1);
13812 $this->extgstates[$n] = array('parms' => $parms);
13813 if ($this->inxobj) {
13814 // we are inside an XObject template
13815 $this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
13816 }
13817 return $n;
13818 }
13819
13820 /**
13821 * Add an extgstate
13822 * @param $gs (array) extgstate
13823 * @protected
13824 * @since 3.0.000 (2008-03-27)
13825 */
13826 protected function setExtGState($gs) {
13827 if ($this->pdfa_mode OR ($this->state != 2)) {
13828 // transparency is not allowed in PDF/A mode
13829 return;
13830 }
13831 $this->_out(sprintf('/GS%d gs', $gs));
13832 }
13833
13834 /**
13835 * Put extgstates for object transparency
13836 * @protected
13837 * @since 3.0.000 (2008-03-27)
13838 */
13839 protected function _putextgstates() {
13840 foreach ($this->extgstates as $i => $ext) {
13841 $this->extgstates[$i]['n'] = $this->_newobj();
13842 $out = '<< /Type /ExtGState';
13843 foreach ($ext['parms'] as $k => $v) {
13844 if (is_float($v)) {
13845 $v = sprintf('%F', $v);
13846 } elseif ($v === true) {
13847 $v = 'true';
13848 } elseif ($v === false) {
13849 $v = 'false';
13850 }
13851 $out .= ' /'.$k.' '.$v;
13852 }
13853 $out .= ' >>';
13854 $out .= "\n".'endobj';
13855 $this->_out($out);
13856 }
13857 }
13858
13859 /**
13860 * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
13861 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13862 * @param $stroking (boolean) If true apply overprint for stroking operations.
13863 * @param $nonstroking (boolean) If true apply overprint for painting operations other than stroking.
13864 * @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).
13865 * @public
13866 * @since 5.9.152 (2012-03-23)
13867 */
13868 public function setOverprint($stroking=true, $nonstroking='', $mode=0) {
13869 if ($this->state != 2) {
13870 return;
13871 }
13872 $stroking = $stroking ? true : false;
13873 if (TCPDF_STATIC::empty_string($nonstroking)) {
13874 // default value if not set
13875 $nonstroking = $stroking;
13876 } else {
13877 $nonstroking = $nonstroking ? true : false;
13878 }
13879 if (($mode != 0) AND ($mode != 1)) {
13880 $mode = 0;
13881 }
13882 $this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
13883 $gs = $this->addExtGState($this->overprint);
13884 $this->setExtGState($gs);
13885 }
13886
13887 /**
13888 * Get the overprint mode array (OP, op, OPM).
13889 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13890 * @return array.
13891 * @public
13892 * @since 5.9.152 (2012-03-23)
13893 */
13894 public function getOverprint() {
13895 return $this->overprint;
13896 }
13897
13898 /**
13899 * Set alpha for stroking (CA) and non-stroking (ca) operations.
13900 * @param $stroking (float) Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
13901 * @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
13902 * @param $nonstroking (float) Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
13903 * @param $ais (boolean)
13904 * @public
13905 * @since 3.0.000 (2008-03-27)
13906 */
13907 public function setAlpha($stroking=1, $bm='Normal', $nonstroking='', $ais=false) {
13908 if ($this->pdfa_mode) {
13909 // transparency is not allowed in PDF/A mode
13910 return;
13911 }
13912 $stroking = floatval($stroking);
13913 if (TCPDF_STATIC::empty_string($nonstroking)) {
13914 // default value if not set
13915 $nonstroking = $stroking;
13916 } else {
13917 $nonstroking = floatval($nonstroking);
13918 }
13919 if ($bm[0] == '/') {
13920 // remove trailing slash
13921 $bm = substr($bm, 1);
13922 }
13923 if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
13924 $bm = 'Normal';
13925 }
13926 $ais = $ais ? true : false;
13927 $this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
13928 $gs = $this->addExtGState($this->alpha);
13929 $this->setExtGState($gs);
13930 }
13931
13932 /**
13933 * Get the alpha mode array (CA, ca, BM, AIS).
13934 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13935 * @return array.
13936 * @public
13937 * @since 5.9.152 (2012-03-23)
13938 */
13939 public function getAlpha() {
13940 return $this->alpha;
13941 }
13942
13943 /**
13944 * Set the default JPEG compression quality (1-100)
13945 * @param $quality (int) JPEG quality, integer between 1 and 100
13946 * @public
13947 * @since 3.0.000 (2008-03-27)
13948 */
13949 public function setJPEGQuality($quality) {
13950 if (($quality < 1) OR ($quality > 100)) {
13951 $quality = 75;
13952 }
13953 $this->jpeg_quality = intval($quality);
13954 }
13955
13956 /**
13957 * Set the default number of columns in a row for HTML tables.
13958 * @param $cols (int) number of columns
13959 * @public
13960 * @since 3.0.014 (2008-06-04)
13961 */
13962 public function setDefaultTableColumns($cols=4) {
13963 $this->default_table_columns = intval($cols);
13964 }
13965
13966 /**
13967 * Set the height of the cell (line height) respect the font height.
13968 * @param $h (int) cell proportion respect font height (typical value = 1.25).
13969 * @public
13970 * @since 3.0.014 (2008-06-04)
13971 */
13972 public function setCellHeightRatio($h) {
13973 $this->cell_height_ratio = $h;
13974 }
13975
13976 /**
13977 * return the height of cell repect font height.
13978 * @public
13979 * @since 4.0.012 (2008-07-24)
13980 */
13981 public function getCellHeightRatio() {
13982 return $this->cell_height_ratio;
13983 }
13984
13985 /**
13986 * Set the PDF version (check PDF reference for valid values).
13987 * @param $version (string) PDF document version.
13988 * @public
13989 * @since 3.1.000 (2008-06-09)
13990 */
13991 public function setPDFVersion($version='1.7') {
13992 if ($this->pdfa_mode) {
13993 // PDF/A mode
13994 $this->PDFVersion = '1.4';
13995 } else {
13996 $this->PDFVersion = $version;
13997 }
13998 }
13999
14000 /**
14001 * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
14002 * (see Section 8.1 of PDF reference, "Viewer Preferences").
14003 * <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>
14004 * @param $preferences (array) array of options.
14005 * @author Nicola Asuni
14006 * @public
14007 * @since 3.1.000 (2008-06-09)
14008 */
14009 public function setViewerPreferences($preferences) {
14010 $this->viewer_preferences = $preferences;
14011 }
14012
14013 /**
14014 * Paints color transition registration bars
14015 * @param $x (float) abscissa of the top left corner of the rectangle.
14016 * @param $y (float) ordinate of the top left corner of the rectangle.
14017 * @param $w (float) width of the rectangle.
14018 * @param $h (float) height of the rectangle.
14019 * @param $transition (boolean) if true prints tcolor transitions to white.
14020 * @param $vertical (boolean) if true prints bar vertically.
14021 * @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.
14022 * @author Nicola Asuni
14023 * @since 4.9.000 (2010-03-26)
14024 * @public
14025 */
14026 public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
14027 if (strpos($colors, 'ALLSPOT') !== false) {
14028 // expand spot colors
14029 $spot_colors = '';
14030 foreach ($this->spot_colors as $spot_color_name => $v) {
14031 $spot_colors .= ','.$spot_color_name;
14032 }
14033 if (!empty($spot_colors)) {
14034 $spot_colors = substr($spot_colors, 1);
14035 $colors = str_replace('ALLSPOT', $spot_colors, $colors);
14036 } else {
14037 $colors = str_replace('ALLSPOT', 'NONE', $colors);
14038 }
14039 }
14040 $bars = explode(',', $colors);
14041 $numbars = count($bars); // number of bars to print
14042 if ($numbars <= 0) {
14043 return;
14044 }
14045 // set bar measures
14046 if ($vertical) {
14047 $coords = array(0, 0, 0, 1);
14048 $wb = $w / $numbars; // bar width
14049 $hb = $h; // bar height
14050 $xd = $wb; // delta x
14051 $yd = 0; // delta y
14052 } else {
14053 $coords = array(1, 0, 0, 0);
14054 $wb = $w; // bar width
14055 $hb = $h / $numbars; // bar height
14056 $xd = 0; // delta x
14057 $yd = $hb; // delta y
14058 }
14059 $xb = $x;
14060 $yb = $y;
14061 foreach ($bars as $col) {
14062 switch ($col) {
14063 // set transition colors
14064 case 'A': { // BLACK (GRAYSCALE)
14065 $col_a = array(255);
14066 $col_b = array(0);
14067 break;
14068 }
14069 case 'W': { // WHITE (GRAYSCALE)
14070 $col_a = array(0);
14071 $col_b = array(255);
14072 break;
14073 }
14074 case 'R': { // RED (RGB)
14075 $col_a = array(255,255,255);
14076 $col_b = array(255,0,0);
14077 break;
14078 }
14079 case 'G': { // GREEN (RGB)
14080 $col_a = array(255,255,255);
14081 $col_b = array(0,255,0);
14082 break;
14083 }
14084 case 'B': { // BLUE (RGB)
14085 $col_a = array(255,255,255);
14086 $col_b = array(0,0,255);
14087 break;
14088 }
14089 case 'C': { // CYAN (CMYK)
14090 $col_a = array(0,0,0,0);
14091 $col_b = array(100,0,0,0);
14092 break;
14093 }
14094 case 'M': { // MAGENTA (CMYK)
14095 $col_a = array(0,0,0,0);
14096 $col_b = array(0,100,0,0);
14097 break;
14098 }
14099 case 'Y': { // YELLOW (CMYK)
14100 $col_a = array(0,0,0,0);
14101 $col_b = array(0,0,100,0);
14102 break;
14103 }
14104 case 'K': { // KEY - BLACK (CMYK)
14105 $col_a = array(0,0,0,0);
14106 $col_b = array(0,0,0,100);
14107 break;
14108 }
14109 case 'RGB': { // BLACK REGISTRATION (RGB)
14110 $col_a = array(255,255,255);
14111 $col_b = array(0,0,0);
14112 break;
14113 }
14114 case 'CMYK': { // BLACK REGISTRATION (CMYK)
14115 $col_a = array(0,0,0,0);
14116 $col_b = array(100,100,100,100);
14117 break;
14118 }
14119 case 'ALL': { // SPOT COLOR REGISTRATION
14120 $col_a = array(0,0,0,0,'None');
14121 $col_b = array(100,100,100,100,'All');
14122 break;
14123 }
14124 case 'NONE': { // SKIP THIS COLOR
14125 $col_a = array(0,0,0,0,'None');
14126 $col_b = array(0,0,0,0,'None');
14127 break;
14128 }
14129 default: { // SPECIFIC SPOT COLOR NAME
14130 $col_a = array(0,0,0,0,'None');
14131 $col_b = TCPDF_COLORS::getSpotColor($col, $this->spot_colors);
14132 if ($col_b === false) {
14133 // in case of error defaults to the registration color
14134 $col_b = array(100,100,100,100,'All');
14135 }
14136 break;
14137 }
14138 }
14139 if ($col != 'NONE') {
14140 if ($transition) {
14141 // color gradient
14142 $this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
14143 } else {
14144 $this->SetFillColorArray($col_b);
14145 // colored rectangle
14146 $this->Rect($xb, $yb, $wb, $hb, 'F', array());
14147 }
14148 $xb += $xd;
14149 $yb += $yd;
14150 }
14151 }
14152 }
14153
14154 /**
14155 * Paints crop marks.
14156 * @param $x (float) abscissa of the crop mark center.
14157 * @param $y (float) ordinate of the crop mark center.
14158 * @param $w (float) width of the crop mark.
14159 * @param $h (float) height of the crop mark.
14160 * @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.
14161 * @param $color (array) crop mark color (default spot registration color).
14162 * @author Nicola Asuni
14163 * @since 4.9.000 (2010-03-26)
14164 * @public
14165 */
14166 public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
14167 $this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
14168 $type = strtoupper($type);
14169 $type = preg_replace('/[^A-Z\-\,]*/', '', $type);
14170 // split type in single components
14171 $type = str_replace('-', ',', $type);
14172 $type = str_replace('TL', 'T,L', $type);
14173 $type = str_replace('TR', 'T,R', $type);
14174 $type = str_replace('BL', 'F,L', $type);
14175 $type = str_replace('BR', 'F,R', $type);
14176 $type = str_replace('A', 'T,L', $type);
14177 $type = str_replace('B', 'T,R', $type);
14178 $type = str_replace('T,RO', 'BO', $type);
14179 $type = str_replace('C', 'F,L', $type);
14180 $type = str_replace('D', 'F,R', $type);
14181 $crops = explode(',', strtoupper($type));
14182 // remove duplicates
14183 $crops = array_unique($crops);
14184 $dw = ($w / 4); // horizontal space to leave before the intersection point
14185 $dh = ($h / 4); // vertical space to leave before the intersection point
14186 foreach ($crops as $crop) {
14187 switch ($crop) {
14188 case 'T':
14189 case 'TOP': {
14190 $x1 = $x;
14191 $y1 = ($y - $h);
14192 $x2 = $x;
14193 $y2 = ($y - $dh);
14194 break;
14195 }
14196 case 'F':
14197 case 'BOTTOM': {
14198 $x1 = $x;
14199 $y1 = ($y + $dh);
14200 $x2 = $x;
14201 $y2 = ($y + $h);
14202 break;
14203 }
14204 case 'L':
14205 case 'LEFT': {
14206 $x1 = ($x - $w);
14207 $y1 = $y;
14208 $x2 = ($x - $dw);
14209 $y2 = $y;
14210 break;
14211 }
14212 case 'R':
14213 case 'RIGHT': {
14214 $x1 = ($x + $dw);
14215 $y1 = $y;
14216 $x2 = ($x + $w);
14217 $y2 = $y;
14218 break;
14219 }
14220 }
14221 $this->Line($x1, $y1, $x2, $y2);
14222 }
14223 }
14224
14225 /**
14226 * Paints a registration mark
14227 * @param $x (float) abscissa of the registration mark center.
14228 * @param $y (float) ordinate of the registration mark center.
14229 * @param $r (float) radius of the crop mark.
14230 * @param $double (boolean) if true print two concentric crop marks.
14231 * @param $cola (array) crop mark color (default spot registration color 'All').
14232 * @param $colb (array) second crop mark color (default spot registration color 'None').
14233 * @author Nicola Asuni
14234 * @since 4.9.000 (2010-03-26)
14235 * @public
14236 */
14237 public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
14238 $line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
14239 $this->SetFillColorArray($cola);
14240 $this->PieSector($x, $y, $r, 90, 180, 'F');
14241 $this->PieSector($x, $y, $r, 270, 360, 'F');
14242 $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14243 if ($double) {
14244 $ri = $r * 0.5;
14245 $this->SetFillColorArray($colb);
14246 $this->PieSector($x, $y, $ri, 90, 180, 'F');
14247 $this->PieSector($x, $y, $ri, 270, 360, 'F');
14248 $this->SetFillColorArray($cola);
14249 $this->PieSector($x, $y, $ri, 0, 90, 'F');
14250 $this->PieSector($x, $y, $ri, 180, 270, 'F');
14251 $this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
14252 }
14253 }
14254
14255 /**
14256 * Paints a CMYK registration mark
14257 * @param $x (float) abscissa of the registration mark center.
14258 * @param $y (float) ordinate of the registration mark center.
14259 * @param $r (float) radius of the crop mark.
14260 * @author Nicola Asuni
14261 * @since 6.0.038 (2013-09-30)
14262 * @public
14263 */
14264 public function registrationMarkCMYK($x, $y, $r) {
14265 // line width
14266 $lw = max((0.5 / $this->k),($r / 8));
14267 // internal radius
14268 $ri = ($r * 0.6);
14269 // external radius
14270 $re = ($r * 1.3);
14271 // Cyan
14272 $this->SetFillColorArray(array(100,0,0,0));
14273 $this->PieSector($x, $y, $ri, 270, 360, 'F');
14274 // Magenta
14275 $this->SetFillColorArray(array(0,100,0,0));
14276 $this->PieSector($x, $y, $ri, 0, 90, 'F');
14277 // Yellow
14278 $this->SetFillColorArray(array(0,0,100,0));
14279 $this->PieSector($x, $y, $ri, 90, 180, 'F');
14280 // Key - black
14281 $this->SetFillColorArray(array(0,0,0,100));
14282 $this->PieSector($x, $y, $ri, 180, 270, 'F');
14283 // registration color
14284 $line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
14285 $this->SetFillColorArray(array(100,100,100,100,'All'));
14286 // external circle
14287 $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14288 // cross lines
14289 $this->Line($x, ($y - $re), $x, ($y - $ri));
14290 $this->Line($x, ($y + $ri), $x, ($y + $re));
14291 $this->Line(($x - $re), $y, ($x - $ri), $y);
14292 $this->Line(($x + $ri), $y, ($x + $re), $y);
14293 }
14294
14295 /**
14296 * Paints a linear colour gradient.
14297 * @param $x (float) abscissa of the top left corner of the rectangle.
14298 * @param $y (float) ordinate of the top left corner of the rectangle.
14299 * @param $w (float) width of the rectangle.
14300 * @param $h (float) height of the rectangle.
14301 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14302 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14303 * @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).
14304 * @author Andreas W\FCrmser, Nicola Asuni
14305 * @since 3.1.000 (2008-06-09)
14306 * @public
14307 */
14308 public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
14309 $this->Clip($x, $y, $w, $h);
14310 $this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14311 }
14312
14313 /**
14314 * Paints a radial colour gradient.
14315 * @param $x (float) abscissa of the top left corner of the rectangle.
14316 * @param $y (float) ordinate of the top left corner of the rectangle.
14317 * @param $w (float) width of the rectangle.
14318 * @param $h (float) height of the rectangle.
14319 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14320 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14321 * @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.
14322 * @author Andreas W\FCrmser, Nicola Asuni
14323 * @since 3.1.000 (2008-06-09)
14324 * @public
14325 */
14326 public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
14327 $this->Clip($x, $y, $w, $h);
14328 $this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14329 }
14330
14331 /**
14332 * Paints a coons patch mesh.
14333 * @param $x (float) abscissa of the top left corner of the rectangle.
14334 * @param $y (float) ordinate of the top left corner of the rectangle.
14335 * @param $w (float) width of the rectangle.
14336 * @param $h (float) height of the rectangle.
14337 * @param $col1 (array) first color (lower left corner) (RGB components).
14338 * @param $col2 (array) second color (lower right corner) (RGB components).
14339 * @param $col3 (array) third color (upper right corner) (RGB components).
14340 * @param $col4 (array) fourth color (upper left corner) (RGB components).
14341 * @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>
14342 * @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
14343 * @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
14344 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14345 * @author Andreas W\FCrmser, Nicola Asuni
14346 * @since 3.1.000 (2008-06-09)
14347 * @public
14348 */
14349 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) {
14350 if ($this->pdfa_mode OR ($this->state != 2)) {
14351 return;
14352 }
14353 $this->Clip($x, $y, $w, $h);
14354 $n = count($this->gradients) + 1;
14355 $this->gradients[$n] = array();
14356 $this->gradients[$n]['type'] = 6; //coons patch mesh
14357 $this->gradients[$n]['coords'] = array();
14358 $this->gradients[$n]['antialias'] = $antialias;
14359 $this->gradients[$n]['colors'] = array();
14360 $this->gradients[$n]['transparency'] = false;
14361 //check the coords array if it is the simple array or the multi patch array
14362 if (!isset($coords[0]['f'])) {
14363 //simple array -> convert to multi patch array
14364 if (!isset($col1[1])) {
14365 $col1[1] = $col1[2] = $col1[0];
14366 }
14367 if (!isset($col2[1])) {
14368 $col2[1] = $col2[2] = $col2[0];
14369 }
14370 if (!isset($col3[1])) {
14371 $col3[1] = $col3[2] = $col3[0];
14372 }
14373 if (!isset($col4[1])) {
14374 $col4[1] = $col4[2] = $col4[0];
14375 }
14376 $patch_array[0]['f'] = 0;
14377 $patch_array[0]['points'] = $coords;
14378 $patch_array[0]['colors'][0]['r'] = $col1[0];
14379 $patch_array[0]['colors'][0]['g'] = $col1[1];
14380 $patch_array[0]['colors'][0]['b'] = $col1[2];
14381 $patch_array[0]['colors'][1]['r'] = $col2[0];
14382 $patch_array[0]['colors'][1]['g'] = $col2[1];
14383 $patch_array[0]['colors'][1]['b'] = $col2[2];
14384 $patch_array[0]['colors'][2]['r'] = $col3[0];
14385 $patch_array[0]['colors'][2]['g'] = $col3[1];
14386 $patch_array[0]['colors'][2]['b'] = $col3[2];
14387 $patch_array[0]['colors'][3]['r'] = $col4[0];
14388 $patch_array[0]['colors'][3]['g'] = $col4[1];
14389 $patch_array[0]['colors'][3]['b'] = $col4[2];
14390 } else {
14391 //multi patch array
14392 $patch_array = $coords;
14393 }
14394 $bpcd = 65535; //16 bits per coordinate
14395 //build the data stream
14396 $this->gradients[$n]['stream'] = '';
14397 $count_patch = count($patch_array);
14398 for ($i=0; $i < $count_patch; ++$i) {
14399 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
14400 $count_points = count($patch_array[$i]['points']);
14401 for ($j=0; $j < $count_points; ++$j) {
14402 //each point as 16 bit
14403 $patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
14404 if ($patch_array[$i]['points'][$j] < 0) {
14405 $patch_array[$i]['points'][$j] = 0;
14406 }
14407 if ($patch_array[$i]['points'][$j] > $bpcd) {
14408 $patch_array[$i]['points'][$j] = $bpcd;
14409 }
14410 $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
14411 $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
14412 }
14413 $count_cols = count($patch_array[$i]['colors']);
14414 for ($j=0; $j < $count_cols; ++$j) {
14415 //each color component as 8 bit
14416 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
14417 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
14418 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
14419 }
14420 }
14421 //paint the gradient
14422 $this->_out('/Sh'.$n.' sh');
14423 //restore previous Graphic State
14424 $this->_outRestoreGraphicsState();
14425 if ($this->inxobj) {
14426 // we are inside an XObject template
14427 $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14428 }
14429 }
14430
14431 /**
14432 * Set a rectangular clipping area.
14433 * @param $x (float) abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
14434 * @param $y (float) ordinate of the top left corner of the rectangle.
14435 * @param $w (float) width of the rectangle.
14436 * @param $h (float) height of the rectangle.
14437 * @author Andreas W\FCrmser, Nicola Asuni
14438 * @since 3.1.000 (2008-06-09)
14439 * @protected
14440 */
14441 protected function Clip($x, $y, $w, $h) {
14442 if ($this->state != 2) {
14443 return;
14444 }
14445 if ($this->rtl) {
14446 $x = $this->w - $x - $w;
14447 }
14448 //save current Graphic State
14449 $s = 'q';
14450 //set clipping area
14451 $s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
14452 //set up transformation matrix for gradient
14453 $s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
14454 $this->_out($s);
14455 }
14456
14457 /**
14458 * Output gradient.
14459 * @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)
14460 * @param $coords (array) array of coordinates.
14461 * @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).
14462 * @param $background (array) An array of colour components appropriate to the colour space, specifying a single background colour value.
14463 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14464 * @author Nicola Asuni
14465 * @since 3.1.000 (2008-06-09)
14466 * @public
14467 */
14468 public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
14469 if ($this->pdfa_mode OR ($this->state != 2)) {
14470 return;
14471 }
14472 $n = count($this->gradients) + 1;
14473 $this->gradients[$n] = array();
14474 $this->gradients[$n]['type'] = $type;
14475 $this->gradients[$n]['coords'] = $coords;
14476 $this->gradients[$n]['antialias'] = $antialias;
14477 $this->gradients[$n]['colors'] = array();
14478 $this->gradients[$n]['transparency'] = false;
14479 // color space
14480 $numcolspace = count($stops[0]['color']);
14481 $bcolor = array_values($background);
14482 switch($numcolspace) {
14483 case 5: // SPOT
14484 case 4: { // CMYK
14485 $this->gradients[$n]['colspace'] = 'DeviceCMYK';
14486 if (!empty($background)) {
14487 $this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
14488 }
14489 break;
14490 }
14491 case 3: { // RGB
14492 $this->gradients[$n]['colspace'] = 'DeviceRGB';
14493 if (!empty($background)) {
14494 $this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
14495 }
14496 break;
14497 }
14498 case 1: { // GRAY SCALE
14499 $this->gradients[$n]['colspace'] = 'DeviceGray';
14500 if (!empty($background)) {
14501 $this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
14502 }
14503 break;
14504 }
14505 }
14506 $num_stops = count($stops);
14507 $last_stop_id = $num_stops - 1;
14508 foreach ($stops as $key => $stop) {
14509 $this->gradients[$n]['colors'][$key] = array();
14510 // offset represents a location along the gradient vector
14511 if (isset($stop['offset'])) {
14512 $this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
14513 } else {
14514 if ($key == 0) {
14515 $this->gradients[$n]['colors'][$key]['offset'] = 0;
14516 } elseif ($key == $last_stop_id) {
14517 $this->gradients[$n]['colors'][$key]['offset'] = 1;
14518 } else {
14519 $offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
14520 $this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
14521 }
14522 }
14523 if (isset($stop['opacity'])) {
14524 $this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
14525 if ((!$this->pdfa_mode) AND ($stop['opacity'] < 1)) {
14526 $this->gradients[$n]['transparency'] = true;
14527 }
14528 } else {
14529 $this->gradients[$n]['colors'][$key]['opacity'] = 1;
14530 }
14531 // exponent for the exponential interpolation function
14532 if (isset($stop['exponent'])) {
14533 $this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
14534 } else {
14535 $this->gradients[$n]['colors'][$key]['exponent'] = 1;
14536 }
14537 // set colors
14538 $color = array_values($stop['color']);
14539 switch($numcolspace) {
14540 case 5: // SPOT
14541 case 4: { // CMYK
14542 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
14543 break;
14544 }
14545 case 3: { // RGB
14546 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
14547 break;
14548 }
14549 case 1: { // GRAY SCALE
14550 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
14551 break;
14552 }
14553 }
14554 }
14555 if ($this->gradients[$n]['transparency']) {
14556 // paint luminosity gradient
14557 $this->_out('/TGS'.$n.' gs');
14558 }
14559 //paint the gradient
14560 $this->_out('/Sh'.$n.' sh');
14561 //restore previous Graphic State
14562 $this->_outRestoreGraphicsState();
14563 if ($this->inxobj) {
14564 // we are inside an XObject template
14565 $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14566 }
14567 }
14568
14569 /**
14570 * Output gradient shaders.
14571 * @author Nicola Asuni
14572 * @since 3.1.000 (2008-06-09)
14573 * @protected
14574 */
14575 function _putshaders() {
14576 if ($this->pdfa_mode) {
14577 return;
14578 }
14579 $idt = count($this->gradients); //index for transparency gradients
14580 foreach ($this->gradients as $id => $grad) {
14581 if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
14582 $fc = $this->_newobj();
14583 $out = '<<';
14584 $out .= ' /FunctionType 3';
14585 $out .= ' /Domain [0 1]';
14586 $functions = '';
14587 $bounds = '';
14588 $encode = '';
14589 $i = 1;
14590 $num_cols = count($grad['colors']);
14591 $lastcols = $num_cols - 1;
14592 for ($i = 1; $i < $num_cols; ++$i) {
14593 $functions .= ($fc + $i).' 0 R ';
14594 if ($i < $lastcols) {
14595 $bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
14596 }
14597 $encode .= '0 1 ';
14598 }
14599 $out .= ' /Functions ['.trim($functions).']';
14600 $out .= ' /Bounds ['.trim($bounds).']';
14601 $out .= ' /Encode ['.trim($encode).']';
14602 $out .= ' >>';
14603 $out .= "\n".'endobj';
14604 $this->_out($out);
14605 for ($i = 1; $i < $num_cols; ++$i) {
14606 $this->_newobj();
14607 $out = '<<';
14608 $out .= ' /FunctionType 2';
14609 $out .= ' /Domain [0 1]';
14610 $out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
14611 $out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
14612 $out .= ' /N '.$grad['colors'][$i]['exponent'];
14613 $out .= ' >>';
14614 $out .= "\n".'endobj';
14615 $this->_out($out);
14616 }
14617 // set transparency functions
14618 if ($grad['transparency']) {
14619 $ft = $this->_newobj();
14620 $out = '<<';
14621 $out .= ' /FunctionType 3';
14622 $out .= ' /Domain [0 1]';
14623 $functions = '';
14624 $i = 1;
14625 $num_cols = count($grad['colors']);
14626 for ($i = 1; $i < $num_cols; ++$i) {
14627 $functions .= ($ft + $i).' 0 R ';
14628 }
14629 $out .= ' /Functions ['.trim($functions).']';
14630 $out .= ' /Bounds ['.trim($bounds).']';
14631 $out .= ' /Encode ['.trim($encode).']';
14632 $out .= ' >>';
14633 $out .= "\n".'endobj';
14634 $this->_out($out);
14635 for ($i = 1; $i < $num_cols; ++$i) {
14636 $this->_newobj();
14637 $out = '<<';
14638 $out .= ' /FunctionType 2';
14639 $out .= ' /Domain [0 1]';
14640 $out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
14641 $out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
14642 $out .= ' /N '.$grad['colors'][$i]['exponent'];
14643 $out .= ' >>';
14644 $out .= "\n".'endobj';
14645 $this->_out($out);
14646 }
14647 }
14648 }
14649 // set shading object
14650 $this->_newobj();
14651 $out = '<< /ShadingType '.$grad['type'];
14652 if (isset($grad['colspace'])) {
14653 $out .= ' /ColorSpace /'.$grad['colspace'];
14654 } else {
14655 $out .= ' /ColorSpace /DeviceRGB';
14656 }
14657 if (isset($grad['background']) AND !empty($grad['background'])) {
14658 $out .= ' /Background ['.$grad['background'].']';
14659 }
14660 if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
14661 $out .= ' /AntiAlias true';
14662 }
14663 if ($grad['type'] == 2) {
14664 $out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
14665 $out .= ' /Domain [0 1]';
14666 $out .= ' /Function '.$fc.' 0 R';
14667 $out .= ' /Extend [true true]';
14668 $out .= ' >>';
14669 } elseif ($grad['type'] == 3) {
14670 //x0, y0, r0, x1, y1, r1
14671 //at this this time radius of inner circle is 0
14672 $out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
14673 $out .= ' /Domain [0 1]';
14674 $out .= ' /Function '.$fc.' 0 R';
14675 $out .= ' /Extend [true true]';
14676 $out .= ' >>';
14677 } elseif ($grad['type'] == 6) {
14678 $out .= ' /BitsPerCoordinate 16';
14679 $out .= ' /BitsPerComponent 8';
14680 $out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
14681 $out .= ' /BitsPerFlag 8';
14682 $stream = $this->_getrawstream($grad['stream']);
14683 $out .= ' /Length '.strlen($stream);
14684 $out .= ' >>';
14685 $out .= ' stream'."\n".$stream."\n".'endstream';
14686 }
14687 $out .= "\n".'endobj';
14688 $this->_out($out);
14689 if ($grad['transparency']) {
14690 $shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
14691 $shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
14692 }
14693 $this->gradients[$id]['id'] = $this->n;
14694 // set pattern object
14695 $this->_newobj();
14696 $out = '<< /Type /Pattern /PatternType 2';
14697 $out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
14698 $out .= ' >>';
14699 $out .= "\n".'endobj';
14700 $this->_out($out);
14701 $this->gradients[$id]['pattern'] = $this->n;
14702 // set shading and pattern for transparency mask
14703 if ($grad['transparency']) {
14704 // luminosity pattern
14705 $idgs = $id + $idt;
14706 $this->_newobj();
14707 $this->_out($shading_transparency);
14708 $this->gradients[$idgs]['id'] = $this->n;
14709 $this->_newobj();
14710 $out = '<< /Type /Pattern /PatternType 2';
14711 $out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
14712 $out .= ' >>';
14713 $out .= "\n".'endobj';
14714 $this->_out($out);
14715 $this->gradients[$idgs]['pattern'] = $this->n;
14716 // luminosity XObject
14717 $oid = $this->_newobj();
14718 $this->xobjects['LX'.$oid] = array('n' => $oid);
14719 $filter = '';
14720 $stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
14721 if ($this->compress) {
14722 $filter = ' /Filter /FlateDecode';
14723 $stream = gzcompress($stream);
14724 }
14725 $stream = $this->_getrawstream($stream);
14726 $out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
14727 $out .= ' /Length '.strlen($stream);
14728 $rect = sprintf('%F %F', $this->wPt, $this->hPt);
14729 $out .= ' /BBox [0 0 '.$rect.']';
14730 $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
14731 $out .= ' /Resources <<';
14732 $out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
14733 $out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
14734 $out .= ' >>';
14735 $out .= ' >> ';
14736 $out .= ' stream'."\n".$stream."\n".'endstream';
14737 $out .= "\n".'endobj';
14738 $this->_out($out);
14739 // SMask
14740 $this->_newobj();
14741 $out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
14742 $this->_out($out);
14743 // ExtGState
14744 $this->_newobj();
14745 $out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
14746 $this->_out($out);
14747 $this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
14748 }
14749 }
14750 }
14751
14752 /**
14753 * Draw the sector of a circle.
14754 * It can be used for instance to render pie charts.
14755 * @param $xc (float) abscissa of the center.
14756 * @param $yc (float) ordinate of the center.
14757 * @param $r (float) radius.
14758 * @param $a (float) start angle (in degrees).
14759 * @param $b (float) end angle (in degrees).
14760 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14761 * @param $cw: (float) indicates whether to go clockwise (default: true).
14762 * @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.
14763 * @author Maxime Delorme, Nicola Asuni
14764 * @since 3.1.000 (2008-06-09)
14765 * @public
14766 */
14767 public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
14768 $this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
14769 }
14770
14771 /**
14772 * Draw the sector of an ellipse.
14773 * It can be used for instance to render pie charts.
14774 * @param $xc (float) abscissa of the center.
14775 * @param $yc (float) ordinate of the center.
14776 * @param $rx (float) the x-axis radius.
14777 * @param $ry (float) the y-axis radius.
14778 * @param $a (float) start angle (in degrees).
14779 * @param $b (float) end angle (in degrees).
14780 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14781 * @param $cw: (float) indicates whether to go clockwise.
14782 * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
14783 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of arc.
14784 * @author Maxime Delorme, Nicola Asuni
14785 * @since 3.1.000 (2008-06-09)
14786 * @public
14787 */
14788 public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
14789 if ($this->state != 2) {
14790 return;
14791 }
14792 if ($this->rtl) {
14793 $xc = ($this->w - $xc);
14794 }
14795 $op = TCPDF_STATIC::getPathPaintOperator($style);
14796 if ($op == 'f') {
14797 $line_style = array();
14798 }
14799 if ($cw) {
14800 $d = $b;
14801 $b = (360 - $a + $o);
14802 $a = (360 - $d + $o);
14803 } else {
14804 $b += $o;
14805 $a += $o;
14806 }
14807 $this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
14808 $this->_out($op);
14809 }
14810
14811 /**
14812 * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
14813 * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
14814 * Only vector drawing is supported, not text or bitmap.
14815 * 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).
14816 * @param $file (string) Name of the file containing the image or a '@' character followed by the EPS/AI data string.
14817 * @param $x (float) Abscissa of the upper-left corner.
14818 * @param $y (float) Ordinate of the upper-left corner.
14819 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
14820 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
14821 * @param $link (mixed) URL or identifier returned by AddLink().
14822 * @param $useBoundingBox (boolean) specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
14823 * @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>
14824 * @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>
14825 * @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)))
14826 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
14827 * @param $fixoutvals (boolean) if true remove values outside the bounding box.
14828 * @author Valentin Schmidt, Nicola Asuni
14829 * @since 3.1.000 (2008-06-09)
14830 * @public
14831 */
14832 public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
14833 if ($this->state != 2) {
14834 return;
14835 }
14836 if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
14837 // convert EPS to raster image using GD or ImageMagick libraries
14838 return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14839 }
14840 if ($x === '') {
14841 $x = $this->x;
14842 }
14843 if ($y === '') {
14844 $y = $this->y;
14845 }
14846 // check page for no-write regions and adapt page margins if necessary
14847 list($x, $y) = $this->checkPageRegions($h, $x, $y);
14848 $k = $this->k;
14849 if ($file[0] === '@') { // image from string
14850 $data = substr($file, 1);
14851 } else { // EPS/AI file
14852 $data = TCPDF_STATIC::fileGetContents($file);
14853 }
14854 if ($data === FALSE) {
14855 $this->Error('EPS file not found: '.$file);
14856 }
14857 $regs = array();
14858 // EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
14859 preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
14860 if (count($regs) > 1) {
14861 $version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
14862 if (strpos($version_str, 'Adobe Illustrator') !== false) {
14863 $versexp = explode(' ', $version_str);
14864 $version = (float)array_pop($versexp);
14865 if ($version >= 9) {
14866 $this->Error('This version of Adobe Illustrator file is not supported: '.$file);
14867 }
14868 }
14869 }
14870 // strip binary bytes in front of PS-header
14871 $start = strpos($data, '%!PS-Adobe');
14872 if ($start > 0) {
14873 $data = substr($data, $start);
14874 }
14875 // find BoundingBox params
14876 preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
14877 if (count($regs) > 1) {
14878 list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
14879 } else {
14880 $this->Error('No BoundingBox found in EPS/AI file: '.$file);
14881 }
14882 $start = strpos($data, '%%EndSetup');
14883 if ($start === false) {
14884 $start = strpos($data, '%%EndProlog');
14885 }
14886 if ($start === false) {
14887 $start = strpos($data, '%%BoundingBox');
14888 }
14889 $data = substr($data, $start);
14890 $end = strpos($data, '%%PageTrailer');
14891 if ($end===false) {
14892 $end = strpos($data, 'showpage');
14893 }
14894 if ($end) {
14895 $data = substr($data, 0, $end);
14896 }
14897 // calculate image width and height on document
14898 if (($w <= 0) AND ($h <= 0)) {
14899 $w = ($x2 - $x1) / $k;
14900 $h = ($y2 - $y1) / $k;
14901 } elseif ($w <= 0) {
14902 $w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
14903 } elseif ($h <= 0) {
14904 $h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
14905 }
14906 // fit the image on available space
14907 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
14908 if ($this->rasterize_vector_images) {
14909 // convert EPS to raster image using GD or ImageMagick libraries
14910 return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14911 }
14912 // set scaling factors
14913 $scale_x = $w / (($x2 - $x1) / $k);
14914 $scale_y = $h / (($y2 - $y1) / $k);
14915 // set alignment
14916 $this->img_rb_y = $y + $h;
14917 // set alignment
14918 if ($this->rtl) {
14919 if ($palign == 'L') {
14920 $ximg = $this->lMargin;
14921 } elseif ($palign == 'C') {
14922 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
14923 } elseif ($palign == 'R') {
14924 $ximg = $this->w - $this->rMargin - $w;
14925 } else {
14926 $ximg = $x - $w;
14927 }
14928 $this->img_rb_x = $ximg;
14929 } else {
14930 if ($palign == 'L') {
14931 $ximg = $this->lMargin;
14932 } elseif ($palign == 'C') {
14933 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
14934 } elseif ($palign == 'R') {
14935 $ximg = $this->w - $this->rMargin - $w;
14936 } else {
14937 $ximg = $x;
14938 }
14939 $this->img_rb_x = $ximg + $w;
14940 }
14941 if ($useBoundingBox) {
14942 $dx = $ximg * $k - $x1;
14943 $dy = $y * $k - $y1;
14944 } else {
14945 $dx = $ximg * $k;
14946 $dy = $y * $k;
14947 }
14948 // save the current graphic state
14949 $this->_out('q'.$this->epsmarker);
14950 // translate
14951 $this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
14952 // scale
14953 if (isset($scale_x)) {
14954 $this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
14955 }
14956 // handle pc/unix/mac line endings
14957 $lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
14958 $u=0;
14959 $cnt = count($lines);
14960 for ($i=0; $i < $cnt; ++$i) {
14961 $line = $lines[$i];
14962 if (($line == '') OR ($line[0] == '%')) {
14963 continue;
14964 }
14965 $len = strlen($line);
14966 // check for spot color names
14967 $color_name = '';
14968 if (strcasecmp('x', substr(trim($line), -1)) == 0) {
14969 if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
14970 // extract spot color name
14971 $color_name = $matches[0];
14972 // remove color name from string
14973 $line = str_replace(' '.$color_name, '', $line);
14974 // remove pharentesis from color name
14975 $color_name = substr($color_name, 1, -1);
14976 }
14977 }
14978 $chunks = explode(' ', $line);
14979 $cmd = trim(array_pop($chunks));
14980 // RGB
14981 if (($cmd == 'Xa') OR ($cmd == 'XA')) {
14982 $b = array_pop($chunks);
14983 $g = array_pop($chunks);
14984 $r = array_pop($chunks);
14985 $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!
14986 continue;
14987 }
14988 $skip = false;
14989 if ($fixoutvals) {
14990 // check for values outside the bounding box
14991 switch ($cmd) {
14992 case 'm':
14993 case 'l':
14994 case 'L': {
14995 // skip values outside bounding box
14996 foreach ($chunks as $key => $val) {
14997 if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
14998 $skip = true;
14999 } elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
15000 $skip = true;
15001 }
15002 }
15003 }
15004 }
15005 }
15006 switch ($cmd) {
15007 case 'm':
15008 case 'l':
15009 case 'v':
15010 case 'y':
15011 case 'c':
15012 case 'k':
15013 case 'K':
15014 case 'g':
15015 case 'G':
15016 case 's':
15017 case 'S':
15018 case 'J':
15019 case 'j':
15020 case 'w':
15021 case 'M':
15022 case 'd':
15023 case 'n': {
15024 if ($skip) {
15025 break;
15026 }
15027 $this->_out($line);
15028 break;
15029 }
15030 case 'x': {// custom fill color
15031 if (empty($color_name)) {
15032 // CMYK color
15033 list($col_c, $col_m, $col_y, $col_k) = $chunks;
15034 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
15035 } else {
15036 // Spot Color (CMYK + tint)
15037 list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15038 $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15039 $color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15040 $this->_out($color_cmd);
15041 }
15042 break;
15043 }
15044 case 'X': { // custom stroke color
15045 if (empty($color_name)) {
15046 // CMYK color
15047 list($col_c, $col_m, $col_y, $col_k) = $chunks;
15048 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
15049 } else {
15050 // Spot Color (CMYK + tint)
15051 list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15052 $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15053 $color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15054 $this->_out($color_cmd);
15055 }
15056 break;
15057 }
15058 case 'Y':
15059 case 'N':
15060 case 'V':
15061 case 'L':
15062 case 'C': {
15063 if ($skip) {
15064 break;
15065 }
15066 $line[($len - 1)] = strtolower($cmd);
15067 $this->_out($line);
15068 break;
15069 }
15070 case 'b':
15071 case 'B': {
15072 $this->_out($cmd . '*');
15073 break;
15074 }
15075 case 'f':
15076 case 'F': {
15077 if ($u > 0) {
15078 $isU = false;
15079 $max = min(($i + 5), $cnt);
15080 for ($j = ($i + 1); $j < $max; ++$j) {
15081 $isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
15082 }
15083 if ($isU) {
15084 $this->_out('f*');
15085 }
15086 } else {
15087 $this->_out('f*');
15088 }
15089 break;
15090 }
15091 case '*u': {
15092 ++$u;
15093 break;
15094 }
15095 case '*U': {
15096 --$u;
15097 break;
15098 }
15099 }
15100 }
15101 // restore previous graphic state
15102 $this->_out($this->epsmarker.'Q');
15103 if (!empty($border)) {
15104 $bx = $this->x;
15105 $by = $this->y;
15106 $this->x = $ximg;
15107 if ($this->rtl) {
15108 $this->x += $w;
15109 }
15110 $this->y = $y;
15111 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
15112 $this->x = $bx;
15113 $this->y = $by;
15114 }
15115 if ($link) {
15116 $this->Link($ximg, $y, $w, $h, $link, 0);
15117 }
15118 // set pointer to align the next text/objects
15119 switch($align) {
15120 case 'T':{
15121 $this->y = $y;
15122 $this->x = $this->img_rb_x;
15123 break;
15124 }
15125 case 'M':{
15126 $this->y = $y + round($h/2);
15127 $this->x = $this->img_rb_x;
15128 break;
15129 }
15130 case 'B':{
15131 $this->y = $this->img_rb_y;
15132 $this->x = $this->img_rb_x;
15133 break;
15134 }
15135 case 'N':{
15136 $this->SetY($this->img_rb_y);
15137 break;
15138 }
15139 default:{
15140 break;
15141 }
15142 }
15143 $this->endlinex = $this->img_rb_x;
15144 }
15145
15146 /**
15147 * Set document barcode.
15148 * @param $bc (string) barcode
15149 * @public
15150 */
15151 public function setBarcode($bc='') {
15152 $this->barcode = $bc;
15153 }
15154
15155 /**
15156 * Get current barcode.
15157 * @return string
15158 * @public
15159 * @since 4.0.012 (2008-07-24)
15160 */
15161 public function getBarcode() {
15162 return $this->barcode;
15163 }
15164
15165 /**
15166 * Print a Linear Barcode.
15167 * @param $code (string) code to print
15168 * @param $type (string) type of barcode (see tcpdf_barcodes_1d.php for supported formats).
15169 * @param $x (int) x position in user units (empty string = current x position)
15170 * @param $y (int) y position in user units (empty string = current y position)
15171 * @param $w (int) width in user units (empty string = remaining page width)
15172 * @param $h (int) height in user units (empty string = remaining page height)
15173 * @param $xres (float) width of the smallest bar in user units (empty string = default value = 0.4mm)
15174 * @param $style (array) array of options:<ul>
15175 * <li>boolean $style['border'] if true prints a border</li>
15176 * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
15177 * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
15178 * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
15179 * <li>array $style['fgcolor'] color array for bars and text</li>
15180 * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
15181 * <li>boolean $style['text'] if true prints text below the barcode</li>
15182 * <li>string $style['label'] override default label</li>
15183 * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
15184 * <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>
15185 * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
15186 * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
15187 * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
15188 * <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>
15189 * <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>
15190 * @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>
15191 * @author Nicola Asuni
15192 * @since 3.1.000 (2008-06-09)
15193 * @public
15194 */
15195 public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style=array(), $align='') {
15196 if (TCPDF_STATIC::empty_string(trim($code))) {
15197 return;
15198 }
15199 require_once(dirname(__FILE__).'/tcpdf_barcodes_1d.php');
15200 // save current graphic settings
15201 $gvars = $this->getGraphicVars();
15202 // create new barcode object
15203 $barcodeobj = new TCPDFBarcode($code, $type);
15204 $arrcode = $barcodeobj->getBarcodeArray();
15205 if (($arrcode === false) OR empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
15206 $this->Error('Error in 1D barcode string');
15207 }
15208 if ($arrcode['maxh'] <= 0) {
15209 $arrcode['maxh'] = 1;
15210 }
15211 // set default values
15212 if (!isset($style['position'])) {
15213 $style['position'] = '';
15214 } elseif ($style['position'] == 'S') {
15215 // keep this for backward compatibility
15216 $style['position'] = '';
15217 $style['stretch'] = true;
15218 }
15219 if (!isset($style['fitwidth'])) {
15220 if (!isset($style['stretch'])) {
15221 $style['fitwidth'] = true;
15222 } else {
15223 $style['fitwidth'] = false;
15224 }
15225 }
15226 if ($style['fitwidth']) {
15227 // disable stretch
15228 $style['stretch'] = false;
15229 }
15230 if (!isset($style['stretch'])) {
15231 if (($w === '') OR ($w <= 0)) {
15232 $style['stretch'] = false;
15233 } else {
15234 $style['stretch'] = true;
15235 }
15236 }
15237 if (!isset($style['fgcolor'])) {
15238 $style['fgcolor'] = array(0,0,0); // default black
15239 }
15240 if (!isset($style['bgcolor'])) {
15241 $style['bgcolor'] = false; // default transparent
15242 }
15243 if (!isset($style['border'])) {
15244 $style['border'] = false;
15245 }
15246 $fontsize = 0;
15247 if (!isset($style['text'])) {
15248 $style['text'] = false;
15249 }
15250 if ($style['text'] AND isset($style['font'])) {
15251 if (isset($style['fontsize'])) {
15252 $fontsize = $style['fontsize'];
15253 }
15254 $this->SetFont($style['font'], '', $fontsize);
15255 }
15256 if (!isset($style['stretchtext'])) {
15257 $style['stretchtext'] = 4;
15258 }
15259 if ($x === '') {
15260 $x = $this->x;
15261 }
15262 if ($y === '') {
15263 $y = $this->y;
15264 }
15265 // check page for no-write regions and adapt page margins if necessary
15266 list($x, $y) = $this->checkPageRegions($h, $x, $y);
15267 if (($w === '') OR ($w <= 0)) {
15268 if ($this->rtl) {
15269 $w = $x - $this->lMargin;
15270 } else {
15271 $w = $this->w - $this->rMargin - $x;
15272 }
15273 }
15274 // padding
15275 if (!isset($style['padding'])) {
15276 $padding = 0;
15277 } elseif ($style['padding'] === 'auto') {
15278 $padding = 10 * ($w / ($arrcode['maxw'] + 20));
15279 } else {
15280 $padding = floatval($style['padding']);
15281 }
15282 // horizontal padding
15283 if (!isset($style['hpadding'])) {
15284 $hpadding = $padding;
15285 } elseif ($style['hpadding'] === 'auto') {
15286 $hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
15287 } else {
15288 $hpadding = floatval($style['hpadding']);
15289 }
15290 // vertical padding
15291 if (!isset($style['vpadding'])) {
15292 $vpadding = $padding;
15293 } elseif ($style['vpadding'] === 'auto') {
15294 $vpadding = ($hpadding / 2);
15295 } else {
15296 $vpadding = floatval($style['vpadding']);
15297 }
15298 // calculate xres (single bar width)
15299 $max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
15300 if ($style['stretch']) {
15301 $xres = $max_xres;
15302 } else {
15303 if (TCPDF_STATIC::empty_string($xres)) {
15304 $xres = (0.141 * $this->k); // default bar width = 0.4 mm
15305 }
15306 if ($xres > $max_xres) {
15307 // correct xres to fit on $w
15308 $xres = $max_xres;
15309 }
15310 if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
15311 OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
15312 $hpadding = 10 * $xres;
15313 if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
15314 $vpadding = ($hpadding / 2);
15315 }
15316 }
15317 }
15318 if ($style['fitwidth']) {
15319 $wold = $w;
15320 $w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
15321 if (isset($style['cellfitalign'])) {
15322 switch ($style['cellfitalign']) {
15323 case 'L': {
15324 if ($this->rtl) {
15325 $x -= ($wold - $w);
15326 }
15327 break;
15328 }
15329 case 'R': {
15330 if (!$this->rtl) {
15331 $x += ($wold - $w);
15332 }
15333 break;
15334 }
15335 case 'C': {
15336 if ($this->rtl) {
15337 $x -= (($wold - $w) / 2);
15338 } else {
15339 $x += (($wold - $w) / 2);
15340 }
15341 break;
15342 }
15343 default : {
15344 break;
15345 }
15346 }
15347 }
15348 }
15349 $text_height = $this->getCellHeight($fontsize / $this->k);
15350 // height
15351 if (($h === '') OR ($h <= 0)) {
15352 // set default height
15353 $h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
15354 }
15355 $barh = $h - $text_height - (2 * $vpadding);
15356 if ($barh <=0) {
15357 // try to reduce font or padding to fit barcode on available height
15358 if ($text_height > $h) {
15359 $fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
15360 $text_height = $this->getCellHeight($fontsize / $this->k);
15361 $this->SetFont($style['font'], '', $fontsize);
15362 }
15363 if ($vpadding > 0) {
15364 $vpadding = (($h - $text_height) / 4);
15365 }
15366 $barh = $h - $text_height - (2 * $vpadding);
15367 }
15368 // fit the barcode on available space
15369 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15370 // set alignment
15371 $this->img_rb_y = $y + $h;
15372 // set alignment
15373 if ($this->rtl) {
15374 if ($style['position'] == 'L') {
15375 $xpos = $this->lMargin;
15376 } elseif ($style['position'] == 'C') {
15377 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15378 } elseif ($style['position'] == 'R') {
15379 $xpos = $this->w - $this->rMargin - $w;
15380 } else {
15381 $xpos = $x - $w;
15382 }
15383 $this->img_rb_x = $xpos;
15384 } else {
15385 if ($style['position'] == 'L') {
15386 $xpos = $this->lMargin;
15387 } elseif ($style['position'] == 'C') {
15388 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15389 } elseif ($style['position'] == 'R') {
15390 $xpos = $this->w - $this->rMargin - $w;
15391 } else {
15392 $xpos = $x;
15393 }
15394 $this->img_rb_x = $xpos + $w;
15395 }
15396 $xpos_rect = $xpos;
15397 if (!isset($style['align'])) {
15398 $style['align'] = 'C';
15399 }
15400 switch ($style['align']) {
15401 case 'L': {
15402 $xpos = $xpos_rect + $hpadding;
15403 break;
15404 }
15405 case 'R': {
15406 $xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
15407 break;
15408 }
15409 case 'C':
15410 default : {
15411 $xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
15412 break;
15413 }
15414 }
15415 $xpos_text = $xpos;
15416 // barcode is always printed in LTR direction
15417 $tempRTL = $this->rtl;
15418 $this->rtl = false;
15419 // print background color
15420 if ($style['bgcolor']) {
15421 $this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15422 } elseif ($style['border']) {
15423 $this->Rect($xpos_rect, $y, $w, $h, 'D');
15424 }
15425 // set foreground color
15426 $this->SetDrawColorArray($style['fgcolor']);
15427 $this->SetTextColorArray($style['fgcolor']);
15428 // print bars
15429 foreach ($arrcode['bcode'] as $k => $v) {
15430 $bw = ($v['w'] * $xres);
15431 if ($v['t']) {
15432 // draw a vertical bar
15433 $ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
15434 $this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
15435 }
15436 $xpos += $bw;
15437 }
15438 // print text
15439 if ($style['text']) {
15440 if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
15441 $label = $style['label'];
15442 } else {
15443 $label = $code;
15444 }
15445 $txtwidth = ($arrcode['maxw'] * $xres);
15446 if ($this->GetStringWidth($label) > $txtwidth) {
15447 $style['stretchtext'] = 2;
15448 }
15449 // print text
15450 $this->x = $xpos_text;
15451 $this->y = $y + $vpadding + $barh;
15452 $cellpadding = $this->cell_padding;
15453 $this->SetCellPadding(0);
15454 $this->Cell($txtwidth, '', $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
15455 $this->cell_padding = $cellpadding;
15456 }
15457 // restore original direction
15458 $this->rtl = $tempRTL;
15459 // restore previous settings
15460 $this->setGraphicVars($gvars);
15461 // set pointer to align the next text/objects
15462 switch($align) {
15463 case 'T':{
15464 $this->y = $y;
15465 $this->x = $this->img_rb_x;
15466 break;
15467 }
15468 case 'M':{
15469 $this->y = $y + round($h / 2);
15470 $this->x = $this->img_rb_x;
15471 break;
15472 }
15473 case 'B':{
15474 $this->y = $this->img_rb_y;
15475 $this->x = $this->img_rb_x;
15476 break;
15477 }
15478 case 'N':{
15479 $this->SetY($this->img_rb_y);
15480 break;
15481 }
15482 default:{
15483 break;
15484 }
15485 }
15486 $this->endlinex = $this->img_rb_x;
15487 }
15488
15489 /**
15490 * Print 2D Barcode.
15491 * @param $code (string) code to print
15492 * @param $type (string) type of barcode (see tcpdf_barcodes_2d.php for supported formats).
15493 * @param $x (int) x position in user units
15494 * @param $y (int) y position in user units
15495 * @param $w (int) width in user units
15496 * @param $h (int) height in user units
15497 * @param $style (array) array of options:<ul>
15498 * <li>boolean $style['border'] if true prints a border around the barcode</li>
15499 * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
15500 * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
15501 * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
15502 * <li>int $style['module_width'] width of a single module in points</li>
15503 * <li>int $style['module_height'] height of a single module in points</li>
15504 * <li>array $style['fgcolor'] color array for bars and text</li>
15505 * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
15506 * <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>
15507 * <li>$style['module_height'] height of a single module in points</li></ul>
15508 * @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>
15509 * @param $distort (boolean) if true distort the barcode to fit width and height, otherwise preserve aspect ratio
15510 * @author Nicola Asuni
15511 * @since 4.5.037 (2009-04-07)
15512 * @public
15513 */
15514 public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style=array(), $align='', $distort=false) {
15515 if (TCPDF_STATIC::empty_string(trim($code))) {
15516 return;
15517 }
15518 require_once(dirname(__FILE__).'/tcpdf_barcodes_2d.php');
15519 // save current graphic settings
15520 $gvars = $this->getGraphicVars();
15521 // create new barcode object
15522 $barcodeobj = new TCPDF2DBarcode($code, $type);
15523 $arrcode = $barcodeobj->getBarcodeArray();
15524 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)) {
15525 $this->Error('Error in 2D barcode string');
15526 }
15527 // set default values
15528 if (!isset($style['position'])) {
15529 $style['position'] = '';
15530 }
15531 if (!isset($style['fgcolor'])) {
15532 $style['fgcolor'] = array(0,0,0); // default black
15533 }
15534 if (!isset($style['bgcolor'])) {
15535 $style['bgcolor'] = false; // default transparent
15536 }
15537 if (!isset($style['border'])) {
15538 $style['border'] = false;
15539 }
15540 // padding
15541 if (!isset($style['padding'])) {
15542 $style['padding'] = 0;
15543 } elseif ($style['padding'] === 'auto') {
15544 $style['padding'] = 4;
15545 }
15546 if (!isset($style['hpadding'])) {
15547 $style['hpadding'] = $style['padding'];
15548 } elseif ($style['hpadding'] === 'auto') {
15549 $style['hpadding'] = 4;
15550 }
15551 if (!isset($style['vpadding'])) {
15552 $style['vpadding'] = $style['padding'];
15553 } elseif ($style['vpadding'] === 'auto') {
15554 $style['vpadding'] = 4;
15555 }
15556 $hpad = (2 * $style['hpadding']);
15557 $vpad = (2 * $style['vpadding']);
15558 // cell (module) dimension
15559 if (!isset($style['module_width'])) {
15560 $style['module_width'] = 1; // width of a single module in points
15561 }
15562 if (!isset($style['module_height'])) {
15563 $style['module_height'] = 1; // height of a single module in points
15564 }
15565 if ($x === '') {
15566 $x = $this->x;
15567 }
15568 if ($y === '') {
15569 $y = $this->y;
15570 }
15571 // check page for no-write regions and adapt page margins if necessary
15572 list($x, $y) = $this->checkPageRegions($h, $x, $y);
15573 // number of barcode columns and rows
15574 $rows = $arrcode['num_rows'];
15575 $cols = $arrcode['num_cols'];
15576 if (($rows <= 0) || ($cols <= 0)){
15577 $this->Error('Error in 2D barcode string');
15578 }
15579 // module width and height
15580 $mw = $style['module_width'];
15581 $mh = $style['module_height'];
15582 if (($mw <= 0) OR ($mh <= 0)) {
15583 $this->Error('Error in 2D barcode string');
15584 }
15585 // get max dimensions
15586 if ($this->rtl) {
15587 $maxw = $x - $this->lMargin;
15588 } else {
15589 $maxw = $this->w - $this->rMargin - $x;
15590 }
15591 $maxh = ($this->h - $this->tMargin - $this->bMargin);
15592 $ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
15593 $ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
15594 if (!$distort) {
15595 if (($maxw * $ratioHW) > $maxh) {
15596 $maxw = $maxh * $ratioWH;
15597 }
15598 if (($maxh * $ratioWH) > $maxw) {
15599 $maxh = $maxw * $ratioHW;
15600 }
15601 }
15602 // set maximum dimensions
15603 if ($w > $maxw) {
15604 $w = $maxw;
15605 }
15606 if ($h > $maxh) {
15607 $h = $maxh;
15608 }
15609 // set dimensions
15610 if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
15611 $w = ($cols + $hpad) * ($mw / $this->k);
15612 $h = ($rows + $vpad) * ($mh / $this->k);
15613 } elseif (($w === '') OR ($w <= 0)) {
15614 $w = $h * $ratioWH;
15615 } elseif (($h === '') OR ($h <= 0)) {
15616 $h = $w * $ratioHW;
15617 }
15618 // barcode size (excluding padding)
15619 $bw = ($w * $cols) / ($cols + $hpad);
15620 $bh = ($h * $rows) / ($rows + $vpad);
15621 // dimension of single barcode cell unit
15622 $cw = $bw / $cols;
15623 $ch = $bh / $rows;
15624 if (!$distort) {
15625 if (($cw / $ch) > ($mw / $mh)) {
15626 // correct horizontal distortion
15627 $cw = $ch * $mw / $mh;
15628 $bw = $cw * $cols;
15629 $style['hpadding'] = ($w - $bw) / (2 * $cw);
15630 } else {
15631 // correct vertical distortion
15632 $ch = $cw * $mh / $mw;
15633 $bh = $ch * $rows;
15634 $style['vpadding'] = ($h - $bh) / (2 * $ch);
15635 }
15636 }
15637 // fit the barcode on available space
15638 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15639 // set alignment
15640 $this->img_rb_y = $y + $h;
15641 // set alignment
15642 if ($this->rtl) {
15643 if ($style['position'] == 'L') {
15644 $xpos = $this->lMargin;
15645 } elseif ($style['position'] == 'C') {
15646 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15647 } elseif ($style['position'] == 'R') {
15648 $xpos = $this->w - $this->rMargin - $w;
15649 } else {
15650 $xpos = $x - $w;
15651 }
15652 $this->img_rb_x = $xpos;
15653 } else {
15654 if ($style['position'] == 'L') {
15655 $xpos = $this->lMargin;
15656 } elseif ($style['position'] == 'C') {
15657 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15658 } elseif ($style['position'] == 'R') {
15659 $xpos = $this->w - $this->rMargin - $w;
15660 } else {
15661 $xpos = $x;
15662 }
15663 $this->img_rb_x = $xpos + $w;
15664 }
15665 $xstart = $xpos + ($style['hpadding'] * $cw);
15666 $ystart = $y + ($style['vpadding'] * $ch);
15667 // barcode is always printed in LTR direction
15668 $tempRTL = $this->rtl;
15669 $this->rtl = false;
15670 // print background color
15671 if ($style['bgcolor']) {
15672 $this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15673 } elseif ($style['border']) {
15674 $this->Rect($xpos, $y, $w, $h, 'D');
15675 }
15676 // set foreground color
15677 $this->SetDrawColorArray($style['fgcolor']);
15678 // print barcode cells
15679 // for each row
15680 for ($r = 0; $r < $rows; ++$r) {
15681 $xr = $xstart;
15682 // for each column
15683 for ($c = 0; $c < $cols; ++$c) {
15684 if ($arrcode['bcode'][$r][$c] == 1) {
15685 // draw a single barcode cell
15686 $this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
15687 }
15688 $xr += $cw;
15689 }
15690 $ystart += $ch;
15691 }
15692 // restore original direction
15693 $this->rtl = $tempRTL;
15694 // restore previous settings
15695 $this->setGraphicVars($gvars);
15696 // set pointer to align the next text/objects
15697 switch($align) {
15698 case 'T':{
15699 $this->y = $y;
15700 $this->x = $this->img_rb_x;
15701 break;
15702 }
15703 case 'M':{
15704 $this->y = $y + round($h/2);
15705 $this->x = $this->img_rb_x;
15706 break;
15707 }
15708 case 'B':{
15709 $this->y = $this->img_rb_y;
15710 $this->x = $this->img_rb_x;
15711 break;
15712 }
15713 case 'N':{
15714 $this->SetY($this->img_rb_y);
15715 break;
15716 }
15717 default:{
15718 break;
15719 }
15720 }
15721 $this->endlinex = $this->img_rb_x;
15722 }
15723
15724 /**
15725 * Returns an array containing current margins:
15726 * <ul>
15727 <li>$ret['left'] = left margin</li>
15728 <li>$ret['right'] = right margin</li>
15729 <li>$ret['top'] = top margin</li>
15730 <li>$ret['bottom'] = bottom margin</li>
15731 <li>$ret['header'] = header margin</li>
15732 <li>$ret['footer'] = footer margin</li>
15733 <li>$ret['cell'] = cell padding array</li>
15734 <li>$ret['padding_left'] = cell left padding</li>
15735 <li>$ret['padding_top'] = cell top padding</li>
15736 <li>$ret['padding_right'] = cell right padding</li>
15737 <li>$ret['padding_bottom'] = cell bottom padding</li>
15738 * </ul>
15739 * @return array containing all margins measures
15740 * @public
15741 * @since 3.2.000 (2008-06-23)
15742 */
15743 public function getMargins() {
15744 $ret = array(
15745 'left' => $this->lMargin,
15746 'right' => $this->rMargin,
15747 'top' => $this->tMargin,
15748 'bottom' => $this->bMargin,
15749 'header' => $this->header_margin,
15750 'footer' => $this->footer_margin,
15751 'cell' => $this->cell_padding,
15752 'padding_left' => $this->cell_padding['L'],
15753 'padding_top' => $this->cell_padding['T'],
15754 'padding_right' => $this->cell_padding['R'],
15755 'padding_bottom' => $this->cell_padding['B']
15756 );
15757 return $ret;
15758 }
15759
15760 /**
15761 * Returns an array containing original margins:
15762 * <ul>
15763 <li>$ret['left'] = left margin</li>
15764 <li>$ret['right'] = right margin</li>
15765 * </ul>
15766 * @return array containing all margins measures
15767 * @public
15768 * @since 4.0.012 (2008-07-24)
15769 */
15770 public function getOriginalMargins() {
15771 $ret = array(
15772 'left' => $this->original_lMargin,
15773 'right' => $this->original_rMargin
15774 );
15775 return $ret;
15776 }
15777
15778 /**
15779 * Returns the current font size.
15780 * @return current font size
15781 * @public
15782 * @since 3.2.000 (2008-06-23)
15783 */
15784 public function getFontSize() {
15785 return $this->FontSize;
15786 }
15787
15788 /**
15789 * Returns the current font size in points unit.
15790 * @return current font size in points unit
15791 * @public
15792 * @since 3.2.000 (2008-06-23)
15793 */
15794 public function getFontSizePt() {
15795 return $this->FontSizePt;
15796 }
15797
15798 /**
15799 * Returns the current font family name.
15800 * @return string current font family name
15801 * @public
15802 * @since 4.3.008 (2008-12-05)
15803 */
15804 public function getFontFamily() {
15805 return $this->FontFamily;
15806 }
15807
15808 /**
15809 * Returns the current font style.
15810 * @return string current font style
15811 * @public
15812 * @since 4.3.008 (2008-12-05)
15813 */
15814 public function getFontStyle() {
15815 return $this->FontStyle;
15816 }
15817
15818 /**
15819 * Cleanup HTML code (requires HTML Tidy library).
15820 * @param $html (string) htmlcode to fix
15821 * @param $default_css (string) CSS commands to add
15822 * @param $tagvs (array) parameters for setHtmlVSpace method
15823 * @param $tidy_options (array) options for tidy_parse_string function
15824 * @return string XHTML code cleaned up
15825 * @author Nicola Asuni
15826 * @public
15827 * @since 5.9.017 (2010-11-16)
15828 * @see setHtmlVSpace()
15829 */
15830 public function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='') {
15831 return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces);
15832 }
15833
15834 /**
15835 * Returns the border width from CSS property
15836 * @param $width (string) border width
15837 * @return int with in user units
15838 * @protected
15839 * @since 5.7.000 (2010-08-02)
15840 */
15841 protected function getCSSBorderWidth($width) {
15842 if ($width == 'thin') {
15843 $width = (2 / $this->k);
15844 } elseif ($width == 'medium') {
15845 $width = (4 / $this->k);
15846 } elseif ($width == 'thick') {
15847 $width = (6 / $this->k);
15848 } else {
15849 $width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
15850 }
15851 return $width;
15852 }
15853
15854 /**
15855 * Returns the border dash style from CSS property
15856 * @param $style (string) border style to convert
15857 * @return int sash style (return -1 in case of none or hidden border)
15858 * @protected
15859 * @since 5.7.000 (2010-08-02)
15860 */
15861 protected function getCSSBorderDashStyle($style) {
15862 switch (strtolower($style)) {
15863 case 'none':
15864 case 'hidden': {
15865 $dash = -1;
15866 break;
15867 }
15868 case 'dotted': {
15869 $dash = 1;
15870 break;
15871 }
15872 case 'dashed': {
15873 $dash = 3;
15874 break;
15875 }
15876 case 'double':
15877 case 'groove':
15878 case 'ridge':
15879 case 'inset':
15880 case 'outset':
15881 case 'solid':
15882 default: {
15883 $dash = 0;
15884 break;
15885 }
15886 }
15887 return $dash;
15888 }
15889
15890 /**
15891 * Returns the border style array from CSS border properties
15892 * @param $cssborder (string) border properties
15893 * @return array containing border properties
15894 * @protected
15895 * @since 5.7.000 (2010-08-02)
15896 */
15897 protected function getCSSBorderStyle($cssborder) {
15898 $bprop = preg_split('/[\s]+/', trim($cssborder));
15899 $border = array(); // value to be returned
15900 switch (count($bprop)) {
15901 case 3: {
15902 $width = $bprop[0];
15903 $style = $bprop[1];
15904 $color = $bprop[2];
15905 break;
15906 }
15907 case 2: {
15908 $width = 'medium';
15909 $style = $bprop[0];
15910 $color = $bprop[1];
15911 break;
15912 }
15913 case 1: {
15914 $width = 'medium';
15915 $style = $bprop[0];
15916 $color = 'black';
15917 break;
15918 }
15919 default: {
15920 $width = 'medium';
15921 $style = 'solid';
15922 $color = 'black';
15923 break;
15924 }
15925 }
15926 if ($style == 'none') {
15927 return array();
15928 }
15929 $border['cap'] = 'square';
15930 $border['join'] = 'miter';
15931 $border['dash'] = $this->getCSSBorderDashStyle($style);
15932 if ($border['dash'] < 0) {
15933 return array();
15934 }
15935 $border['width'] = $this->getCSSBorderWidth($width);
15936 $border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
15937 return $border;
15938 }
15939
15940 /**
15941 * Get the internal Cell padding from CSS attribute.
15942 * @param $csspadding (string) padding properties
15943 * @param $width (float) width of the containing element
15944 * @return array of cell paddings
15945 * @public
15946 * @since 5.9.000 (2010-10-04)
15947 */
15948 public function getCSSPadding($csspadding, $width=0) {
15949 $padding = preg_split('/[\s]+/', trim($csspadding));
15950 $cell_padding = array(); // value to be returned
15951 switch (count($padding)) {
15952 case 4: {
15953 $cell_padding['T'] = $padding[0];
15954 $cell_padding['R'] = $padding[1];
15955 $cell_padding['B'] = $padding[2];
15956 $cell_padding['L'] = $padding[3];
15957 break;
15958 }
15959 case 3: {
15960 $cell_padding['T'] = $padding[0];
15961 $cell_padding['R'] = $padding[1];
15962 $cell_padding['B'] = $padding[2];
15963 $cell_padding['L'] = $padding[1];
15964 break;
15965 }
15966 case 2: {
15967 $cell_padding['T'] = $padding[0];
15968 $cell_padding['R'] = $padding[1];
15969 $cell_padding['B'] = $padding[0];
15970 $cell_padding['L'] = $padding[1];
15971 break;
15972 }
15973 case 1: {
15974 $cell_padding['T'] = $padding[0];
15975 $cell_padding['R'] = $padding[0];
15976 $cell_padding['B'] = $padding[0];
15977 $cell_padding['L'] = $padding[0];
15978 break;
15979 }
15980 default: {
15981 return $this->cell_padding;
15982 }
15983 }
15984 if ($width == 0) {
15985 $width = $this->w - $this->lMargin - $this->rMargin;
15986 }
15987 $cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
15988 $cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
15989 $cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
15990 $cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
15991 return $cell_padding;
15992 }
15993
15994 /**
15995 * Get the internal Cell margin from CSS attribute.
15996 * @param $cssmargin (string) margin properties
15997 * @param $width (float) width of the containing element
15998 * @return array of cell margins
15999 * @public
16000 * @since 5.9.000 (2010-10-04)
16001 */
16002 public function getCSSMargin($cssmargin, $width=0) {
16003 $margin = preg_split('/[\s]+/', trim($cssmargin));
16004 $cell_margin = array(); // value to be returned
16005 switch (count($margin)) {
16006 case 4: {
16007 $cell_margin['T'] = $margin[0];
16008 $cell_margin['R'] = $margin[1];
16009 $cell_margin['B'] = $margin[2];
16010 $cell_margin['L'] = $margin[3];
16011 break;
16012 }
16013 case 3: {
16014 $cell_margin['T'] = $margin[0];
16015 $cell_margin['R'] = $margin[1];
16016 $cell_margin['B'] = $margin[2];
16017 $cell_margin['L'] = $margin[1];
16018 break;
16019 }
16020 case 2: {
16021 $cell_margin['T'] = $margin[0];
16022 $cell_margin['R'] = $margin[1];
16023 $cell_margin['B'] = $margin[0];
16024 $cell_margin['L'] = $margin[1];
16025 break;
16026 }
16027 case 1: {
16028 $cell_margin['T'] = $margin[0];
16029 $cell_margin['R'] = $margin[0];
16030 $cell_margin['B'] = $margin[0];
16031 $cell_margin['L'] = $margin[0];
16032 break;
16033 }
16034 default: {
16035 return $this->cell_margin;
16036 }
16037 }
16038 if ($width == 0) {
16039 $width = $this->w - $this->lMargin - $this->rMargin;
16040 }
16041 $cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
16042 $cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
16043 $cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
16044 $cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
16045 return $cell_margin;
16046 }
16047
16048 /**
16049 * Get the border-spacing from CSS attribute.
16050 * @param $cssbspace (string) border-spacing CSS properties
16051 * @param $width (float) width of the containing element
16052 * @return array of border spacings
16053 * @public
16054 * @since 5.9.010 (2010-10-27)
16055 */
16056 public function getCSSBorderMargin($cssbspace, $width=0) {
16057 $space = preg_split('/[\s]+/', trim($cssbspace));
16058 $border_spacing = array(); // value to be returned
16059 switch (count($space)) {
16060 case 2: {
16061 $border_spacing['H'] = $space[0];
16062 $border_spacing['V'] = $space[1];
16063 break;
16064 }
16065 case 1: {
16066 $border_spacing['H'] = $space[0];
16067 $border_spacing['V'] = $space[0];
16068 break;
16069 }
16070 default: {
16071 return array('H' => 0, 'V' => 0);
16072 }
16073 }
16074 if ($width == 0) {
16075 $width = $this->w - $this->lMargin - $this->rMargin;
16076 }
16077 $border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
16078 $border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
16079 return $border_spacing;
16080 }
16081
16082 /**
16083 * Returns the letter-spacing value from CSS value
16084 * @param $spacing (string) letter-spacing value
16085 * @param $parent (float) font spacing (tracking) value of the parent element
16086 * @return float quantity to increases or decreases the space between characters in a text.
16087 * @protected
16088 * @since 5.9.000 (2010-10-02)
16089 */
16090 protected function getCSSFontSpacing($spacing, $parent=0) {
16091 $val = 0; // value to be returned
16092 $spacing = trim($spacing);
16093 switch ($spacing) {
16094 case 'normal': {
16095 $val = 0;
16096 break;
16097 }
16098 case 'inherit': {
16099 if ($parent == 'normal') {
16100 $val = 0;
16101 } else {
16102 $val = $parent;
16103 }
16104 break;
16105 }
16106 default: {
16107 $val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
16108 }
16109 }
16110 return $val;
16111 }
16112
16113 /**
16114 * Returns the percentage of font stretching from CSS value
16115 * @param $stretch (string) stretch mode
16116 * @param $parent (float) stretch value of the parent element
16117 * @return float font stretching percentage
16118 * @protected
16119 * @since 5.9.000 (2010-10-02)
16120 */
16121 protected function getCSSFontStretching($stretch, $parent=100) {
16122 $val = 100; // value to be returned
16123 $stretch = trim($stretch);
16124 switch ($stretch) {
16125 case 'ultra-condensed': {
16126 $val = 40;
16127 break;
16128 }
16129 case 'extra-condensed': {
16130 $val = 55;
16131 break;
16132 }
16133 case 'condensed': {
16134 $val = 70;
16135 break;
16136 }
16137 case 'semi-condensed': {
16138 $val = 85;
16139 break;
16140 }
16141 case 'normal': {
16142 $val = 100;
16143 break;
16144 }
16145 case 'semi-expanded': {
16146 $val = 115;
16147 break;
16148 }
16149 case 'expanded': {
16150 $val = 130;
16151 break;
16152 }
16153 case 'extra-expanded': {
16154 $val = 145;
16155 break;
16156 }
16157 case 'ultra-expanded': {
16158 $val = 160;
16159 break;
16160 }
16161 case 'wider': {
16162 $val = ($parent + 10);
16163 break;
16164 }
16165 case 'narrower': {
16166 $val = ($parent - 10);
16167 break;
16168 }
16169 case 'inherit': {
16170 if ($parent == 'normal') {
16171 $val = 100;
16172 } else {
16173 $val = $parent;
16174 }
16175 break;
16176 }
16177 default: {
16178 $val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
16179 }
16180 }
16181 return $val;
16182 }
16183
16184 /**
16185 * Convert HTML string containing font size value to points
16186 * @param $val (string) String containing font size value and unit.
16187 * @param $refsize (float) Reference font size in points.
16188 * @param $parent_size (float) Parent font size in points.
16189 * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
16190 * @return float value in points
16191 * @public
16192 */
16193 public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
16194 $refsize = TCPDF_FONTS::getFontRefSize($refsize);
16195 $parent_size = TCPDF_FONTS::getFontRefSize($parent_size, $refsize);
16196 switch ($val) {
16197 case 'xx-small': {
16198 $size = ($refsize - 4);
16199 break;
16200 }
16201 case 'x-small': {
16202 $size = ($refsize - 3);
16203 break;
16204 }
16205 case 'small': {
16206 $size = ($refsize - 2);
16207 break;
16208 }
16209 case 'medium': {
16210 $size = $refsize;
16211 break;
16212 }
16213 case 'large': {
16214 $size = ($refsize + 2);
16215 break;
16216 }
16217 case 'x-large': {
16218 $size = ($refsize + 4);
16219 break;
16220 }
16221 case 'xx-large': {
16222 $size = ($refsize + 6);
16223 break;
16224 }
16225 case 'smaller': {
16226 $size = ($parent_size - 3);
16227 break;
16228 }
16229 case 'larger': {
16230 $size = ($parent_size + 3);
16231 break;
16232 }
16233 default: {
16234 $size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
16235 }
16236 }
16237 return $size;
16238 }
16239
16240 /**
16241 * Returns the HTML DOM array.
16242 * @param $html (string) html code
16243 * @return array
16244 * @protected
16245 * @since 3.2.000 (2008-06-20)
16246 */
16247 protected function getHtmlDomArray($html) {
16248 // array of CSS styles ( selector => properties).
16249 $css = array();
16250 // get CSS array defined at previous call
16251 $matches = array();
16252 if (preg_match_all('/<cssarray>([^\<]*)<\/cssarray>/isU', $html, $matches) > 0) {
16253 if (isset($matches[1][0])) {
16254 $css = array_merge($css, json_decode($this->unhtmlentities($matches[1][0]), true));
16255 }
16256 $html = preg_replace('/<cssarray>(.*?)<\/cssarray>/isU', '', $html);
16257 }
16258 // extract external CSS files
16259 $matches = array();
16260 if (preg_match_all('/<link([^\>]*)>/isU', $html, $matches) > 0) {
16261 foreach ($matches[1] as $key => $link) {
16262 $type = array();
16263 if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
16264 $type = array();
16265 preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
16266 // get 'all' and 'print' media, other media types are discarded
16267 // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16268 if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16269 $type = array();
16270 if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
16271 // read CSS data file
16272 $cssdata = TCPDF_STATIC::fileGetContents(trim($type[1]));
16273 if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
16274 $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16275 }
16276 }
16277 }
16278 }
16279 }
16280 }
16281 // extract style tags
16282 $matches = array();
16283 if (preg_match_all('/<style([^\>]*)>([^\<]*)<\/style>/isU', $html, $matches) > 0) {
16284 foreach ($matches[1] as $key => $media) {
16285 $type = array();
16286 preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
16287 // get 'all' and 'print' media, other media types are discarded
16288 // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16289 if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16290 $cssdata = $matches[2][$key];
16291 $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16292 }
16293 }
16294 }
16295 // create a special tag to contain the CSS array (used for table content)
16296 $csstagarray = '<cssarray>'.htmlentities(json_encode($css)).'</cssarray>';
16297 // remove head and style blocks
16298 $html = preg_replace('/<head([^\>]*)>(.*?)<\/head>/siU', '', $html);
16299 $html = preg_replace('/<style([^\>]*)>([^\<]*)<\/style>/isU', '', $html);
16300 // define block tags
16301 $blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
16302 // define self-closing tags
16303 $selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
16304 // remove all unsupported tags (the line below lists all supported tags)
16305 $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>');
16306 //replace some blank characters
16307 $html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
16308 $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);
16309 $html = preg_replace('@(\r\n|\r)@', "\n", $html);
16310 $repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
16311 $html = strtr($html, $repTable);
16312 $offset = 0;
16313 while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
16314 $html_a = substr($html, 0, $offset);
16315 $html_b = substr($html, $offset, ($pos - $offset + 6));
16316 while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
16317 // preserve newlines on <pre> tag
16318 $html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
16319 }
16320 while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
16321 // preserve spaces on <pre> tag
16322 $html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
16323 }
16324 $html = $html_a.$html_b.substr($html, $pos + 6);
16325 $offset = strlen($html_a.$html_b);
16326 }
16327 $offset = 0;
16328 while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
16329 $html_a = substr($html, 0, $offset);
16330 $html_b = substr($html, $offset, ($pos - $offset + 11));
16331 while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
16332 // preserve newlines on <textarea> tag
16333 $html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
16334 $html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
16335 }
16336 $html = $html_a.$html_b.substr($html, $pos + 11);
16337 $offset = strlen($html_a.$html_b);
16338 }
16339 $html = preg_replace('/([\s]*)<option/si', '<option', $html);
16340 $html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
16341 $offset = 0;
16342 while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
16343 $html_a = substr($html, 0, $offset);
16344 $html_b = substr($html, $offset, ($pos - $offset + 9));
16345 while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
16346 $html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
16347 $html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
16348 }
16349 $html = $html_a.$html_b.substr($html, $pos + 9);
16350 $offset = strlen($html_a.$html_b);
16351 }
16352 if (preg_match("'</select'si", $html)) {
16353 $html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
16354 $html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
16355 }
16356 $html = str_replace("\n", ' ', $html);
16357 // restore textarea newlines
16358 $html = str_replace('<TBR>', "\n", $html);
16359 // remove extra spaces from code
16360 $html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
16361 $html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
16362 $html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
16363 $html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
16364 $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);
16365 $html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
16366 $html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
16367 $html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
16368 $html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
16369 $html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
16370 $html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
16371 $html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
16372 $html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
16373 $html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
16374 $html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
16375 $html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
16376 $html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
16377 $html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
16378 $html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
16379 // trim string
16380 $html = $this->stringTrim($html);
16381 // fix br tag after li
16382 $html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
16383 // fix first image tag alignment
16384 $html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
16385 // pattern for generic tag
16386 $tagpattern = '/(<[^>]+>)/';
16387 // explodes the string
16388 $a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
16389 // count elements
16390 $maxel = count($a);
16391 $elkey = 0;
16392 $key = 0;
16393 // create an array of elements
16394 $dom = array();
16395 $dom[$key] = array();
16396 // set inheritable properties fot the first void element
16397 // 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
16398 $dom[$key]['tag'] = false;
16399 $dom[$key]['block'] = false;
16400 $dom[$key]['value'] = '';
16401 $dom[$key]['parent'] = 0;
16402 $dom[$key]['hide'] = false;
16403 $dom[$key]['fontname'] = $this->FontFamily;
16404 $dom[$key]['fontstyle'] = $this->FontStyle;
16405 $dom[$key]['fontsize'] = $this->FontSizePt;
16406 $dom[$key]['font-stretch'] = $this->font_stretching;
16407 $dom[$key]['letter-spacing'] = $this->font_spacing;
16408 $dom[$key]['stroke'] = $this->textstrokewidth;
16409 $dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
16410 $dom[$key]['clip'] = ($this->textrendermode > 3);
16411 $dom[$key]['line-height'] = $this->cell_height_ratio;
16412 $dom[$key]['bgcolor'] = false;
16413 $dom[$key]['fgcolor'] = $this->fgcolor; // color
16414 $dom[$key]['strokecolor'] = $this->strokecolor;
16415 $dom[$key]['align'] = '';
16416 $dom[$key]['listtype'] = '';
16417 $dom[$key]['text-indent'] = 0;
16418 $dom[$key]['text-transform'] = '';
16419 $dom[$key]['border'] = array();
16420 $dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
16421 $thead = false; // true when we are inside the THEAD tag
16422 ++$key;
16423 $level = array();
16424 array_push($level, 0); // root
16425 while ($elkey < $maxel) {
16426 $dom[$key] = array();
16427 $element = $a[$elkey];
16428 $dom[$key]['elkey'] = $elkey;
16429 if (preg_match($tagpattern, $element)) {
16430 // html tag
16431 $element = substr($element, 1, -1);
16432 // get tag name
16433 preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
16434 $tagname = strtolower($tag[1]);
16435 // check if we are inside a table header
16436 if ($tagname == 'thead') {
16437 if ($element[0] == '/') {
16438 $thead = false;
16439 } else {
16440 $thead = true;
16441 }
16442 ++$elkey;
16443 continue;
16444 }
16445 $dom[$key]['tag'] = true;
16446 $dom[$key]['value'] = $tagname;
16447 if (in_array($dom[$key]['value'], $blocktags)) {
16448 $dom[$key]['block'] = true;
16449 } else {
16450 $dom[$key]['block'] = false;
16451 }
16452 if ($element[0] == '/') {
16453 // *** closing html tag
16454 $dom[$key]['opening'] = false;
16455 $dom[$key]['parent'] = end($level);
16456 array_pop($level);
16457 $dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
16458 $dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
16459 $dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
16460 $dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
16461 $dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
16462 $dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
16463 $dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
16464 $dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
16465 $dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
16466 $dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
16467 $dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
16468 $dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
16469 $dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
16470 $dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
16471 $dom[$key]['text-transform'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['text-transform'];
16472 $dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
16473 if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
16474 $dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
16475 }
16476 // set the number of columns in table tag
16477 if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
16478 $dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
16479 }
16480 if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
16481 $dom[($dom[$key]['parent'])]['content'] = $csstagarray;
16482 for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
16483 $dom[($dom[$key]['parent'])]['content'] .= stripslashes($a[$dom[$i]['elkey']]);
16484 }
16485 $key = $i;
16486 // mark nested tables
16487 $dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
16488 // remove thead sections from nested tables
16489 $dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
16490 $dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
16491 }
16492 // store header rows on a new table
16493 if (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['thead'] === true)) {
16494 if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
16495 $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
16496 }
16497 for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
16498 $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
16499 }
16500 if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
16501 $dom[($dom[$key]['parent'])]['attribute'] = array();
16502 }
16503 // header elements must be always contained in a single page
16504 $dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
16505 }
16506 if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
16507 // remove the nobr attributes from the table header
16508 $dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
16509 $dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
16510 }
16511 } else {
16512 // *** opening or self-closing html tag
16513 $dom[$key]['opening'] = true;
16514 $dom[$key]['parent'] = end($level);
16515 if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
16516 // self-closing tag
16517 $dom[$key]['self'] = true;
16518 } else {
16519 // opening tag
16520 array_push($level, $key);
16521 $dom[$key]['self'] = false;
16522 }
16523 // copy some values from parent
16524 $parentkey = 0;
16525 if ($key > 0) {
16526 $parentkey = $dom[$key]['parent'];
16527 $dom[$key]['hide'] = $dom[$parentkey]['hide'];
16528 $dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
16529 $dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
16530 $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
16531 $dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
16532 $dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
16533 $dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
16534 $dom[$key]['fill'] = $dom[$parentkey]['fill'];
16535 $dom[$key]['clip'] = $dom[$parentkey]['clip'];
16536 $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16537 $dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
16538 $dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
16539 $dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
16540 $dom[$key]['align'] = $dom[$parentkey]['align'];
16541 $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16542 $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16543 $dom[$key]['text-transform'] = $dom[$parentkey]['text-transform'];
16544 $dom[$key]['border'] = array();
16545 $dom[$key]['dir'] = $dom[$parentkey]['dir'];
16546 }
16547 // get attributes
16548 preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
16549 $dom[$key]['attribute'] = array(); // reset attribute array
16550 foreach($attr_array[1] as $id => $name) {
16551 $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
16552 }
16553 if (!empty($css)) {
16554 // merge CSS style to current style
16555 list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC::getCSSdataArray($dom, $key, $css);
16556 $dom[$key]['attribute']['style'] = TCPDF_STATIC::getTagStyleFromCSSarray($dom[$key]['cssdata']);
16557 }
16558 // split style attributes
16559 if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
16560 // get style attributes
16561 preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
16562 $dom[$key]['style'] = array(); // reset style attribute array
16563 foreach($style_array[1] as $id => $name) {
16564 // in case of duplicate attribute the last replace the previous
16565 $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
16566 }
16567 // --- get some style attributes ---
16568 // text direction
16569 if (isset($dom[$key]['style']['direction'])) {
16570 $dom[$key]['dir'] = $dom[$key]['style']['direction'];
16571 }
16572 // display
16573 if (isset($dom[$key]['style']['display'])) {
16574 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
16575 }
16576 // font family
16577 if (isset($dom[$key]['style']['font-family'])) {
16578 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
16579 }
16580 // list-style-type
16581 if (isset($dom[$key]['style']['list-style-type'])) {
16582 $dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
16583 if ($dom[$key]['listtype'] == 'inherit') {
16584 $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16585 }
16586 }
16587 // text-indent
16588 if (isset($dom[$key]['style']['text-indent'])) {
16589 $dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
16590 if ($dom[$key]['text-indent'] == 'inherit') {
16591 $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16592 }
16593 }
16594 // text-transform
16595 if (isset($dom[$key]['style']['text-transform'])) {
16596 $dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
16597 }
16598 // font size
16599 if (isset($dom[$key]['style']['font-size'])) {
16600 $fsize = trim($dom[$key]['style']['font-size']);
16601 $dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
16602 }
16603 // font-stretch
16604 if (isset($dom[$key]['style']['font-stretch'])) {
16605 $dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
16606 }
16607 // letter-spacing
16608 if (isset($dom[$key]['style']['letter-spacing'])) {
16609 $dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
16610 }
16611 // line-height (internally is the cell height ratio)
16612 if (isset($dom[$key]['style']['line-height'])) {
16613 $lineheight = trim($dom[$key]['style']['line-height']);
16614 switch ($lineheight) {
16615 // A normal line height. This is default
16616 case 'normal': {
16617 $dom[$key]['line-height'] = $dom[0]['line-height'];
16618 break;
16619 }
16620 case 'inherit': {
16621 $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16622 }
16623 default: {
16624 if (is_numeric($lineheight)) {
16625 // convert to percentage of font height
16626 $lineheight = ($lineheight * 100).'%';
16627 }
16628 $dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
16629 if (substr($lineheight, -1) !== '%') {
16630 if ($dom[$key]['fontsize'] <= 0) {
16631 $dom[$key]['line-height'] = 1;
16632 } else {
16633 $dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding['T'] - $this->cell_padding['B']) / $dom[$key]['fontsize']);
16634 }
16635 }
16636 }
16637 }
16638 }
16639 // font style
16640 if (isset($dom[$key]['style']['font-weight'])) {
16641 if (strtolower($dom[$key]['style']['font-weight'][0]) == 'n') {
16642 if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
16643 $dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
16644 }
16645 } elseif (strtolower($dom[$key]['style']['font-weight'][0]) == 'b') {
16646 $dom[$key]['fontstyle'] .= 'B';
16647 }
16648 }
16649 if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style'][0]) == 'i')) {
16650 $dom[$key]['fontstyle'] .= 'I';
16651 }
16652 // font color
16653 if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['color']))) {
16654 $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors);
16655 } elseif ($dom[$key]['value'] == 'a') {
16656 $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16657 }
16658 // background color
16659 if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['background-color']))) {
16660 $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors);
16661 }
16662 // text-decoration
16663 if (isset($dom[$key]['style']['text-decoration'])) {
16664 $decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
16665 foreach ($decors as $dec) {
16666 $dec = trim($dec);
16667 if (!TCPDF_STATIC::empty_string($dec)) {
16668 if ($dec[0] == 'u') {
16669 // underline
16670 $dom[$key]['fontstyle'] .= 'U';
16671 } elseif ($dec[0] == 'l') {
16672 // line-through
16673 $dom[$key]['fontstyle'] .= 'D';
16674 } elseif ($dec[0] == 'o') {
16675 // overline
16676 $dom[$key]['fontstyle'] .= 'O';
16677 }
16678 }
16679 }
16680 } elseif ($dom[$key]['value'] == 'a') {
16681 $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16682 }
16683 // check for width attribute
16684 if (isset($dom[$key]['style']['width'])) {
16685 $dom[$key]['width'] = $dom[$key]['style']['width'];
16686 }
16687 // check for height attribute
16688 if (isset($dom[$key]['style']['height'])) {
16689 $dom[$key]['height'] = $dom[$key]['style']['height'];
16690 }
16691 // check for text alignment
16692 if (isset($dom[$key]['style']['text-align'])) {
16693 $dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align'][0]);
16694 }
16695 // check for CSS border properties
16696 if (isset($dom[$key]['style']['border'])) {
16697 $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
16698 if (!empty($borderstyle)) {
16699 $dom[$key]['border']['LTRB'] = $borderstyle;
16700 }
16701 }
16702 if (isset($dom[$key]['style']['border-color'])) {
16703 $brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
16704 if (isset($brd_colors[3])) {
16705 $dom[$key]['border']['L']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[3], $this->spot_colors);
16706 }
16707 if (isset($brd_colors[1])) {
16708 $dom[$key]['border']['R']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[1], $this->spot_colors);
16709 }
16710 if (isset($brd_colors[0])) {
16711 $dom[$key]['border']['T']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[0], $this->spot_colors);
16712 }
16713 if (isset($brd_colors[2])) {
16714 $dom[$key]['border']['B']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[2], $this->spot_colors);
16715 }
16716 }
16717 if (isset($dom[$key]['style']['border-width'])) {
16718 $brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
16719 if (isset($brd_widths[3])) {
16720 $dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
16721 }
16722 if (isset($brd_widths[1])) {
16723 $dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
16724 }
16725 if (isset($brd_widths[0])) {
16726 $dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
16727 }
16728 if (isset($brd_widths[2])) {
16729 $dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
16730 }
16731 }
16732 if (isset($dom[$key]['style']['border-style'])) {
16733 $brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
16734 if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
16735 $dom[$key]['border']['L']['cap'] = 'square';
16736 $dom[$key]['border']['L']['join'] = 'miter';
16737 $dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
16738 if ($dom[$key]['border']['L']['dash'] < 0) {
16739 $dom[$key]['border']['L'] = array();
16740 }
16741 }
16742 if (isset($brd_styles[1])) {
16743 $dom[$key]['border']['R']['cap'] = 'square';
16744 $dom[$key]['border']['R']['join'] = 'miter';
16745 $dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
16746 if ($dom[$key]['border']['R']['dash'] < 0) {
16747 $dom[$key]['border']['R'] = array();
16748 }
16749 }
16750 if (isset($brd_styles[0])) {
16751 $dom[$key]['border']['T']['cap'] = 'square';
16752 $dom[$key]['border']['T']['join'] = 'miter';
16753 $dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
16754 if ($dom[$key]['border']['T']['dash'] < 0) {
16755 $dom[$key]['border']['T'] = array();
16756 }
16757 }
16758 if (isset($brd_styles[2])) {
16759 $dom[$key]['border']['B']['cap'] = 'square';
16760 $dom[$key]['border']['B']['join'] = 'miter';
16761 $dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
16762 if ($dom[$key]['border']['B']['dash'] < 0) {
16763 $dom[$key]['border']['B'] = array();
16764 }
16765 }
16766 }
16767 $cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
16768 foreach ($cellside as $bsk => $bsv) {
16769 if (isset($dom[$key]['style']['border-'.$bsv])) {
16770 $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
16771 if (!empty($borderstyle)) {
16772 $dom[$key]['border'][$bsk] = $borderstyle;
16773 }
16774 }
16775 if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
16776 $dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors);
16777 }
16778 if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
16779 $dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
16780 }
16781 if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
16782 $dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
16783 if ($dom[$key]['border'][$bsk]['dash'] < 0) {
16784 $dom[$key]['border'][$bsk] = array();
16785 }
16786 }
16787 }
16788 // check for CSS padding properties
16789 if (isset($dom[$key]['style']['padding'])) {
16790 $dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
16791 } else {
16792 $dom[$key]['padding'] = $this->cell_padding;
16793 }
16794 foreach ($cellside as $psk => $psv) {
16795 if (isset($dom[$key]['style']['padding-'.$psv])) {
16796 $dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
16797 }
16798 }
16799 // check for CSS margin properties
16800 if (isset($dom[$key]['style']['margin'])) {
16801 $dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
16802 } else {
16803 $dom[$key]['margin'] = $this->cell_margin;
16804 }
16805 foreach ($cellside as $psk => $psv) {
16806 if (isset($dom[$key]['style']['margin-'.$psv])) {
16807 $dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
16808 }
16809 }
16810 // check for CSS border-spacing properties
16811 if (isset($dom[$key]['style']['border-spacing'])) {
16812 $dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
16813 }
16814 // page-break-inside
16815 if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
16816 $dom[$key]['attribute']['nobr'] = 'true';
16817 }
16818 // page-break-before
16819 if (isset($dom[$key]['style']['page-break-before'])) {
16820 if ($dom[$key]['style']['page-break-before'] == 'always') {
16821 $dom[$key]['attribute']['pagebreak'] = 'true';
16822 } elseif ($dom[$key]['style']['page-break-before'] == 'left') {
16823 $dom[$key]['attribute']['pagebreak'] = 'left';
16824 } elseif ($dom[$key]['style']['page-break-before'] == 'right') {
16825 $dom[$key]['attribute']['pagebreak'] = 'right';
16826 }
16827 }
16828 // page-break-after
16829 if (isset($dom[$key]['style']['page-break-after'])) {
16830 if ($dom[$key]['style']['page-break-after'] == 'always') {
16831 $dom[$key]['attribute']['pagebreakafter'] = 'true';
16832 } elseif ($dom[$key]['style']['page-break-after'] == 'left') {
16833 $dom[$key]['attribute']['pagebreakafter'] = 'left';
16834 } elseif ($dom[$key]['style']['page-break-after'] == 'right') {
16835 $dom[$key]['attribute']['pagebreakafter'] = 'right';
16836 }
16837 }
16838 }
16839 if (isset($dom[$key]['attribute']['display'])) {
16840 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
16841 }
16842 if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
16843 $borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
16844 if (!empty($borderstyle)) {
16845 $dom[$key]['border']['LTRB'] = $borderstyle;
16846 }
16847 }
16848 // check for font tag
16849 if ($dom[$key]['value'] == 'font') {
16850 // font family
16851 if (isset($dom[$key]['attribute']['face'])) {
16852 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
16853 }
16854 // font size
16855 if (isset($dom[$key]['attribute']['size'])) {
16856 if ($key > 0) {
16857 if ($dom[$key]['attribute']['size'][0] == '+') {
16858 $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
16859 } elseif ($dom[$key]['attribute']['size'][0] == '-') {
16860 $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
16861 } else {
16862 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16863 }
16864 } else {
16865 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16866 }
16867 }
16868 }
16869 // force natural alignment for lists
16870 if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
16871 AND (!isset($dom[$key]['align']) OR TCPDF_STATIC::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
16872 if ($this->rtl) {
16873 $dom[$key]['align'] = 'R';
16874 } else {
16875 $dom[$key]['align'] = 'L';
16876 }
16877 }
16878 if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
16879 if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
16880 $dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
16881 }
16882 }
16883 if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
16884 $dom[$key]['fontstyle'] .= 'B';
16885 }
16886 if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
16887 $dom[$key]['fontstyle'] .= 'I';
16888 }
16889 if ($dom[$key]['value'] == 'u') {
16890 $dom[$key]['fontstyle'] .= 'U';
16891 }
16892 if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
16893 $dom[$key]['fontstyle'] .= 'D';
16894 }
16895 if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
16896 $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16897 }
16898 if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
16899 $dom[$key]['fontname'] = $this->default_monospaced_font;
16900 }
16901 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)) {
16902 // headings h1, h2, h3, h4, h5, h6
16903 if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
16904 $headsize = (4 - intval($dom[$key]['value']{1})) * 2;
16905 $dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
16906 }
16907 if (!isset($dom[$key]['style']['font-weight'])) {
16908 $dom[$key]['fontstyle'] .= 'B';
16909 }
16910 }
16911 if (($dom[$key]['value'] == 'table')) {
16912 $dom[$key]['rows'] = 0; // number of rows
16913 $dom[$key]['trids'] = array(); // IDs of TR elements
16914 $dom[$key]['thead'] = ''; // table header rows
16915 }
16916 if (($dom[$key]['value'] == 'tr')) {
16917 $dom[$key]['cols'] = 0;
16918 if ($thead) {
16919 $dom[$key]['thead'] = true;
16920 // rows on thead block are printed as a separate table
16921 } else {
16922 $dom[$key]['thead'] = false;
16923 // store the number of rows on table element
16924 ++$dom[($dom[$key]['parent'])]['rows'];
16925 // store the TR elements IDs on table element
16926 array_push($dom[($dom[$key]['parent'])]['trids'], $key);
16927 }
16928 }
16929 if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
16930 if (isset($dom[$key]['attribute']['colspan'])) {
16931 $colspan = intval($dom[$key]['attribute']['colspan']);
16932 } else {
16933 $colspan = 1;
16934 }
16935 $dom[$key]['attribute']['colspan'] = $colspan;
16936 $dom[($dom[$key]['parent'])]['cols'] += $colspan;
16937 }
16938 // text direction
16939 if (isset($dom[$key]['attribute']['dir'])) {
16940 $dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
16941 }
16942 // set foreground color attribute
16943 if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['color']))) {
16944 $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors);
16945 } elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
16946 $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16947 }
16948 // set background color attribute
16949 if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['bgcolor']))) {
16950 $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors);
16951 }
16952 // set stroke color attribute
16953 if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['strokecolor']))) {
16954 $dom[$key]['strokecolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors);
16955 }
16956 // check for width attribute
16957 if (isset($dom[$key]['attribute']['width'])) {
16958 $dom[$key]['width'] = $dom[$key]['attribute']['width'];
16959 }
16960 // check for height attribute
16961 if (isset($dom[$key]['attribute']['height'])) {
16962 $dom[$key]['height'] = $dom[$key]['attribute']['height'];
16963 }
16964 // check for text alignment
16965 if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
16966 $dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align'][0]);
16967 }
16968 // check for text rendering mode (the following attributes do not exist in HTML)
16969 if (isset($dom[$key]['attribute']['stroke'])) {
16970 // font stroke width
16971 $dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
16972 }
16973 if (isset($dom[$key]['attribute']['fill'])) {
16974 // font fill
16975 if ($dom[$key]['attribute']['fill'] == 'true') {
16976 $dom[$key]['fill'] = true;
16977 } else {
16978 $dom[$key]['fill'] = false;
16979 }
16980 }
16981 if (isset($dom[$key]['attribute']['clip'])) {
16982 // clipping mode
16983 if ($dom[$key]['attribute']['clip'] == 'true') {
16984 $dom[$key]['clip'] = true;
16985 } else {
16986 $dom[$key]['clip'] = false;
16987 }
16988 }
16989 } // end opening tag
16990 } else {
16991 // text
16992 $dom[$key]['tag'] = false;
16993 $dom[$key]['block'] = false;
16994 $dom[$key]['parent'] = end($level);
16995 $dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
16996 if (!empty($dom[$dom[$key]['parent']]['text-transform'])) {
16997 // text-transform for unicode requires mb_convert_case (Multibyte String Functions)
16998 if (function_exists('mb_convert_case')) {
16999 $ttm = array('capitalize' => MB_CASE_TITLE, 'uppercase' => MB_CASE_UPPER, 'lowercase' => MB_CASE_LOWER);
17000 if (isset($ttm[$dom[$dom[$key]['parent']]['text-transform']])) {
17001 $element = mb_convert_case($element, $ttm[$dom[$dom[$key]['parent']]['text-transform']], $this->encoding);
17002 }
17003 } elseif (!$this->isunicode) {
17004 switch ($dom[$dom[$key]['parent']]['text-transform']) {
17005 case 'capitalize': {
17006 $element = ucwords(strtolower($element));
17007 break;
17008 }
17009 case 'uppercase': {
17010 $element = strtoupper($element);
17011 break;
17012 }
17013 case 'lowercase': {
17014 $element = strtolower($element);
17015 break;
17016 }
17017 }
17018 }
17019 }
17020 $dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
17021 }
17022 ++$elkey;
17023 ++$key;
17024 }
17025 return $dom;
17026 }
17027
17028 /**
17029 * Returns the string used to find spaces
17030 * @return string
17031 * @protected
17032 * @author Nicola Asuni
17033 * @since 4.8.024 (2010-01-15)
17034 */
17035 protected function getSpaceString() {
17036 $spacestr = chr(32);
17037 if ($this->isUnicodeFont()) {
17038 $spacestr = chr(0).chr(32);
17039 }
17040 return $spacestr;
17041 }
17042
17043 /**
17044 * Return an hash code used to ensure that the serialized data has been generated by this TCPDF instance.
17045 * @param $data (string) serialized data
17046 * @return string
17047 * @public static
17048 */
17049 protected function getHashForTCPDFtagParams($data) {
17050 return md5(strlen($data).$this->file_id.$data);
17051 }
17052
17053 /**
17054 * Serialize an array of parameters to be used with TCPDF tag in HTML code.
17055 * @param $data (array) parameters array
17056 * @return string containing serialized data
17057 * @public static
17058 */
17059 public function serializeTCPDFtagParameters($data) {
17060 $encoded = urlencode(json_encode($data));
17061 return $this->getHashForTCPDFtagParams($encoded).$encoded;
17062 }
17063
17064 /**
17065 * Unserialize parameters to be used with TCPDF tag in HTML code.
17066 * @param $data (string) serialized data
17067 * @return array containing unserialized data
17068 * @protected static
17069 */
17070 protected function unserializeTCPDFtagParameters($data) {
17071 $hash = substr($data, 0, 32);
17072 $encoded = substr($data, 32);
17073 if ($hash != $this->getHashForTCPDFtagParams($encoded)) {
17074 $this->Error('Invalid parameters');
17075 }
17076 return json_decode(urldecode($encoded), true);
17077 }
17078
17079 /**
17080 * Prints a cell (rectangular area) with optional borders, background color and html text string.
17081 * 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 />
17082 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
17083 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17084 * 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
17085 * NOTE: all the HTML attributes must be enclosed in double-quote.
17086 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
17087 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
17088 * @param $x (float) upper-left corner X coordinate
17089 * @param $y (float) upper-left corner Y coordinate
17090 * @param $html (string) html text to print. Default value: empty string.
17091 * @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)))
17092 * @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>
17093 Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
17094 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
17095 * @param $reseth (boolean) if true reset the last cell height (default true).
17096 * @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>
17097 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
17098 * @see Multicell(), writeHTML()
17099 * @public
17100 */
17101 public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
17102 return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
17103 }
17104
17105 /**
17106 * Allows to preserve some HTML formatting (limited support).<br />
17107 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17108 * 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
17109 * NOTE: all the HTML attributes must be enclosed in double-quote.
17110 * @param $html (string) text to display
17111 * @param $ln (boolean) if true add a new line after text (default = true)
17112 * @param $fill (boolean) Indicates if the background must be painted (true) or transparent (false).
17113 * @param $reseth (boolean) if true reset the last cell height (default false).
17114 * @param $cell (boolean) if true add the current left (or right for RTL) padding to each Write (default false).
17115 * @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>
17116 * @public
17117 */
17118 public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
17119 $gvars = $this->getGraphicVars();
17120 // store current values
17121 $prev_cell_margin = $this->cell_margin;
17122 $prev_cell_padding = $this->cell_padding;
17123 $prevPage = $this->page;
17124 $prevlMargin = $this->lMargin;
17125 $prevrMargin = $this->rMargin;
17126 $curfontname = $this->FontFamily;
17127 $curfontstyle = $this->FontStyle;
17128 $curfontsize = $this->FontSizePt;
17129 $curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
17130 $curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
17131 $curfontstretcing = $this->font_stretching;
17132 $curfonttracking = $this->font_spacing;
17133 $this->newline = true;
17134 $newline = true;
17135 $startlinepage = $this->page;
17136 $minstartliney = $this->y;
17137 $maxbottomliney = 0;
17138 $startlinex = $this->x;
17139 $startliney = $this->y;
17140 $yshift = 0;
17141 $loop = 0;
17142 $curpos = 0;
17143 $this_method_vars = array();
17144 $undo = false;
17145 $fontaligned = false;
17146 $reverse_dir = false; // true when the text direction is reversed
17147 $this->premode = false;
17148 if ($this->inxobj) {
17149 // we are inside an XObject template
17150 $pask = count($this->xobjects[$this->xobjid]['annotations']);
17151 } elseif (isset($this->PageAnnots[$this->page])) {
17152 $pask = count($this->PageAnnots[$this->page]);
17153 } else {
17154 $pask = 0;
17155 }
17156 if ($this->inxobj) {
17157 // we are inside an XObject template
17158 $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17159 } elseif (!$this->InFooter) {
17160 if (isset($this->footerlen[$this->page])) {
17161 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17162 } else {
17163 $this->footerpos[$this->page] = $this->pagelen[$this->page];
17164 }
17165 $startlinepos = $this->footerpos[$this->page];
17166 } else {
17167 // we are inside the footer
17168 $startlinepos = $this->pagelen[$this->page];
17169 }
17170 $lalign = $align;
17171 $plalign = $align;
17172 if ($this->rtl) {
17173 $w = $this->x - $this->lMargin;
17174 } else {
17175 $w = $this->w - $this->rMargin - $this->x;
17176 }
17177 $w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
17178 if ($cell) {
17179 if ($this->rtl) {
17180 $this->x -= $this->cell_padding['R'];
17181 $this->lMargin += $this->cell_padding['L'];
17182 } else {
17183 $this->x += $this->cell_padding['L'];
17184 $this->rMargin += $this->cell_padding['R'];
17185 }
17186 }
17187 if ($this->customlistindent >= 0) {
17188 $this->listindent = $this->customlistindent;
17189 } else {
17190 $this->listindent = $this->GetStringWidth('000000');
17191 }
17192 $this->listindentlevel = 0;
17193 // save previous states
17194 $prev_cell_height_ratio = $this->cell_height_ratio;
17195 $prev_listnum = $this->listnum;
17196 $prev_listordered = $this->listordered;
17197 $prev_listcount = $this->listcount;
17198 $prev_lispacer = $this->lispacer;
17199 $this->listnum = 0;
17200 $this->listordered = array();
17201 $this->listcount = array();
17202 $this->lispacer = '';
17203 if ((TCPDF_STATIC::empty_string($this->lasth)) OR ($reseth)) {
17204 // reset row height
17205 $this->resetLastH();
17206 }
17207 $dom = $this->getHtmlDomArray($html);
17208 $maxel = count($dom);
17209 $key = 0;
17210 while ($key < $maxel) {
17211 if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND $dom[$key]['hide']) {
17212 // store the node key
17213 $hidden_node_key = $key;
17214 if ($dom[$key]['self']) {
17215 // skip just this self-closing tag
17216 ++$key;
17217 } else {
17218 // skip this and all children tags
17219 while (($key < $maxel) AND (!$dom[$key]['tag'] OR $dom[$key]['opening'] OR ($dom[$key]['parent'] != $hidden_node_key))) {
17220 // skip hidden objects
17221 ++$key;
17222 }
17223 ++$key;
17224 }
17225 }
17226 if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
17227 // check for pagebreak
17228 if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
17229 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17230 $this->checkPageBreak($this->PageBreakTrigger + 1);
17231 $this->htmlvspace = ($this->PageBreakTrigger + 1);
17232 }
17233 if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
17234 OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
17235 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17236 $this->checkPageBreak($this->PageBreakTrigger + 1);
17237 $this->htmlvspace = ($this->PageBreakTrigger + 1);
17238 }
17239 }
17240 if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
17241 if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
17242 $dom[$key]['attribute']['nobr'] = false;
17243 } else {
17244 // store current object
17245 $this->startTransaction();
17246 // save this method vars
17247 $this_method_vars['html'] = $html;
17248 $this_method_vars['ln'] = $ln;
17249 $this_method_vars['fill'] = $fill;
17250 $this_method_vars['reseth'] = $reseth;
17251 $this_method_vars['cell'] = $cell;
17252 $this_method_vars['align'] = $align;
17253 $this_method_vars['gvars'] = $gvars;
17254 $this_method_vars['prevPage'] = $prevPage;
17255 $this_method_vars['prev_cell_margin'] = $prev_cell_margin;
17256 $this_method_vars['prev_cell_padding'] = $prev_cell_padding;
17257 $this_method_vars['prevlMargin'] = $prevlMargin;
17258 $this_method_vars['prevrMargin'] = $prevrMargin;
17259 $this_method_vars['curfontname'] = $curfontname;
17260 $this_method_vars['curfontstyle'] = $curfontstyle;
17261 $this_method_vars['curfontsize'] = $curfontsize;
17262 $this_method_vars['curfontascent'] = $curfontascent;
17263 $this_method_vars['curfontdescent'] = $curfontdescent;
17264 $this_method_vars['curfontstretcing'] = $curfontstretcing;
17265 $this_method_vars['curfonttracking'] = $curfonttracking;
17266 $this_method_vars['minstartliney'] = $minstartliney;
17267 $this_method_vars['maxbottomliney'] = $maxbottomliney;
17268 $this_method_vars['yshift'] = $yshift;
17269 $this_method_vars['startlinepage'] = $startlinepage;
17270 $this_method_vars['startlinepos'] = $startlinepos;
17271 $this_method_vars['startlinex'] = $startlinex;
17272 $this_method_vars['startliney'] = $startliney;
17273 $this_method_vars['newline'] = $newline;
17274 $this_method_vars['loop'] = $loop;
17275 $this_method_vars['curpos'] = $curpos;
17276 $this_method_vars['pask'] = $pask;
17277 $this_method_vars['lalign'] = $lalign;
17278 $this_method_vars['plalign'] = $plalign;
17279 $this_method_vars['w'] = $w;
17280 $this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
17281 $this_method_vars['prev_listnum'] = $prev_listnum;
17282 $this_method_vars['prev_listordered'] = $prev_listordered;
17283 $this_method_vars['prev_listcount'] = $prev_listcount;
17284 $this_method_vars['prev_lispacer'] = $prev_lispacer;
17285 $this_method_vars['fontaligned'] = $fontaligned;
17286 $this_method_vars['key'] = $key;
17287 $this_method_vars['dom'] = $dom;
17288 }
17289 }
17290 // print THEAD block
17291 if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
17292 if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC::empty_string($dom[$dom[$key]['parent']]['thead'])) {
17293 $this->inthead = true;
17294 // print table header (thead)
17295 $this->writeHTML($this->thead, false, false, false, false, '');
17296 // check if we are on a new page or on a new column
17297 if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
17298 // we are on a new page or on a new column and the total object height is less than the available vertical space.
17299 // restore previous object
17300 $this->rollbackTransaction(true);
17301 // restore previous values
17302 foreach ($this_method_vars as $vkey => $vval) {
17303 $$vkey = $vval;
17304 }
17305 // disable table header
17306 $tmp_thead = $this->thead;
17307 $this->thead = '';
17308 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17309 $pre_y = $this->y;
17310 if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
17311 // fix for multicolumn mode
17312 $startliney = $this->y;
17313 }
17314 $this->start_transaction_page = $this->page;
17315 $this->start_transaction_y = $this->y;
17316 // restore table header
17317 $this->thead = $tmp_thead;
17318 // fix table border properties
17319 if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
17320 $tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
17321 } elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
17322 $tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
17323 } else {
17324 $tmp_cellspacing = 0;
17325 }
17326 $dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
17327 $dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
17328 $dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
17329 $xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
17330 $dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
17331 $dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
17332 // print table header (thead)
17333 $this->writeHTML($this->thead, false, false, false, false, '');
17334 }
17335 }
17336 // move $key index forward to skip THEAD block
17337 while ( ($key < $maxel) AND (!(
17338 ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
17339 OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
17340 ++$key;
17341 }
17342 }
17343 if ($dom[$key]['tag'] OR ($key == 0)) {
17344 if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
17345 $dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
17346 }
17347 // vertically align image in line
17348 if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
17349 // get image height
17350 $imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], ($dom[$key]['fontsize'] / $this->k), 'px');
17351 $autolinebreak = false;
17352 if (!empty($dom[$key]['width'])) {
17353 $imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], ($dom[$key]['fontsize'] / $this->k), 'px', false);
17354 if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
17355 AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
17356 OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
17357 // add automatic line break
17358 $autolinebreak = true;
17359 $this->Ln('', $cell);
17360 if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
17361 // go back to evaluate this line break
17362 --$key;
17363 }
17364 }
17365 }
17366 if (!$autolinebreak) {
17367 if ($this->inPageBody()) {
17368 $pre_y = $this->y;
17369 // check for page break
17370 if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
17371 // fix for multicolumn mode
17372 $startliney = $this->y;
17373 }
17374 }
17375 if ($this->page > $startlinepage) {
17376 // fix line splitted over two pages
17377 if (isset($this->footerlen[$startlinepage])) {
17378 $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17379 }
17380 // line to be moved one page forward
17381 $pagebuff = $this->getPageBuffer($startlinepage);
17382 $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17383 $tstart = substr($pagebuff, 0, $startlinepos);
17384 $tend = substr($this->getPageBuffer($startlinepage), $curpos);
17385 // remove line from previous page
17386 $this->setPageBuffer($startlinepage, $tstart.''.$tend);
17387 $pagebuff = $this->getPageBuffer($this->page);
17388 $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17389 $tend = substr($pagebuff, $this->cntmrk[$this->page]);
17390 // add line start to current page
17391 $yshift = ($minstartliney - $this->y);
17392 if ($fontaligned) {
17393 $yshift += ($curfontsize / $this->k);
17394 }
17395 $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17396 $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17397 // shift the annotations and links
17398 if (isset($this->PageAnnots[$this->page])) {
17399 $next_pask = count($this->PageAnnots[$this->page]);
17400 } else {
17401 $next_pask = 0;
17402 }
17403 if (isset($this->PageAnnots[$startlinepage])) {
17404 foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17405 if ($pak >= $pask) {
17406 $this->PageAnnots[$this->page][] = $pac;
17407 unset($this->PageAnnots[$startlinepage][$pak]);
17408 $npak = count($this->PageAnnots[$this->page]) - 1;
17409 $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17410 }
17411 }
17412 }
17413 $pask = $next_pask;
17414 $startlinepos = $this->cntmrk[$this->page];
17415 $startlinepage = $this->page;
17416 $startliney = $this->y;
17417 $this->newline = false;
17418 }
17419 $this->y += ($this->getCellHeight($curfontsize / $this->k) - ($curfontdescent * $this->cell_height_ratio) - $imgh);
17420 $minstartliney = min($this->y, $minstartliney);
17421 $maxbottomliney = ($startliney + $this->getCellHeight($curfontsize / $this->k));
17422 }
17423 } elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
17424 // account for different font size
17425 $pfontname = $curfontname;
17426 $pfontstyle = $curfontstyle;
17427 $pfontsize = $curfontsize;
17428 $fontname = (isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname);
17429 $fontstyle = (isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle);
17430 $fontsize = (isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize);
17431 $fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
17432 $fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
17433 if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
17434 OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17435 OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
17436 if (($key < ($maxel - 1)) AND (
17437 ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
17438 OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17439 OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize)
17440 AND ($fontsize >= 0) AND ($curfontsize >= 0)
17441 AND (($fontsize != $curfontsize) OR ($fontstyle != $curfontstyle) OR ($fontname != $curfontname)))
17442 )) {
17443 if ($this->page > $startlinepage) {
17444 // fix lines splitted over two pages
17445 if (isset($this->footerlen[$startlinepage])) {
17446 $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17447 }
17448 // line to be moved one page forward
17449 $pagebuff = $this->getPageBuffer($startlinepage);
17450 $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17451 $tstart = substr($pagebuff, 0, $startlinepos);
17452 $tend = substr($this->getPageBuffer($startlinepage), $curpos);
17453 // remove line start from previous page
17454 $this->setPageBuffer($startlinepage, $tstart.''.$tend);
17455 $pagebuff = $this->getPageBuffer($this->page);
17456 $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17457 $tend = substr($pagebuff, $this->cntmrk[$this->page]);
17458 // add line start to current page
17459 $yshift = ($minstartliney - $this->y);
17460 $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17461 $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17462 // shift the annotations and links
17463 if (isset($this->PageAnnots[$this->page])) {
17464 $next_pask = count($this->PageAnnots[$this->page]);
17465 } else {
17466 $next_pask = 0;
17467 }
17468 if (isset($this->PageAnnots[$startlinepage])) {
17469 foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17470 if ($pak >= $pask) {
17471 $this->PageAnnots[$this->page][] = $pac;
17472 unset($this->PageAnnots[$startlinepage][$pak]);
17473 $npak = count($this->PageAnnots[$this->page]) - 1;
17474 $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17475 }
17476 }
17477 }
17478 $pask = $next_pask;
17479 $startlinepos = $this->cntmrk[$this->page];
17480 $startlinepage = $this->page;
17481 $startliney = $this->y;
17482 }
17483 if (!isset($dom[$key]['line-height'])) {
17484 $dom[$key]['line-height'] = $this->cell_height_ratio;
17485 }
17486 if (!$dom[$key]['block']) {
17487 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']))) {
17488 $this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
17489 }
17490 if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
17491 $current_line_align_data = array($key, $minstartliney, $maxbottomliney);
17492 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)))) {
17493 $minstartliney = min($this->y, $line_align_data[1]);
17494 $maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $line_align_data[2]);
17495 } else {
17496 $minstartliney = min($this->y, $minstartliney);
17497 $maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $maxbottomliney);
17498 }
17499 $line_align_data = $current_line_align_data;
17500 }
17501 }
17502 $this->cell_height_ratio = $dom[$key]['line-height'];
17503 $fontaligned = true;
17504 }
17505 $this->SetFont($fontname, $fontstyle, $fontsize);
17506 // reset row height
17507 $this->resetLastH();
17508 $curfontname = $fontname;
17509 $curfontstyle = $fontstyle;
17510 $curfontsize = $fontsize;
17511 $curfontascent = $fontascent;
17512 $curfontdescent = $fontdescent;
17513 }
17514 }
17515 // set text rendering mode
17516 $textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
17517 $textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
17518 $textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
17519 $this->setTextRenderingMode($textstroke, $textfill, $textclip);
17520 if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
17521 $this->setFontStretching($dom[$key]['font-stretch']);
17522 }
17523 if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
17524 $this->setFontSpacing($dom[$key]['letter-spacing']);
17525 }
17526 if (($plalign == 'J') AND $dom[$key]['block']) {
17527 $plalign = '';
17528 }
17529 // get current position on page buffer
17530 $curpos = $this->pagelen[$startlinepage];
17531 if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
17532 $this->SetFillColorArray($dom[$key]['bgcolor']);
17533 $wfill = true;
17534 } else {
17535 $wfill = $fill | false;
17536 }
17537 if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
17538 $this->SetTextColorArray($dom[$key]['fgcolor']);
17539 }
17540 if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
17541 $this->SetDrawColorArray($dom[$key]['strokecolor']);
17542 }
17543 if (isset($dom[$key]['align'])) {
17544 $lalign = $dom[$key]['align'];
17545 }
17546 if (TCPDF_STATIC::empty_string($lalign)) {
17547 $lalign = $align;
17548 }
17549 }
17550 // align lines
17551 if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
17552 $newline = true;
17553 $fontaligned = false;
17554 // we are at the beginning of a new line
17555 if (isset($startlinex)) {
17556 $yshift = ($minstartliney - $startliney);
17557 if (($yshift > 0) OR ($this->page > $startlinepage)) {
17558 $yshift = 0;
17559 }
17560 $t_x = 0;
17561 // the last line must be shifted to be aligned as requested
17562 $linew = abs($this->endlinex - $startlinex);
17563 if ($this->inxobj) {
17564 // we are inside an XObject template
17565 $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
17566 if (isset($opentagpos)) {
17567 $midpos = $opentagpos;
17568 } else {
17569 $midpos = 0;
17570 }
17571 if ($midpos > 0) {
17572 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
17573 $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
17574 } else {
17575 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
17576 $pend = '';
17577 }
17578 } else {
17579 $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
17580 if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17581 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17582 $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
17583 } elseif (isset($opentagpos)) {
17584 $midpos = $opentagpos;
17585 } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17586 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17587 $midpos = $this->footerpos[$startlinepage];
17588 } else {
17589 $midpos = 0;
17590 }
17591 if ($midpos > 0) {
17592 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
17593 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
17594 } else {
17595 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
17596 $pend = '';
17597 }
17598 }
17599 if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
17600 // calculate shifting amount
17601 $tw = $w;
17602 if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
17603 $tw += $this->cell_padding['R'];
17604 }
17605 if ($this->lMargin != $prevlMargin) {
17606 $tw += ($prevlMargin - $this->lMargin);
17607 }
17608 if ($this->rMargin != $prevrMargin) {
17609 $tw += ($prevrMargin - $this->rMargin);
17610 }
17611 $one_space_width = $this->GetStringWidth(chr(32));
17612 $no = 0; // number of spaces on a line contained on a single block
17613 if ($this->isRTLTextDir()) { // RTL
17614 // remove left space if exist
17615 $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
17616 if ($pos1 > 0) {
17617 $pos1 = intval($pos1);
17618 if ($this->isUnicodeFont()) {
17619 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
17620 $spacelen = 2;
17621 } else {
17622 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
17623 $spacelen = 1;
17624 }
17625 if ($pos1 == $pos2) {
17626 $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
17627 if (substr($pmid, $pos1, 4) == '[()]') {
17628 $linew -= $one_space_width;
17629 } elseif ($pos1 == strpos($pmid, '[(')) {
17630 $no = 1;
17631 }
17632 }
17633 }
17634 } else { // LTR
17635 // remove right space if exist
17636 $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
17637 if ($pos1 > 0) {
17638 $pos1 = intval($pos1);
17639 if ($this->isUnicodeFont()) {
17640 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
17641 $spacelen = 2;
17642 } else {
17643 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
17644 $spacelen = 1;
17645 }
17646 if ($pos1 == $pos2) {
17647 $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
17648 $linew -= $one_space_width;
17649 }
17650 }
17651 }
17652 $mdiff = ($tw - $linew);
17653 if ($plalign == 'C') {
17654 if ($this->rtl) {
17655 $t_x = -($mdiff / 2);
17656 } else {
17657 $t_x = ($mdiff / 2);
17658 }
17659 } elseif ($plalign == 'R') {
17660 // right alignment on LTR document
17661 $t_x = $mdiff;
17662 } elseif ($plalign == 'L') {
17663 // left alignment on RTL document
17664 $t_x = -$mdiff;
17665 } elseif (($plalign == 'J') AND ($plalign == $lalign)) {
17666 // Justification
17667 if ($this->isRTLTextDir()) {
17668 // align text on the left
17669 $t_x = -$mdiff;
17670 }
17671 $ns = 0; // number of spaces
17672 $pmidtemp = $pmid;
17673 // escape special characters
17674 $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
17675 $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
17676 // search spaces
17677 if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
17678 $spacestr = $this->getSpaceString();
17679 $maxkk = count($lnstring[1]) - 1;
17680 for ($kk=0; $kk <= $maxkk; ++$kk) {
17681 // restore special characters
17682 $lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
17683 $lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
17684 // store number of spaces on the strings
17685 $lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
17686 // count total spaces on line
17687 $ns += $lnstring[2][$kk];
17688 $lnstring[3][$kk] = $ns;
17689 }
17690 if ($ns == 0) {
17691 $ns = 1;
17692 }
17693 // calculate additional space to add to each existing space
17694 $spacewidth = ($mdiff / ($ns - $no)) * $this->k;
17695 if ($this->FontSize <= 0) {
17696 $this->FontSize = 1;
17697 }
17698 $spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
17699 if ($this->font_spacing != 0) {
17700 // fixed spacing mode
17701 $osw = -1000 * $this->font_spacing / $this->FontSize;
17702 $spacewidthu += $osw;
17703 }
17704 $nsmax = $ns;
17705 $ns = 0;
17706 reset($lnstring);
17707 $offset = 0;
17708 $strcount = 0;
17709 $prev_epsposbeg = 0;
17710 $textpos = 0;
17711 if ($this->isRTLTextDir()) {
17712 $textpos = $this->wPt;
17713 }
17714 while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
17715 // check if we are inside a string section '[( ... )]'
17716 $stroffset = strpos($pmid, '[(', $offset);
17717 if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
17718 // set offset to the end of string section
17719 $offset = strpos($pmid, ')]', $stroffset);
17720 while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
17721 $offset = strpos($pmid, ')]', ($offset + 1));
17722 }
17723 if ($offset === false) {
17724 $this->Error('HTML Justification: malformed PDF code.');
17725 }
17726 continue;
17727 }
17728 if ($this->isRTLTextDir()) {
17729 $spacew = ($spacewidth * ($nsmax - $ns));
17730 } else {
17731 $spacew = ($spacewidth * $ns);
17732 }
17733 $offset = $strpiece[2][1] + strlen($strpiece[2][0]);
17734 $epsposend = strpos($pmid, $this->epsmarker.'Q', $offset);
17735 if ($epsposend !== null) {
17736 $epsposend += strlen($this->epsmarker.'Q');
17737 $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
17738 if ($epsposbeg === null) {
17739 $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
17740 $prev_epsposbeg = $epsposbeg;
17741 }
17742 if (($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend)) {
17743 // shift EPS images
17744 $trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
17745 $pmid_b = substr($pmid, 0, $epsposbeg);
17746 $pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
17747 $pmid_e = substr($pmid, $epsposend);
17748 $pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
17749 $offset = $epsposend;
17750 continue;
17751 }
17752 }
17753 $currentxpos = 0;
17754 // shift blocks of code
17755 switch ($strpiece[2][0]) {
17756 case 'Td':
17757 case 'cm':
17758 case 'm':
17759 case 'l': {
17760 // get current X position
17761 preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
17762 if (!isset($xmatches[1])) {
17763 break;
17764 }
17765 $currentxpos = $xmatches[1];
17766 $textpos = $currentxpos;
17767 if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
17768 $ns = $lnstring[3][$strcount];
17769 if ($this->isRTLTextDir()) {
17770 $spacew = ($spacewidth * ($nsmax - $ns));
17771 }
17772 ++$strcount;
17773 }
17774 // justify block
17775 if (preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $pmatch) == 1) {
17776 $newpmid = sprintf('%F',(floatval($pmatch[1]) + $spacew)).' '.$pmatch[2].' x*#!#*x'.$pmatch[3].$pmatch[4];
17777 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17778 unset($pmatch, $newpmid);
17779 }
17780 break;
17781 }
17782 case 're': {
17783 // justify block
17784 if (!TCPDF_STATIC::empty_string($this->lispacer)) {
17785 $this->lispacer = '';
17786 continue;
17787 }
17788 preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
17789 if (!isset($xmatches[1])) {
17790 break;
17791 }
17792 $currentxpos = $xmatches[1];
17793 $x_diff = 0;
17794 $w_diff = 0;
17795 if ($this->isRTLTextDir()) { // RTL
17796 if ($currentxpos < $textpos) {
17797 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
17798 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
17799 } else {
17800 if ($strcount > 0) {
17801 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
17802 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17803 }
17804 }
17805 } else { // LTR
17806 if ($currentxpos > $textpos) {
17807 if ($strcount > 0) {
17808 $x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
17809 }
17810 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
17811 } else {
17812 if ($strcount > 1) {
17813 $x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
17814 }
17815 if ($strcount > 0) {
17816 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17817 }
17818 }
17819 }
17820 if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $pmatch) == 1) {
17821 $newx = sprintf('%F',(floatval($pmatch[1]) + $x_diff));
17822 $neww = sprintf('%F',(floatval($pmatch[3]) + $w_diff));
17823 $newpmid = $newx.' '.$pmatch[2].' '.$neww.' '.$pmatch[4].' x*#!#*x'.$pmatch[5].$pmatch[6];
17824 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17825 unset($pmatch, $newpmid, $newx, $neww);
17826 }
17827 break;
17828 }
17829 case 'c': {
17830 // get current X position
17831 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);
17832 if (!isset($xmatches[1])) {
17833 break;
17834 }
17835 $currentxpos = $xmatches[1];
17836 // justify block
17837 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) {
17838 $newx1 = sprintf('%F',(floatval($pmatch[1]) + $spacew));
17839 $newx2 = sprintf('%F',(floatval($pmatch[3]) + $spacew));
17840 $newx3 = sprintf('%F',(floatval($pmatch[5]) + $spacew));
17841 $newpmid = $newx1.' '.$pmatch[2].' '.$newx2.' '.$pmatch[4].' '.$newx3.' '.$pmatch[6].' x*#!#*x'.$pmatch[7].$pmatch[8];
17842 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17843 unset($pmatch, $newpmid, $newx1, $newx2, $newx3);
17844 }
17845 break;
17846 }
17847 }
17848 // shift the annotations and links
17849 $cxpos = ($currentxpos / $this->k);
17850 $lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
17851 if ($this->inxobj) {
17852 // we are inside an XObject template
17853 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
17854 if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17855 if ($cxpos > $lmpos) {
17856 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
17857 $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17858 } else {
17859 $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17860 }
17861 break;
17862 }
17863 }
17864 } elseif (isset($this->PageAnnots[$this->page])) {
17865 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
17866 if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17867 if ($cxpos > $lmpos) {
17868 $this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
17869 $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17870 } else {
17871 $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17872 }
17873 break;
17874 }
17875 }
17876 }
17877 } // end of while
17878 // remove markers
17879 $pmid = str_replace('x*#!#*x', '', $pmid);
17880 if ($this->isUnicodeFont()) {
17881 // multibyte characters
17882 $spacew = $spacewidthu;
17883 if ($this->font_stretching != 100) {
17884 // word spacing is affected by stretching
17885 $spacew /= ($this->font_stretching / 100);
17886 }
17887 // escape special characters
17888 $pos = 0;
17889 $pmid = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmid);
17890 $pmid = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmid);
17891 if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmid, $pamatch) > 0) {
17892 foreach($pamatch[0] as $pk => $pmatch) {
17893 $replace = $pamatch[1][$pk];
17894 $replace = str_replace('#!#OP#!#', '(', $replace);
17895 $replace = str_replace('#!#CP#!#', ')', $replace);
17896 $newpmid = '[('.str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacew).' (', $replace).')]';
17897 $pos = strpos($pmid, $pmatch, $pos);
17898 if ($pos !== FALSE) {
17899 $pmid = substr_replace($pmid, $newpmid, $pos, strlen($pmatch));
17900 }
17901 ++$pos;
17902 }
17903 unset($pamatch);
17904 }
17905 if ($this->inxobj) {
17906 // we are inside an XObject template
17907 $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
17908 } else {
17909 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
17910 }
17911 $endlinepos = strlen($pstart."\n".$pmid."\n");
17912 } else {
17913 // non-unicode (single-byte characters)
17914 if ($this->font_stretching != 100) {
17915 // word spacing (Tw) is affected by stretching
17916 $spacewidth /= ($this->font_stretching / 100);
17917 }
17918 $rs = sprintf('%F Tw', $spacewidth);
17919 $pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
17920 if ($this->inxobj) {
17921 // we are inside an XObject template
17922 $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
17923 } else {
17924 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
17925 }
17926 $endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
17927 }
17928 }
17929 } // end of J
17930 } // end if $startlinex
17931 if (($t_x != 0) OR ($yshift < 0)) {
17932 // shift the line
17933 $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
17934 $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
17935 $endlinepos = strlen($pstart);
17936 if ($this->inxobj) {
17937 // we are inside an XObject template
17938 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
17939 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
17940 if ($pak >= $pask) {
17941 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
17942 $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
17943 }
17944 }
17945 } else {
17946 $this->setPageBuffer($startlinepage, $pstart.$pend);
17947 // shift the annotations and links
17948 if (isset($this->PageAnnots[$this->page])) {
17949 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
17950 if ($pak >= $pask) {
17951 $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
17952 $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
17953 }
17954 }
17955 }
17956 }
17957 $this->y -= $yshift;
17958 }
17959 }
17960 $pbrk = $this->checkPageBreak($this->lasth);
17961 $this->newline = false;
17962 $startlinex = $this->x;
17963 $startliney = $this->y;
17964 if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
17965 $startliney -= ((0.3 * $this->FontSizePt) / $this->k);
17966 } elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
17967 $startliney -= (($this->FontSizePt / 0.7) / $this->k);
17968 } else {
17969 $minstartliney = $startliney;
17970 $maxbottomliney = ($this->y + $this->getCellHeight($fontsize / $this->k));
17971 }
17972 $startlinepage = $this->page;
17973 if (isset($endlinepos) AND (!$pbrk)) {
17974 $startlinepos = $endlinepos;
17975 } else {
17976 if ($this->inxobj) {
17977 // we are inside an XObject template
17978 $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17979 } elseif (!$this->InFooter) {
17980 if (isset($this->footerlen[$this->page])) {
17981 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17982 } else {
17983 $this->footerpos[$this->page] = $this->pagelen[$this->page];
17984 }
17985 $startlinepos = $this->footerpos[$this->page];
17986 } else {
17987 $startlinepos = $this->pagelen[$this->page];
17988 }
17989 }
17990 unset($endlinepos);
17991 $plalign = $lalign;
17992 if (isset($this->PageAnnots[$this->page])) {
17993 $pask = count($this->PageAnnots[$this->page]);
17994 } else {
17995 $pask = 0;
17996 }
17997 if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
17998 AND (isset($this->emptypagemrk[$this->page]))
17999 AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
18000 $this->SetFont($fontname, $fontstyle, $fontsize);
18001 if ($wfill) {
18002 $this->SetFillColorArray($this->bgcolor);
18003 }
18004 }
18005 } // end newline
18006 if (isset($opentagpos)) {
18007 unset($opentagpos);
18008 }
18009 if ($dom[$key]['tag']) {
18010 if ($dom[$key]['opening']) {
18011 // get text indentation (if any)
18012 if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
18013 $this->textindent = $dom[$key]['text-indent'];
18014 $this->newline = true;
18015 }
18016 // table
18017 if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
18018 // available page width
18019 if ($this->rtl) {
18020 $wtmp = $this->x - $this->lMargin;
18021 } else {
18022 $wtmp = $this->w - $this->rMargin - $this->x;
18023 }
18024 // get cell spacing
18025 if (isset($dom[$key]['attribute']['cellspacing'])) {
18026 $clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
18027 $cellspacing = array('H' => $clsp, 'V' => $clsp);
18028 } elseif (isset($dom[$key]['border-spacing'])) {
18029 $cellspacing = $dom[$key]['border-spacing'];
18030 } else {
18031 $cellspacing = array('H' => 0, 'V' => 0);
18032 }
18033 // table width
18034 if (isset($dom[$key]['width'])) {
18035 $table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
18036 } else {
18037 $table_width = $wtmp;
18038 }
18039 $table_width -= (2 * $cellspacing['H']);
18040 if (!$this->inthead) {
18041 $this->y += $cellspacing['V'];
18042 }
18043 if ($this->rtl) {
18044 $cellspacingx = -$cellspacing['H'];
18045 } else {
18046 $cellspacingx = $cellspacing['H'];
18047 }
18048 // total table width without cellspaces
18049 $table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
18050 // minimum column width
18051 $table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
18052 // array of custom column widths
18053 $table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
18054 }
18055 // table row
18056 if ($dom[$key]['value'] == 'tr') {
18057 // reset column counter
18058 $colid = 0;
18059 }
18060 // table cell
18061 if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18062 $trid = $dom[$key]['parent'];
18063 $table_el = $dom[$trid]['parent'];
18064 if (!isset($dom[$table_el]['cols'])) {
18065 $dom[$table_el]['cols'] = $dom[$trid]['cols'];
18066 }
18067 // store border info
18068 $tdborder = 0;
18069 if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
18070 $tdborder = $dom[$key]['border'];
18071 }
18072 $colspan = intval($dom[$key]['attribute']['colspan']);
18073 if ($colspan <= 0) {
18074 $colspan = 1;
18075 }
18076 $old_cell_padding = $this->cell_padding;
18077 if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
18078 $crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
18079 $current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
18080 } elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
18081 $current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
18082 } else {
18083 $current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
18084 }
18085 $this->cell_padding = $current_cell_padding;
18086 if (isset($dom[$key]['height'])) {
18087 // minimum cell height
18088 $cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
18089 } else {
18090 $cellh = 0;
18091 }
18092 if (isset($dom[$key]['content'])) {
18093 $cell_content = $dom[$key]['content'];
18094 } else {
18095 $cell_content = '&nbsp;';
18096 }
18097 $tagtype = $dom[$key]['value'];
18098 $parentid = $key;
18099 while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
18100 // move $key index forward
18101 ++$key;
18102 }
18103 if (!isset($dom[$trid]['startpage'])) {
18104 $dom[$trid]['startpage'] = $this->page;
18105 } else {
18106 $this->setPage($dom[$trid]['startpage']);
18107 }
18108 if (!isset($dom[$trid]['startcolumn'])) {
18109 $dom[$trid]['startcolumn'] = $this->current_column;
18110 } elseif ($this->current_column != $dom[$trid]['startcolumn']) {
18111 $tmpx = $this->x;
18112 $this->selectColumn($dom[$trid]['startcolumn']);
18113 $this->x = $tmpx;
18114 }
18115 if (!isset($dom[$trid]['starty'])) {
18116 $dom[$trid]['starty'] = $this->y;
18117 } else {
18118 $this->y = $dom[$trid]['starty'];
18119 }
18120 if (!isset($dom[$trid]['startx'])) {
18121 $dom[$trid]['startx'] = $this->x;
18122 $this->x += $cellspacingx;
18123 } else {
18124 $this->x += ($cellspacingx / 2);
18125 }
18126 if (isset($dom[$parentid]['attribute']['rowspan'])) {
18127 $rowspan = intval($dom[$parentid]['attribute']['rowspan']);
18128 } else {
18129 $rowspan = 1;
18130 }
18131 // skip row-spanned cells started on the previous rows
18132 if (isset($dom[$table_el]['rowspans'])) {
18133 $rsk = 0;
18134 $rskmax = count($dom[$table_el]['rowspans']);
18135 while ($rsk < $rskmax) {
18136 $trwsp = $dom[$table_el]['rowspans'][$rsk];
18137 $rsstartx = $trwsp['startx'];
18138 $rsendx = $trwsp['endx'];
18139 // account for margin changes
18140 if ($trwsp['startpage'] < $this->page) {
18141 if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
18142 $dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
18143 $rsstartx -= $dl;
18144 $rsendx -= $dl;
18145 } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
18146 $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
18147 $rsstartx += $dl;
18148 $rsendx += $dl;
18149 }
18150 }
18151 if (($trwsp['rowspan'] > 0)
18152 AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
18153 AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
18154 AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
18155 // set the starting X position of the current cell
18156 $this->x = $rsendx + $cellspacingx;
18157 // increment column indicator
18158 $colid += $trwsp['colspan'];
18159 if (($trwsp['rowspan'] == 1)
18160 AND (isset($dom[$trid]['endy']))
18161 AND (isset($dom[$trid]['endpage']))
18162 AND (isset($dom[$trid]['endcolumn']))
18163 AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
18164 AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18165 // set ending Y position for row
18166 $dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18167 $dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
18168 }
18169 $rsk = 0;
18170 } else {
18171 ++$rsk;
18172 }
18173 }
18174 }
18175 if (isset($dom[$parentid]['width'])) {
18176 // user specified width
18177 $cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
18178 $tmpcw = ($cellw / $colspan);
18179 for ($i = 0; $i < $colspan; ++$i) {
18180 $table_colwidths[($colid + $i)] = $tmpcw;
18181 }
18182 } else {
18183 // inherit column width
18184 $cellw = 0;
18185 for ($i = 0; $i < $colspan; ++$i) {
18186 $cellw += (isset($table_colwidths[($colid + $i)]) ? $table_colwidths[($colid + $i)] : 0);
18187 }
18188 }
18189 $cellw += (($colspan - 1) * $cellspacing['H']);
18190 // increment column indicator
18191 $colid += $colspan;
18192 // add rowspan information to table element
18193 if ($rowspan > 1) {
18194 $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));
18195 }
18196 $cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
18197 if ($rowspan > 1) {
18198 $dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
18199 }
18200 // push background colors
18201 if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
18202 $dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
18203 }
18204 // store border info
18205 if (isset($tdborder) AND !empty($tdborder)) {
18206 $dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
18207 }
18208 $prevLastH = $this->lasth;
18209 // store some info for multicolumn mode
18210 if ($this->rtl) {
18211 $this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
18212 } else {
18213 $this->colxshift['x'] = $this->x - $this->lMargin;
18214 }
18215 $this->colxshift['s'] = $cellspacing;
18216 $this->colxshift['p'] = $current_cell_padding;
18217 // ****** write the cell content ******
18218 $this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
18219 // restore some values
18220 $this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
18221 $this->lasth = $prevLastH;
18222 $this->cell_padding = $old_cell_padding;
18223 $dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
18224 // update the end of row position
18225 if ($rowspan <= 1) {
18226 if (isset($dom[$trid]['endy'])) {
18227 if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
18228 $dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
18229 } elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
18230 $dom[$trid]['endy'] = $this->y;
18231 }
18232 } else {
18233 $dom[$trid]['endy'] = $this->y;
18234 }
18235 if (isset($dom[$trid]['endpage'])) {
18236 $dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
18237 } else {
18238 $dom[$trid]['endpage'] = $this->page;
18239 }
18240 if (isset($dom[$trid]['endcolumn'])) {
18241 $dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
18242 } else {
18243 $dom[$trid]['endcolumn'] = $this->current_column;
18244 }
18245 } else {
18246 // account for row-spanned cells
18247 $dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
18248 $dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
18249 $dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
18250 $dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
18251 }
18252 if (isset($dom[$table_el]['rowspans'])) {
18253 // update endy and endpage on rowspanned cells
18254 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
18255 if ($trwsp['rowspan'] > 0) {
18256 if (isset($dom[$trid]['endpage'])) {
18257 if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18258 $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18259 } elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
18260 $dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
18261 $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
18262 $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
18263 } else {
18264 $dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
18265 }
18266 }
18267 }
18268 }
18269 }
18270 $this->x += ($cellspacingx / 2);
18271 } else {
18272 // opening tag (or self-closing tag)
18273 if (!isset($opentagpos)) {
18274 if ($this->inxobj) {
18275 // we are inside an XObject template
18276 $opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
18277 } elseif (!$this->InFooter) {
18278 if (isset($this->footerlen[$this->page])) {
18279 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18280 } else {
18281 $this->footerpos[$this->page] = $this->pagelen[$this->page];
18282 }
18283 $opentagpos = $this->footerpos[$this->page];
18284 }
18285 }
18286 $dom = $this->openHTMLTagHandler($dom, $key, $cell);
18287 }
18288 } else { // closing tag
18289 $prev_numpages = $this->numpages;
18290 $old_bordermrk = $this->bordermrk[$this->page];
18291 $dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
18292 if ($this->bordermrk[$this->page] > $old_bordermrk) {
18293 $startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
18294 }
18295 if ($prev_numpages > $this->numpages) {
18296 $startlinepage = $this->page;
18297 }
18298 }
18299 } elseif (strlen($dom[$key]['value']) > 0) {
18300 // print list-item
18301 if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) {
18302 $this->SetFont($pfontname, $pfontstyle, $pfontsize);
18303 $this->resetLastH();
18304 $minstartliney = $this->y;
18305 $maxbottomliney = ($startliney + $this->getCellHeight($this->FontSize));
18306 if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
18307 $this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
18308 }
18309 $this->SetFont($curfontname, $curfontstyle, $curfontsize);
18310 $this->resetLastH();
18311 if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
18312 $pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
18313 $pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
18314 $this->y += ($this->getCellHeight(($pfontsize - $curfontsize) / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
18315 $minstartliney = min($this->y, $minstartliney);
18316 $maxbottomliney = max(($this->y + $this->getCellHeight($pfontsize / $this->k)), $maxbottomliney);
18317 }
18318 }
18319 // text
18320 $this->htmlvspace = 0;
18321 if ((!$this->premode) AND $this->isRTLTextDir()) {
18322 // reverse spaces order
18323 $lsp = ''; // left spaces
18324 $rsp = ''; // right spaces
18325 if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18326 $lsp = $matches[1];
18327 }
18328 if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18329 $rsp = $matches[1];
18330 }
18331 $dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
18332 }
18333 if ($newline) {
18334 if (!$this->premode) {
18335 $prelen = strlen($dom[$key]['value']);
18336 if ($this->isRTLTextDir()) {
18337 // right trim except non-breaking space
18338 $dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
18339 } else {
18340 // left trim except non-breaking space
18341 $dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
18342 }
18343 $postlen = strlen($dom[$key]['value']);
18344 if (($postlen == 0) AND ($prelen > 0)) {
18345 $dom[$key]['trimmed_space'] = true;
18346 }
18347 }
18348 $newline = false;
18349 $firstblock = true;
18350 } else {
18351 $firstblock = false;
18352 // replace empty multiple spaces string with a single space
18353 $dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
18354 }
18355 $strrest = '';
18356 if ($this->rtl) {
18357 $this->x -= $this->textindent;
18358 } else {
18359 $this->x += $this->textindent;
18360 }
18361 if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
18362 $strlinelen = $this->GetStringWidth($dom[$key]['value']);
18363 if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
18364 // HTML <a> Link
18365 $hrefcolor = '';
18366 if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
18367 $hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
18368 }
18369 $hrefstyle = -1;
18370 if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
18371 $hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
18372 }
18373 $strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
18374 } else {
18375 $wadj = 0; // space to leave for block continuity
18376 if ($this->rtl) {
18377 $cwa = ($this->x - $this->lMargin);
18378 } else {
18379 $cwa = ($this->w - $this->rMargin - $this->x);
18380 }
18381 if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
18382 // check the next text blocks for continuity
18383 $nkey = ($key + 1);
18384 $write_block = true;
18385 $same_textdir = true;
18386 $tmp_fontname = $this->FontFamily;
18387 $tmp_fontstyle = $this->FontStyle;
18388 $tmp_fontsize = $this->FontSizePt;
18389 while ($write_block AND isset($dom[$nkey])) {
18390 if ($dom[$nkey]['tag']) {
18391 if ($dom[$nkey]['block']) {
18392 // end of block
18393 $write_block = false;
18394 }
18395 $tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
18396 $tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
18397 $tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
18398 $same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
18399 } else {
18400 $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'+/', $this->re_space['m'], $dom[$nkey]['value']);
18401 if (isset($nextstr[0]) AND $same_textdir) {
18402 $wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
18403 if (isset($nextstr[1])) {
18404 $write_block = false;
18405 }
18406 }
18407 }
18408 ++$nkey;
18409 }
18410 }
18411 if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
18412 $wadj = 0;
18413 $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $dom[$key]['value']);
18414 $numblks = count($nextstr);
18415 if ($numblks > 1) {
18416 // try to split on blank spaces
18417 $wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
18418 } else {
18419 // set the entire block on new line
18420 $wadj = $this->GetStringWidth($nextstr[0]);
18421 }
18422 }
18423 // check for reversed text direction
18424 if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
18425 // LTR text on RTL direction or RTL text on LTR direction
18426 $reverse_dir = true;
18427 $this->rtl = !$this->rtl;
18428 $revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
18429 if ($this->rtl) {
18430 $this->x += $revshift;
18431 } else {
18432 $this->x -= $revshift;
18433 }
18434 $xws = $this->x;
18435 }
18436 // ****** write only until the end of the line and get the rest ******
18437 $strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
18438 // restore default direction
18439 if ($reverse_dir AND ($wadj == 0)) {
18440 $this->x = $xws;
18441 $this->rtl = !$this->rtl;
18442 $reverse_dir = false;
18443 }
18444 }
18445 }
18446 $this->textindent = 0;
18447 if (strlen($strrest) > 0) {
18448 // store the remaining string on the previous $key position
18449 $this->newline = true;
18450 if ($strrest == $dom[$key]['value']) {
18451 // used to avoid infinite loop
18452 ++$loop;
18453 } else {
18454 $loop = 0;
18455 }
18456 $dom[$key]['value'] = $strrest;
18457 if ($cell) {
18458 if ($this->rtl) {
18459 $this->x -= $this->cell_padding['R'];
18460 } else {
18461 $this->x += $this->cell_padding['L'];
18462 }
18463 }
18464 if ($loop < 3) {
18465 --$key;
18466 }
18467 } else {
18468 $loop = 0;
18469 // add the positive font spacing of the last character (if any)
18470 if ($this->font_spacing > 0) {
18471 if ($this->rtl) {
18472 $this->x -= $this->font_spacing;
18473 } else {
18474 $this->x += $this->font_spacing;
18475 }
18476 }
18477 }
18478 }
18479 ++$key;
18480 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')) {
18481 // check if we are on a new page or on a new column
18482 if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
18483 // we are on a new page or on a new column and the total object height is less than the available vertical space.
18484 // restore previous object
18485 $this->rollbackTransaction(true);
18486 // restore previous values
18487 foreach ($this_method_vars as $vkey => $vval) {
18488 $$vkey = $vval;
18489 }
18490 if (!empty($dom[$key]['thead'])) {
18491 $this->inthead = true;
18492 }
18493 // add a page (or trig AcceptPageBreak() for multicolumn mode)
18494 $pre_y = $this->y;
18495 if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
18496 $startliney = $this->y;
18497 }
18498 $undo = true; // avoid infinite loop
18499 } else {
18500 $undo = false;
18501 }
18502 }
18503 } // end for each $key
18504 // align the last line
18505 if (isset($startlinex)) {
18506 $yshift = ($minstartliney - $startliney);
18507 if (($yshift > 0) OR ($this->page > $startlinepage)) {
18508 $yshift = 0;
18509 }
18510 $t_x = 0;
18511 // the last line must be shifted to be aligned as requested
18512 $linew = abs($this->endlinex - $startlinex);
18513 if ($this->inxobj) {
18514 // we are inside an XObject template
18515 $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
18516 if (isset($opentagpos)) {
18517 $midpos = $opentagpos;
18518 } else {
18519 $midpos = 0;
18520 }
18521 if ($midpos > 0) {
18522 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
18523 $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
18524 } else {
18525 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
18526 $pend = '';
18527 }
18528 } else {
18529 $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
18530 if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18531 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18532 $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
18533 } elseif (isset($opentagpos)) {
18534 $midpos = $opentagpos;
18535 } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18536 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18537 $midpos = $this->footerpos[$startlinepage];
18538 } else {
18539 $midpos = 0;
18540 }
18541 if ($midpos > 0) {
18542 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
18543 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
18544 } else {
18545 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
18546 $pend = '';
18547 }
18548 }
18549 if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
18550 // calculate shifting amount
18551 $tw = $w;
18552 if ($this->lMargin != $prevlMargin) {
18553 $tw += ($prevlMargin - $this->lMargin);
18554 }
18555 if ($this->rMargin != $prevrMargin) {
18556 $tw += ($prevrMargin - $this->rMargin);
18557 }
18558 $one_space_width = $this->GetStringWidth(chr(32));
18559 $no = 0; // number of spaces on a line contained on a single block
18560 if ($this->isRTLTextDir()) { // RTL
18561 // remove left space if exist
18562 $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
18563 if ($pos1 > 0) {
18564 $pos1 = intval($pos1);
18565 if ($this->isUnicodeFont()) {
18566 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
18567 $spacelen = 2;
18568 } else {
18569 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
18570 $spacelen = 1;
18571 }
18572 if ($pos1 == $pos2) {
18573 $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
18574 if (substr($pmid, $pos1, 4) == '[()]') {
18575 $linew -= $one_space_width;
18576 } elseif ($pos1 == strpos($pmid, '[(')) {
18577 $no = 1;
18578 }
18579 }
18580 }
18581 } else { // LTR
18582 // remove right space if exist
18583 $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
18584 if ($pos1 > 0) {
18585 $pos1 = intval($pos1);
18586 if ($this->isUnicodeFont()) {
18587 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
18588 $spacelen = 2;
18589 } else {
18590 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
18591 $spacelen = 1;
18592 }
18593 if ($pos1 == $pos2) {
18594 $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
18595 $linew -= $one_space_width;
18596 }
18597 }
18598 }
18599 $mdiff = ($tw - $linew);
18600 if ($plalign == 'C') {
18601 if ($this->rtl) {
18602 $t_x = -($mdiff / 2);
18603 } else {
18604 $t_x = ($mdiff / 2);
18605 }
18606 } elseif ($plalign == 'R') {
18607 // right alignment on LTR document
18608 $t_x = $mdiff;
18609 } elseif ($plalign == 'L') {
18610 // left alignment on RTL document
18611 $t_x = -$mdiff;
18612 }
18613 } // end if startlinex
18614 if (($t_x != 0) OR ($yshift < 0)) {
18615 // shift the line
18616 $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18617 $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18618 $endlinepos = strlen($pstart);
18619 if ($this->inxobj) {
18620 // we are inside an XObject template
18621 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18622 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18623 if ($pak >= $pask) {
18624 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18625 $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18626 }
18627 }
18628 } else {
18629 $this->setPageBuffer($startlinepage, $pstart.$pend);
18630 // shift the annotations and links
18631 if (isset($this->PageAnnots[$this->page])) {
18632 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18633 if ($pak >= $pask) {
18634 $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18635 $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18636 }
18637 }
18638 }
18639 }
18640 $this->y -= $yshift;
18641 $yshift = 0;
18642 }
18643 }
18644 // restore previous values
18645 $this->setGraphicVars($gvars);
18646 if ($this->num_columns > 1) {
18647 $this->selectColumn();
18648 } elseif ($this->page > $prevPage) {
18649 $this->lMargin = $this->pagedim[$this->page]['olm'];
18650 $this->rMargin = $this->pagedim[$this->page]['orm'];
18651 }
18652 // restore previous list state
18653 $this->cell_height_ratio = $prev_cell_height_ratio;
18654 $this->listnum = $prev_listnum;
18655 $this->listordered = $prev_listordered;
18656 $this->listcount = $prev_listcount;
18657 $this->lispacer = $prev_lispacer;
18658 if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
18659 $this->Ln($this->lasth);
18660 if (($this->y < $maxbottomliney) AND ($startlinepage == $this->page)) {
18661 $this->y = $maxbottomliney;
18662 }
18663 }
18664 unset($dom);
18665 }
18666
18667 /**
18668 * Process opening tags.
18669 * @param $dom (array) html dom array
18670 * @param $key (int) current element id
18671 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
18672 * @return $dom array
18673 * @protected
18674 */
18675 protected function openHTMLTagHandler($dom, $key, $cell) {
18676 $tag = $dom[$key];
18677 $parent = $dom[($dom[$key]['parent'])];
18678 $firsttag = ($key == 1);
18679 // check for text direction attribute
18680 if (isset($tag['dir'])) {
18681 $this->setTempRTL($tag['dir']);
18682 } else {
18683 $this->tmprtl = false;
18684 }
18685 if ($tag['block']) {
18686 $hbz = 0; // distance from y to line bottom
18687 $hb = 0; // vertical space between block tags
18688 // calculate vertical space for block tags
18689 if (isset($this->tagvspaces[$tag['value']][0]['h']) AND ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
18690 $cur_h = $this->tagvspaces[$tag['value']][0]['h'];
18691 } elseif (isset($tag['fontsize'])) {
18692 $cur_h = $this->getCellHeight($tag['fontsize'] / $this->k);
18693 } else {
18694 $cur_h = $this->getCellHeight($this->FontSize);
18695 }
18696 if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
18697 $on = $this->tagvspaces[$tag['value']][0]['n'];
18698 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18699 $on = 0.6;
18700 } else {
18701 $on = 1;
18702 }
18703 if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br', 'hr')))) {
18704 $hb = 0;
18705 } else {
18706 $hb = ($on * $cur_h);
18707 }
18708 if (($this->htmlvspace <= 0) AND ($on > 0)) {
18709 if (isset($parent['fontsize'])) {
18710 $hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
18711 } else {
18712 $hbz = $this->getCellHeight($this->FontSize);
18713 }
18714 }
18715 if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
18716 // fix vertical space after table
18717 $hbz = 0;
18718 }
18719 // closing vertical space
18720 $hbc = 0;
18721 if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
18722 $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
18723 } elseif (isset($parent['fontsize'])) {
18724 $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
18725 } else {
18726 $pre_h = $this->getCellHeight($this->FontSize);
18727 }
18728 if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
18729 $cn = $this->tagvspaces[$tag['value']][1]['n'];
18730 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18731 $cn = 0.6;
18732 } else {
18733 $cn = 1;
18734 }
18735 if (isset($this->tagvspaces[$tag['value']][1])) {
18736 $hbc = ($cn * $pre_h);
18737 }
18738 }
18739 // Opening tag
18740 switch($tag['value']) {
18741 case 'table': {
18742 $cp = 0;
18743 $cs = 0;
18744 $dom[$key]['rowspans'] = array();
18745 if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
18746 $this->htmlvspace = 0;
18747 // set table header
18748 if (!TCPDF_STATIC::empty_string($dom[$key]['thead'])) {
18749 // set table header
18750 $this->thead = $dom[$key]['thead'];
18751 if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
18752 $this->theadMargins = array();
18753 $this->theadMargins['cell_padding'] = $this->cell_padding;
18754 $this->theadMargins['lmargin'] = $this->lMargin;
18755 $this->theadMargins['rmargin'] = $this->rMargin;
18756 $this->theadMargins['page'] = $this->page;
18757 $this->theadMargins['cell'] = $cell;
18758 $this->theadMargins['gvars'] = $this->getGraphicVars();
18759 }
18760 }
18761 }
18762 // store current margins and page
18763 $dom[$key]['old_cell_padding'] = $this->cell_padding;
18764 if (isset($tag['attribute']['cellpadding'])) {
18765 $pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
18766 $this->SetCellPadding($pad);
18767 } elseif (isset($tag['padding'])) {
18768 $this->cell_padding = $tag['padding'];
18769 }
18770 if (isset($tag['attribute']['cellspacing'])) {
18771 $cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
18772 } elseif (isset($tag['border-spacing'])) {
18773 $cs = $tag['border-spacing']['V'];
18774 }
18775 $prev_y = $this->y;
18776 if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
18777 $this->inthead = true;
18778 // add a page (or trig AcceptPageBreak() for multicolumn mode)
18779 $this->checkPageBreak($this->PageBreakTrigger + 1);
18780 }
18781 break;
18782 }
18783 case 'tr': {
18784 // array of columns positions
18785 $dom[$key]['cellpos'] = array();
18786 break;
18787 }
18788 case 'hr': {
18789 if ((isset($tag['height'])) AND ($tag['height'] != '')) {
18790 $hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
18791 } else {
18792 $hrHeight = $this->GetLineWidth();
18793 }
18794 $this->addHTMLVertSpace($hbz, max($hb, ($hrHeight / 2)), $cell, $firsttag);
18795 $x = $this->GetX();
18796 $y = $this->GetY();
18797 $wtmp = $this->w - $this->lMargin - $this->rMargin;
18798 if ($cell) {
18799 $wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
18800 }
18801 if ((isset($tag['width'])) AND ($tag['width'] != '')) {
18802 $hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
18803 } else {
18804 $hrWidth = $wtmp;
18805 }
18806 $prevlinewidth = $this->GetLineWidth();
18807 $this->SetLineWidth($hrHeight);
18808 $this->Line($x, $y, $x + $hrWidth, $y);
18809 $this->SetLineWidth($prevlinewidth);
18810 $this->addHTMLVertSpace(max($hbc, ($hrHeight / 2)), 0, $cell, !isset($dom[($key + 1)]));
18811 break;
18812 }
18813 case 'a': {
18814 if (array_key_exists('href', $tag['attribute'])) {
18815 $this->HREF['url'] = $tag['attribute']['href'];
18816 }
18817 break;
18818 }
18819 case 'img': {
18820 if (!empty($tag['attribute']['src'])) {
18821 if ($tag['attribute']['src'][0] === '@') {
18822 // data stream
18823 $tag['attribute']['src'] = '@'.base64_decode(substr($tag['attribute']['src'], 1));
18824 $type = '';
18825 } else {
18826 // get image type
18827 $type = TCPDF_IMAGES::getImageFileType($tag['attribute']['src']);
18828 }
18829 if (!isset($tag['width'])) {
18830 $tag['width'] = 0;
18831 }
18832 if (!isset($tag['height'])) {
18833 $tag['height'] = 0;
18834 }
18835 //if (!isset($tag['attribute']['align'])) {
18836 // the only alignment supported is "bottom"
18837 // further development is required for other modes.
18838 $tag['attribute']['align'] = 'bottom';
18839 //}
18840 switch($tag['attribute']['align']) {
18841 case 'top': {
18842 $align = 'T';
18843 break;
18844 }
18845 case 'middle': {
18846 $align = 'M';
18847 break;
18848 }
18849 case 'bottom': {
18850 $align = 'B';
18851 break;
18852 }
18853 default: {
18854 $align = 'B';
18855 break;
18856 }
18857 }
18858 $prevy = $this->y;
18859 $xpos = $this->x;
18860 $imglink = '';
18861 if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
18862 $imglink = $this->HREF['url'];
18863 if ($imglink[0] == '#') {
18864 // convert url to internal link
18865 $lnkdata = explode(',', $imglink);
18866 if (isset($lnkdata[0])) {
18867 $page = intval(substr($lnkdata[0], 1));
18868 if (empty($page) OR ($page <= 0)) {
18869 $page = $this->page;
18870 }
18871 if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
18872 $lnky = floatval($lnkdata[1]);
18873 } else {
18874 $lnky = 0;
18875 }
18876 $imglink = $this->AddLink();
18877 $this->SetLink($imglink, $lnky, $page);
18878 }
18879 }
18880 }
18881 $border = 0;
18882 if (isset($tag['border']) AND !empty($tag['border'])) {
18883 // currently only support 1 (frame) or a combination of 'LTRB'
18884 $border = $tag['border'];
18885 }
18886 $iw = '';
18887 if (isset($tag['width'])) {
18888 $iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k), 'px', false);
18889 }
18890 $ih = '';
18891 if (isset($tag['height'])) {
18892 $ih = $this->getHTMLUnitToUnits($tag['height'], ($tag['fontsize'] / $this->k), 'px', false);
18893 }
18894 if (($type == 'eps') OR ($type == 'ai')) {
18895 $this->ImageEps($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
18896 } elseif ($type == 'svg') {
18897 $this->ImageSVG($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
18898 } else {
18899 $this->Image($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
18900 }
18901 switch($align) {
18902 case 'T': {
18903 $this->y = $prevy;
18904 break;
18905 }
18906 case 'M': {
18907 $this->y = (($this->img_rb_y + $prevy - ($this->getCellHeight($tag['fontsize'] / $this->k))) / 2);
18908 break;
18909 }
18910 case 'B': {
18911 $this->y = $this->img_rb_y - ($this->getCellHeight($tag['fontsize'] / $this->k) - ($this->getFontDescent($tag['fontname'], $tag['fontstyle'], $tag['fontsize']) * $this->cell_height_ratio));
18912 break;
18913 }
18914 }
18915 }
18916 break;
18917 }
18918 case 'dl': {
18919 ++$this->listnum;
18920 if ($this->listnum == 1) {
18921 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18922 } else {
18923 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
18924 }
18925 break;
18926 }
18927 case 'dt': {
18928 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18929 break;
18930 }
18931 case 'dd': {
18932 if ($this->rtl) {
18933 $this->rMargin += $this->listindent;
18934 } else {
18935 $this->lMargin += $this->listindent;
18936 }
18937 ++$this->listindentlevel;
18938 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18939 break;
18940 }
18941 case 'ul':
18942 case 'ol': {
18943 ++$this->listnum;
18944 if ($tag['value'] == 'ol') {
18945 $this->listordered[$this->listnum] = true;
18946 } else {
18947 $this->listordered[$this->listnum] = false;
18948 }
18949 if (isset($tag['attribute']['start'])) {
18950 $this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
18951 } else {
18952 $this->listcount[$this->listnum] = 0;
18953 }
18954 if ($this->rtl) {
18955 $this->rMargin += $this->listindent;
18956 $this->x -= $this->listindent;
18957 } else {
18958 $this->lMargin += $this->listindent;
18959 $this->x += $this->listindent;
18960 }
18961 ++$this->listindentlevel;
18962 if ($this->listnum == 1) {
18963 if ($key > 1) {
18964 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18965 }
18966 } else {
18967 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
18968 }
18969 break;
18970 }
18971 case 'li': {
18972 if ($key > 2) {
18973 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18974 }
18975 if ($this->listordered[$this->listnum]) {
18976 // ordered item
18977 if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
18978 $this->lispacer = $parent['attribute']['type'];
18979 } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
18980 $this->lispacer = $parent['listtype'];
18981 } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
18982 $this->lispacer = $this->lisymbol;
18983 } else {
18984 $this->lispacer = '#';
18985 }
18986 ++$this->listcount[$this->listnum];
18987 if (isset($tag['attribute']['value'])) {
18988 $this->listcount[$this->listnum] = intval($tag['attribute']['value']);
18989 }
18990 } else {
18991 // unordered item
18992 if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
18993 $this->lispacer = $parent['attribute']['type'];
18994 } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
18995 $this->lispacer = $parent['listtype'];
18996 } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
18997 $this->lispacer = $this->lisymbol;
18998 } else {
18999 $this->lispacer = '!';
19000 }
19001 }
19002 break;
19003 }
19004 case 'blockquote': {
19005 if ($this->rtl) {
19006 $this->rMargin += $this->listindent;
19007 } else {
19008 $this->lMargin += $this->listindent;
19009 }
19010 ++$this->listindentlevel;
19011 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19012 break;
19013 }
19014 case 'br': {
19015 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19016 break;
19017 }
19018 case 'div': {
19019 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19020 break;
19021 }
19022 case 'p': {
19023 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19024 break;
19025 }
19026 case 'pre': {
19027 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19028 $this->premode = true;
19029 break;
19030 }
19031 case 'sup': {
19032 $this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
19033 break;
19034 }
19035 case 'sub': {
19036 $this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
19037 break;
19038 }
19039 case 'h1':
19040 case 'h2':
19041 case 'h3':
19042 case 'h4':
19043 case 'h5':
19044 case 'h6': {
19045 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19046 break;
19047 }
19048 // Form fields (since 4.8.000 - 2009-09-07)
19049 case 'form': {
19050 if (isset($tag['attribute']['action'])) {
19051 $this->form_action = $tag['attribute']['action'];
19052 } else {
19053 $this->Error('Please explicitly set action attribute path!');
19054 }
19055 if (isset($tag['attribute']['enctype'])) {
19056 $this->form_enctype = $tag['attribute']['enctype'];
19057 } else {
19058 $this->form_enctype = 'application/x-www-form-urlencoded';
19059 }
19060 if (isset($tag['attribute']['method'])) {
19061 $this->form_mode = $tag['attribute']['method'];
19062 } else {
19063 $this->form_mode = 'post';
19064 }
19065 break;
19066 }
19067 case 'input': {
19068 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19069 $name = $tag['attribute']['name'];
19070 } else {
19071 break;
19072 }
19073 $prop = array();
19074 $opt = array();
19075 if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19076 $prop['readonly'] = true;
19077 }
19078 if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19079 $value = $tag['attribute']['value'];
19080 }
19081 if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC::empty_string($tag['attribute']['maxlength'])) {
19082 $opt['maxlen'] = intval($tag['attribute']['maxlength']);
19083 }
19084 $h = $this->getCellHeight($this->FontSize);
19085 if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19086 $w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
19087 } else {
19088 $w = $h;
19089 }
19090 if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
19091 $checked = true;
19092 } else {
19093 $checked = false;
19094 }
19095 if (isset($tag['align'])) {
19096 switch ($tag['align']) {
19097 case 'C': {
19098 $opt['q'] = 1;
19099 break;
19100 }
19101 case 'R': {
19102 $opt['q'] = 2;
19103 break;
19104 }
19105 case 'L':
19106 default: {
19107 break;
19108 }
19109 }
19110 }
19111 switch ($tag['attribute']['type']) {
19112 case 'text': {
19113 if (isset($value)) {
19114 $opt['v'] = $value;
19115 }
19116 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19117 break;
19118 }
19119 case 'password': {
19120 if (isset($value)) {
19121 $opt['v'] = $value;
19122 }
19123 $prop['password'] = 'true';
19124 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19125 break;
19126 }
19127 case 'checkbox': {
19128 if (!isset($value)) {
19129 break;
19130 }
19131 $this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
19132 break;
19133 }
19134 case 'radio': {
19135 if (!isset($value)) {
19136 break;
19137 }
19138 $this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
19139 break;
19140 }
19141 case 'submit': {
19142 if (!isset($value)) {
19143 $value = 'submit';
19144 }
19145 $w = $this->GetStringWidth($value) * 1.5;
19146 $h *= 1.6;
19147 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19148 $action = array();
19149 $action['S'] = 'SubmitForm';
19150 $action['F'] = $this->form_action;
19151 if ($this->form_enctype != 'FDF') {
19152 $action['Flags'] = array('ExportFormat');
19153 }
19154 if ($this->form_mode == 'get') {
19155 $action['Flags'] = array('GetMethod');
19156 }
19157 $this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
19158 break;
19159 }
19160 case 'reset': {
19161 if (!isset($value)) {
19162 $value = 'reset';
19163 }
19164 $w = $this->GetStringWidth($value) * 1.5;
19165 $h *= 1.6;
19166 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19167 $this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
19168 break;
19169 }
19170 case 'file': {
19171 $prop['fileSelect'] = 'true';
19172 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19173 if (!isset($value)) {
19174 $value = '*';
19175 }
19176 $w = $this->GetStringWidth($value) * 2;
19177 $h *= 1.2;
19178 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19179 $jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
19180 $this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19181 break;
19182 }
19183 case 'hidden': {
19184 if (isset($value)) {
19185 $opt['v'] = $value;
19186 }
19187 $opt['f'] = array('invisible', 'hidden');
19188 $this->TextField($name, 0, 0, $prop, $opt, '', '', false);
19189 break;
19190 }
19191 case 'image': {
19192 // THIS TYPE MUST BE FIXED
19193 if (isset($tag['attribute']['src']) AND !TCPDF_STATIC::empty_string($tag['attribute']['src'])) {
19194 $img = $tag['attribute']['src'];
19195 } else {
19196 break;
19197 }
19198 $value = 'img';
19199 //$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
19200 if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19201 $jsaction = $tag['attribute']['onclick'];
19202 } else {
19203 $jsaction = '';
19204 }
19205 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19206 break;
19207 }
19208 case 'button': {
19209 if (!isset($value)) {
19210 $value = ' ';
19211 }
19212 $w = $this->GetStringWidth($value) * 1.5;
19213 $h *= 1.6;
19214 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19215 if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19216 $jsaction = $tag['attribute']['onclick'];
19217 } else {
19218 $jsaction = '';
19219 }
19220 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19221 break;
19222 }
19223 }
19224 break;
19225 }
19226 case 'textarea': {
19227 $prop = array();
19228 $opt = array();
19229 if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19230 $prop['readonly'] = true;
19231 }
19232 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19233 $name = $tag['attribute']['name'];
19234 } else {
19235 break;
19236 }
19237 if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19238 $opt['v'] = $tag['attribute']['value'];
19239 }
19240 if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC::empty_string($tag['attribute']['cols'])) {
19241 $w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
19242 } else {
19243 $w = 40;
19244 }
19245 if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
19246 $h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize);
19247 } else {
19248 $h = 10;
19249 }
19250 $prop['multiline'] = 'true';
19251 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19252 break;
19253 }
19254 case 'select': {
19255 $h = $this->getCellHeight($this->FontSize);
19256 if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19257 $h *= ($tag['attribute']['size'] + 1);
19258 }
19259 $prop = array();
19260 $opt = array();
19261 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19262 $name = $tag['attribute']['name'];
19263 } else {
19264 break;
19265 }
19266 $w = 0;
19267 if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
19268 $options = explode('#!NwL!#', $tag['attribute']['opt']);
19269 $values = array();
19270 foreach ($options as $val) {
19271 if (strpos($val, '#!TaB!#') !== false) {
19272 $opts = explode('#!TaB!#', $val);
19273 $values[] = $opts;
19274 $w = max($w, $this->GetStringWidth($opts[1]));
19275 } else {
19276 $values[] = $val;
19277 $w = max($w, $this->GetStringWidth($val));
19278 }
19279 }
19280 } else {
19281 break;
19282 }
19283 $w *= 2;
19284 if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
19285 $prop['multipleSelection'] = 'true';
19286 $this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19287 } else {
19288 $this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19289 }
19290 break;
19291 }
19292 case 'tcpdf': {
19293 if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
19294 // Special tag used to call TCPDF methods
19295 if (isset($tag['attribute']['method'])) {
19296 $tcpdf_method = $tag['attribute']['method'];
19297 if (method_exists($this, $tcpdf_method)) {
19298 if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
19299 $params = $this->unserializeTCPDFtagParameters($tag['attribute']['params']);
19300 call_user_func_array(array($this, $tcpdf_method), $params);
19301 } else {
19302 $this->$tcpdf_method();
19303 }
19304 $this->newline = true;
19305 }
19306 }
19307 }
19308 break;
19309 }
19310 default: {
19311 break;
19312 }
19313 }
19314 // define tags that support borders and background colors
19315 $bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
19316 if (in_array($tag['value'], $bordertags)) {
19317 // set border
19318 $dom[$key]['borderposition'] = $this->getBorderStartPosition();
19319 }
19320 if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
19321 $pba = $dom[$key]['attribute']['pagebreakafter'];
19322 // check for pagebreak
19323 if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19324 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19325 $this->checkPageBreak($this->PageBreakTrigger + 1);
19326 }
19327 if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19328 OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19329 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19330 $this->checkPageBreak($this->PageBreakTrigger + 1);
19331 }
19332 }
19333 return $dom;
19334 }
19335
19336 /**
19337 * Process closing tags.
19338 * @param $dom (array) html dom array
19339 * @param $key (int) current element id
19340 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19341 * @param $maxbottomliney (int) maximum y value of current line
19342 * @return $dom array
19343 * @protected
19344 */
19345 protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
19346 $tag = $dom[$key];
19347 $parent = $dom[($dom[$key]['parent'])];
19348 $lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
19349 $in_table_head = false;
19350 // maximum x position (used to draw borders)
19351 if ($this->rtl) {
19352 $xmax = $this->w;
19353 } else {
19354 $xmax = 0;
19355 }
19356 if ($tag['block']) {
19357 $hbz = 0; // distance from y to line bottom
19358 $hb = 0; // vertical space between block tags
19359 // calculate vertical space for block tags
19360 if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
19361 $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
19362 } elseif (isset($parent['fontsize'])) {
19363 $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
19364 } else {
19365 $pre_h = $this->getCellHeight($this->FontSize);
19366 }
19367 if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
19368 $cn = $this->tagvspaces[$tag['value']][1]['n'];
19369 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19370 $cn = 0.6;
19371 } else {
19372 $cn = 1;
19373 }
19374 if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
19375 $hb = 0;
19376 } else {
19377 $hb = ($cn * $pre_h);
19378 }
19379 if ($maxbottomliney > $this->PageBreakTrigger) {
19380 $hbz = $this->getCellHeight($this->FontSize);
19381 } elseif ($this->y < $maxbottomliney) {
19382 $hbz = ($maxbottomliney - $this->y);
19383 }
19384 }
19385 // Closing tag
19386 switch($tag['value']) {
19387 case 'tr': {
19388 $table_el = $dom[($dom[$key]['parent'])]['parent'];
19389 if (!isset($parent['endy'])) {
19390 $dom[($dom[$key]['parent'])]['endy'] = $this->y;
19391 $parent['endy'] = $this->y;
19392 }
19393 if (!isset($parent['endpage'])) {
19394 $dom[($dom[$key]['parent'])]['endpage'] = $this->page;
19395 $parent['endpage'] = $this->page;
19396 }
19397 if (!isset($parent['endcolumn'])) {
19398 $dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
19399 $parent['endcolumn'] = $this->current_column;
19400 }
19401 // update row-spanned cells
19402 if (isset($dom[$table_el]['rowspans'])) {
19403 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19404 $dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
19405 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19406 if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
19407 $dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
19408 } elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
19409 $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19410 $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19411 $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19412 }
19413 }
19414 }
19415 // report new endy and endpage to the rowspanned cells
19416 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19417 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19418 $dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
19419 $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19420 $dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
19421 $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19422 $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
19423 $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19424 }
19425 }
19426 // update remaining rowspanned cells
19427 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19428 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19429 $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
19430 $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
19431 $dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
19432 }
19433 }
19434 }
19435 $prev_page = $this->page;
19436 $this->setPage($dom[($dom[$key]['parent'])]['endpage']);
19437 if ($this->num_columns > 1) {
19438 if (($prev_page < $this->page)
19439 AND ((($this->current_column == 0) AND ($dom[($dom[$key]['parent'])]['endcolumn'] == ($this->num_columns - 1)))
19440 OR ($this->current_column == $dom[($dom[$key]['parent'])]['endcolumn']))) {
19441 // page jump
19442 $this->selectColumn(0);
19443 $dom[($dom[$key]['parent'])]['endcolumn'] = 0;
19444 $dom[($dom[$key]['parent'])]['endy'] = $this->y;
19445 } else {
19446 $this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
19447 $this->y = $dom[($dom[$key]['parent'])]['endy'];
19448 }
19449 } else {
19450 $this->y = $dom[($dom[$key]['parent'])]['endy'];
19451 }
19452 if (isset($dom[$table_el]['attribute']['cellspacing'])) {
19453 $this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
19454 } elseif (isset($dom[$table_el]['border-spacing'])) {
19455 $this->y += $dom[$table_el]['border-spacing']['V'];
19456 }
19457 $this->Ln(0, $cell);
19458 if ($this->current_column == $parent['startcolumn']) {
19459 $this->x = $parent['startx'];
19460 }
19461 // account for booklet mode
19462 if ($this->page > $parent['startpage']) {
19463 if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
19464 $this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
19465 } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
19466 $this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
19467 }
19468 }
19469 break;
19470 }
19471 case 'tablehead':
19472 // closing tag used for the thead part
19473 $in_table_head = true;
19474 $this->inthead = false;
19475 case 'table': {
19476 $table_el = $parent;
19477 // set default border
19478 if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
19479 // set default border
19480 $border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
19481 } else {
19482 $border = 0;
19483 }
19484 $default_border = $border;
19485 // fix bottom line alignment of last line before page break
19486 foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
19487 // update row-spanned cells
19488 if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19489 foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19490 if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
19491 $dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
19492 }
19493 if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
19494 $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
19495 }
19496 }
19497 }
19498 if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
19499 $pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
19500 $dom[$prevtrkey]['endy'] = $pgendy;
19501 // update row-spanned cells
19502 if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19503 foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19504 if (($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
19505 $dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
19506 $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
19507 }
19508 }
19509 }
19510 }
19511 $prevtrkey = $trkey;
19512 $table_el = $dom[($dom[$key]['parent'])];
19513 }
19514 // for each row
19515 if (count($table_el['trids']) > 0) {
19516 unset($xmax);
19517 }
19518 foreach ($table_el['trids'] as $j => $trkey) {
19519 $parent = $dom[$trkey];
19520 if (!isset($xmax)) {
19521 $xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
19522 }
19523 // for each cell on the row
19524 foreach ($parent['cellpos'] as $k => $cellpos) {
19525 if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
19526 $cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
19527 $cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
19528 $endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
19529 $startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
19530 $endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
19531 $startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
19532 $endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
19533 } else {
19534 $endy = $parent['endy'];
19535 $startpage = $parent['startpage'];
19536 $endpage = $parent['endpage'];
19537 $startcolumn = $parent['startcolumn'];
19538 $endcolumn = $parent['endcolumn'];
19539 }
19540 if ($this->num_columns == 0) {
19541 $this->num_columns = 1;
19542 }
19543 if (isset($cellpos['border'])) {
19544 $border = $cellpos['border'];
19545 }
19546 if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
19547 $this->SetFillColorArray($cellpos['bgcolor']);
19548 $fill = true;
19549 } else {
19550 $fill = false;
19551 }
19552 $x = $cellpos['startx'];
19553 $y = $parent['starty'];
19554 $starty = $y;
19555 $w = abs($cellpos['endx'] - $cellpos['startx']);
19556 // get border modes
19557 $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
19558 $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
19559 $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19560 // design borders around HTML cells.
19561 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
19562 $ccode = '';
19563 $this->setPage($page);
19564 if ($this->num_columns < 2) {
19565 // single-column mode
19566 $this->x = $x;
19567 $this->y = $this->tMargin;
19568 }
19569 // account for margin changes
19570 if ($page > $startpage) {
19571 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
19572 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
19573 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
19574 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
19575 }
19576 }
19577 if ($startpage == $endpage) { // single page
19578 $deltacol = 0;
19579 $deltath = 0;
19580 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
19581 $this->selectColumn($column);
19582 if ($startcolumn == $endcolumn) { // single column
19583 $cborder = $border;
19584 $h = $endy - $parent['starty'];
19585 $this->y = $y;
19586 $this->x = $x;
19587 } elseif ($column == $startcolumn) { // first column
19588 $cborder = $border_start;
19589 $this->y = $starty;
19590 $this->x = $x;
19591 $h = $this->h - $this->y - $this->bMargin;
19592 if ($this->rtl) {
19593 $deltacol = $this->x + $this->rMargin - $this->w;
19594 } else {
19595 $deltacol = $this->x - $this->lMargin;
19596 }
19597 } elseif ($column == $endcolumn) { // end column
19598 $cborder = $border_end;
19599 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19600 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19601 }
19602 $this->x += $deltacol;
19603 $h = $endy - $this->y;
19604 } else { // middle column
19605 $cborder = $border_middle;
19606 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19607 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19608 }
19609 $this->x += $deltacol;
19610 $h = $this->h - $this->y - $this->bMargin;
19611 }
19612 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19613 } // end for each column
19614 } elseif ($page == $startpage) { // first page
19615 $deltacol = 0;
19616 $deltath = 0;
19617 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
19618 $this->selectColumn($column);
19619 if ($column == $startcolumn) { // first column
19620 $cborder = $border_start;
19621 $this->y = $starty;
19622 $this->x = $x;
19623 $h = $this->h - $this->y - $this->bMargin;
19624 if ($this->rtl) {
19625 $deltacol = $this->x + $this->rMargin - $this->w;
19626 } else {
19627 $deltacol = $this->x - $this->lMargin;
19628 }
19629 } else { // middle column
19630 $cborder = $border_middle;
19631 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19632 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19633 }
19634 $this->x += $deltacol;
19635 $h = $this->h - $this->y - $this->bMargin;
19636 }
19637 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19638 } // end for each column
19639 } elseif ($page == $endpage) { // last page
19640 $deltacol = 0;
19641 $deltath = 0;
19642 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
19643 $this->selectColumn($column);
19644 if ($column == $endcolumn) { // end column
19645 $cborder = $border_end;
19646 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19647 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19648 }
19649 $this->x += $deltacol;
19650 $h = $endy - $this->y;
19651 } else { // middle column
19652 $cborder = $border_middle;
19653 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19654 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19655 }
19656 $this->x += $deltacol;
19657 $h = $this->h - $this->y - $this->bMargin;
19658 }
19659 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19660 } // end for each column
19661 } else { // middle page
19662 $deltacol = 0;
19663 $deltath = 0;
19664 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
19665 $this->selectColumn($column);
19666 $cborder = $border_middle;
19667 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19668 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19669 }
19670 $this->x += $deltacol;
19671 $h = $this->h - $this->y - $this->bMargin;
19672 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19673 } // end for each column
19674 }
19675 if (!empty($cborder) OR !empty($fill)) {
19676 $offsetlen = strlen($ccode);
19677 // draw border and fill
19678 if ($this->inxobj) {
19679 // we are inside an XObject template
19680 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
19681 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
19682 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
19683 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
19684 } else {
19685 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
19686 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
19687 }
19688 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
19689 $pstart = substr($pagebuff, 0, $pagemark);
19690 $pend = substr($pagebuff, $pagemark);
19691 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
19692 } else {
19693 // draw border and fill
19694 if (end($this->transfmrk[$this->page]) !== false) {
19695 $pagemarkkey = key($this->transfmrk[$this->page]);
19696 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
19697 } elseif ($this->InFooter) {
19698 $pagemark = $this->footerpos[$this->page];
19699 } else {
19700 $pagemark = $this->intmrk[$this->page];
19701 }
19702 $pagebuff = $this->getPageBuffer($this->page);
19703 $pstart = substr($pagebuff, 0, $pagemark);
19704 $pend = substr($pagebuff, $pagemark);
19705 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
19706 }
19707 }
19708 } // end for each page
19709 // restore default border
19710 $border = $default_border;
19711 } // end for each cell on the row
19712 if (isset($table_el['attribute']['cellspacing'])) {
19713 $this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
19714 } elseif (isset($table_el['border-spacing'])) {
19715 $this->y += $table_el['border-spacing']['V'];
19716 }
19717 $this->Ln(0, $cell);
19718 $this->x = $parent['startx'];
19719 if ($endpage > $startpage) {
19720 if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
19721 $this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
19722 } elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
19723 $this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
19724 }
19725 }
19726 }
19727 if (!$in_table_head) { // we are not inside a thead section
19728 $this->cell_padding = $table_el['old_cell_padding'];
19729 // reset row height
19730 $this->resetLastH();
19731 if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
19732 $plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
19733 if (($plendiff > 0) AND ($plendiff < 60)) {
19734 $pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
19735 if (substr($pagediff, 0, 5) == 'BT /F') {
19736 // the difference is only a font setting
19737 $plendiff = 0;
19738 }
19739 }
19740 if ($plendiff == 0) {
19741 // remove last blank page
19742 $this->deletePage($this->numpages);
19743 }
19744 }
19745 if (isset($this->theadMargins['top'])) {
19746 // restore top margin
19747 $this->tMargin = $this->theadMargins['top'];
19748 }
19749 if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
19750 // reset main table header
19751 $this->thead = '';
19752 $this->theadMargins = array();
19753 $this->pagedim[$this->page]['tm'] = $this->tMargin;
19754 }
19755 }
19756 $parent = $table_el;
19757 break;
19758 }
19759 case 'a': {
19760 $this->HREF = array();
19761 break;
19762 }
19763 case 'sup': {
19764 $this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
19765 break;
19766 }
19767 case 'sub': {
19768 $this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
19769 break;
19770 }
19771 case 'div': {
19772 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19773 break;
19774 }
19775 case 'blockquote': {
19776 if ($this->rtl) {
19777 $this->rMargin -= $this->listindent;
19778 } else {
19779 $this->lMargin -= $this->listindent;
19780 }
19781 --$this->listindentlevel;
19782 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19783 break;
19784 }
19785 case 'p': {
19786 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19787 break;
19788 }
19789 case 'pre': {
19790 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19791 $this->premode = false;
19792 break;
19793 }
19794 case 'dl': {
19795 --$this->listnum;
19796 if ($this->listnum <= 0) {
19797 $this->listnum = 0;
19798 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19799 } else {
19800 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19801 }
19802 $this->resetLastH();
19803 break;
19804 }
19805 case 'dt': {
19806 $this->lispacer = '';
19807 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19808 break;
19809 }
19810 case 'dd': {
19811 $this->lispacer = '';
19812 if ($this->rtl) {
19813 $this->rMargin -= $this->listindent;
19814 } else {
19815 $this->lMargin -= $this->listindent;
19816 }
19817 --$this->listindentlevel;
19818 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19819 break;
19820 }
19821 case 'ul':
19822 case 'ol': {
19823 --$this->listnum;
19824 $this->lispacer = '';
19825 if ($this->rtl) {
19826 $this->rMargin -= $this->listindent;
19827 } else {
19828 $this->lMargin -= $this->listindent;
19829 }
19830 --$this->listindentlevel;
19831 if ($this->listnum <= 0) {
19832 $this->listnum = 0;
19833 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19834 } else {
19835 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19836 }
19837 $this->resetLastH();
19838 break;
19839 }
19840 case 'li': {
19841 $this->lispacer = '';
19842 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19843 break;
19844 }
19845 case 'h1':
19846 case 'h2':
19847 case 'h3':
19848 case 'h4':
19849 case 'h5':
19850 case 'h6': {
19851 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19852 break;
19853 }
19854 // Form fields (since 4.8.000 - 2009-09-07)
19855 case 'form': {
19856 $this->form_action = '';
19857 $this->form_enctype = 'application/x-www-form-urlencoded';
19858 break;
19859 }
19860 default : {
19861 break;
19862 }
19863 }
19864 // draw border and background (if any)
19865 $this->drawHTMLTagBorder($parent, $xmax);
19866 if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
19867 $pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
19868 // check for pagebreak
19869 if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19870 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19871 $this->checkPageBreak($this->PageBreakTrigger + 1);
19872 }
19873 if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19874 OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19875 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19876 $this->checkPageBreak($this->PageBreakTrigger + 1);
19877 }
19878 }
19879 $this->tmprtl = false;
19880 return $dom;
19881 }
19882
19883 /**
19884 * Add vertical spaces if needed.
19885 * @param $hbz (string) Distance between current y and line bottom.
19886 * @param $hb (string) The height of the break.
19887 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19888 * @param $firsttag (boolean) set to true when the tag is the first.
19889 * @param $lasttag (boolean) set to true when the tag is the last.
19890 * @protected
19891 */
19892 protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
19893 if ($firsttag) {
19894 $this->Ln(0, $cell);
19895 $this->htmlvspace = 0;
19896 return;
19897 }
19898 if ($lasttag) {
19899 $this->Ln($hbz, $cell);
19900 $this->htmlvspace = 0;
19901 return;
19902 }
19903 if ($hb < $this->htmlvspace) {
19904 $hd = 0;
19905 } else {
19906 $hd = $hb - $this->htmlvspace;
19907 $this->htmlvspace = $hb;
19908 }
19909 $this->Ln(($hbz + $hd), $cell);
19910 }
19911
19912 /**
19913 * Return the starting coordinates to draw an html border
19914 * @return array containing top-left border coordinates
19915 * @protected
19916 * @since 5.7.000 (2010-08-03)
19917 */
19918 protected function getBorderStartPosition() {
19919 if ($this->rtl) {
19920 $xmax = $this->lMargin;
19921 } else {
19922 $xmax = $this->w - $this->rMargin;
19923 }
19924 return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
19925 }
19926
19927 /**
19928 * Draw an HTML block border and fill
19929 * @param $tag (array) array of tag properties.
19930 * @param $xmax (int) end X coordinate for border.
19931 * @protected
19932 * @since 5.7.000 (2010-08-03)
19933 */
19934 protected function drawHTMLTagBorder($tag, $xmax) {
19935 if (!isset($tag['borderposition'])) {
19936 // nothing to draw
19937 return;
19938 }
19939 $prev_x = $this->x;
19940 $prev_y = $this->y;
19941 $prev_lasth = $this->lasth;
19942 $border = 0;
19943 $fill = false;
19944 $this->lasth = 0;
19945 if (isset($tag['border']) AND !empty($tag['border'])) {
19946 // get border style
19947 $border = $tag['border'];
19948 if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
19949 // border for table header
19950 $border = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19951 }
19952 }
19953 if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
19954 // get background color
19955 $old_bgcolor = $this->bgcolor;
19956 $this->SetFillColorArray($tag['bgcolor']);
19957 $fill = true;
19958 }
19959 if (!$border AND !$fill) {
19960 // nothing to draw
19961 return;
19962 }
19963 if (isset($tag['attribute']['cellspacing'])) {
19964 $clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
19965 $cellspacing = array('H' => $clsp, 'V' => $clsp);
19966 } elseif (isset($tag['border-spacing'])) {
19967 $cellspacing = $tag['border-spacing'];
19968 } else {
19969 $cellspacing = array('H' => 0, 'V' => 0);
19970 }
19971 if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
19972 // draw the border externally respect the sqare edge.
19973 $border['mode'] = 'ext';
19974 }
19975 if ($this->rtl) {
19976 if ($xmax >= $tag['borderposition']['x']) {
19977 $xmax = $tag['borderposition']['xmax'];
19978 }
19979 $w = ($tag['borderposition']['x'] - $xmax);
19980 } else {
19981 if ($xmax <= $tag['borderposition']['x']) {
19982 $xmax = $tag['borderposition']['xmax'];
19983 }
19984 $w = ($xmax - $tag['borderposition']['x']);
19985 }
19986 if ($w <= 0) {
19987 return;
19988 }
19989 $w += $cellspacing['H'];
19990 $startpage = $tag['borderposition']['page'];
19991 $startcolumn = $tag['borderposition']['column'];
19992 $x = $tag['borderposition']['x'];
19993 $y = $tag['borderposition']['y'];
19994 $endpage = $this->page;
19995 $starty = $tag['borderposition']['y'] - $cellspacing['V'];
19996 $currentY = $this->y;
19997 $this->x = $x;
19998 // get latest column
19999 $endcolumn = $this->current_column;
20000 if ($this->num_columns == 0) {
20001 $this->num_columns = 1;
20002 }
20003 // get border modes
20004 $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
20005 $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
20006 $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20007 // temporary disable page regions
20008 $temp_page_regions = $this->page_regions;
20009 $this->page_regions = array();
20010 // design borders around HTML cells.
20011 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
20012 $ccode = '';
20013 $this->setPage($page);
20014 if ($this->num_columns < 2) {
20015 // single-column mode
20016 $this->x = $x;
20017 $this->y = $this->tMargin;
20018 }
20019 // account for margin changes
20020 if ($page > $startpage) {
20021 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
20022 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
20023 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
20024 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
20025 }
20026 }
20027 if ($startpage == $endpage) {
20028 // single page
20029 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
20030 $this->selectColumn($column);
20031 if ($startcolumn == $endcolumn) { // single column
20032 $cborder = $border;
20033 $h = ($currentY - $y) + $cellspacing['V'];
20034 $this->y = $starty;
20035 } elseif ($column == $startcolumn) { // first column
20036 $cborder = $border_start;
20037 $this->y = $starty;
20038 $h = $this->h - $this->y - $this->bMargin;
20039 } elseif ($column == $endcolumn) { // end column
20040 $cborder = $border_end;
20041 $h = $currentY - $this->y;
20042 } else { // middle column
20043 $cborder = $border_middle;
20044 $h = $this->h - $this->y - $this->bMargin;
20045 }
20046 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20047 } // end for each column
20048 } elseif ($page == $startpage) { // first page
20049 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
20050 $this->selectColumn($column);
20051 if ($column == $startcolumn) { // first column
20052 $cborder = $border_start;
20053 $this->y = $starty;
20054 $h = $this->h - $this->y - $this->bMargin;
20055 } else { // middle column
20056 $cborder = $border_middle;
20057 $h = $this->h - $this->y - $this->bMargin;
20058 }
20059 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20060 } // end for each column
20061 } elseif ($page == $endpage) { // last page
20062 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
20063 $this->selectColumn($column);
20064 if ($column == $endcolumn) {
20065 // end column
20066 $cborder = $border_end;
20067 $h = $currentY - $this->y;
20068 } else {
20069 // middle column
20070 $cborder = $border_middle;
20071 $h = $this->h - $this->y - $this->bMargin;
20072 }
20073 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20074 } // end for each column
20075 } else { // middle page
20076 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
20077 $this->selectColumn($column);
20078 $cborder = $border_middle;
20079 $h = $this->h - $this->y - $this->bMargin;
20080 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20081 } // end for each column
20082 }
20083 if ($cborder OR $fill) {
20084 $offsetlen = strlen($ccode);
20085 // draw border and fill
20086 if ($this->inxobj) {
20087 // we are inside an XObject template
20088 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
20089 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
20090 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
20091 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
20092 } else {
20093 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
20094 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
20095 }
20096 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
20097 $pstart = substr($pagebuff, 0, $pagemark);
20098 $pend = substr($pagebuff, $pagemark);
20099 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
20100 } else {
20101 if (end($this->transfmrk[$this->page]) !== false) {
20102 $pagemarkkey = key($this->transfmrk[$this->page]);
20103 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
20104 } elseif ($this->InFooter) {
20105 $pagemark = $this->footerpos[$this->page];
20106 } else {
20107 $pagemark = $this->intmrk[$this->page];
20108 }
20109 $pagebuff = $this->getPageBuffer($this->page);
20110 $pstart = substr($pagebuff, 0, $pagemark);
20111 $pend = substr($pagebuff, $pagemark);
20112 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
20113 $this->bordermrk[$this->page] += $offsetlen;
20114 $this->cntmrk[$this->page] += $offsetlen;
20115 }
20116 }
20117 } // end for each page
20118 // restore page regions
20119 $this->page_regions = $temp_page_regions;
20120 if (isset($old_bgcolor)) {
20121 // restore background color
20122 $this->SetFillColorArray($old_bgcolor);
20123 }
20124 // restore pointer position
20125 $this->x = $prev_x;
20126 $this->y = $prev_y;
20127 $this->lasth = $prev_lasth;
20128 }
20129
20130 /**
20131 * Set the default bullet to be used as LI bullet symbol
20132 * @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')
20133 * @public
20134 * @since 4.0.028 (2008-09-26)
20135 */
20136 public function setLIsymbol($symbol='!') {
20137 // check for custom image symbol
20138 if (substr($symbol, 0, 4) == 'img|') {
20139 $this->lisymbol = $symbol;
20140 return;
20141 }
20142 $symbol = strtolower($symbol);
20143 $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');
20144 if (in_array($symbol, $valid_symbols)) {
20145 $this->lisymbol = $symbol;
20146 } else {
20147 $this->lisymbol = '';
20148 }
20149 }
20150
20151 /**
20152 * Set the booklet mode for double-sided pages.
20153 * @param $booklet (boolean) true set the booklet mode on, false otherwise.
20154 * @param $inner (float) Inner page margin.
20155 * @param $outer (float) Outer page margin.
20156 * @public
20157 * @since 4.2.000 (2008-10-29)
20158 */
20159 public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
20160 $this->booklet = $booklet;
20161 if ($inner >= 0) {
20162 $this->lMargin = $inner;
20163 }
20164 if ($outer >= 0) {
20165 $this->rMargin = $outer;
20166 }
20167 }
20168
20169 /**
20170 * Swap the left and right margins.
20171 * @param $reverse (boolean) if true swap left and right margins.
20172 * @protected
20173 * @since 4.2.000 (2008-10-29)
20174 */
20175 protected function swapMargins($reverse=true) {
20176 if ($reverse) {
20177 // swap left and right margins
20178 $mtemp = $this->original_lMargin;
20179 $this->original_lMargin = $this->original_rMargin;
20180 $this->original_rMargin = $mtemp;
20181 $deltam = $this->original_lMargin - $this->original_rMargin;
20182 $this->lMargin += $deltam;
20183 $this->rMargin -= $deltam;
20184 }
20185 }
20186
20187 /**
20188 * Set the vertical spaces for HTML tags.
20189 * The array must have the following structure (example):
20190 * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
20191 * The first array level contains the tag names,
20192 * the second level contains 0 for opening tags or 1 for closing tags,
20193 * the third level contains the vertical space unit (h) and the number spaces to add (n).
20194 * If the h parameter is not specified, default values are used.
20195 * @param $tagvs (array) array of tags and relative vertical spaces.
20196 * @public
20197 * @since 4.2.001 (2008-10-30)
20198 */
20199 public function setHtmlVSpace($tagvs) {
20200 $this->tagvspaces = $tagvs;
20201 }
20202
20203 /**
20204 * Set custom width for list indentation.
20205 * @param $width (float) width of the indentation. Use negative value to disable it.
20206 * @public
20207 * @since 4.2.007 (2008-11-12)
20208 */
20209 public function setListIndentWidth($width) {
20210 return $this->customlistindent = floatval($width);
20211 }
20212
20213 /**
20214 * Set the top/bottom cell sides to be open or closed when the cell cross the page.
20215 * @param $isopen (boolean) if true keeps the top/bottom border open for the cell sides that cross the page.
20216 * @public
20217 * @since 4.2.010 (2008-11-14)
20218 */
20219 public function setOpenCell($isopen) {
20220 $this->opencell = $isopen;
20221 }
20222
20223 /**
20224 * Set the color and font style for HTML links.
20225 * @param $color (array) RGB array of colors
20226 * @param $fontstyle (string) additional font styles to add
20227 * @public
20228 * @since 4.4.003 (2008-12-09)
20229 */
20230 public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
20231 $this->htmlLinkColorArray = $color;
20232 $this->htmlLinkFontStyle = $fontstyle;
20233 }
20234
20235 /**
20236 * Convert HTML string containing value and unit of measure to user's units or points.
20237 * @param $htmlval (string) String containing values and unit.
20238 * @param $refsize (string) Reference value in points.
20239 * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
20240 * @param $points (boolean) If true returns points, otherwise returns value in user's units.
20241 * @return float value in user's unit or point if $points=true
20242 * @public
20243 * @since 4.4.004 (2008-12-10)
20244 */
20245 public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
20246 $supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
20247 $retval = 0;
20248 $value = 0;
20249 $unit = 'px';
20250 if ($points) {
20251 $k = 1;
20252 } else {
20253 $k = $this->k;
20254 }
20255 if (in_array($defaultunit, $supportedunits)) {
20256 $unit = $defaultunit;
20257 }
20258 if (is_numeric($htmlval)) {
20259 $value = floatval($htmlval);
20260 } elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
20261 $value = floatval($mnum[1]);
20262 if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
20263 if (in_array($munit[1], $supportedunits)) {
20264 $unit = $munit[1];
20265 }
20266 }
20267 }
20268 switch ($unit) {
20269 // percentage
20270 case '%': {
20271 $retval = (($value * $refsize) / 100);
20272 break;
20273 }
20274 // relative-size
20275 case 'em': {
20276 $retval = ($value * $refsize);
20277 break;
20278 }
20279 // height of lower case 'x' (about half the font-size)
20280 case 'ex': {
20281 $retval = ($value * ($refsize / 2));
20282 break;
20283 }
20284 // absolute-size
20285 case 'in': {
20286 $retval = (($value * $this->dpi) / $k);
20287 break;
20288 }
20289 // centimeters
20290 case 'cm': {
20291 $retval = (($value / 2.54 * $this->dpi) / $k);
20292 break;
20293 }
20294 // millimeters
20295 case 'mm': {
20296 $retval = (($value / 25.4 * $this->dpi) / $k);
20297 break;
20298 }
20299 // one pica is 12 points
20300 case 'pc': {
20301 $retval = (($value * 12) / $k);
20302 break;
20303 }
20304 // points
20305 case 'pt': {
20306 $retval = ($value / $k);
20307 break;
20308 }
20309 // pixels
20310 case 'px': {
20311 $retval = $this->pixelsToUnits($value);
20312 if ($points) {
20313 $retval *= $this->k;
20314 }
20315 break;
20316 }
20317 }
20318 return $retval;
20319 }
20320
20321 /**
20322 * Output an HTML list bullet or ordered item symbol
20323 * @param $listdepth (int) list nesting level
20324 * @param $listtype (string) type of list
20325 * @param $size (float) current font size
20326 * @protected
20327 * @since 4.4.004 (2008-12-10)
20328 */
20329 protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
20330 if ($this->state != 2) {
20331 return;
20332 }
20333 $size /= $this->k;
20334 $fill = '';
20335 $bgcolor = $this->bgcolor;
20336 $color = $this->fgcolor;
20337 $strokecolor = $this->strokecolor;
20338 $width = 0;
20339 $textitem = '';
20340 $tmpx = $this->x;
20341 $lspace = $this->GetStringWidth(' ');
20342 if ($listtype == '^') {
20343 // special symbol used for avoid justification of rect bullet
20344 $this->lispacer = '';
20345 return;
20346 } elseif ($listtype == '!') {
20347 // set default list type for unordered list
20348 $deftypes = array('disc', 'circle', 'square');
20349 $listtype = $deftypes[($listdepth - 1) % 3];
20350 } elseif ($listtype == '#') {
20351 // set default list type for ordered list
20352 $listtype = 'decimal';
20353 } elseif (substr($listtype, 0, 4) == 'img|') {
20354 // custom image type ('img|type|width|height|image.ext')
20355 $img = explode('|', $listtype);
20356 $listtype = 'img';
20357 }
20358 switch ($listtype) {
20359 // unordered types
20360 case 'none': {
20361 break;
20362 }
20363 case 'disc': {
20364 $r = $size / 6;
20365 $lspace += (2 * $r);
20366 if ($this->rtl) {
20367 $this->x += $lspace;
20368 } else {
20369 $this->x -= $lspace;
20370 }
20371 $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
20372 break;
20373 }
20374 case 'circle': {
20375 $r = $size / 6;
20376 $lspace += (2 * $r);
20377 if ($this->rtl) {
20378 $this->x += $lspace;
20379 } else {
20380 $this->x -= $lspace;
20381 }
20382 $prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
20383 $new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
20384 $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
20385 $this->_out($prev_line_style); // restore line settings
20386 break;
20387 }
20388 case 'square': {
20389 $l = $size / 3;
20390 $lspace += $l;
20391 if ($this->rtl) {;
20392 $this->x += $lspace;
20393 } else {
20394 $this->x -= $lspace;
20395 }
20396 $this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
20397 break;
20398 }
20399 case 'img': {
20400 // 1=>type, 2=>width, 3=>height, 4=>image.ext
20401 $lspace += $img[2];
20402 if ($this->rtl) {;
20403 $this->x += $lspace;
20404 } else {
20405 $this->x -= $lspace;
20406 }
20407 $imgtype = strtolower($img[1]);
20408 $prev_y = $this->y;
20409 switch ($imgtype) {
20410 case 'svg': {
20411 $this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
20412 break;
20413 }
20414 case 'ai':
20415 case 'eps': {
20416 $this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
20417 break;
20418 }
20419 default: {
20420 $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);
20421 break;
20422 }
20423 }
20424 $this->y = $prev_y;
20425 break;
20426 }
20427 // ordered types
20428 // $this->listcount[$this->listnum];
20429 // $textitem
20430 case '1':
20431 case 'decimal': {
20432 $textitem = $this->listcount[$this->listnum];
20433 break;
20434 }
20435 case 'decimal-leading-zero': {
20436 $textitem = sprintf('%02d', $this->listcount[$this->listnum]);
20437 break;
20438 }
20439 case 'i':
20440 case 'lower-roman': {
20441 $textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
20442 break;
20443 }
20444 case 'I':
20445 case 'upper-roman': {
20446 $textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
20447 break;
20448 }
20449 case 'a':
20450 case 'lower-alpha':
20451 case 'lower-latin': {
20452 $textitem = chr(97 + $this->listcount[$this->listnum] - 1);
20453 break;
20454 }
20455 case 'A':
20456 case 'upper-alpha':
20457 case 'upper-latin': {
20458 $textitem = chr(65 + $this->listcount[$this->listnum] - 1);
20459 break;
20460 }
20461 case 'lower-greek': {
20462 $textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
20463 break;
20464 }
20465 /*
20466 // Types to be implemented (special handling)
20467 case 'hebrew': {
20468 break;
20469 }
20470 case 'armenian': {
20471 break;
20472 }
20473 case 'georgian': {
20474 break;
20475 }
20476 case 'cjk-ideographic': {
20477 break;
20478 }
20479 case 'hiragana': {
20480 break;
20481 }
20482 case 'katakana': {
20483 break;
20484 }
20485 case 'hiragana-iroha': {
20486 break;
20487 }
20488 case 'katakana-iroha': {
20489 break;
20490 }
20491 */
20492 default: {
20493 $textitem = $this->listcount[$this->listnum];
20494 }
20495 }
20496 if (!TCPDF_STATIC::empty_string($textitem)) {
20497 // Check whether we need a new page or new column
20498 $prev_y = $this->y;
20499 $h = $this->getCellHeight($this->FontSize);
20500 if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
20501 $tmpx = $this->x;
20502 }
20503 // print ordered item
20504 if ($this->rtl) {
20505 $textitem = '.'.$textitem;
20506 } else {
20507 $textitem = $textitem.'.';
20508 }
20509 $lspace += $this->GetStringWidth($textitem);
20510 if ($this->rtl) {
20511 $this->x += $lspace;
20512 } else {
20513 $this->x -= $lspace;
20514 }
20515 $this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
20516 }
20517 $this->x = $tmpx;
20518 $this->lispacer = '^';
20519 // restore colors
20520 $this->SetFillColorArray($bgcolor);
20521 $this->SetDrawColorArray($strokecolor);
20522 $this->SettextColorArray($color);
20523 }
20524
20525 /**
20526 * Returns current graphic variables as array.
20527 * @return array of graphic variables
20528 * @protected
20529 * @since 4.2.010 (2008-11-14)
20530 */
20531 protected function getGraphicVars() {
20532 $grapvars = array(
20533 'FontFamily' => $this->FontFamily,
20534 'FontStyle' => $this->FontStyle,
20535 'FontSizePt' => $this->FontSizePt,
20536 'rMargin' => $this->rMargin,
20537 'lMargin' => $this->lMargin,
20538 'cell_padding' => $this->cell_padding,
20539 'cell_margin' => $this->cell_margin,
20540 'LineWidth' => $this->LineWidth,
20541 'linestyleWidth' => $this->linestyleWidth,
20542 'linestyleCap' => $this->linestyleCap,
20543 'linestyleJoin' => $this->linestyleJoin,
20544 'linestyleDash' => $this->linestyleDash,
20545 'textrendermode' => $this->textrendermode,
20546 'textstrokewidth' => $this->textstrokewidth,
20547 'DrawColor' => $this->DrawColor,
20548 'FillColor' => $this->FillColor,
20549 'TextColor' => $this->TextColor,
20550 'ColorFlag' => $this->ColorFlag,
20551 'bgcolor' => $this->bgcolor,
20552 'fgcolor' => $this->fgcolor,
20553 'htmlvspace' => $this->htmlvspace,
20554 'listindent' => $this->listindent,
20555 'listindentlevel' => $this->listindentlevel,
20556 'listnum' => $this->listnum,
20557 'listordered' => $this->listordered,
20558 'listcount' => $this->listcount,
20559 'lispacer' => $this->lispacer,
20560 'cell_height_ratio' => $this->cell_height_ratio,
20561 'font_stretching' => $this->font_stretching,
20562 'font_spacing' => $this->font_spacing,
20563 'alpha' => $this->alpha,
20564 // extended
20565 'lasth' => $this->lasth,
20566 'tMargin' => $this->tMargin,
20567 'bMargin' => $this->bMargin,
20568 'AutoPageBreak' => $this->AutoPageBreak,
20569 'PageBreakTrigger' => $this->PageBreakTrigger,
20570 'x' => $this->x,
20571 'y' => $this->y,
20572 'w' => $this->w,
20573 'h' => $this->h,
20574 'wPt' => $this->wPt,
20575 'hPt' => $this->hPt,
20576 'fwPt' => $this->fwPt,
20577 'fhPt' => $this->fhPt,
20578 'page' => $this->page,
20579 'current_column' => $this->current_column,
20580 'num_columns' => $this->num_columns
20581 );
20582 return $grapvars;
20583 }
20584
20585 /**
20586 * Set graphic variables.
20587 * @param $gvars (array) array of graphic variablesto restore
20588 * @param $extended (boolean) if true restore extended graphic variables
20589 * @protected
20590 * @since 4.2.010 (2008-11-14)
20591 */
20592 protected function setGraphicVars($gvars, $extended=false) {
20593 if ($this->state != 2) {
20594 return;
20595 }
20596 $this->FontFamily = $gvars['FontFamily'];
20597 $this->FontStyle = $gvars['FontStyle'];
20598 $this->FontSizePt = $gvars['FontSizePt'];
20599 $this->rMargin = $gvars['rMargin'];
20600 $this->lMargin = $gvars['lMargin'];
20601 $this->cell_padding = $gvars['cell_padding'];
20602 $this->cell_margin = $gvars['cell_margin'];
20603 $this->LineWidth = $gvars['LineWidth'];
20604 $this->linestyleWidth = $gvars['linestyleWidth'];
20605 $this->linestyleCap = $gvars['linestyleCap'];
20606 $this->linestyleJoin = $gvars['linestyleJoin'];
20607 $this->linestyleDash = $gvars['linestyleDash'];
20608 $this->textrendermode = $gvars['textrendermode'];
20609 $this->textstrokewidth = $gvars['textstrokewidth'];
20610 $this->DrawColor = $gvars['DrawColor'];
20611 $this->FillColor = $gvars['FillColor'];
20612 $this->TextColor = $gvars['TextColor'];
20613 $this->ColorFlag = $gvars['ColorFlag'];
20614 $this->bgcolor = $gvars['bgcolor'];
20615 $this->fgcolor = $gvars['fgcolor'];
20616 $this->htmlvspace = $gvars['htmlvspace'];
20617 $this->listindent = $gvars['listindent'];
20618 $this->listindentlevel = $gvars['listindentlevel'];
20619 $this->listnum = $gvars['listnum'];
20620 $this->listordered = $gvars['listordered'];
20621 $this->listcount = $gvars['listcount'];
20622 $this->lispacer = $gvars['lispacer'];
20623 $this->cell_height_ratio = $gvars['cell_height_ratio'];
20624 $this->font_stretching = $gvars['font_stretching'];
20625 $this->font_spacing = $gvars['font_spacing'];
20626 $this->alpha = $gvars['alpha'];
20627 if ($extended) {
20628 // restore extended values
20629 $this->lasth = $gvars['lasth'];
20630 $this->tMargin = $gvars['tMargin'];
20631 $this->bMargin = $gvars['bMargin'];
20632 $this->AutoPageBreak = $gvars['AutoPageBreak'];
20633 $this->PageBreakTrigger = $gvars['PageBreakTrigger'];
20634 $this->x = $gvars['x'];
20635 $this->y = $gvars['y'];
20636 $this->w = $gvars['w'];
20637 $this->h = $gvars['h'];
20638 $this->wPt = $gvars['wPt'];
20639 $this->hPt = $gvars['hPt'];
20640 $this->fwPt = $gvars['fwPt'];
20641 $this->fhPt = $gvars['fhPt'];
20642 $this->page = $gvars['page'];
20643 $this->current_column = $gvars['current_column'];
20644 $this->num_columns = $gvars['num_columns'];
20645 }
20646 $this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
20647 if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
20648 $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
20649 }
20650 }
20651
20652 /**
20653 * Outputs the "save graphics state" operator 'q'
20654 * @protected
20655 */
20656 protected function _outSaveGraphicsState() {
20657 $this->_out('q');
20658 }
20659
20660 /**
20661 * Outputs the "restore graphics state" operator 'Q'
20662 * @protected
20663 */
20664 protected function _outRestoreGraphicsState() {
20665 $this->_out('Q');
20666 }
20667
20668 /**
20669 * Set buffer content (always append data).
20670 * @param $data (string) data
20671 * @protected
20672 * @since 4.5.000 (2009-01-02)
20673 */
20674 protected function setBuffer($data) {
20675 $this->bufferlen += strlen($data);
20676 $this->buffer .= $data;
20677 }
20678
20679 /**
20680 * Replace the buffer content
20681 * @param $data (string) data
20682 * @protected
20683 * @since 5.5.000 (2010-06-22)
20684 */
20685 protected function replaceBuffer($data) {
20686 $this->bufferlen = strlen($data);
20687 $this->buffer = $data;
20688 }
20689
20690 /**
20691 * Get buffer content.
20692 * @return string buffer content
20693 * @protected
20694 * @since 4.5.000 (2009-01-02)
20695 */
20696 protected function getBuffer() {
20697 return $this->buffer;
20698 }
20699
20700 /**
20701 * Set page buffer content.
20702 * @param $page (int) page number
20703 * @param $data (string) page data
20704 * @param $append (boolean) if true append data, false replace.
20705 * @protected
20706 * @since 4.5.000 (2008-12-31)
20707 */
20708 protected function setPageBuffer($page, $data, $append=false) {
20709 if ($append) {
20710 $this->pages[$page] .= $data;
20711 } else {
20712 $this->pages[$page] = $data;
20713 }
20714 if ($append AND isset($this->pagelen[$page])) {
20715 $this->pagelen[$page] += strlen($data);
20716 } else {
20717 $this->pagelen[$page] = strlen($data);
20718 }
20719 }
20720
20721 /**
20722 * Get page buffer content.
20723 * @param $page (int) page number
20724 * @return string page buffer content or false in case of error
20725 * @protected
20726 * @since 4.5.000 (2008-12-31)
20727 */
20728 protected function getPageBuffer($page) {
20729 if (isset($this->pages[$page])) {
20730 return $this->pages[$page];
20731 }
20732 return false;
20733 }
20734
20735 /**
20736 * Set image buffer content.
20737 * @param $image (string) image key
20738 * @param $data (array) image data
20739 * @return int image index number
20740 * @protected
20741 * @since 4.5.000 (2008-12-31)
20742 */
20743 protected function setImageBuffer($image, $data) {
20744 if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
20745 $this->imagekeys[$this->numimages] = $image;
20746 $data['i'] = $this->numimages;
20747 ++$this->numimages;
20748 }
20749 $this->images[$image] = $data;
20750 return $data['i'];
20751 }
20752
20753 /**
20754 * Set image buffer content for a specified sub-key.
20755 * @param $image (string) image key
20756 * @param $key (string) image sub-key
20757 * @param $data (array) image data
20758 * @protected
20759 * @since 4.5.000 (2008-12-31)
20760 */
20761 protected function setImageSubBuffer($image, $key, $data) {
20762 if (!isset($this->images[$image])) {
20763 $this->setImageBuffer($image, array());
20764 }
20765 $this->images[$image][$key] = $data;
20766 }
20767
20768 /**
20769 * Get image buffer content.
20770 * @param $image (string) image key
20771 * @return string image buffer content or false in case of error
20772 * @protected
20773 * @since 4.5.000 (2008-12-31)
20774 */
20775 protected function getImageBuffer($image) {
20776 if (isset($this->images[$image])) {
20777 return $this->images[$image];
20778 }
20779 return false;
20780 }
20781
20782 /**
20783 * Set font buffer content.
20784 * @param $font (string) font key
20785 * @param $data (array) font data
20786 * @protected
20787 * @since 4.5.000 (2009-01-02)
20788 */
20789 protected function setFontBuffer($font, $data) {
20790 $this->fonts[$font] = $data;
20791 if (!in_array($font, $this->fontkeys)) {
20792 $this->fontkeys[] = $font;
20793 // store object ID for current font
20794 ++$this->n;
20795 $this->font_obj_ids[$font] = $this->n;
20796 $this->setFontSubBuffer($font, 'n', $this->n);
20797 }
20798 }
20799
20800 /**
20801 * Set font buffer content.
20802 * @param $font (string) font key
20803 * @param $key (string) font sub-key
20804 * @param $data (array) font data
20805 * @protected
20806 * @since 4.5.000 (2009-01-02)
20807 */
20808 protected function setFontSubBuffer($font, $key, $data) {
20809 if (!isset($this->fonts[$font])) {
20810 $this->setFontBuffer($font, array());
20811 }
20812 $this->fonts[$font][$key] = $data;
20813 }
20814
20815 /**
20816 * Get font buffer content.
20817 * @param $font (string) font key
20818 * @return string font buffer content or false in case of error
20819 * @protected
20820 * @since 4.5.000 (2009-01-02)
20821 */
20822 protected function getFontBuffer($font) {
20823 if (isset($this->fonts[$font])) {
20824 return $this->fonts[$font];
20825 }
20826 return false;
20827 }
20828
20829 /**
20830 * Move a page to a previous position.
20831 * @param $frompage (int) number of the source page
20832 * @param $topage (int) number of the destination page (must be less than $frompage)
20833 * @return true in case of success, false in case of error.
20834 * @public
20835 * @since 4.5.000 (2009-01-02)
20836 */
20837 public function movePage($frompage, $topage) {
20838 if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
20839 return false;
20840 }
20841 if ($frompage == $this->page) {
20842 // close the page before moving it
20843 $this->endPage();
20844 }
20845 // move all page-related states
20846 $tmppage = $this->getPageBuffer($frompage);
20847 $tmppagedim = $this->pagedim[$frompage];
20848 $tmppagelen = $this->pagelen[$frompage];
20849 $tmpintmrk = $this->intmrk[$frompage];
20850 $tmpbordermrk = $this->bordermrk[$frompage];
20851 $tmpcntmrk = $this->cntmrk[$frompage];
20852 $tmppageobjects = $this->pageobjects[$frompage];
20853 if (isset($this->footerpos[$frompage])) {
20854 $tmpfooterpos = $this->footerpos[$frompage];
20855 }
20856 if (isset($this->footerlen[$frompage])) {
20857 $tmpfooterlen = $this->footerlen[$frompage];
20858 }
20859 if (isset($this->transfmrk[$frompage])) {
20860 $tmptransfmrk = $this->transfmrk[$frompage];
20861 }
20862 if (isset($this->PageAnnots[$frompage])) {
20863 $tmpannots = $this->PageAnnots[$frompage];
20864 }
20865 if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
20866 for ($i = $frompage; $i > $topage; --$i) {
20867 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
20868 --$this->pagegroups[$this->newpagegroup[$i]];
20869 break;
20870 }
20871 }
20872 for ($i = $topage; $i > 0; --$i) {
20873 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
20874 ++$this->pagegroups[$this->newpagegroup[$i]];
20875 break;
20876 }
20877 }
20878 }
20879 for ($i = $frompage; $i > $topage; --$i) {
20880 $j = $i - 1;
20881 // shift pages down
20882 $this->setPageBuffer($i, $this->getPageBuffer($j));
20883 $this->pagedim[$i] = $this->pagedim[$j];
20884 $this->pagelen[$i] = $this->pagelen[$j];
20885 $this->intmrk[$i] = $this->intmrk[$j];
20886 $this->bordermrk[$i] = $this->bordermrk[$j];
20887 $this->cntmrk[$i] = $this->cntmrk[$j];
20888 $this->pageobjects[$i] = $this->pageobjects[$j];
20889 if (isset($this->footerpos[$j])) {
20890 $this->footerpos[$i] = $this->footerpos[$j];
20891 } elseif (isset($this->footerpos[$i])) {
20892 unset($this->footerpos[$i]);
20893 }
20894 if (isset($this->footerlen[$j])) {
20895 $this->footerlen[$i] = $this->footerlen[$j];
20896 } elseif (isset($this->footerlen[$i])) {
20897 unset($this->footerlen[$i]);
20898 }
20899 if (isset($this->transfmrk[$j])) {
20900 $this->transfmrk[$i] = $this->transfmrk[$j];
20901 } elseif (isset($this->transfmrk[$i])) {
20902 unset($this->transfmrk[$i]);
20903 }
20904 if (isset($this->PageAnnots[$j])) {
20905 $this->PageAnnots[$i] = $this->PageAnnots[$j];
20906 } elseif (isset($this->PageAnnots[$i])) {
20907 unset($this->PageAnnots[$i]);
20908 }
20909 if (isset($this->newpagegroup[$j])) {
20910 $this->newpagegroup[$i] = $this->newpagegroup[$j];
20911 unset($this->newpagegroup[$j]);
20912 }
20913 if ($this->currpagegroup == $j) {
20914 $this->currpagegroup = $i;
20915 }
20916 }
20917 $this->setPageBuffer($topage, $tmppage);
20918 $this->pagedim[$topage] = $tmppagedim;
20919 $this->pagelen[$topage] = $tmppagelen;
20920 $this->intmrk[$topage] = $tmpintmrk;
20921 $this->bordermrk[$topage] = $tmpbordermrk;
20922 $this->cntmrk[$topage] = $tmpcntmrk;
20923 $this->pageobjects[$topage] = $tmppageobjects;
20924 if (isset($tmpfooterpos)) {
20925 $this->footerpos[$topage] = $tmpfooterpos;
20926 } elseif (isset($this->footerpos[$topage])) {
20927 unset($this->footerpos[$topage]);
20928 }
20929 if (isset($tmpfooterlen)) {
20930 $this->footerlen[$topage] = $tmpfooterlen;
20931 } elseif (isset($this->footerlen[$topage])) {
20932 unset($this->footerlen[$topage]);
20933 }
20934 if (isset($tmptransfmrk)) {
20935 $this->transfmrk[$topage] = $tmptransfmrk;
20936 } elseif (isset($this->transfmrk[$topage])) {
20937 unset($this->transfmrk[$topage]);
20938 }
20939 if (isset($tmpannots)) {
20940 $this->PageAnnots[$topage] = $tmpannots;
20941 } elseif (isset($this->PageAnnots[$topage])) {
20942 unset($this->PageAnnots[$topage]);
20943 }
20944 // adjust outlines
20945 $tmpoutlines = $this->outlines;
20946 foreach ($tmpoutlines as $key => $outline) {
20947 if (!$outline['f']) {
20948 if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
20949 $this->outlines[$key]['p'] = ($outline['p'] + 1);
20950 } elseif ($outline['p'] == $frompage) {
20951 $this->outlines[$key]['p'] = $topage;
20952 }
20953 }
20954 }
20955 // adjust dests
20956 $tmpdests = $this->dests;
20957 foreach ($tmpdests as $key => $dest) {
20958 if (!$dest['f']) {
20959 if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
20960 $this->dests[$key]['p'] = ($dest['p'] + 1);
20961 } elseif ($dest['p'] == $frompage) {
20962 $this->dests[$key]['p'] = $topage;
20963 }
20964 }
20965 }
20966 // adjust links
20967 $tmplinks = $this->links;
20968 foreach ($tmplinks as $key => $link) {
20969 if (!$link['f']) {
20970 if (($link['p'] >= $topage) AND ($link['p'] < $frompage)) {
20971 $this->links[$key]['p'] = ($link['p'] + 1);
20972 } elseif ($link['p'] == $frompage) {
20973 $this->links[$key]['p'] = $topage;
20974 }
20975 }
20976 }
20977 // adjust javascript
20978 $jfrompage = $frompage;
20979 $jtopage = $topage;
20980 if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
20981 foreach($pamatch[0] as $pk => $pmatch) {
20982 $pagenum = intval($pamatch[3][$pk]) + 1;
20983 if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
20984 $newpage = ($pagenum + 1);
20985 } elseif ($pagenum == $jfrompage) {
20986 $newpage = $jtopage;
20987 } else {
20988 $newpage = $pagenum;
20989 }
20990 --$newpage;
20991 $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
20992 $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
20993 }
20994 unset($pamatch);
20995 }
20996 // return to last page
20997 $this->lastPage(true);
20998 return true;
20999 }
21000
21001 /**
21002 * Remove the specified page.
21003 * @param $page (int) page to remove
21004 * @return true in case of success, false in case of error.
21005 * @public
21006 * @since 4.6.004 (2009-04-23)
21007 */
21008 public function deletePage($page) {
21009 if (($page < 1) OR ($page > $this->numpages)) {
21010 return false;
21011 }
21012 // delete current page
21013 unset($this->pages[$page]);
21014 unset($this->pagedim[$page]);
21015 unset($this->pagelen[$page]);
21016 unset($this->intmrk[$page]);
21017 unset($this->bordermrk[$page]);
21018 unset($this->cntmrk[$page]);
21019 foreach ($this->pageobjects[$page] as $oid) {
21020 if (isset($this->offsets[$oid])){
21021 unset($this->offsets[$oid]);
21022 }
21023 }
21024 unset($this->pageobjects[$page]);
21025 if (isset($this->footerpos[$page])) {
21026 unset($this->footerpos[$page]);
21027 }
21028 if (isset($this->footerlen[$page])) {
21029 unset($this->footerlen[$page]);
21030 }
21031 if (isset($this->transfmrk[$page])) {
21032 unset($this->transfmrk[$page]);
21033 }
21034 if (isset($this->PageAnnots[$page])) {
21035 unset($this->PageAnnots[$page]);
21036 }
21037 if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21038 for ($i = $page; $i > 0; --$i) {
21039 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
21040 --$this->pagegroups[$this->newpagegroup[$i]];
21041 break;
21042 }
21043 }
21044 }
21045 if (isset($this->pageopen[$page])) {
21046 unset($this->pageopen[$page]);
21047 }
21048 if ($page < $this->numpages) {
21049 // update remaining pages
21050 for ($i = $page; $i < $this->numpages; ++$i) {
21051 $j = $i + 1;
21052 // shift pages
21053 $this->setPageBuffer($i, $this->getPageBuffer($j));
21054 $this->pagedim[$i] = $this->pagedim[$j];
21055 $this->pagelen[$i] = $this->pagelen[$j];
21056 $this->intmrk[$i] = $this->intmrk[$j];
21057 $this->bordermrk[$i] = $this->bordermrk[$j];
21058 $this->cntmrk[$i] = $this->cntmrk[$j];
21059 $this->pageobjects[$i] = $this->pageobjects[$j];
21060 if (isset($this->footerpos[$j])) {
21061 $this->footerpos[$i] = $this->footerpos[$j];
21062 } elseif (isset($this->footerpos[$i])) {
21063 unset($this->footerpos[$i]);
21064 }
21065 if (isset($this->footerlen[$j])) {
21066 $this->footerlen[$i] = $this->footerlen[$j];
21067 } elseif (isset($this->footerlen[$i])) {
21068 unset($this->footerlen[$i]);
21069 }
21070 if (isset($this->transfmrk[$j])) {
21071 $this->transfmrk[$i] = $this->transfmrk[$j];
21072 } elseif (isset($this->transfmrk[$i])) {
21073 unset($this->transfmrk[$i]);
21074 }
21075 if (isset($this->PageAnnots[$j])) {
21076 $this->PageAnnots[$i] = $this->PageAnnots[$j];
21077 } elseif (isset($this->PageAnnots[$i])) {
21078 unset($this->PageAnnots[$i]);
21079 }
21080 if (isset($this->newpagegroup[$j])) {
21081 $this->newpagegroup[$i] = $this->newpagegroup[$j];
21082 unset($this->newpagegroup[$j]);
21083 }
21084 if ($this->currpagegroup == $j) {
21085 $this->currpagegroup = $i;
21086 }
21087 if (isset($this->pageopen[$j])) {
21088 $this->pageopen[$i] = $this->pageopen[$j];
21089 } elseif (isset($this->pageopen[$i])) {
21090 unset($this->pageopen[$i]);
21091 }
21092 }
21093 // remove last page
21094 unset($this->pages[$this->numpages]);
21095 unset($this->pagedim[$this->numpages]);
21096 unset($this->pagelen[$this->numpages]);
21097 unset($this->intmrk[$this->numpages]);
21098 unset($this->bordermrk[$this->numpages]);
21099 unset($this->cntmrk[$this->numpages]);
21100 foreach ($this->pageobjects[$this->numpages] as $oid) {
21101 if (isset($this->offsets[$oid])){
21102 unset($this->offsets[$oid]);
21103 }
21104 }
21105 unset($this->pageobjects[$this->numpages]);
21106 if (isset($this->footerpos[$this->numpages])) {
21107 unset($this->footerpos[$this->numpages]);
21108 }
21109 if (isset($this->footerlen[$this->numpages])) {
21110 unset($this->footerlen[$this->numpages]);
21111 }
21112 if (isset($this->transfmrk[$this->numpages])) {
21113 unset($this->transfmrk[$this->numpages]);
21114 }
21115 if (isset($this->PageAnnots[$this->numpages])) {
21116 unset($this->PageAnnots[$this->numpages]);
21117 }
21118 if (isset($this->newpagegroup[$this->numpages])) {
21119 unset($this->newpagegroup[$this->numpages]);
21120 }
21121 if ($this->currpagegroup == $this->numpages) {
21122 $this->currpagegroup = ($this->numpages - 1);
21123 }
21124 if (isset($this->pagegroups[$this->numpages])) {
21125 unset($this->pagegroups[$this->numpages]);
21126 }
21127 if (isset($this->pageopen[$this->numpages])) {
21128 unset($this->pageopen[$this->numpages]);
21129 }
21130 }
21131 --$this->numpages;
21132 $this->page = $this->numpages;
21133 // adjust outlines
21134 $tmpoutlines = $this->outlines;
21135 foreach ($tmpoutlines as $key => $outline) {
21136 if (!$outline['f']) {
21137 if ($outline['p'] > $page) {
21138 $this->outlines[$key]['p'] = $outline['p'] - 1;
21139 } elseif ($outline['p'] == $page) {
21140 unset($this->outlines[$key]);
21141 }
21142 }
21143 }
21144 // adjust dests
21145 $tmpdests = $this->dests;
21146 foreach ($tmpdests as $key => $dest) {
21147 if (!$dest['f']) {
21148 if ($dest['p'] > $page) {
21149 $this->dests[$key]['p'] = $dest['p'] - 1;
21150 } elseif ($dest['p'] == $page) {
21151 unset($this->dests[$key]);
21152 }
21153 }
21154 }
21155 // adjust links
21156 $tmplinks = $this->links;
21157 foreach ($tmplinks as $key => $link) {
21158 if (!$link['f']) {
21159 if ($link['p'] > $page) {
21160 $this->links[$key]['p'] = $link['p'] - 1;
21161 } elseif ($link['p'] == $page) {
21162 unset($this->links[$key]);
21163 }
21164 }
21165 }
21166 // adjust javascript
21167 $jpage = $page;
21168 if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21169 foreach($pamatch[0] as $pk => $pmatch) {
21170 $pagenum = intval($pamatch[3][$pk]) + 1;
21171 if ($pagenum >= $jpage) {
21172 $newpage = ($pagenum - 1);
21173 } elseif ($pagenum == $jpage) {
21174 $newpage = 1;
21175 } else {
21176 $newpage = $pagenum;
21177 }
21178 --$newpage;
21179 $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21180 $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21181 }
21182 unset($pamatch);
21183 }
21184 // return to last page
21185 if ($this->numpages > 0) {
21186 $this->lastPage(true);
21187 }
21188 return true;
21189 }
21190
21191 /**
21192 * Clone the specified page to a new page.
21193 * @param $page (int) number of page to copy (0 = current page)
21194 * @return true in case of success, false in case of error.
21195 * @public
21196 * @since 4.9.015 (2010-04-20)
21197 */
21198 public function copyPage($page=0) {
21199 if ($page == 0) {
21200 // default value
21201 $page = $this->page;
21202 }
21203 if (($page < 1) OR ($page > $this->numpages)) {
21204 return false;
21205 }
21206 // close the last page
21207 $this->endPage();
21208 // copy all page-related states
21209 ++$this->numpages;
21210 $this->page = $this->numpages;
21211 $this->setPageBuffer($this->page, $this->getPageBuffer($page));
21212 $this->pagedim[$this->page] = $this->pagedim[$page];
21213 $this->pagelen[$this->page] = $this->pagelen[$page];
21214 $this->intmrk[$this->page] = $this->intmrk[$page];
21215 $this->bordermrk[$this->page] = $this->bordermrk[$page];
21216 $this->cntmrk[$this->page] = $this->cntmrk[$page];
21217 $this->pageobjects[$this->page] = $this->pageobjects[$page];
21218 $this->pageopen[$this->page] = false;
21219 if (isset($this->footerpos[$page])) {
21220 $this->footerpos[$this->page] = $this->footerpos[$page];
21221 }
21222 if (isset($this->footerlen[$page])) {
21223 $this->footerlen[$this->page] = $this->footerlen[$page];
21224 }
21225 if (isset($this->transfmrk[$page])) {
21226 $this->transfmrk[$this->page] = $this->transfmrk[$page];
21227 }
21228 if (isset($this->PageAnnots[$page])) {
21229 $this->PageAnnots[$this->page] = $this->PageAnnots[$page];
21230 }
21231 if (isset($this->newpagegroup[$page])) {
21232 // start a new group
21233 $this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
21234 $this->currpagegroup = $this->newpagegroup[$this->page];
21235 $this->pagegroups[$this->currpagegroup] = 1;
21236 } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
21237 ++$this->pagegroups[$this->currpagegroup];
21238 }
21239 // copy outlines
21240 $tmpoutlines = $this->outlines;
21241 foreach ($tmpoutlines as $key => $outline) {
21242 if ($outline['p'] == $page) {
21243 $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']);
21244 }
21245 }
21246 // copy links
21247 $tmplinks = $this->links;
21248 foreach ($tmplinks as $key => $link) {
21249 if ($link['p'] == $page) {
21250 $this->links[] = array('p' => $this->page, 'y' => $link['y'], 'f' => $link['f']);
21251 }
21252 }
21253 // return to last page
21254 $this->lastPage(true);
21255 return true;
21256 }
21257
21258 /**
21259 * Output a Table of Content Index (TOC).
21260 * This method must be called after all Bookmarks were set.
21261 * Before calling this method you have to open the page using the addTOCPage() method.
21262 * After calling this method you have to call endTOCPage() to close the TOC page.
21263 * You can override this method to achieve different styles.
21264 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21265 * @param $numbersfont (string) set the font for page numbers (please use monospaced font for better alignment).
21266 * @param $filler (string) string used to fill the space between text and page number.
21267 * @param $toc_name (string) name to use for TOC bookmark.
21268 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21269 * @param $color (array) RGB color array for bookmark title (values from 0 to 255).
21270 * @public
21271 * @author Nicola Asuni
21272 * @since 4.5.000 (2009-01-02)
21273 * @see addTOCPage(), endTOCPage(), addHTMLTOC()
21274 */
21275 public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
21276 $fontsize = $this->FontSizePt;
21277 $fontfamily = $this->FontFamily;
21278 $fontstyle = $this->FontStyle;
21279 $w = $this->w - $this->lMargin - $this->rMargin;
21280 $spacer = $this->GetStringWidth(chr(32)) * 4;
21281 $lmargin = $this->lMargin;
21282 $rmargin = $this->rMargin;
21283 $x_start = $this->GetX();
21284 $page_first = $this->page;
21285 $current_page = $this->page;
21286 $page_fill_start = false;
21287 $page_fill_end = false;
21288 $current_column = $this->current_column;
21289 if (TCPDF_STATIC::empty_string($numbersfont)) {
21290 $numbersfont = $this->default_monospaced_font;
21291 }
21292 if (TCPDF_STATIC::empty_string($filler)) {
21293 $filler = ' ';
21294 }
21295 if (TCPDF_STATIC::empty_string($page)) {
21296 $gap = ' ';
21297 } else {
21298 $gap = '';
21299 if ($page < 1) {
21300 $page = 1;
21301 }
21302 }
21303 $this->SetFont($numbersfont, $fontstyle, $fontsize);
21304 $numwidth = $this->GetStringWidth('00000');
21305 $maxpage = 0; //used for pages on attached documents
21306 foreach ($this->outlines as $key => $outline) {
21307 // check for extra pages (used for attachments)
21308 if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
21309 $outline['p'] += ($this->page - $page_first);
21310 }
21311 if ($this->rtl) {
21312 $aligntext = 'R';
21313 $alignnum = 'L';
21314 } else {
21315 $aligntext = 'L';
21316 $alignnum = 'R';
21317 }
21318 if ($outline['l'] == 0) {
21319 $this->SetFont($fontfamily, $outline['s'].'B', $fontsize);
21320 } else {
21321 $this->SetFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
21322 }
21323 $this->SetTextColorArray($outline['c']);
21324 // check for page break
21325 $this->checkPageBreak(2 * $this->getCellHeight($this->FontSize));
21326 // set margins and X position
21327 if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
21328 $this->lMargin = $lmargin;
21329 $this->rMargin = $rmargin;
21330 } else {
21331 if ($this->current_column != $current_column) {
21332 if ($this->rtl) {
21333 $x_start = $this->w - $this->columns[$this->current_column]['x'];
21334 } else {
21335 $x_start = $this->columns[$this->current_column]['x'];
21336 }
21337 }
21338 $lmargin = $this->lMargin;
21339 $rmargin = $this->rMargin;
21340 $current_page = $this->page;
21341 $current_column = $this->current_column;
21342 }
21343 $this->SetX($x_start);
21344 $indent = ($spacer * $outline['l']);
21345 if ($this->rtl) {
21346 $this->x -= $indent;
21347 $this->rMargin = $this->w - $this->x;
21348 } else {
21349 $this->x += $indent;
21350 $this->lMargin = $this->x;
21351 }
21352 $link = $this->AddLink();
21353 $this->SetLink($link, $outline['y'], $outline['p']);
21354 // write the text
21355 if ($this->rtl) {
21356 $txt = ' '.$outline['t'];
21357 } else {
21358 $txt = $outline['t'].' ';
21359 }
21360 $this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
21361 if ($this->rtl) {
21362 $tw = $this->x - $this->lMargin;
21363 } else {
21364 $tw = $this->w - $this->rMargin - $this->x;
21365 }
21366 $this->SetFont($numbersfont, $fontstyle, $fontsize);
21367 if (TCPDF_STATIC::empty_string($page)) {
21368 $pagenum = $outline['p'];
21369 } else {
21370 // placemark to be replaced with the correct number
21371 $pagenum = '{#'.($outline['p']).'}';
21372 if ($this->isUnicodeFont()) {
21373 $pagenum = '{'.$pagenum.'}';
21374 }
21375 $maxpage = max($maxpage, $outline['p']);
21376 }
21377 $fw = ($tw - $this->GetStringWidth($pagenum.$filler));
21378 $wfiller = $this->GetStringWidth($filler);
21379 if ($wfiller > 0) {
21380 $numfills = floor($fw / $wfiller);
21381 } else {
21382 $numfills = 0;
21383 }
21384 if ($numfills > 0) {
21385 $rowfill = str_repeat($filler, $numfills);
21386 } else {
21387 $rowfill = '';
21388 }
21389 if ($this->rtl) {
21390 $pagenum = $pagenum.$gap.$rowfill;
21391 } else {
21392 $pagenum = $rowfill.$gap.$pagenum;
21393 }
21394 // write the number
21395 $this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
21396 }
21397 $page_last = $this->getPage();
21398 $numpages = ($page_last - $page_first + 1);
21399 // account for booklet mode
21400 if ($this->booklet) {
21401 // check if a blank page is required before TOC
21402 $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21403 $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21404 if ($page_fill_start) {
21405 // add a page at the end (to be moved before TOC)
21406 $this->addPage();
21407 ++$page_last;
21408 ++$numpages;
21409 }
21410 if ($page_fill_end) {
21411 // add a page at the end
21412 $this->addPage();
21413 ++$page_last;
21414 ++$numpages;
21415 }
21416 }
21417 $maxpage = max($maxpage, $page_last);
21418 if (!TCPDF_STATIC::empty_string($page)) {
21419 for ($p = $page_first; $p <= $page_last; ++$p) {
21420 // get page data
21421 $temppage = $this->getPageBuffer($p);
21422 for ($n = 1; $n <= $maxpage; ++$n) {
21423 // update page numbers
21424 $a = '{#'.$n.'}';
21425 // get page number aliases
21426 $pnalias = $this->getInternalPageNumberAliases($a);
21427 // calculate replacement number
21428 if (($n >= $page) AND ($n <= $this->numpages)) {
21429 $np = $n + $numpages;
21430 } else {
21431 $np = $n;
21432 }
21433 $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21434 $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21435 // replace aliases with numbers
21436 foreach ($pnalias['u'] as $u) {
21437 $sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
21438 if ($this->rtl) {
21439 $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21440 } else {
21441 $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21442 }
21443 $temppage = str_replace($u, $nr, $temppage);
21444 }
21445 foreach ($pnalias['a'] as $a) {
21446 $sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
21447 if ($this->rtl) {
21448 $nr = $na.' '.$sfill;
21449 } else {
21450 $nr = $sfill.' '.$na;
21451 }
21452 $temppage = str_replace($a, $nr, $temppage);
21453 }
21454 }
21455 // save changes
21456 $this->setPageBuffer($p, $temppage);
21457 }
21458 // move pages
21459 $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21460 if ($page_fill_start) {
21461 $this->movePage($page_last, $page_first);
21462 }
21463 for ($i = 0; $i < $numpages; ++$i) {
21464 $this->movePage($page_last, $page);
21465 }
21466 }
21467 }
21468
21469 /**
21470 * Output a Table Of Content Index (TOC) using HTML templates.
21471 * This method must be called after all Bookmarks were set.
21472 * Before calling this method you have to open the page using the addTOCPage() method.
21473 * After calling this method you have to call endTOCPage() to close the TOC page.
21474 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21475 * @param $toc_name (string) name to use for TOC bookmark.
21476 * @param $templates (array) array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
21477 * @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)
21478 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21479 * @param $color (array) RGB color array for title (values from 0 to 255).
21480 * @public
21481 * @author Nicola Asuni
21482 * @since 5.0.001 (2010-05-06)
21483 * @see addTOCPage(), endTOCPage(), addTOC()
21484 */
21485 public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
21486 $filler = ' ';
21487 $prev_htmlLinkColorArray = $this->htmlLinkColorArray;
21488 $prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
21489 // set new style for link
21490 $this->htmlLinkColorArray = array();
21491 $this->htmlLinkFontStyle = '';
21492 $page_first = $this->getPage();
21493 $page_fill_start = false;
21494 $page_fill_end = false;
21495 // get the font type used for numbers in each template
21496 $current_font = $this->FontFamily;
21497 foreach ($templates as $level => $html) {
21498 $dom = $this->getHtmlDomArray($html);
21499 foreach ($dom as $key => $value) {
21500 if ($value['value'] == '#TOC_PAGE_NUMBER#') {
21501 $this->SetFont($dom[($key - 1)]['fontname']);
21502 $templates['F'.$level] = $this->isUnicodeFont();
21503 }
21504 }
21505 }
21506 $this->SetFont($current_font);
21507 $maxpage = 0; //used for pages on attached documents
21508 foreach ($this->outlines as $key => $outline) {
21509 // get HTML template
21510 $row = $templates[$outline['l']];
21511 if (TCPDF_STATIC::empty_string($page)) {
21512 $pagenum = $outline['p'];
21513 } else {
21514 // placemark to be replaced with the correct number
21515 $pagenum = '{#'.($outline['p']).'}';
21516 if (isset($templates['F'.$outline['l']]) && $templates['F'.$outline['l']]) {
21517 $pagenum = '{'.$pagenum.'}';
21518 }
21519 $maxpage = max($maxpage, $outline['p']);
21520 }
21521 // replace templates with current values
21522 $row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
21523 $row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
21524 // add link to page
21525 $row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
21526 // write bookmark entry
21527 $this->writeHTML($row, false, false, true, false, '');
21528 }
21529 // restore link styles
21530 $this->htmlLinkColorArray = $prev_htmlLinkColorArray;
21531 $this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
21532 // move TOC page and replace numbers
21533 $page_last = $this->getPage();
21534 $numpages = ($page_last - $page_first + 1);
21535 // account for booklet mode
21536 if ($this->booklet) {
21537 // check if a blank page is required before TOC
21538 $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21539 $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21540 if ($page_fill_start) {
21541 // add a page at the end (to be moved before TOC)
21542 $this->addPage();
21543 ++$page_last;
21544 ++$numpages;
21545 }
21546 if ($page_fill_end) {
21547 // add a page at the end
21548 $this->addPage();
21549 ++$page_last;
21550 ++$numpages;
21551 }
21552 }
21553 $maxpage = max($maxpage, $page_last);
21554 if (!TCPDF_STATIC::empty_string($page)) {
21555 for ($p = $page_first; $p <= $page_last; ++$p) {
21556 // get page data
21557 $temppage = $this->getPageBuffer($p);
21558 for ($n = 1; $n <= $maxpage; ++$n) {
21559 // update page numbers
21560 $a = '{#'.$n.'}';
21561 // get page number aliases
21562 $pnalias = $this->getInternalPageNumberAliases($a);
21563 // calculate replacement number
21564 if ($n >= $page) {
21565 $np = $n + $numpages;
21566 } else {
21567 $np = $n;
21568 }
21569 $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21570 $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21571 // replace aliases with numbers
21572 foreach ($pnalias['u'] as $u) {
21573 if ($correct_align) {
21574 $sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
21575 if ($this->rtl) {
21576 $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21577 } else {
21578 $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21579 }
21580 } else {
21581 $nr = $nu;
21582 }
21583 $temppage = str_replace($u, $nr, $temppage);
21584 }
21585 foreach ($pnalias['a'] as $a) {
21586 if ($correct_align) {
21587 $sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
21588 if ($this->rtl) {
21589 $nr = $na.' '.$sfill;
21590 } else {
21591 $nr = $sfill.' '.$na;
21592 }
21593 } else {
21594 $nr = $na;
21595 }
21596 $temppage = str_replace($a, $nr, $temppage);
21597 }
21598 }
21599 // save changes
21600 $this->setPageBuffer($p, $temppage);
21601 }
21602 // move pages
21603 $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21604 if ($page_fill_start) {
21605 $this->movePage($page_last, $page_first);
21606 }
21607 for ($i = 0; $i < $numpages; ++$i) {
21608 $this->movePage($page_last, $page);
21609 }
21610 }
21611 }
21612
21613 /**
21614 * Stores a copy of the current TCPDF object used for undo operation.
21615 * @public
21616 * @since 4.5.029 (2009-03-19)
21617 */
21618 public function startTransaction() {
21619 if (isset($this->objcopy)) {
21620 // remove previous copy
21621 $this->commitTransaction();
21622 }
21623 // record current page number and Y position
21624 $this->start_transaction_page = $this->page;
21625 $this->start_transaction_y = $this->y;
21626 // clone current object
21627 $this->objcopy = TCPDF_STATIC::objclone($this);
21628 }
21629
21630 /**
21631 * Delete the copy of the current TCPDF object used for undo operation.
21632 * @public
21633 * @since 4.5.029 (2009-03-19)
21634 */
21635 public function commitTransaction() {
21636 if (isset($this->objcopy)) {
21637 $this->objcopy->_destroy(true, true);
21638 unset($this->objcopy);
21639 }
21640 }
21641
21642 /**
21643 * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
21644 * @param $self (boolean) if true restores current class object to previous state without the need of reassignment via the returned value.
21645 * @return TCPDF object.
21646 * @public
21647 * @since 4.5.029 (2009-03-19)
21648 */
21649 public function rollbackTransaction($self=false) {
21650 if (isset($this->objcopy)) {
21651 $this->_destroy(true, true);
21652 if ($self) {
21653 $objvars = get_object_vars($this->objcopy);
21654 foreach ($objvars as $key => $value) {
21655 $this->$key = $value;
21656 }
21657 }
21658 return $this->objcopy;
21659 }
21660 return $this;
21661 }
21662
21663 // --- MULTI COLUMNS METHODS -----------------------
21664
21665 /**
21666 * Set multiple columns of the same size
21667 * @param $numcols (int) number of columns (set to zero to disable columns mode)
21668 * @param $width (int) column width
21669 * @param $y (int) column starting Y position (leave empty for current Y position)
21670 * @public
21671 * @since 4.9.001 (2010-03-28)
21672 */
21673 public function setEqualColumns($numcols=0, $width=0, $y='') {
21674 $this->columns = array();
21675 if ($numcols < 2) {
21676 $numcols = 0;
21677 $this->columns = array();
21678 } else {
21679 // maximum column width
21680 $maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
21681 if (($width == 0) OR ($width > $maxwidth)) {
21682 $width = $maxwidth;
21683 }
21684 if (TCPDF_STATIC::empty_string($y)) {
21685 $y = $this->y;
21686 }
21687 // space between columns
21688 $space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
21689 // fill the columns array (with, space, starting Y position)
21690 for ($i = 0; $i < $numcols; ++$i) {
21691 $this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
21692 }
21693 }
21694 $this->num_columns = $numcols;
21695 $this->current_column = 0;
21696 $this->column_start_page = $this->page;
21697 $this->selectColumn(0);
21698 }
21699
21700 /**
21701 * Remove columns and reset page margins.
21702 * @public
21703 * @since 5.9.072 (2011-04-26)
21704 */
21705 public function resetColumns() {
21706 $this->lMargin = $this->original_lMargin;
21707 $this->rMargin = $this->original_rMargin;
21708 $this->setEqualColumns();
21709 }
21710
21711 /**
21712 * Set columns array.
21713 * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
21714 * @param $columns (array)
21715 * @public
21716 * @since 4.9.001 (2010-03-28)
21717 */
21718 public function setColumnsArray($columns) {
21719 $this->columns = $columns;
21720 $this->num_columns = count($columns);
21721 $this->current_column = 0;
21722 $this->column_start_page = $this->page;
21723 $this->selectColumn(0);
21724 }
21725
21726 /**
21727 * Set position at a given column
21728 * @param $col (int) column number (from 0 to getNumberOfColumns()-1); empty string = current column.
21729 * @public
21730 * @since 4.9.001 (2010-03-28)
21731 */
21732 public function selectColumn($col='') {
21733 if (is_string($col)) {
21734 $col = $this->current_column;
21735 } elseif ($col >= $this->num_columns) {
21736 $col = 0;
21737 }
21738 $xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
21739 $enable_thead = false;
21740 if ($this->num_columns > 1) {
21741 if ($col != $this->current_column) {
21742 // move Y pointer at the top of the column
21743 if ($this->column_start_page == $this->page) {
21744 $this->y = $this->columns[$col]['y'];
21745 } else {
21746 $this->y = $this->tMargin;
21747 }
21748 // Avoid to write table headers more than once
21749 if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
21750 $enable_thead = true;
21751 $this->maxselcol['page'] = $this->page;
21752 $this->maxselcol['column'] = $col;
21753 }
21754 }
21755 $xshift = $this->colxshift;
21756 // set X position of the current column by case
21757 $listindent = ($this->listindentlevel * $this->listindent);
21758 // calculate column X position
21759 $colpos = 0;
21760 for ($i = 0; $i < $col; ++$i) {
21761 $colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
21762 }
21763 if ($this->rtl) {
21764 $x = $this->w - $this->original_rMargin - $colpos;
21765 $this->rMargin = ($this->w - $x + $listindent);
21766 $this->lMargin = ($x - $this->columns[$col]['w']);
21767 $this->x = $x - $listindent;
21768 } else {
21769 $x = $this->original_lMargin + $colpos;
21770 $this->lMargin = ($x + $listindent);
21771 $this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
21772 $this->x = $x + $listindent;
21773 }
21774 $this->columns[$col]['x'] = $x;
21775 }
21776 $this->current_column = $col;
21777 // fix for HTML mode
21778 $this->newline = true;
21779 // print HTML table header (if any)
21780 if ((!TCPDF_STATIC::empty_string($this->thead)) AND (!$this->inthead)) {
21781 if ($enable_thead) {
21782 // print table header
21783 $this->writeHTML($this->thead, false, false, false, false, '');
21784 $this->y += $xshift['s']['V'];
21785 // store end of header position
21786 if (!isset($this->columns[$col]['th'])) {
21787 $this->columns[$col]['th'] = array();
21788 }
21789 $this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
21790 $this->lasth = 0;
21791 } elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
21792 $this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
21793 }
21794 }
21795 // account for an html table cell over multiple columns
21796 if ($this->rtl) {
21797 $this->rMargin += $xshift['x'];
21798 $this->x -= ($xshift['x'] + $xshift['p']['R']);
21799 } else {
21800 $this->lMargin += $xshift['x'];
21801 $this->x += $xshift['x'] + $xshift['p']['L'];
21802 }
21803 }
21804
21805 /**
21806 * Return the current column number
21807 * @return int current column number
21808 * @public
21809 * @since 5.5.011 (2010-07-08)
21810 */
21811 public function getColumn() {
21812 return $this->current_column;
21813 }
21814
21815 /**
21816 * Return the current number of columns.
21817 * @return int number of columns
21818 * @public
21819 * @since 5.8.018 (2010-08-25)
21820 */
21821 public function getNumberOfColumns() {
21822 return $this->num_columns;
21823 }
21824
21825 /**
21826 * Set Text rendering mode.
21827 * @param $stroke (int) outline size in user units (0 = disable).
21828 * @param $fill (boolean) if true fills the text (default).
21829 * @param $clip (boolean) if true activate clipping mode
21830 * @public
21831 * @since 4.9.008 (2009-04-02)
21832 */
21833 public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
21834 // Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
21835 // convert text rendering parameters
21836 if ($stroke < 0) {
21837 $stroke = 0;
21838 }
21839 if ($fill === true) {
21840 if ($stroke > 0) {
21841 if ($clip === true) {
21842 // Fill, then stroke text and add to path for clipping
21843 $textrendermode = 6;
21844 } else {
21845 // Fill, then stroke text
21846 $textrendermode = 2;
21847 }
21848 $textstrokewidth = $stroke;
21849 } else {
21850 if ($clip === true) {
21851 // Fill text and add to path for clipping
21852 $textrendermode = 4;
21853 } else {
21854 // Fill text
21855 $textrendermode = 0;
21856 }
21857 }
21858 } else {
21859 if ($stroke > 0) {
21860 if ($clip === true) {
21861 // Stroke text and add to path for clipping
21862 $textrendermode = 5;
21863 } else {
21864 // Stroke text
21865 $textrendermode = 1;
21866 }
21867 $textstrokewidth = $stroke;
21868 } else {
21869 if ($clip === true) {
21870 // Add text to path for clipping
21871 $textrendermode = 7;
21872 } else {
21873 // Neither fill nor stroke text (invisible)
21874 $textrendermode = 3;
21875 }
21876 }
21877 }
21878 $this->textrendermode = $textrendermode;
21879 $this->textstrokewidth = $stroke;
21880 }
21881
21882 /**
21883 * Set parameters for drop shadow effect for text.
21884 * @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.
21885 * @since 5.9.174 (2012-07-25)
21886 * @public
21887 */
21888 public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
21889 if (isset($params['enabled'])) {
21890 $this->txtshadow['enabled'] = $params['enabled']?true:false;
21891 } else {
21892 $this->txtshadow['enabled'] = false;
21893 }
21894 if (isset($params['depth_w'])) {
21895 $this->txtshadow['depth_w'] = floatval($params['depth_w']);
21896 } else {
21897 $this->txtshadow['depth_w'] = 0;
21898 }
21899 if (isset($params['depth_h'])) {
21900 $this->txtshadow['depth_h'] = floatval($params['depth_h']);
21901 } else {
21902 $this->txtshadow['depth_h'] = 0;
21903 }
21904 if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
21905 $this->txtshadow['color'] = $params['color'];
21906 } else {
21907 $this->txtshadow['color'] = $this->strokecolor;
21908 }
21909 if (isset($params['opacity'])) {
21910 $this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
21911 } else {
21912 $this->txtshadow['opacity'] = 1;
21913 }
21914 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'))) {
21915 $this->txtshadow['blend_mode'] = $params['blend_mode'];
21916 } else {
21917 $this->txtshadow['blend_mode'] = 'Normal';
21918 }
21919 if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
21920 $this->txtshadow['enabled'] = false;
21921 }
21922 }
21923
21924 /**
21925 * Return the text shadow parameters array.
21926 * @return Array of parameters.
21927 * @since 5.9.174 (2012-07-25)
21928 * @public
21929 */
21930 public function getTextShadow() {
21931 return $this->txtshadow;
21932 }
21933
21934 /**
21935 * Returns an array of chars containing soft hyphens.
21936 * @param $word (array) array of chars
21937 * @param $patterns (array) Array of hypenation patterns.
21938 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algorithm.
21939 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
21940 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
21941 * @param $charmin (int) Minimum word length to apply the hyphenation algorithm.
21942 * @param $charmax (int) Maximum length of broken piece of word.
21943 * @return array text with soft hyphens
21944 * @author Nicola Asuni
21945 * @since 4.9.012 (2010-04-12)
21946 * @protected
21947 */
21948 protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
21949 $hyphenword = array(); // hyphens positions
21950 $numchars = count($word);
21951 if ($numchars <= $charmin) {
21952 return $word;
21953 }
21954 $word_string = TCPDF_FONTS::UTF8ArrSubString($word, '', '', $this->isunicode);
21955 // some words will be returned as-is
21956 $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})(\]?)$/';
21957 if (preg_match($pattern, $word_string) > 0) {
21958 // email
21959 return $word;
21960 }
21961 $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})(\]?)$/';
21962 if (preg_match($pattern, $word_string) > 0) {
21963 // URL
21964 return $word;
21965 }
21966 if (isset($dictionary[$word_string])) {
21967 return TCPDF_FONTS::UTF8StringToArray($dictionary[$word_string], $this->isunicode, $this->CurrentFont);
21968 }
21969 // surround word with '_' characters
21970 $tmpword = array_merge(array(46), $word, array(46));
21971 $tmpnumchars = $numchars + 2;
21972 $maxpos = $tmpnumchars - 1;
21973 for ($pos = 0; $pos < $maxpos; ++$pos) {
21974 $imax = min(($tmpnumchars - $pos), $charmax);
21975 for ($i = 1; $i <= $imax; ++$i) {
21976 $subword = strtolower(TCPDF_FONTS::UTF8ArrSubString($tmpword, $pos, ($pos + $i), $this->isunicode));
21977 if (isset($patterns[$subword])) {
21978 $pattern = TCPDF_FONTS::UTF8StringToArray($patterns[$subword], $this->isunicode, $this->CurrentFont);
21979 $pattern_length = count($pattern);
21980 $digits = 1;
21981 for ($j = 0; $j < $pattern_length; ++$j) {
21982 // check if $pattern[$j] is a number = hyphenation level (only numbers from 1 to 5 are valid)
21983 if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
21984 if ($j == 0) {
21985 $zero = $pos - 1;
21986 } else {
21987 $zero = $pos + $j - $digits;
21988 }
21989 // get hyphenation level
21990 $level = ($pattern[$j] - 48);
21991 // if two levels from two different patterns match at the same point, the higher one is selected.
21992 if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] < $level)) {
21993 $hyphenword[$zero] = $level;
21994 }
21995 ++$digits;
21996 }
21997 }
21998 }
21999 }
22000 }
22001 $inserted = 0;
22002 $maxpos = $numchars - $rightmin;
22003 for ($i = $leftmin; $i <= $maxpos; ++$i) {
22004 // only odd levels indicate allowed hyphenation points
22005 if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
22006 // 173 = soft hyphen character
22007 array_splice($word, $i + $inserted, 0, 173);
22008 ++$inserted;
22009 }
22010 }
22011 return $word;
22012 }
22013
22014 /**
22015 * Returns text with soft hyphens.
22016 * @param $text (string) text to process
22017 * @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/
22018 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algorithm.
22019 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
22020 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
22021 * @param $charmin (int) Minimum word length to apply the hyphenation algorithm.
22022 * @param $charmax (int) Maximum length of broken piece of word.
22023 * @return array text with soft hyphens
22024 * @author Nicola Asuni
22025 * @since 4.9.012 (2010-04-12)
22026 * @public
22027 */
22028 public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22029 $text = $this->unhtmlentities($text);
22030 $word = array(); // last word
22031 $txtarr = array(); // text to be returned
22032 $intag = false; // true if we are inside an HTML tag
22033 $skip = false; // true to skip hyphenation
22034 if (!is_array($patterns)) {
22035 $patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patterns);
22036 }
22037 // get array of characters
22038 $unichars = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
22039 // for each char
22040 foreach ($unichars as $char) {
22041 if ((!$intag) AND (!$skip) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
22042 // letter character
22043 $word[] = $char;
22044 } else {
22045 // other type of character
22046 if (!TCPDF_STATIC::empty_string($word)) {
22047 // hypenate the word
22048 $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22049 $word = array();
22050 }
22051 $txtarr[] = $char;
22052 if (chr($char) == '<') {
22053 // we are inside an HTML tag
22054 $intag = true;
22055 } elseif ($intag AND (chr($char) == '>')) {
22056 // end of HTML tag
22057 $intag = false;
22058 // check for style tag
22059 $expected = array(115, 116, 121, 108, 101); // = 'style'
22060 $current = array_slice($txtarr, -6, 5); // last 5 chars
22061 $compare = array_diff($expected, $current);
22062 if (empty($compare)) {
22063 // check if it is a closing tag
22064 $expected = array(47); // = '/'
22065 $current = array_slice($txtarr, -7, 1);
22066 $compare = array_diff($expected, $current);
22067 if (empty($compare)) {
22068 // closing style tag
22069 $skip = false;
22070 } else {
22071 // opening style tag
22072 $skip = true;
22073 }
22074 }
22075 }
22076 }
22077 }
22078 if (!TCPDF_STATIC::empty_string($word)) {
22079 // hypenate the word
22080 $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22081 }
22082 // convert char array to string and return
22083 return TCPDF_FONTS::UTF8ArrSubString($txtarr, '', '', $this->isunicode);
22084 }
22085
22086 /**
22087 * Enable/disable rasterization of vector images using ImageMagick library.
22088 * @param $mode (boolean) if true enable rasterization, false otherwise.
22089 * @public
22090 * @since 5.0.000 (2010-04-27)
22091 */
22092 public function setRasterizeVectorImages($mode) {
22093 $this->rasterize_vector_images = $mode;
22094 }
22095
22096 /**
22097 * Enable or disable default option for font subsetting.
22098 * @param $enable (boolean) if true enable font subsetting by default.
22099 * @author Nicola Asuni
22100 * @public
22101 * @since 5.3.002 (2010-06-07)
22102 */
22103 public function setFontSubsetting($enable=true) {
22104 if ($this->pdfa_mode) {
22105 $this->font_subsetting = false;
22106 } else {
22107 $this->font_subsetting = $enable ? true : false;
22108 }
22109 }
22110
22111 /**
22112 * Return the default option for font subsetting.
22113 * @return boolean default font subsetting state.
22114 * @author Nicola Asuni
22115 * @public
22116 * @since 5.3.002 (2010-06-07)
22117 */
22118 public function getFontSubsetting() {
22119 return $this->font_subsetting;
22120 }
22121
22122 /**
22123 * Left trim the input string
22124 * @param $str (string) string to trim
22125 * @param $replace (string) string that replace spaces.
22126 * @return left trimmed string
22127 * @author Nicola Asuni
22128 * @public
22129 * @since 5.8.000 (2010-08-11)
22130 */
22131 public function stringLeftTrim($str, $replace='') {
22132 return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
22133 }
22134
22135 /**
22136 * Right trim the input string
22137 * @param $str (string) string to trim
22138 * @param $replace (string) string that replace spaces.
22139 * @return right trimmed string
22140 * @author Nicola Asuni
22141 * @public
22142 * @since 5.8.000 (2010-08-11)
22143 */
22144 public function stringRightTrim($str, $replace='') {
22145 return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
22146 }
22147
22148 /**
22149 * Trim the input string
22150 * @param $str (string) string to trim
22151 * @param $replace (string) string that replace spaces.
22152 * @return trimmed string
22153 * @author Nicola Asuni
22154 * @public
22155 * @since 5.8.000 (2010-08-11)
22156 */
22157 public function stringTrim($str, $replace='') {
22158 $str = $this->stringLeftTrim($str, $replace);
22159 $str = $this->stringRightTrim($str, $replace);
22160 return $str;
22161 }
22162
22163 /**
22164 * Return true if the current font is unicode type.
22165 * @return true for unicode font, false otherwise.
22166 * @author Nicola Asuni
22167 * @public
22168 * @since 5.8.002 (2010-08-14)
22169 */
22170 public function isUnicodeFont() {
22171 return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
22172 }
22173
22174 /**
22175 * Return normalized font name
22176 * @param $fontfamily (string) property string containing font family names
22177 * @return string normalized font name
22178 * @author Nicola Asuni
22179 * @public
22180 * @since 5.8.004 (2010-08-17)
22181 */
22182 public function getFontFamilyName($fontfamily) {
22183 // remove spaces and symbols
22184 $fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
22185 // extract all font names
22186 $fontslist = preg_split('/[,]/', $fontfamily);
22187 // find first valid font name
22188 foreach ($fontslist as $font) {
22189 // replace font variations
22190 $font = preg_replace('/regular$/', '', $font);
22191 $font = preg_replace('/italic$/', 'I', $font);
22192 $font = preg_replace('/oblique$/', 'I', $font);
22193 $font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
22194 // replace common family names and core fonts
22195 $pattern = array();
22196 $replacement = array();
22197 $pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
22198 $replacement[] = 'times';
22199 $pattern[] = '/^sansserif/';
22200 $replacement[] = 'helvetica';
22201 $pattern[] = '/^monospace/';
22202 $replacement[] = 'courier';
22203 $font = preg_replace($pattern, $replacement, $font);
22204 if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
22205 return $font;
22206 }
22207 }
22208 // return current font as default
22209 return $this->CurrentFont['fontkey'];
22210 }
22211
22212 /**
22213 * Start a new XObject Template.
22214 * 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).
22215 * 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.
22216 * Note: X,Y coordinates will be reset to 0,0.
22217 * @param $w (int) Template width in user units (empty string or zero = page width less margins).
22218 * @param $h (int) Template height in user units (empty string or zero = page height less margins).
22219 * @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).
22220 * @return int the XObject Template ID in case of success or false in case of error.
22221 * @author Nicola Asuni
22222 * @public
22223 * @since 5.8.017 (2010-08-24)
22224 * @see endTemplate(), printTemplate()
22225 */
22226 public function startTemplate($w=0, $h=0, $group=false) {
22227 if ($this->inxobj) {
22228 // we are already inside an XObject template
22229 return false;
22230 }
22231 $this->inxobj = true;
22232 ++$this->n;
22233 // XObject ID
22234 $this->xobjid = 'XT'.$this->n;
22235 // object ID
22236 $this->xobjects[$this->xobjid] = array('n' => $this->n);
22237 // store current graphic state
22238 $this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
22239 // initialize data
22240 $this->xobjects[$this->xobjid]['intmrk'] = 0;
22241 $this->xobjects[$this->xobjid]['transfmrk'] = array();
22242 $this->xobjects[$this->xobjid]['outdata'] = '';
22243 $this->xobjects[$this->xobjid]['xobjects'] = array();
22244 $this->xobjects[$this->xobjid]['images'] = array();
22245 $this->xobjects[$this->xobjid]['fonts'] = array();
22246 $this->xobjects[$this->xobjid]['annotations'] = array();
22247 $this->xobjects[$this->xobjid]['extgstates'] = array();
22248 $this->xobjects[$this->xobjid]['gradients'] = array();
22249 $this->xobjects[$this->xobjid]['spot_colors'] = array();
22250 // set new environment
22251 $this->num_columns = 1;
22252 $this->current_column = 0;
22253 $this->SetAutoPageBreak(false);
22254 if (($w === '') OR ($w <= 0)) {
22255 $w = $this->w - $this->lMargin - $this->rMargin;
22256 }
22257 if (($h === '') OR ($h <= 0)) {
22258 $h = $this->h - $this->tMargin - $this->bMargin;
22259 }
22260 $this->xobjects[$this->xobjid]['x'] = 0;
22261 $this->xobjects[$this->xobjid]['y'] = 0;
22262 $this->xobjects[$this->xobjid]['w'] = $w;
22263 $this->xobjects[$this->xobjid]['h'] = $h;
22264 $this->w = $w;
22265 $this->h = $h;
22266 $this->wPt = $this->w * $this->k;
22267 $this->hPt = $this->h * $this->k;
22268 $this->fwPt = $this->wPt;
22269 $this->fhPt = $this->hPt;
22270 $this->x = 0;
22271 $this->y = 0;
22272 $this->lMargin = 0;
22273 $this->rMargin = 0;
22274 $this->tMargin = 0;
22275 $this->bMargin = 0;
22276 // set group mode
22277 $this->xobjects[$this->xobjid]['group'] = $group;
22278 return $this->xobjid;
22279 }
22280
22281 /**
22282 * End the current XObject Template started with startTemplate() and restore the previous graphic state.
22283 * 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).
22284 * 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.
22285 * @return int the XObject Template ID in case of success or false in case of error.
22286 * @author Nicola Asuni
22287 * @public
22288 * @since 5.8.017 (2010-08-24)
22289 * @see startTemplate(), printTemplate()
22290 */
22291 public function endTemplate() {
22292 if (!$this->inxobj) {
22293 // we are not inside a template
22294 return false;
22295 }
22296 $this->inxobj = false;
22297 // restore previous graphic state
22298 $this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
22299 return $this->xobjid;
22300 }
22301
22302 /**
22303 * Print an XObject Template.
22304 * You can print an XObject Template inside the currently opened Template.
22305 * 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).
22306 * 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.
22307 * @param $id (string) The ID of XObject Template to print.
22308 * @param $x (int) X position in user units (empty string = current x position)
22309 * @param $y (int) Y position in user units (empty string = current y position)
22310 * @param $w (int) Width in user units (zero = remaining page width)
22311 * @param $h (int) Height in user units (zero = remaining page height)
22312 * @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>
22313 * @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>
22314 * @param $fitonpage (boolean) If true the template is resized to not exceed page dimensions.
22315 * @author Nicola Asuni
22316 * @public
22317 * @since 5.8.017 (2010-08-24)
22318 * @see startTemplate(), endTemplate()
22319 */
22320 public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
22321 if ($this->state != 2) {
22322 return;
22323 }
22324 if (!isset($this->xobjects[$id])) {
22325 $this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
22326 }
22327 if ($this->inxobj) {
22328 if ($id == $this->xobjid) {
22329 // close current template
22330 $this->endTemplate();
22331 } else {
22332 // use the template as resource for the template currently opened
22333 $this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
22334 }
22335 }
22336 // set default values
22337 if ($x === '') {
22338 $x = $this->x;
22339 }
22340 if ($y === '') {
22341 $y = $this->y;
22342 }
22343 // check page for no-write regions and adapt page margins if necessary
22344 list($x, $y) = $this->checkPageRegions($h, $x, $y);
22345 $ow = $this->xobjects[$id]['w'];
22346 if ($ow <= 0) {
22347 $ow = 1;
22348 }
22349 $oh = $this->xobjects[$id]['h'];
22350 if ($oh <= 0) {
22351 $oh = 1;
22352 }
22353 // calculate template width and height on document
22354 if (($w <= 0) AND ($h <= 0)) {
22355 $w = $ow;
22356 $h = $oh;
22357 } elseif ($w <= 0) {
22358 $w = $h * $ow / $oh;
22359 } elseif ($h <= 0) {
22360 $h = $w * $oh / $ow;
22361 }
22362 // fit the template on available space
22363 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22364 // set page alignment
22365 $rb_y = $y + $h;
22366 // set alignment
22367 if ($this->rtl) {
22368 if ($palign == 'L') {
22369 $xt = $this->lMargin;
22370 } elseif ($palign == 'C') {
22371 $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22372 } elseif ($palign == 'R') {
22373 $xt = $this->w - $this->rMargin - $w;
22374 } else {
22375 $xt = $x - $w;
22376 }
22377 $rb_x = $xt;
22378 } else {
22379 if ($palign == 'L') {
22380 $xt = $this->lMargin;
22381 } elseif ($palign == 'C') {
22382 $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22383 } elseif ($palign == 'R') {
22384 $xt = $this->w - $this->rMargin - $w;
22385 } else {
22386 $xt = $x;
22387 }
22388 $rb_x = $xt + $w;
22389 }
22390 // print XObject Template + Transformation matrix
22391 $this->StartTransform();
22392 // translate and scale
22393 $sx = ($w / $ow);
22394 $sy = ($h / $oh);
22395 $tm = array();
22396 $tm[0] = $sx;
22397 $tm[1] = 0;
22398 $tm[2] = 0;
22399 $tm[3] = $sy;
22400 $tm[4] = $xt * $this->k;
22401 $tm[5] = ($this->h - $h - $y) * $this->k;
22402 $this->Transform($tm);
22403 // set object
22404 $this->_out('/'.$id.' Do');
22405 $this->StopTransform();
22406 // add annotations
22407 if (!empty($this->xobjects[$id]['annotations'])) {
22408 foreach ($this->xobjects[$id]['annotations'] as $annot) {
22409 // transform original coordinates
22410 $coordlt = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
22411 $ax = ($coordlt[4] / $this->k);
22412 $ay = ($this->h - $h - ($coordlt[5] / $this->k));
22413 $coordrb = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
22414 $aw = ($coordrb[4] / $this->k) - $ax;
22415 $ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
22416 $this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
22417 }
22418 }
22419 // set pointer to align the next text/objects
22420 switch($align) {
22421 case 'T': {
22422 $this->y = $y;
22423 $this->x = $rb_x;
22424 break;
22425 }
22426 case 'M': {
22427 $this->y = $y + round($h/2);
22428 $this->x = $rb_x;
22429 break;
22430 }
22431 case 'B': {
22432 $this->y = $rb_y;
22433 $this->x = $rb_x;
22434 break;
22435 }
22436 case 'N': {
22437 $this->SetY($rb_y);
22438 break;
22439 }
22440 default:{
22441 break;
22442 }
22443 }
22444 }
22445
22446 /**
22447 * Set the percentage of character stretching.
22448 * @param $perc (int) percentage of stretching (100 = no stretching)
22449 * @author Nicola Asuni
22450 * @public
22451 * @since 5.9.000 (2010-09-29)
22452 */
22453 public function setFontStretching($perc=100) {
22454 $this->font_stretching = $perc;
22455 }
22456
22457 /**
22458 * Get the percentage of character stretching.
22459 * @return float stretching value
22460 * @author Nicola Asuni
22461 * @public
22462 * @since 5.9.000 (2010-09-29)
22463 */
22464 public function getFontStretching() {
22465 return $this->font_stretching;
22466 }
22467
22468 /**
22469 * Set the amount to increase or decrease the space between characters in a text.
22470 * @param $spacing (float) amount to increase or decrease the space between characters in a text (0 = default spacing)
22471 * @author Nicola Asuni
22472 * @public
22473 * @since 5.9.000 (2010-09-29)
22474 */
22475 public function setFontSpacing($spacing=0) {
22476 $this->font_spacing = $spacing;
22477 }
22478
22479 /**
22480 * Get the amount to increase or decrease the space between characters in a text.
22481 * @return int font spacing (tracking) value
22482 * @author Nicola Asuni
22483 * @public
22484 * @since 5.9.000 (2010-09-29)
22485 */
22486 public function getFontSpacing() {
22487 return $this->font_spacing;
22488 }
22489
22490 /**
22491 * Return an array of no-write page regions
22492 * @return array of no-write page regions
22493 * @author Nicola Asuni
22494 * @public
22495 * @since 5.9.003 (2010-10-13)
22496 * @see setPageRegions(), addPageRegion()
22497 */
22498 public function getPageRegions() {
22499 return $this->page_regions;
22500 }
22501
22502 /**
22503 * Set no-write regions on page.
22504 * 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.
22505 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22506 * You can set multiple regions for the same page.
22507 * @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.
22508 * @author Nicola Asuni
22509 * @public
22510 * @since 5.9.003 (2010-10-13)
22511 * @see addPageRegion(), getPageRegions()
22512 */
22513 public function setPageRegions($regions=array()) {
22514 // empty current regions array
22515 $this->page_regions = array();
22516 // add regions
22517 foreach ($regions as $data) {
22518 $this->addPageRegion($data);
22519 }
22520 }
22521
22522 /**
22523 * Add a single no-write region on selected page.
22524 * 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.
22525 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22526 * You can set multiple regions for the same page.
22527 * @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).
22528 * @author Nicola Asuni
22529 * @public
22530 * @since 5.9.003 (2010-10-13)
22531 * @see setPageRegions(), getPageRegions()
22532 */
22533 public function addPageRegion($region) {
22534 if (!isset($region['page']) OR empty($region['page'])) {
22535 $region['page'] = $this->page;
22536 }
22537 if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
22538 AND isset($region['yt']) AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
22539 AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
22540 $this->page_regions[] = $region;
22541 }
22542 }
22543
22544 /**
22545 * Remove a single no-write region.
22546 * @param $key (int) region key
22547 * @author Nicola Asuni
22548 * @public
22549 * @since 5.9.003 (2010-10-13)
22550 * @see setPageRegions(), getPageRegions()
22551 */
22552 public function removePageRegion($key) {
22553 if (isset($this->page_regions[$key])) {
22554 unset($this->page_regions[$key]);
22555 }
22556 }
22557
22558 /**
22559 * Check page for no-write regions and adapt current coordinates and page margins if necessary.
22560 * 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.
22561 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22562 * @param $h (float) height of the text/image/object to print in user units
22563 * @param $x (float) current X coordinate in user units
22564 * @param $y (float) current Y coordinate in user units
22565 * @return array($x, $y)
22566 * @author Nicola Asuni
22567 * @protected
22568 * @since 5.9.003 (2010-10-13)
22569 */
22570 protected function checkPageRegions($h, $x, $y) {
22571 // set default values
22572 if ($x === '') {
22573 $x = $this->x;
22574 }
22575 if ($y === '') {
22576 $y = $this->y;
22577 }
22578 if (!$this->check_page_regions OR empty($this->page_regions)) {
22579 // no page regions defined
22580 return array($x, $y);
22581 }
22582 if (empty($h)) {
22583 $h = $this->getCellHeight($this->FontSize);
22584 }
22585 // check for page break
22586 if ($this->checkPageBreak($h, $y)) {
22587 // the content will be printed on a new page
22588 $x = $this->x;
22589 $y = $this->y;
22590 }
22591 if ($this->num_columns > 1) {
22592 if ($this->rtl) {
22593 $this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22594 } else {
22595 $this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22596 }
22597 } else {
22598 if ($this->rtl) {
22599 $this->lMargin = max($this->clMargin, $this->original_lMargin);
22600 } else {
22601 $this->rMargin = max($this->crMargin, $this->original_rMargin);
22602 }
22603 }
22604 // adjust coordinates and page margins
22605 foreach ($this->page_regions as $regid => $regdata) {
22606 if ($regdata['page'] == $this->page) {
22607 // check region boundaries
22608 if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
22609 // Y is inside the region
22610 $minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
22611 $yt = max($y, $regdata['yt']);
22612 $yb = min(($yt + $h), $regdata['yb']);
22613 $xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
22614 $xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
22615 if ($regdata['side'] == 'L') { // left side
22616 $new_margin = max($xt, $xb);
22617 if ($this->lMargin < $new_margin) {
22618 if ($this->rtl) {
22619 // adjust left page margin
22620 $this->lMargin = max(0, $new_margin);
22621 }
22622 if ($x < $new_margin) {
22623 // adjust x position
22624 $x = $new_margin;
22625 if ($new_margin > ($this->w - $this->rMargin)) {
22626 // adjust y position
22627 $y = $regdata['yb'] - $h;
22628 }
22629 }
22630 }
22631 } elseif ($regdata['side'] == 'R') { // right side
22632 $new_margin = min($xt, $xb);
22633 if (($this->w - $this->rMargin) > $new_margin) {
22634 if (!$this->rtl) {
22635 // adjust right page margin
22636 $this->rMargin = max(0, ($this->w - $new_margin));
22637 }
22638 if ($x > $new_margin) {
22639 // adjust x position
22640 $x = $new_margin;
22641 if ($new_margin > $this->lMargin) {
22642 // adjust y position
22643 $y = $regdata['yb'] - $h;
22644 }
22645 }
22646 }
22647 }
22648 }
22649 }
22650 }
22651 return array($x, $y);
22652 }
22653
22654 // --- SVG METHODS ---------------------------------------------------------
22655
22656 /**
22657 * Embedd a Scalable Vector Graphics (SVG) image.
22658 * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
22659 * @param $file (string) Name of the SVG file or a '@' character followed by the SVG data string.
22660 * @param $x (float) Abscissa of the upper-left corner.
22661 * @param $y (float) Ordinate of the upper-left corner.
22662 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
22663 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
22664 * @param $link (mixed) URL or identifier returned by AddLink().
22665 * @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.
22666 * @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>
22667 * @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)))
22668 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
22669 * @author Nicola Asuni
22670 * @since 5.0.000 (2010-05-02)
22671 * @public
22672 */
22673 public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
22674 if ($this->state != 2) {
22675 return;
22676 }
22677 // reset SVG vars
22678 $this->svggradients = array();
22679 $this->svggradientid = 0;
22680 $this->svgdefsmode = false;
22681 $this->svgdefs = array();
22682 $this->svgclipmode = false;
22683 $this->svgclippaths = array();
22684 $this->svgcliptm = array();
22685 $this->svgclipid = 0;
22686 $this->svgtext = '';
22687 $this->svgtextmode = array();
22688 if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
22689 // convert SVG to raster image using GD or ImageMagick libraries
22690 return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22691 }
22692 if ($file[0] === '@') { // image from string
22693 $this->svgdir = '';
22694 $svgdata = substr($file, 1);
22695 } else { // SVG file
22696 $this->svgdir = dirname($file);
22697 $svgdata = TCPDF_STATIC::fileGetContents($file);
22698 }
22699 if ($svgdata === FALSE) {
22700 $this->Error('SVG file not found: '.$file);
22701 }
22702 if ($x === '') {
22703 $x = $this->x;
22704 }
22705 if ($y === '') {
22706 $y = $this->y;
22707 }
22708 // check page for no-write regions and adapt page margins if necessary
22709 list($x, $y) = $this->checkPageRegions($h, $x, $y);
22710 $k = $this->k;
22711 $ox = 0;
22712 $oy = 0;
22713 $ow = $w;
22714 $oh = $h;
22715 $aspect_ratio_align = 'xMidYMid';
22716 $aspect_ratio_ms = 'meet';
22717 $regs = array();
22718 // get original image width and height
22719 preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
22720 if (isset($regs[1]) AND !empty($regs[1])) {
22721 $tmp = array();
22722 if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22723 $ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22724 }
22725 $tmp = array();
22726 if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22727 $oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22728 }
22729 $tmp = array();
22730 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22731 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22732 }
22733 $tmp = array();
22734 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22735 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22736 }
22737 $tmp = array();
22738 $view_box = array();
22739 if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
22740 if (count($tmp) == 5) {
22741 array_shift($tmp);
22742 foreach ($tmp as $key => $val) {
22743 $view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
22744 }
22745 $ox = $view_box[0];
22746 $oy = $view_box[1];
22747 }
22748 // get aspect ratio
22749 $tmp = array();
22750 if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22751 $aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
22752 switch (count($aspect_ratio)) {
22753 case 3: {
22754 $aspect_ratio_align = $aspect_ratio[1];
22755 $aspect_ratio_ms = $aspect_ratio[2];
22756 break;
22757 }
22758 case 2: {
22759 $aspect_ratio_align = $aspect_ratio[0];
22760 $aspect_ratio_ms = $aspect_ratio[1];
22761 break;
22762 }
22763 case 1: {
22764 $aspect_ratio_align = $aspect_ratio[0];
22765 $aspect_ratio_ms = 'meet';
22766 break;
22767 }
22768 }
22769 }
22770 }
22771 }
22772 if ($ow <= 0) {
22773 $ow = 1;
22774 }
22775 if ($oh <= 0) {
22776 $oh = 1;
22777 }
22778 // calculate image width and height on document
22779 if (($w <= 0) AND ($h <= 0)) {
22780 // convert image size to document unit
22781 $w = $ow;
22782 $h = $oh;
22783 } elseif ($w <= 0) {
22784 $w = $h * $ow / $oh;
22785 } elseif ($h <= 0) {
22786 $h = $w * $oh / $ow;
22787 }
22788 // fit the image on available space
22789 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22790 if ($this->rasterize_vector_images) {
22791 // convert SVG to raster image using GD or ImageMagick libraries
22792 return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22793 }
22794 // set alignment
22795 $this->img_rb_y = $y + $h;
22796 // set alignment
22797 if ($this->rtl) {
22798 if ($palign == 'L') {
22799 $ximg = $this->lMargin;
22800 } elseif ($palign == 'C') {
22801 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22802 } elseif ($palign == 'R') {
22803 $ximg = $this->w - $this->rMargin - $w;
22804 } else {
22805 $ximg = $x - $w;
22806 }
22807 $this->img_rb_x = $ximg;
22808 } else {
22809 if ($palign == 'L') {
22810 $ximg = $this->lMargin;
22811 } elseif ($palign == 'C') {
22812 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22813 } elseif ($palign == 'R') {
22814 $ximg = $this->w - $this->rMargin - $w;
22815 } else {
22816 $ximg = $x;
22817 }
22818 $this->img_rb_x = $ximg + $w;
22819 }
22820 // store current graphic vars
22821 $gvars = $this->getGraphicVars();
22822 // store SVG position and scale factors
22823 $svgoffset_x = ($ximg - $ox) * $this->k;
22824 $svgoffset_y = -($y - $oy) * $this->k;
22825 if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
22826 $ow = $view_box[2];
22827 $oh = $view_box[3];
22828 } else {
22829 if ($ow <= 0) {
22830 $ow = $w;
22831 }
22832 if ($oh <= 0) {
22833 $oh = $h;
22834 }
22835 }
22836 $svgscale_x = $w / $ow;
22837 $svgscale_y = $h / $oh;
22838 // scaling and alignment
22839 if ($aspect_ratio_align != 'none') {
22840 // store current scaling values
22841 $svgscale_old_x = $svgscale_x;
22842 $svgscale_old_y = $svgscale_y;
22843 // force uniform scaling
22844 if ($aspect_ratio_ms == 'slice') {
22845 // the entire viewport is covered by the viewBox
22846 if ($svgscale_x > $svgscale_y) {
22847 $svgscale_y = $svgscale_x;
22848 } elseif ($svgscale_x < $svgscale_y) {
22849 $svgscale_x = $svgscale_y;
22850 }
22851 } else { // meet
22852 // the entire viewBox is visible within the viewport
22853 if ($svgscale_x < $svgscale_y) {
22854 $svgscale_y = $svgscale_x;
22855 } elseif ($svgscale_x > $svgscale_y) {
22856 $svgscale_x = $svgscale_y;
22857 }
22858 }
22859 // correct X alignment
22860 switch (substr($aspect_ratio_align, 1, 3)) {
22861 case 'Min': {
22862 // do nothing
22863 break;
22864 }
22865 case 'Max': {
22866 $svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
22867 break;
22868 }
22869 default:
22870 case 'Mid': {
22871 $svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
22872 break;
22873 }
22874 }
22875 // correct Y alignment
22876 switch (substr($aspect_ratio_align, 5)) {
22877 case 'Min': {
22878 // do nothing
22879 break;
22880 }
22881 case 'Max': {
22882 $svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
22883 break;
22884 }
22885 default:
22886 case 'Mid': {
22887 $svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
22888 break;
22889 }
22890 }
22891 }
22892 // store current page break mode
22893 $page_break_mode = $this->AutoPageBreak;
22894 $page_break_margin = $this->getBreakMargin();
22895 $cell_padding = $this->cell_padding;
22896 $this->SetCellPadding(0);
22897 $this->SetAutoPageBreak(false);
22898 // save the current graphic state
22899 $this->_out('q'.$this->epsmarker);
22900 // set initial clipping mask
22901 $this->Rect($ximg, $y, $w, $h, 'CNZ', array(), array());
22902 // scale and translate
22903 $e = $ox * $this->k * (1 - $svgscale_x);
22904 $f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
22905 $this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
22906 // creates a new XML parser to be used by the other XML functions
22907 $this->parser = xml_parser_create('UTF-8');
22908 // the following function allows to use parser inside object
22909 xml_set_object($this->parser, $this);
22910 // disable case-folding for this XML parser
22911 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
22912 // sets the element handler functions for the XML parser
22913 xml_set_element_handler($this->parser, 'startSVGElementHandler', 'endSVGElementHandler');
22914 // sets the character data handler function for the XML parser
22915 xml_set_character_data_handler($this->parser, 'segSVGContentHandler');
22916 // start parsing an XML document
22917 if (!xml_parse($this->parser, $svgdata)) {
22918 $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));
22919 $this->Error($error_message);
22920 }
22921 // free this XML parser
22922 xml_parser_free($this->parser);
22923 // restore previous graphic state
22924 $this->_out($this->epsmarker.'Q');
22925 // restore graphic vars
22926 $this->setGraphicVars($gvars);
22927 $this->lasth = $gvars['lasth'];
22928 if (!empty($border)) {
22929 $bx = $this->x;
22930 $by = $this->y;
22931 $this->x = $ximg;
22932 if ($this->rtl) {
22933 $this->x += $w;
22934 }
22935 $this->y = $y;
22936 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
22937 $this->x = $bx;
22938 $this->y = $by;
22939 }
22940 if ($link) {
22941 $this->Link($ximg, $y, $w, $h, $link, 0);
22942 }
22943 // set pointer to align the next text/objects
22944 switch($align) {
22945 case 'T':{
22946 $this->y = $y;
22947 $this->x = $this->img_rb_x;
22948 break;
22949 }
22950 case 'M':{
22951 $this->y = $y + round($h/2);
22952 $this->x = $this->img_rb_x;
22953 break;
22954 }
22955 case 'B':{
22956 $this->y = $this->img_rb_y;
22957 $this->x = $this->img_rb_x;
22958 break;
22959 }
22960 case 'N':{
22961 $this->SetY($this->img_rb_y);
22962 break;
22963 }
22964 default:{
22965 // restore pointer to starting position
22966 $this->x = $gvars['x'];
22967 $this->y = $gvars['y'];
22968 $this->page = $gvars['page'];
22969 $this->current_column = $gvars['current_column'];
22970 $this->tMargin = $gvars['tMargin'];
22971 $this->bMargin = $gvars['bMargin'];
22972 $this->w = $gvars['w'];
22973 $this->h = $gvars['h'];
22974 $this->wPt = $gvars['wPt'];
22975 $this->hPt = $gvars['hPt'];
22976 $this->fwPt = $gvars['fwPt'];
22977 $this->fhPt = $gvars['fhPt'];
22978 break;
22979 }
22980 }
22981 $this->endlinex = $this->img_rb_x;
22982 // restore page break
22983 $this->SetAutoPageBreak($page_break_mode, $page_break_margin);
22984 $this->cell_padding = $cell_padding;
22985 }
22986
22987 /**
22988 * Convert SVG transformation matrix to PDF.
22989 * @param $tm (array) original SVG transformation matrix
22990 * @return array transformation matrix
22991 * @protected
22992 * @since 5.0.000 (2010-05-02)
22993 */
22994 protected function convertSVGtMatrix($tm) {
22995 $a = $tm[0];
22996 $b = -$tm[1];
22997 $c = -$tm[2];
22998 $d = $tm[3];
22999 $e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
23000 $f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
23001 $x = 0;
23002 $y = $this->h * $this->k;
23003 $e = ($x * (1 - $a)) - ($y * $c) + $e;
23004 $f = ($y * (1 - $d)) - ($x * $b) + $f;
23005 return array($a, $b, $c, $d, $e, $f);
23006 }
23007
23008 /**
23009 * Apply SVG graphic transformation matrix.
23010 * @param $tm (array) original SVG transformation matrix
23011 * @protected
23012 * @since 5.0.000 (2010-05-02)
23013 */
23014 protected function SVGTransform($tm) {
23015 $this->Transform($this->convertSVGtMatrix($tm));
23016 }
23017
23018 /**
23019 * Apply the requested SVG styles (*** TO BE COMPLETED ***)
23020 * @param $svgstyle (array) array of SVG styles to apply
23021 * @param $prevsvgstyle (array) array of previous SVG style
23022 * @param $x (int) X origin of the bounding box
23023 * @param $y (int) Y origin of the bounding box
23024 * @param $w (int) width of the bounding box
23025 * @param $h (int) height of the bounding box
23026 * @param $clip_function (string) clip function
23027 * @param $clip_params (array) array of parameters for clipping function
23028 * @return object style
23029 * @author Nicola Asuni
23030 * @since 5.0.000 (2010-05-02)
23031 * @protected
23032 */
23033 protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
23034 if ($this->state != 2) {
23035 return;
23036 }
23037 $objstyle = '';
23038 $minlen = (0.01 / $this->k); // minimum acceptable length
23039 if (!isset($svgstyle['opacity'])) {
23040 return $objstyle;
23041 }
23042 // clip-path
23043 $regs = array();
23044 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
23045 $clip_path = $this->svgclippaths[$regs[1]];
23046 foreach ($clip_path as $cp) {
23047 $this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
23048 }
23049 }
23050 // opacity
23051 if ($svgstyle['opacity'] != 1) {
23052 $this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
23053 }
23054 // color
23055 $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
23056 $this->SetFillColorArray($fill_color);
23057 // text color
23058 $text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
23059 $this->SetTextColorArray($text_color);
23060 // clip
23061 if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
23062 $top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
23063 $right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
23064 $bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
23065 $left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
23066 $cx = $x + $left;
23067 $cy = $y + $top;
23068 $cw = $w - $left - $right;
23069 $ch = $h - $top - $bottom;
23070 if ($svgstyle['clip-rule'] == 'evenodd') {
23071 $clip_rule = 'CNZ';
23072 } else {
23073 $clip_rule = 'CEO';
23074 }
23075 $this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
23076 }
23077 // fill
23078 $regs = array();
23079 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
23080 // gradient
23081 $gradient = $this->svggradients[$regs[1]];
23082 if (isset($gradient['xref'])) {
23083 // reference to another gradient definition
23084 $newgradient = $this->svggradients[$gradient['xref']];
23085 $newgradient['coords'] = $gradient['coords'];
23086 $newgradient['mode'] = $gradient['mode'];
23087 $newgradient['type'] = $gradient['type'];
23088 $newgradient['gradientUnits'] = $gradient['gradientUnits'];
23089 if (isset($gradient['gradientTransform'])) {
23090 $newgradient['gradientTransform'] = $gradient['gradientTransform'];
23091 }
23092 $gradient = $newgradient;
23093 }
23094 //save current Graphic State
23095 $this->_outSaveGraphicsState();
23096 //set clipping area
23097 if (!empty($clip_function) AND method_exists($this, $clip_function)) {
23098 $bbox = call_user_func_array(array($this, $clip_function), $clip_params);
23099 if ((!isset($gradient['type']) OR ($gradient['type'] != 3)) AND is_array($bbox) AND (count($bbox) == 4)) {
23100 list($x, $y, $w, $h) = $bbox;
23101 }
23102 }
23103 if ($gradient['mode'] == 'measure') {
23104 if (!isset($gradient['coords'][4])) {
23105 $gradient['coords'][4] = 0.5;
23106 }
23107 if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
23108 $gtm = $gradient['gradientTransform'];
23109 // apply transformation matrix
23110 $xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
23111 $ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
23112 $xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
23113 $yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
23114 $r = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
23115 $gradient['coords'][0] = $xa;
23116 $gradient['coords'][1] = $ya;
23117 $gradient['coords'][2] = $xb;
23118 $gradient['coords'][3] = $yb;
23119 $gradient['coords'][4] = $r;
23120 }
23121 // convert SVG coordinates to user units
23122 $gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
23123 $gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
23124 $gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
23125 $gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
23126 $gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
23127 if ($w <= $minlen) {
23128 $w = $minlen;
23129 }
23130 if ($h <= $minlen) {
23131 $h = $minlen;
23132 }
23133 // shift units
23134 if ($gradient['gradientUnits'] == 'objectBoundingBox') {
23135 // convert to SVG coordinate system
23136 $gradient['coords'][0] += $x;
23137 $gradient['coords'][1] += $y;
23138 $gradient['coords'][2] += $x;
23139 $gradient['coords'][3] += $y;
23140 }
23141 // calculate percentages
23142 $gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
23143 $gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
23144 $gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
23145 $gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
23146 $gradient['coords'][4] /= $w;
23147 } elseif ($gradient['mode'] == 'percentage') {
23148 foreach($gradient['coords'] as $key => $val) {
23149 $gradient['coords'][$key] = (intval($val) / 100);
23150 if ($val < 0) {
23151 $gradient['coords'][$key] = 0;
23152 } elseif ($val > 1) {
23153 $gradient['coords'][$key] = 1;
23154 }
23155 }
23156 }
23157 if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
23158 // single color (no shading)
23159 $gradient['coords'][0] = 1;
23160 $gradient['coords'][1] = 0;
23161 $gradient['coords'][2] = 0.999;
23162 $gradient['coords'][3] = 0;
23163 }
23164 // swap Y coordinates
23165 $tmp = $gradient['coords'][1];
23166 $gradient['coords'][1] = $gradient['coords'][3];
23167 $gradient['coords'][3] = $tmp;
23168 // set transformation map for gradient
23169 $cy = ($this->h - $y);
23170 if ($gradient['type'] == 3) {
23171 // circular gradient
23172 $cy -= ($gradient['coords'][1] * ($w + $h));
23173 $h = $w = max($w, $h);
23174 } else {
23175 $cy -= $h;
23176 }
23177 $this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), ($cy * $this->k)));
23178 if (count($gradient['stops']) > 1) {
23179 $this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
23180 }
23181 } elseif ($svgstyle['fill'] != 'none') {
23182 $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
23183 if ($svgstyle['fill-opacity'] != 1) {
23184 $this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
23185 }
23186 $this->SetFillColorArray($fill_color);
23187 if ($svgstyle['fill-rule'] == 'evenodd') {
23188 $objstyle .= 'F*';
23189 } else {
23190 $objstyle .= 'F';
23191 }
23192 }
23193 // stroke
23194 if ($svgstyle['stroke'] != 'none') {
23195 if ($svgstyle['stroke-opacity'] != 1) {
23196 $this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
23197 } elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['stroke'], $rgba_matches)) {
23198 $this->setAlpha($rgba_matches[1], 'Normal', $this->alpha['ca'], false);
23199 }
23200 $stroke_style = array(
23201 'color' => TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors),
23202 'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
23203 'cap' => $svgstyle['stroke-linecap'],
23204 'join' => $svgstyle['stroke-linejoin']
23205 );
23206 if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
23207 $stroke_style['dash'] = $svgstyle['stroke-dasharray'];
23208 }
23209 $this->SetLineStyle($stroke_style);
23210 $objstyle .= 'D';
23211 }
23212 // font
23213 $regs = array();
23214 if (!empty($svgstyle['font'])) {
23215 if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
23216 $font_family = $this->getFontFamilyName($regs[1]);
23217 } else {
23218 $font_family = $svgstyle['font-family'];
23219 }
23220 if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23221 $font_size = trim($regs[1]);
23222 } else {
23223 $font_size = $svgstyle['font-size'];
23224 }
23225 if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23226 $font_style = trim($regs[1]);
23227 } else {
23228 $font_style = $svgstyle['font-style'];
23229 }
23230 if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23231 $font_weight = trim($regs[1]);
23232 } else {
23233 $font_weight = $svgstyle['font-weight'];
23234 }
23235 if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23236 $font_stretch = trim($regs[1]);
23237 } else {
23238 $font_stretch = $svgstyle['font-stretch'];
23239 }
23240 if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23241 $font_spacing = trim($regs[1]);
23242 } else {
23243 $font_spacing = $svgstyle['letter-spacing'];
23244 }
23245 } else {
23246 $font_family = $this->getFontFamilyName($svgstyle['font-family']);
23247 $font_size = $svgstyle['font-size'];
23248 $font_style = $svgstyle['font-style'];
23249 $font_weight = $svgstyle['font-weight'];
23250 $font_stretch = $svgstyle['font-stretch'];
23251 $font_spacing = $svgstyle['letter-spacing'];
23252 }
23253 $font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit);
23254 $font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
23255 $font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
23256 switch ($font_style) {
23257 case 'italic': {
23258 $font_style = 'I';
23259 break;
23260 }
23261 case 'oblique': {
23262 $font_style = 'I';
23263 break;
23264 }
23265 default:
23266 case 'normal': {
23267 $font_style = '';
23268 break;
23269 }
23270 }
23271 switch ($font_weight) {
23272 case 'bold':
23273 case 'bolder': {
23274 $font_style .= 'B';
23275 break;
23276 }
23277 case 'normal': {
23278 if ((substr($font_family, -1) == 'I') AND (substr($font_family, -2, 1) == 'B')) {
23279 $font_family = substr($font_family, 0, -2).'I';
23280 } elseif (substr($font_family, -1) == 'B') {
23281 $font_family = substr($font_family, 0, -1);
23282 }
23283 break;
23284 }
23285 }
23286 switch ($svgstyle['text-decoration']) {
23287 case 'underline': {
23288 $font_style .= 'U';
23289 break;
23290 }
23291 case 'overline': {
23292 $font_style .= 'O';
23293 break;
23294 }
23295 case 'line-through': {
23296 $font_style .= 'D';
23297 break;
23298 }
23299 default:
23300 case 'none': {
23301 break;
23302 }
23303 }
23304 $this->SetFont($font_family, $font_style, $font_size);
23305 $this->setFontStretching($font_stretch);
23306 $this->setFontSpacing($font_spacing);
23307 return $objstyle;
23308 }
23309
23310 /**
23311 * Draws an SVG path
23312 * @param $d (string) attribute d of the path SVG element
23313 * @param $style (string) Style of rendering. Possible values are:
23314 * <ul>
23315 * <li>D or empty string: Draw (default).</li>
23316 * <li>F: Fill.</li>
23317 * <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23318 * <li>DF or FD: Draw and fill.</li>
23319 * <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23320 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
23321 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
23322 * </ul>
23323 * @return array of container box measures (x, y, w, h)
23324 * @author Nicola Asuni
23325 * @since 5.0.000 (2010-05-02)
23326 * @protected
23327 */
23328 protected function SVGPath($d, $style='') {
23329 if ($this->state != 2) {
23330 return;
23331 }
23332 // set fill/stroke style
23333 $op = TCPDF_STATIC::getPathPaintOperator($style, '');
23334 if (empty($op)) {
23335 return;
23336 }
23337 $paths = array();
23338 $d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
23339 preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
23340 $x = 0;
23341 $y = 0;
23342 $x1 = 0;
23343 $y1 = 0;
23344 $x2 = 0;
23345 $y2 = 0;
23346 $xmin = 2147483647;
23347 $xmax = 0;
23348 $ymin = 2147483647;
23349 $ymax = 0;
23350 $relcoord = false;
23351 $minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
23352 $firstcmd = true; // used to print first point
23353 // draw curve pieces
23354 foreach ($paths as $key => $val) {
23355 // get curve type
23356 $cmd = trim($val[1]);
23357 if (strtolower($cmd) == $cmd) {
23358 // use relative coordinated instead of absolute
23359 $relcoord = true;
23360 $xoffset = $x;
23361 $yoffset = $y;
23362 } else {
23363 $relcoord = false;
23364 $xoffset = 0;
23365 $yoffset = 0;
23366 }
23367 $params = array();
23368 if (isset($val[2])) {
23369 // get curve parameters
23370 $rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
23371 $params = array();
23372 foreach ($rawparams as $ck => $cp) {
23373 $params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
23374 if (abs($params[$ck]) < $minlen) {
23375 // approximate little values to zero
23376 $params[$ck] = 0;
23377 }
23378 }
23379 }
23380 // store current origin point
23381 $x0 = $x;
23382 $y0 = $y;
23383 switch (strtoupper($cmd)) {
23384 case 'M': { // moveto
23385 foreach ($params as $ck => $cp) {
23386 if (($ck % 2) == 0) {
23387 $x = $cp + $xoffset;
23388 } else {
23389 $y = $cp + $yoffset;
23390 if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23391 if ($ck == 1) {
23392 $this->_outPoint($x, $y);
23393 $firstcmd = false;
23394 } else {
23395 $this->_outLine($x, $y);
23396 }
23397 $x0 = $x;
23398 $y0 = $y;
23399 }
23400 $xmin = min($xmin, $x);
23401 $ymin = min($ymin, $y);
23402 $xmax = max($xmax, $x);
23403 $ymax = max($ymax, $y);
23404 if ($relcoord) {
23405 $xoffset = $x;
23406 $yoffset = $y;
23407 }
23408 }
23409 }
23410 break;
23411 }
23412 case 'L': { // lineto
23413 foreach ($params as $ck => $cp) {
23414 if (($ck % 2) == 0) {
23415 $x = $cp + $xoffset;
23416 } else {
23417 $y = $cp + $yoffset;
23418 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23419 $this->_outLine($x, $y);
23420 $x0 = $x;
23421 $y0 = $y;
23422 }
23423 $xmin = min($xmin, $x);
23424 $ymin = min($ymin, $y);
23425 $xmax = max($xmax, $x);
23426 $ymax = max($ymax, $y);
23427 if ($relcoord) {
23428 $xoffset = $x;
23429 $yoffset = $y;
23430 }
23431 }
23432 }
23433 break;
23434 }
23435 case 'H': { // horizontal lineto
23436 foreach ($params as $ck => $cp) {
23437 $x = $cp + $xoffset;
23438 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23439 $this->_outLine($x, $y);
23440 $x0 = $x;
23441 $y0 = $y;
23442 }
23443 $xmin = min($xmin, $x);
23444 $xmax = max($xmax, $x);
23445 if ($relcoord) {
23446 $xoffset = $x;
23447 }
23448 }
23449 break;
23450 }
23451 case 'V': { // vertical lineto
23452 foreach ($params as $ck => $cp) {
23453 $y = $cp + $yoffset;
23454 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23455 $this->_outLine($x, $y);
23456 $x0 = $x;
23457 $y0 = $y;
23458 }
23459 $ymin = min($ymin, $y);
23460 $ymax = max($ymax, $y);
23461 if ($relcoord) {
23462 $yoffset = $y;
23463 }
23464 }
23465 break;
23466 }
23467 case 'C': { // curveto
23468 foreach ($params as $ck => $cp) {
23469 $params[$ck] = $cp;
23470 if ((($ck + 1) % 6) == 0) {
23471 $x1 = $params[($ck - 5)] + $xoffset;
23472 $y1 = $params[($ck - 4)] + $yoffset;
23473 $x2 = $params[($ck - 3)] + $xoffset;
23474 $y2 = $params[($ck - 2)] + $yoffset;
23475 $x = $params[($ck - 1)] + $xoffset;
23476 $y = $params[($ck)] + $yoffset;
23477 $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23478 $xmin = min($xmin, $x, $x1, $x2);
23479 $ymin = min($ymin, $y, $y1, $y2);
23480 $xmax = max($xmax, $x, $x1, $x2);
23481 $ymax = max($ymax, $y, $y1, $y2);
23482 if ($relcoord) {
23483 $xoffset = $x;
23484 $yoffset = $y;
23485 }
23486 }
23487 }
23488 break;
23489 }
23490 case 'S': { // shorthand/smooth curveto
23491 foreach ($params as $ck => $cp) {
23492 $params[$ck] = $cp;
23493 if ((($ck + 1) % 4) == 0) {
23494 if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
23495 $x1 = (2 * $x) - $x2;
23496 $y1 = (2 * $y) - $y2;
23497 } else {
23498 $x1 = $x;
23499 $y1 = $y;
23500 }
23501 $x2 = $params[($ck - 3)] + $xoffset;
23502 $y2 = $params[($ck - 2)] + $yoffset;
23503 $x = $params[($ck - 1)] + $xoffset;
23504 $y = $params[($ck)] + $yoffset;
23505 $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23506 $xmin = min($xmin, $x, $x1, $x2);
23507 $ymin = min($ymin, $y, $y1, $y2);
23508 $xmax = max($xmax, $x, $x1, $x2);
23509 $ymax = max($ymax, $y, $y1, $y2);
23510 if ($relcoord) {
23511 $xoffset = $x;
23512 $yoffset = $y;
23513 }
23514 }
23515 }
23516 break;
23517 }
23518 case 'Q': { // quadratic Bezier curveto
23519 foreach ($params as $ck => $cp) {
23520 $params[$ck] = $cp;
23521 if ((($ck + 1) % 4) == 0) {
23522 // convert quadratic points to cubic points
23523 $x1 = $params[($ck - 3)] + $xoffset;
23524 $y1 = $params[($ck - 2)] + $yoffset;
23525 $xa = ($x + (2 * $x1)) / 3;
23526 $ya = ($y + (2 * $y1)) / 3;
23527 $x = $params[($ck - 1)] + $xoffset;
23528 $y = $params[($ck)] + $yoffset;
23529 $xb = ($x + (2 * $x1)) / 3;
23530 $yb = ($y + (2 * $y1)) / 3;
23531 $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23532 $xmin = min($xmin, $x, $xa, $xb);
23533 $ymin = min($ymin, $y, $ya, $yb);
23534 $xmax = max($xmax, $x, $xa, $xb);
23535 $ymax = max($ymax, $y, $ya, $yb);
23536 if ($relcoord) {
23537 $xoffset = $x;
23538 $yoffset = $y;
23539 }
23540 }
23541 }
23542 break;
23543 }
23544 case 'T': { // shorthand/smooth quadratic Bezier curveto
23545 foreach ($params as $ck => $cp) {
23546 $params[$ck] = $cp;
23547 if (($ck % 2) != 0) {
23548 if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
23549 $x1 = (2 * $x) - $x1;
23550 $y1 = (2 * $y) - $y1;
23551 } else {
23552 $x1 = $x;
23553 $y1 = $y;
23554 }
23555 // convert quadratic points to cubic points
23556 $xa = ($x + (2 * $x1)) / 3;
23557 $ya = ($y + (2 * $y1)) / 3;
23558 $x = $params[($ck - 1)] + $xoffset;
23559 $y = $params[($ck)] + $yoffset;
23560 $xb = ($x + (2 * $x1)) / 3;
23561 $yb = ($y + (2 * $y1)) / 3;
23562 $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23563 $xmin = min($xmin, $x, $xa, $xb);
23564 $ymin = min($ymin, $y, $ya, $yb);
23565 $xmax = max($xmax, $x, $xa, $xb);
23566 $ymax = max($ymax, $y, $ya, $yb);
23567 if ($relcoord) {
23568 $xoffset = $x;
23569 $yoffset = $y;
23570 }
23571 }
23572 }
23573 break;
23574 }
23575 case 'A': { // elliptical arc
23576 foreach ($params as $ck => $cp) {
23577 $params[$ck] = $cp;
23578 if ((($ck + 1) % 7) == 0) {
23579 $x0 = $x;
23580 $y0 = $y;
23581 $rx = abs($params[($ck - 6)]);
23582 $ry = abs($params[($ck - 5)]);
23583 $ang = -$rawparams[($ck - 4)];
23584 $angle = deg2rad($ang);
23585 $fa = $rawparams[($ck - 3)]; // large-arc-flag
23586 $fs = $rawparams[($ck - 2)]; // sweep-flag
23587 $x = $params[($ck - 1)] + $xoffset;
23588 $y = $params[$ck] + $yoffset;
23589 if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
23590 // endpoints are almost identical
23591 $xmin = min($xmin, $x);
23592 $ymin = min($ymin, $y);
23593 $xmax = max($xmax, $x);
23594 $ymax = max($ymax, $y);
23595 } else {
23596 $cos_ang = cos($angle);
23597 $sin_ang = sin($angle);
23598 $a = (($x0 - $x) / 2);
23599 $b = (($y0 - $y) / 2);
23600 $xa = ($a * $cos_ang) - ($b * $sin_ang);
23601 $ya = ($a * $sin_ang) + ($b * $cos_ang);
23602 $rx2 = $rx * $rx;
23603 $ry2 = $ry * $ry;
23604 $xa2 = $xa * $xa;
23605 $ya2 = $ya * $ya;
23606 $delta = ($xa2 / $rx2) + ($ya2 / $ry2);
23607 if ($delta > 1) {
23608 $rx *= sqrt($delta);
23609 $ry *= sqrt($delta);
23610 $rx2 = $rx * $rx;
23611 $ry2 = $ry * $ry;
23612 }
23613 $numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
23614 if ($numerator < 0) {
23615 $root = 0;
23616 } else {
23617 $root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
23618 }
23619 if ($fa == $fs){
23620 $root *= -1;
23621 }
23622 $cax = $root * (($rx * $ya) / $ry);
23623 $cay = -$root * (($ry * $xa) / $rx);
23624 // coordinates of ellipse center
23625 $cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
23626 $cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
23627 // get angles
23628 $angs = TCPDF_STATIC::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
23629 $dang = TCPDF_STATIC::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
23630 if (($fs == 0) AND ($dang > 0)) {
23631 $dang -= (2 * M_PI);
23632 } elseif (($fs == 1) AND ($dang < 0)) {
23633 $dang += (2 * M_PI);
23634 }
23635 $angf = $angs - $dang;
23636 if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
23637 // reverse angles
23638 $tmp = $angs;
23639 $angs = $angf;
23640 $angf = $tmp;
23641 }
23642 $angs = round(rad2deg($angs), 6);
23643 $angf = round(rad2deg($angf), 6);
23644 // covent angles to positive values
23645 if (($angs < 0) AND ($angf < 0)) {
23646 $angs += 360;
23647 $angf += 360;
23648 }
23649 $pie = false;
23650 if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
23651 $pie = true;
23652 }
23653 list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
23654 $xmin = min($xmin, $x, $axmin);
23655 $ymin = min($ymin, $y, $aymin);
23656 $xmax = max($xmax, $x, $axmax);
23657 $ymax = max($ymax, $y, $aymax);
23658 }
23659 if ($relcoord) {
23660 $xoffset = $x;
23661 $yoffset = $y;
23662 }
23663 }
23664 }
23665 break;
23666 }
23667 case 'Z': {
23668 $this->_out('h');
23669 break;
23670 }
23671 }
23672 $firstcmd = false;
23673 } // end foreach
23674 if (!empty($op)) {
23675 $this->_out($op);
23676 }
23677 return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
23678 }
23679
23680 /**
23681 * Return the tag name without the namespace
23682 * @param $name (string) Tag name
23683 * @protected
23684 */
23685 protected function removeTagNamespace($name) {
23686 if(strpos($name, ':') !== false) {
23687 $parts = explode(':', $name);
23688 return $parts[(sizeof($parts) - 1)];
23689 }
23690 return $name;
23691 }
23692
23693 /**
23694 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
23695 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
23696 * @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.
23697 * @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.
23698 * @param $ctm (array) tranformation matrix for clipping mode (starting transformation matrix).
23699 * @author Nicola Asuni
23700 * @since 5.0.000 (2010-05-02)
23701 * @protected
23702 */
23703 protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
23704 $name = $this->removeTagNamespace($name);
23705 // check if we are in clip mode
23706 if ($this->svgclipmode) {
23707 $this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
23708 return;
23709 }
23710 if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
23711 if (isset($attribs['id'])) {
23712 $attribs['child_elements'] = array();
23713 $this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23714 return;
23715 }
23716 if (end($this->svgdefs) !== FALSE) {
23717 $last_svgdefs_id = key($this->svgdefs);
23718 if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
23719 $attribs['id'] = 'DF_'.(count($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements']) + 1);
23720 $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23721 return;
23722 }
23723 }
23724 return;
23725 }
23726 $clipping = false;
23727 if ($parser == 'clip-path') {
23728 // set clipping mode
23729 $clipping = true;
23730 }
23731 // get styling properties
23732 $prev_svgstyle = $this->svgstyles[max(0,(count($this->svgstyles) - 1))]; // previous style
23733 $svgstyle = $this->svgstyles[0]; // set default style
23734 if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
23735 // default fill attribute for clipping
23736 $attribs['fill'] = 'none';
23737 }
23738 if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
23739 // fix style for regular expression
23740 $attribs['style'] = ';'.$attribs['style'];
23741 }
23742 foreach ($prev_svgstyle as $key => $val) {
23743 if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
23744 // inherit previous value
23745 $svgstyle[$key] = $val;
23746 }
23747 if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
23748 // specific attribute settings
23749 if ($attribs[$key] == 'inherit') {
23750 $svgstyle[$key] = $val;
23751 } else {
23752 $svgstyle[$key] = $attribs[$key];
23753 }
23754 } elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
23755 // CSS style syntax
23756 $attrval = array();
23757 if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
23758 if ($attrval[1] == 'inherit') {
23759 $svgstyle[$key] = $val;
23760 } else {
23761 $svgstyle[$key] = $attrval[1];
23762 }
23763 }
23764 }
23765 }
23766 // transformation matrix
23767 if (!empty($ctm)) {
23768 $tm = $ctm;
23769 } else {
23770 $tm = array(1,0,0,1,0,0);
23771 }
23772 if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
23773 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
23774 }
23775 $svgstyle['transfmatrix'] = $tm;
23776 $invisible = false;
23777 if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
23778 // the current graphics element is invisible (nothing is painted)
23779 $invisible = true;
23780 }
23781 // process tag
23782 switch($name) {
23783 case 'defs': {
23784 $this->svgdefsmode = true;
23785 break;
23786 }
23787 // clipPath
23788 case 'clipPath': {
23789 if ($invisible) {
23790 break;
23791 }
23792 $this->svgclipmode = true;
23793 if (!isset($attribs['id'])) {
23794 $attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
23795 }
23796 $this->svgclipid = $attribs['id'];
23797 $this->svgclippaths[$this->svgclipid] = array();
23798 $this->svgcliptm[$this->svgclipid] = $tm;
23799 break;
23800 }
23801 case 'svg': {
23802 // start of SVG object
23803 if(++$this->svg_tag_depth <= 1) {
23804 break;
23805 }
23806 // inner SVG
23807 array_push($this->svgstyles, $svgstyle);
23808 $this->StartTransform();
23809 $svgX = (isset($attribs['x'])?$attribs['x']:0);
23810 $svgY = (isset($attribs['y'])?$attribs['y']:0);
23811 $svgW = (isset($attribs['width'])?$attribs['width']:0);
23812 $svgH = (isset($attribs['height'])?$attribs['height']:0);
23813 // set x, y position using transform matrix
23814 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array( 1, 0, 0, 1, $svgX, $svgY));
23815 $this->SVGTransform($tm);
23816 // set clipping for width and height
23817 $x = 0;
23818 $y = 0;
23819 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):$this->w);
23820 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):$this->h);
23821 // draw clipping rect
23822 $this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
23823 // parse viewbox, calculate extra transformation matrix
23824 if (isset($attribs['viewBox'])) {
23825 $tmp = array();
23826 preg_match_all("/[0-9]+/", $attribs['viewBox'], $tmp);
23827 $tmp = $tmp[0];
23828 if (sizeof($tmp) == 4) {
23829 $vx = $tmp[0];
23830 $vy = $tmp[1];
23831 $vw = $tmp[2];
23832 $vh = $tmp[3];
23833 // get aspect ratio
23834 $tmp = array();
23835 $aspectX = 'xMid';
23836 $aspectY = 'YMid';
23837 $fit = 'meet';
23838 if (isset($attribs['preserveAspectRatio'])) {
23839 if($attribs['preserveAspectRatio'] == 'none') {
23840 $fit = 'none';
23841 } else {
23842 preg_match_all('/[a-zA-Z]+/', $attribs['preserveAspectRatio'], $tmp);
23843 $tmp = $tmp[0];
23844 if ((sizeof($tmp) == 2) AND (strlen($tmp[0]) == 8) AND (in_array($tmp[1], array('meet', 'slice', 'none')))) {
23845 $aspectX = substr($tmp[0], 0, 4);
23846 $aspectY = substr($tmp[0], 4, 4);
23847 $fit = $tmp[1];
23848 }
23849 }
23850 }
23851 $wr = ($svgW / $vw);
23852 $hr = ($svgH / $vh);
23853 $ax = $ay = 0;
23854 if ((($fit == 'meet') AND ($hr < $wr)) OR (($fit == 'slice') AND ($hr > $wr))) {
23855 if ($aspectX == 'xMax') {
23856 $ax = (($vw * ($wr / $hr)) - $vw);
23857 }
23858 if ($aspectX == 'xMid') {
23859 $ax = ((($vw * ($wr / $hr)) - $vw) / 2);
23860 }
23861 $wr = $hr;
23862 } elseif ((($fit == 'meet') AND ($hr > $wr)) OR (($fit == 'slice') AND ($hr < $wr))) {
23863 if ($aspectY == 'YMax') {
23864 $ay = (($vh * ($hr / $wr)) - $vh);
23865 }
23866 if ($aspectY == 'YMid') {
23867 $ay = ((($vh * ($hr / $wr)) - $vh) / 2);
23868 }
23869 $hr = $wr;
23870 }
23871 $newtm = array($wr, 0, 0, $hr, (($wr * ($ax - $vx)) - $svgX), (($hr * ($ay - $vy)) - $svgY));
23872 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, $newtm);
23873 $this->SVGTransform($tm);
23874 }
23875 }
23876 $this->setSVGStyles($svgstyle, $prev_svgstyle);
23877 break;
23878 }
23879 case 'g': {
23880 // group together related graphics elements
23881 array_push($this->svgstyles, $svgstyle);
23882 $this->StartTransform();
23883 $x = (isset($attribs['x'])?$attribs['x']:0);
23884 $y = (isset($attribs['y'])?$attribs['y']:0);
23885 $w = 1;//(isset($attribs['width'])?$attribs['width']:1);
23886 $h = 1;//(isset($attribs['height'])?$attribs['height']:1);
23887 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
23888 $this->SVGTransform($tm);
23889 $this->setSVGStyles($svgstyle, $prev_svgstyle);
23890 break;
23891 }
23892 case 'linearGradient': {
23893 if ($this->pdfa_mode) {
23894 break;
23895 }
23896 if (!isset($attribs['id'])) {
23897 $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
23898 }
23899 $this->svggradientid = $attribs['id'];
23900 $this->svggradients[$this->svggradientid] = array();
23901 $this->svggradients[$this->svggradientid]['type'] = 2;
23902 $this->svggradients[$this->svggradientid]['stops'] = array();
23903 if (isset($attribs['gradientUnits'])) {
23904 $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
23905 } else {
23906 $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
23907 }
23908 //$attribs['spreadMethod']
23909 if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
23910 OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
23911 OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
23912 OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
23913 OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
23914 $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
23915 } else {
23916 $this->svggradients[$this->svggradientid]['mode'] = 'measure';
23917 }
23918 $x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
23919 $y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
23920 $x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
23921 $y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
23922 if (isset($attribs['gradientTransform'])) {
23923 $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
23924 }
23925 $this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
23926 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
23927 // gradient is defined on another place
23928 $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
23929 }
23930 break;
23931 }
23932 case 'radialGradient': {
23933 if ($this->pdfa_mode) {
23934 break;
23935 }
23936 if (!isset($attribs['id'])) {
23937 $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
23938 }
23939 $this->svggradientid = $attribs['id'];
23940 $this->svggradients[$this->svggradientid] = array();
23941 $this->svggradients[$this->svggradientid]['type'] = 3;
23942 $this->svggradients[$this->svggradientid]['stops'] = array();
23943 if (isset($attribs['gradientUnits'])) {
23944 $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
23945 } else {
23946 $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
23947 }
23948 //$attribs['spreadMethod']
23949 if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
23950 OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
23951 OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')))) {
23952 $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
23953 } elseif (isset($attribs['r']) AND is_numeric($attribs['r']) AND ($attribs['r']) <= 1) {
23954 $this->svggradients[$this->svggradientid]['mode'] = 'ratio';
23955 } else {
23956 $this->svggradients[$this->svggradientid]['mode'] = 'measure';
23957 }
23958 $cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
23959 $cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
23960 $fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
23961 $fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
23962 $r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
23963 if (isset($attribs['gradientTransform'])) {
23964 $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
23965 }
23966 $this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
23967 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
23968 // gradient is defined on another place
23969 $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
23970 }
23971 break;
23972 }
23973 case 'stop': {
23974 // gradient stops
23975 if (substr($attribs['offset'], -1) == '%') {
23976 $offset = floatval(substr($attribs['offset'], -1)) / 100;
23977 } else {
23978 $offset = floatval($attribs['offset']);
23979 if ($offset > 1) {
23980 $offset /= 100;
23981 }
23982 }
23983 $stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
23984 $opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
23985 $this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
23986 break;
23987 }
23988 // paths
23989 case 'path': {
23990 if ($invisible) {
23991 break;
23992 }
23993 if (isset($attribs['d'])) {
23994 $d = trim($attribs['d']);
23995 if (!empty($d)) {
23996 $x = (isset($attribs['x'])?$attribs['x']:0);
23997 $y = (isset($attribs['y'])?$attribs['y']:0);
23998 $w = (isset($attribs['width'])?$attribs['width']:1);
23999 $h = (isset($attribs['height'])?$attribs['height']:1);
24000 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24001 if ($clipping) {
24002 $this->SVGTransform($tm);
24003 $this->SVGPath($d, 'CNZ');
24004 } else {
24005 $this->StartTransform();
24006 $this->SVGTransform($tm);
24007 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
24008 if (!empty($obstyle)) {
24009 $this->SVGPath($d, $obstyle);
24010 }
24011 $this->StopTransform();
24012 }
24013 }
24014 }
24015 break;
24016 }
24017 // shapes
24018 case 'rect': {
24019 if ($invisible) {
24020 break;
24021 }
24022 $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24023 $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24024 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24025 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24026 $rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
24027 $ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
24028 if ($clipping) {
24029 $this->SVGTransform($tm);
24030 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
24031 } else {
24032 $this->StartTransform();
24033 $this->SVGTransform($tm);
24034 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
24035 if (!empty($obstyle)) {
24036 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
24037 }
24038 $this->StopTransform();
24039 }
24040 break;
24041 }
24042 case 'circle': {
24043 if ($invisible) {
24044 break;
24045 }
24046 $r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
24047 $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24048 $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24049 $x = ($cx - $r);
24050 $y = ($cy - $r);
24051 $w = (2 * $r);
24052 $h = $w;
24053 if ($clipping) {
24054 $this->SVGTransform($tm);
24055 $this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
24056 } else {
24057 $this->StartTransform();
24058 $this->SVGTransform($tm);
24059 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
24060 if (!empty($obstyle)) {
24061 $this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
24062 }
24063 $this->StopTransform();
24064 }
24065 break;
24066 }
24067 case 'ellipse': {
24068 if ($invisible) {
24069 break;
24070 }
24071 $rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
24072 $ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
24073 $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24074 $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24075 $x = ($cx - $rx);
24076 $y = ($cy - $ry);
24077 $w = (2 * $rx);
24078 $h = (2 * $ry);
24079 if ($clipping) {
24080 $this->SVGTransform($tm);
24081 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
24082 } else {
24083 $this->StartTransform();
24084 $this->SVGTransform($tm);
24085 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
24086 if (!empty($obstyle)) {
24087 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
24088 }
24089 $this->StopTransform();
24090 }
24091 break;
24092 }
24093 case 'line': {
24094 if ($invisible) {
24095 break;
24096 }
24097 $x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
24098 $y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
24099 $x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
24100 $y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
24101 $x = $x1;
24102 $y = $y1;
24103 $w = abs($x2 - $x1);
24104 $h = abs($y2 - $y1);
24105 if (!$clipping) {
24106 $this->StartTransform();
24107 $this->SVGTransform($tm);
24108 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
24109 $this->Line($x1, $y1, $x2, $y2);
24110 $this->StopTransform();
24111 }
24112 break;
24113 }
24114 case 'polyline':
24115 case 'polygon': {
24116 if ($invisible) {
24117 break;
24118 }
24119 $points = (isset($attribs['points'])?$attribs['points']:'0 0');
24120 $points = trim($points);
24121 // note that point may use a complex syntax not covered here
24122 $points = preg_split('/[\,\s]+/si', $points);
24123 if (count($points) < 4) {
24124 break;
24125 }
24126 $p = array();
24127 $xmin = 2147483647;
24128 $xmax = 0;
24129 $ymin = 2147483647;
24130 $ymax = 0;
24131 foreach ($points as $key => $val) {
24132 $p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24133 if (($key % 2) == 0) {
24134 // X coordinate
24135 $xmin = min($xmin, $p[$key]);
24136 $xmax = max($xmax, $p[$key]);
24137 } else {
24138 // Y coordinate
24139 $ymin = min($ymin, $p[$key]);
24140 $ymax = max($ymax, $p[$key]);
24141 }
24142 }
24143 $x = $xmin;
24144 $y = $ymin;
24145 $w = ($xmax - $xmin);
24146 $h = ($ymax - $ymin);
24147 if ($name == 'polyline') {
24148 $this->StartTransform();
24149 $this->SVGTransform($tm);
24150 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
24151 if (!empty($obstyle)) {
24152 $this->PolyLine($p, $obstyle, array(), array());
24153 }
24154 $this->StopTransform();
24155 } else { // polygon
24156 if ($clipping) {
24157 $this->SVGTransform($tm);
24158 $this->Polygon($p, 'CNZ', array(), array(), true);
24159 } else {
24160 $this->StartTransform();
24161 $this->SVGTransform($tm);
24162 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
24163 if (!empty($obstyle)) {
24164 $this->Polygon($p, $obstyle, array(), array(), true);
24165 }
24166 $this->StopTransform();
24167 }
24168 }
24169 break;
24170 }
24171 // image
24172 case 'image': {
24173 if ($invisible) {
24174 break;
24175 }
24176 if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
24177 break;
24178 }
24179 $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24180 $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24181 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24182 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24183 $img = $attribs['xlink:href'];
24184 if (!$clipping) {
24185 $this->StartTransform();
24186 $this->SVGTransform($tm);
24187 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
24188 if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
24189 // embedded image encoded as base64
24190 $img = '@'.base64_decode(substr($img, strlen($m[0])));
24191 } else {
24192 // fix image path
24193 if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
24194 // replace relative path with full server path
24195 $img = $this->svgdir.'/'.$img;
24196 }
24197 if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
24198 $findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
24199 if (($findroot === false) OR ($findroot > 1)) {
24200 if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
24201 $img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
24202 } else {
24203 $img = $_SERVER['DOCUMENT_ROOT'].$img;
24204 }
24205 }
24206 }
24207 $img = urldecode($img);
24208 $testscrtype = @parse_url($img);
24209 if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
24210 // convert URL to server path
24211 $img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
24212 }
24213 }
24214 // get image type
24215 $imgtype = TCPDF_IMAGES::getImageFileType($img);
24216 if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
24217 $this->ImageEps($img, $x, $y, $w, $h);
24218 } elseif ($imgtype == 'svg') {
24219 // store SVG vars
24220 $svggradients = $this->svggradients;
24221 $svggradientid = $this->svggradientid;
24222 $svgdefsmode = $this->svgdefsmode;
24223 $svgdefs = $this->svgdefs;
24224 $svgclipmode = $this->svgclipmode;
24225 $svgclippaths = $this->svgclippaths;
24226 $svgcliptm = $this->svgcliptm;
24227 $svgclipid = $this->svgclipid;
24228 $svgtext = $this->svgtext;
24229 $svgtextmode = $this->svgtextmode;
24230 $this->ImageSVG($img, $x, $y, $w, $h);
24231 // restore SVG vars
24232 $this->svggradients = $svggradients;
24233 $this->svggradientid = $svggradientid;
24234 $this->svgdefsmode = $svgdefsmode;
24235 $this->svgdefs = $svgdefs;
24236 $this->svgclipmode = $svgclipmode;
24237 $this->svgclippaths = $svgclippaths;
24238 $this->svgcliptm = $svgcliptm;
24239 $this->svgclipid = $svgclipid;
24240 $this->svgtext = $svgtext;
24241 $this->svgtextmode = $svgtextmode;
24242 } else {
24243 $this->Image($img, $x, $y, $w, $h);
24244 }
24245 $this->StopTransform();
24246 }
24247 break;
24248 }
24249 // text
24250 case 'text':
24251 case 'tspan': {
24252 if (isset($this->svgtextmode['text-anchor']) AND !empty($this->svgtext)) {
24253 // @TODO: unsupported feature
24254 }
24255 // only basic support - advanced features must be implemented
24256 $this->svgtextmode['invisible'] = $invisible;
24257 if ($invisible) {
24258 break;
24259 }
24260 array_push($this->svgstyles, $svgstyle);
24261 if (isset($attribs['x'])) {
24262 $x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
24263 } elseif ($name == 'tspan') {
24264 $x = $this->x;
24265 } else {
24266 $x = 0;
24267 }
24268 if (isset($attribs['dx'])) {
24269 $x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
24270 }
24271 if (isset($attribs['y'])) {
24272 $y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
24273 } elseif ($name == 'tspan') {
24274 $y = $this->y;
24275 } else {
24276 $y = 0;
24277 }
24278 if (isset($attribs['dy'])) {
24279 $y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
24280 }
24281 $svgstyle['text-color'] = $svgstyle['fill'];
24282 $this->svgtext = '';
24283 if (isset($svgstyle['text-anchor'])) {
24284 $this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
24285 } else {
24286 $this->svgtextmode['text-anchor'] = 'start';
24287 }
24288 if (isset($svgstyle['direction'])) {
24289 if ($svgstyle['direction'] == 'rtl') {
24290 $this->svgtextmode['rtl'] = true;
24291 } else {
24292 $this->svgtextmode['rtl'] = false;
24293 }
24294 } else {
24295 $this->svgtextmode['rtl'] = false;
24296 }
24297 if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
24298 $this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
24299 } else {
24300 $this->svgtextmode['stroke'] = false;
24301 }
24302 $this->StartTransform();
24303 $this->SVGTransform($tm);
24304 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
24305 $this->x = $x;
24306 $this->y = $y;
24307 break;
24308 }
24309 // use
24310 case 'use': {
24311 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24312 $svgdefid = substr($attribs['xlink:href'], 1);
24313 if (isset($this->svgdefs[$svgdefid])) {
24314 $use = $this->svgdefs[$svgdefid];
24315 if (isset($attribs['xlink:href'])) {
24316 unset($attribs['xlink:href']);
24317 }
24318 if (isset($attribs['id'])) {
24319 unset($attribs['id']);
24320 }
24321 if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
24322 $attribs['x'] += $use['attribs']['x'];
24323 }
24324 if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
24325 $attribs['y'] += $use['attribs']['y'];
24326 }
24327 if (empty($attribs['style'])) {
24328 $attribs['style'] = '';
24329 }
24330 if (!empty($use['attribs']['style'])) {
24331 // merge styles
24332 $attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
24333 }
24334 $attribs = array_merge($use['attribs'], $attribs);
24335 $this->startSVGElementHandler($parser, $use['name'], $attribs);
24336 return;
24337 }
24338 }
24339 break;
24340 }
24341 default: {
24342 break;
24343 }
24344 } // end of switch
24345 // process child elements
24346 if (!empty($attribs['child_elements'])) {
24347 $child_elements = $attribs['child_elements'];
24348 unset($attribs['child_elements']);
24349 foreach($child_elements as $child_element) {
24350 if (empty($child_element['attribs']['closing_tag'])) {
24351 $this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
24352 } else {
24353 if (isset($child_element['attribs']['content'])) {
24354 $this->svgtext = $child_element['attribs']['content'];
24355 }
24356 $this->endSVGElementHandler('child-tag', $child_element['name']);
24357 }
24358 }
24359 }
24360 }
24361
24362 /**
24363 * Sets the closing SVG element handler function for the XML parser.
24364 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24365 * @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.
24366 * @author Nicola Asuni
24367 * @since 5.0.000 (2010-05-02)
24368 * @protected
24369 */
24370 protected function endSVGElementHandler($parser, $name) {
24371 $name = $this->removeTagNamespace($name);
24372 if ($this->svgdefsmode AND !in_array($name, array('defs', 'clipPath', 'linearGradient', 'radialGradient', 'stop'))) {;
24373 if (end($this->svgdefs) !== FALSE) {
24374 $last_svgdefs_id = key($this->svgdefs);
24375 if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
24376 foreach($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'] as $child_element) {
24377 if (isset($child_element['attribs']['id']) AND ($child_element['name'] == $name)) {
24378 $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$child_element['attribs']['id'].'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24379 return;
24380 }
24381 }
24382 if ($this->svgdefs[$last_svgdefs_id]['name'] == $name) {
24383 $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$last_svgdefs_id.'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24384 return;
24385 }
24386 }
24387 }
24388 return;
24389 }
24390 switch($name) {
24391 case 'defs': {
24392 $this->svgdefsmode = false;
24393 break;
24394 }
24395 // clipPath
24396 case 'clipPath': {
24397 $this->svgclipmode = false;
24398 break;
24399 }
24400 case 'svg': {
24401 if (--$this->svg_tag_depth <= 0) {
24402 break;
24403 }
24404 }
24405 case 'g': {
24406 // ungroup: remove last style from array
24407 array_pop($this->svgstyles);
24408 $this->StopTransform();
24409 break;
24410 }
24411 case 'text':
24412 case 'tspan': {
24413 if ($this->svgtextmode['invisible']) {
24414 // This implementation must be fixed to following the rule:
24415 // 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.
24416 break;
24417 }
24418 // print text
24419 $text = $this->svgtext;
24420 //$text = $this->stringTrim($text);
24421 $textlen = $this->GetStringWidth($text);
24422 if ($this->svgtextmode['text-anchor'] != 'start') {
24423 // check if string is RTL text
24424 if ($this->svgtextmode['text-anchor'] == 'end') {
24425 if ($this->svgtextmode['rtl']) {
24426 $this->x += $textlen;
24427 } else {
24428 $this->x -= $textlen;
24429 }
24430 } elseif ($this->svgtextmode['text-anchor'] == 'middle') {
24431 if ($this->svgtextmode['rtl']) {
24432 $this->x += ($textlen / 2);
24433 } else {
24434 $this->x -= ($textlen / 2);
24435 }
24436 }
24437 }
24438 $textrendermode = $this->textrendermode;
24439 $textstrokewidth = $this->textstrokewidth;
24440 $this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
24441 if ($name == 'text') {
24442 // store current coordinates
24443 $tmpx = $this->x;
24444 $tmpy = $this->y;
24445 }
24446 // print the text
24447 $this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
24448 if ($name == 'text') {
24449 // restore coordinates
24450 $this->x = $tmpx;
24451 $this->y = $tmpy;
24452 }
24453 // restore previous rendering mode
24454 $this->textrendermode = $textrendermode;
24455 $this->textstrokewidth = $textstrokewidth;
24456 $this->svgtext = '';
24457 $this->StopTransform();
24458 if (!$this->svgdefsmode) {
24459 array_pop($this->svgstyles);
24460 }
24461 break;
24462 }
24463 default: {
24464 break;
24465 }
24466 }
24467 }
24468
24469 /**
24470 * Sets the character data handler function for the XML parser.
24471 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24472 * @param $data (string) The second parameter, data, contains the character data as a string.
24473 * @author Nicola Asuni
24474 * @since 5.0.000 (2010-05-02)
24475 * @protected
24476 */
24477 protected function segSVGContentHandler($parser, $data) {
24478 $this->svgtext .= $data;
24479 }
24480
24481 // --- END SVG METHODS -----------------------------------------------------
24482
24483 } // END OF TCPDF CLASS
24484
24485 //============================================================+
24486 // END OF FILE
24487 //============================================================+
24488