PluginProbe
Welcart e-Commerce / 2.12.4
Welcart e-Commerce v2.12.4
2.12.4 2.12.3 2.11.35 2.12.2 2.12.1 2.11.34 2.11.33 2.11.32 2.11.31 2.11.30 1.3.16 1.3.17 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 All 292 releases
usc-e-shop / pdf / tcpdf / tcpdf.php

tcpdf.php in Welcart e-Commerce 2.12.4, at pdf/tcpdf/tcpdf.php

24,586 lines 904.3 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.3.2
5 // Begin : 2002-08-03
6 // Last Update : 2019-09-20
7 // Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
8 // License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
9 // -------------------------------------------------------------------
10 // Copyright (C) 2002-2019 Nicola Asuni - Tecnick.com LTD
11 //
12 // This file is part of TCPDF software library.
13 //
14 // TCPDF is free software: you can redistribute it and/or modify it
15 // under the terms of the GNU Lesser General Public License as
16 // published by the Free Software Foundation, either version 3 of the
17 // License, or (at your option) any later version.
18 //
19 // TCPDF is distributed in the hope that it will be useful, but
20 // WITHOUT ANY WARRANTY; without even the implied warranty of
21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22 // See the GNU Lesser General Public License for more details.
23 //
24 // You should have received a copy of the License
25 // along with TCPDF. If not, see
26 // <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
27 //
28 // See LICENSE.TXT file for more information.
29 // -------------------------------------------------------------------
30 //
31 // Description :
32 // This is a PHP class for generating PDF documents without requiring external extensions.
33 //
34 // NOTE:
35 // This class was originally derived in 2002 from the Public
36 // Domain FPDF class by Olivier Plathey (http://www.fpdf.org),
37 // but now is almost entirely rewritten and contains thousands of
38 // new lines of code and hundreds new features.
39 //
40 // Main features:
41 // * no external libraries are required for the basic functions;
42 // * all standard page formats, custom page formats, custom margins and units of measure;
43 // * UTF-8 Unicode and Right-To-Left languages;
44 // * TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;
45 // * font subsetting;
46 // * methods to publish some XHTML + CSS code, Javascript and Forms;
47 // * images, graphic (geometric figures) and transformation methods;
48 // * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImageMagick (http://www.imagemagick.org/www/formats.html)
49 // * 1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;
50 // * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;
51 // * automatic page header and footer management;
52 // * document encryption up to 256 bit and digital signature certifications;
53 // * transactions to UNDO commands;
54 // * PDF annotations, including links, text and file attachments;
55 // * text rendering modes (fill, stroke and clipping);
56 // * multiple columns mode;
57 // * no-write page regions;
58 // * bookmarks, named destinations and table of content;
59 // * text hyphenation;
60 // * text stretching and spacing (tracking);
61 // * automatic page break, line break and text alignments including justification;
62 // * automatic page numbering and page groups;
63 // * move and delete pages;
64 // * page compression (requires php-zlib extension);
65 // * XOBject Templates;
66 // * Layers and object visibility.
67 // * PDF/A-1b support
68 //============================================================+
69
70 /**
71 * @file
72 * This is a PHP class for generating PDF documents without requiring external extensions.<br>
73 * TCPDF project (http://www.tcpdf.org) was originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
74 * <h3>TCPDF main features are:</h3>
75 * <ul>
76 * <li>no external libraries are required for the basic functions;</li>
77 * <li>all standard page formats, custom page formats, custom margins and units of measure;</li>
78 * <li>UTF-8 Unicode and Right-To-Left languages;</li>
79 * <li>TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;</li>
80 * <li>font subsetting;</li>
81 * <li>methods to publish some XHTML + CSS code, Javascript and Forms;</li>
82 * <li>images, graphic (geometric figures) and transformation methods;
83 * <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImageMagick (http://www.imagemagick.org/www/formats.html)</li>
84 * <li>1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;</li>
85 * <li>JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;</li>
86 * <li>automatic page header and footer management;</li>
87 * <li>document encryption up to 256 bit and digital signature certifications;</li>
88 * <li>transactions to UNDO commands;</li>
89 * <li>PDF annotations, including links, text and file attachments;</li>
90 * <li>text rendering modes (fill, stroke and clipping);</li>
91 * <li>multiple columns mode;</li>
92 * <li>no-write page regions;</li>
93 * <li>bookmarks, named destinations and table of content;</li>
94 * <li>text hyphenation;</li>
95 * <li>text stretching and spacing (tracking);</li>
96 * <li>automatic page break, line break and text alignments including justification;</li>
97 * <li>automatic page numbering and page groups;</li>
98 * <li>move and delete pages;</li>
99 * <li>page compression (requires php-zlib extension);</li>
100 * <li>XOBject Templates;</li>
101 * <li>Layers and object visibility;</li>
102 * <li>PDF/A-1b support.</li>
103 * </ul>
104 * Tools to encode your unicode fonts are on fonts/utils directory.</p>
105 * @package com.tecnick.tcpdf
106 * @author Nicola Asuni
107 * @version 6.3.2
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.3.2
132 * @author Nicola Asuni - info@tecnick.com
133 * @IgnoreAnnotation("protected")
134 * @IgnoreAnnotation("public")
135 * @IgnoreAnnotation("pre")
136 */
137 class TCPDF {
138
139 // Protected properties
140
141 /**
142 * Current page number.
143 * @protected
144 */
145 protected $page;
146
147 /**
148 * Current object number.
149 * @protected
150 */
151 protected $n;
152
153 /**
154 * Array of object offsets.
155 * @protected
156 */
157 protected $offsets = array();
158
159 /**
160 * Array of object IDs for each page.
161 * @protected
162 */
163 protected $pageobjects = array();
164
165 /**
166 * Buffer holding in-memory PDF.
167 * @protected
168 */
169 protected $buffer;
170
171 /**
172 * Array containing pages.
173 * @protected
174 */
175 protected $pages = array();
176
177 /**
178 * Current document state.
179 * @protected
180 */
181 protected $state;
182
183 /**
184 * Compression flag.
185 * @protected
186 */
187 protected $compress;
188
189 /**
190 * Current page orientation (P = Portrait, L = Landscape).
191 * @protected
192 */
193 protected $CurOrientation;
194
195 /**
196 * Page dimensions.
197 * @protected
198 */
199 protected $pagedim = array();
200
201 /**
202 * Scale factor (number of points in user unit).
203 * @protected
204 */
205 protected $k;
206
207 /**
208 * Width of page format in points.
209 * @protected
210 */
211 protected $fwPt;
212
213 /**
214 * Height of page format in points.
215 * @protected
216 */
217 protected $fhPt;
218
219 /**
220 * Current width of page in points.
221 * @protected
222 */
223 protected $wPt;
224
225 /**
226 * Current height of page in points.
227 * @protected
228 */
229 protected $hPt;
230
231 /**
232 * Current width of page in user unit.
233 * @protected
234 */
235 protected $w;
236
237 /**
238 * Current height of page in user unit.
239 * @protected
240 */
241 protected $h;
242
243 /**
244 * Left margin.
245 * @protected
246 */
247 protected $lMargin;
248
249 /**
250 * Right margin.
251 * @protected
252 */
253 protected $rMargin;
254
255 /**
256 * Cell left margin (used by regions).
257 * @protected
258 */
259 protected $clMargin;
260
261 /**
262 * Cell right margin (used by regions).
263 * @protected
264 */
265 protected $crMargin;
266
267 /**
268 * Top margin.
269 * @protected
270 */
271 protected $tMargin;
272
273 /**
274 * Page break margin.
275 * @protected
276 */
277 protected $bMargin;
278
279 /**
280 * Array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
281 * @since 5.9.000 (2010-10-03)
282 * @protected
283 */
284 protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
285
286 /**
287 * Array of cell margins ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
288 * @since 5.9.000 (2010-10-04)
289 * @protected
290 */
291 protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
292
293 /**
294 * Current horizontal position in user unit for cell positioning.
295 * @protected
296 */
297 protected $x;
298
299 /**
300 * Current vertical position in user unit for cell positioning.
301 * @protected
302 */
303 protected $y;
304
305 /**
306 * Height of last cell printed.
307 * @protected
308 */
309 protected $lasth;
310
311 /**
312 * Line width in user unit.
313 * @protected
314 */
315 protected $LineWidth;
316
317 /**
318 * Array of standard font names.
319 * @protected
320 */
321 protected $CoreFonts;
322
323 /**
324 * Array of used fonts.
325 * @protected
326 */
327 protected $fonts = array();
328
329 /**
330 * Array of font files.
331 * @protected
332 */
333 protected $FontFiles = array();
334
335 /**
336 * Array of encoding differences.
337 * @protected
338 */
339 protected $diffs = array();
340
341 /**
342 * Array of used images.
343 * @protected
344 */
345 protected $images = array();
346
347 /**
348 * Depth of the svg tag, to keep track if the svg tag is a subtag or the root tag.
349 * @protected
350 */
351 protected $svg_tag_depth = 0;
352
353 /**
354 * Array of Annotations in pages.
355 * @protected
356 */
357 protected $PageAnnots = array();
358
359 /**
360 * Array of internal links.
361 * @protected
362 */
363 protected $links = array();
364
365 /**
366 * Current font family.
367 * @protected
368 */
369 protected $FontFamily;
370
371 /**
372 * Current font style.
373 * @protected
374 */
375 protected $FontStyle;
376
377 /**
378 * Current font ascent (distance between font top and baseline).
379 * @protected
380 * @since 2.8.000 (2007-03-29)
381 */
382 protected $FontAscent;
383
384 /**
385 * Current font descent (distance between font bottom and baseline).
386 * @protected
387 * @since 2.8.000 (2007-03-29)
388 */
389 protected $FontDescent;
390
391 /**
392 * Underlining flag.
393 * @protected
394 */
395 protected $underline;
396
397 /**
398 * Overlining flag.
399 * @protected
400 */
401 protected $overline;
402
403 /**
404 * Current font info.
405 * @protected
406 */
407 protected $CurrentFont;
408
409 /**
410 * Current font size in points.
411 * @protected
412 */
413 protected $FontSizePt;
414
415 /**
416 * Current font size in user unit.
417 * @protected
418 */
419 protected $FontSize;
420
421 /**
422 * Commands for drawing color.
423 * @protected
424 */
425 protected $DrawColor;
426
427 /**
428 * Commands for filling color.
429 * @protected
430 */
431 protected $FillColor;
432
433 /**
434 * Commands for text color.
435 * @protected
436 */
437 protected $TextColor;
438
439 /**
440 * Indicates whether fill and text colors are different.
441 * @protected
442 */
443 protected $ColorFlag;
444
445 /**
446 * Automatic page breaking.
447 * @protected
448 */
449 protected $AutoPageBreak;
450
451 /**
452 * Threshold used to trigger page breaks.
453 * @protected
454 */
455 protected $PageBreakTrigger;
456
457 /**
458 * Flag set when processing page header.
459 * @protected
460 */
461 protected $InHeader = false;
462
463 /**
464 * Flag set when processing page footer.
465 * @protected
466 */
467 protected $InFooter = false;
468
469 /**
470 * Zoom display mode.
471 * @protected
472 */
473 protected $ZoomMode;
474
475 /**
476 * Layout display mode.
477 * @protected
478 */
479 protected $LayoutMode;
480
481 /**
482 * If true set the document information dictionary in Unicode.
483 * @protected
484 */
485 protected $docinfounicode = true;
486
487 /**
488 * Document title.
489 * @protected
490 */
491 protected $title = '';
492
493 /**
494 * Document subject.
495 * @protected
496 */
497 protected $subject = '';
498
499 /**
500 * Document author.
501 * @protected
502 */
503 protected $author = '';
504
505 /**
506 * Document keywords.
507 * @protected
508 */
509 protected $keywords = '';
510
511 /**
512 * Document creator.
513 * @protected
514 */
515 protected $creator = '';
516
517 /**
518 * Starting page number.
519 * @protected
520 */
521 protected $starting_page_number = 1;
522
523 /**
524 * The right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image.
525 * @since 2002-07-31
526 * @author Nicola Asuni
527 * @protected
528 */
529 protected $img_rb_x;
530
531 /**
532 * The right-bottom corner Y coordinate of last inserted image.
533 * @since 2002-07-31
534 * @author Nicola Asuni
535 * @protected
536 */
537 protected $img_rb_y;
538
539 /**
540 * Adjusting factor to convert pixels to user units.
541 * @since 2004-06-14
542 * @author Nicola Asuni
543 * @protected
544 */
545 protected $imgscale = 1;
546
547 /**
548 * Boolean flag set to true when the input text is unicode (require unicode fonts).
549 * @since 2005-01-02
550 * @author Nicola Asuni
551 * @protected
552 */
553 protected $isunicode = false;
554
555 /**
556 * PDF version.
557 * @since 1.5.3
558 * @protected
559 */
560 protected $PDFVersion = '1.7';
561
562 /**
563 * ID of the stored default header template (-1 = not set).
564 * @protected
565 */
566 protected $header_xobjid = false;
567
568 /**
569 * If true reset the Header Xobject template at each page
570 * @protected
571 */
572 protected $header_xobj_autoreset = false;
573
574 /**
575 * Minimum distance between header and top page margin.
576 * @protected
577 */
578 protected $header_margin;
579
580 /**
581 * Minimum distance between footer and bottom page margin.
582 * @protected
583 */
584 protected $footer_margin;
585
586 /**
587 * Original left margin value.
588 * @protected
589 * @since 1.53.0.TC013
590 */
591 protected $original_lMargin;
592
593 /**
594 * Original right margin value.
595 * @protected
596 * @since 1.53.0.TC013
597 */
598 protected $original_rMargin;
599
600 /**
601 * Default font used on page header.
602 * @protected
603 */
604 protected $header_font;
605
606 /**
607 * Default font used on page footer.
608 * @protected
609 */
610 protected $footer_font;
611
612 /**
613 * Language templates.
614 * @protected
615 */
616 protected $l;
617
618 /**
619 * Barcode to print on page footer (only if set).
620 * @protected
621 */
622 protected $barcode = false;
623
624 /**
625 * Boolean flag to print/hide page header.
626 * @protected
627 */
628 protected $print_header = true;
629
630 /**
631 * Boolean flag to print/hide page footer.
632 * @protected
633 */
634 protected $print_footer = true;
635
636 /**
637 * Header image logo.
638 * @protected
639 */
640 protected $header_logo = '';
641
642 /**
643 * Width of header image logo in user units.
644 * @protected
645 */
646 protected $header_logo_width = 30;
647
648 /**
649 * Title to be printed on default page header.
650 * @protected
651 */
652 protected $header_title = '';
653
654 /**
655 * String to pring on page header after title.
656 * @protected
657 */
658 protected $header_string = '';
659
660 /**
661 * Color for header text (RGB array).
662 * @since 5.9.174 (2012-07-25)
663 * @protected
664 */
665 protected $header_text_color = array(0,0,0);
666
667 /**
668 * Color for header line (RGB array).
669 * @since 5.9.174 (2012-07-25)
670 * @protected
671 */
672 protected $header_line_color = array(0,0,0);
673
674 /**
675 * Color for footer text (RGB array).
676 * @since 5.9.174 (2012-07-25)
677 * @protected
678 */
679 protected $footer_text_color = array(0,0,0);
680
681 /**
682 * Color for footer line (RGB array).
683 * @since 5.9.174 (2012-07-25)
684 * @protected
685 */
686 protected $footer_line_color = array(0,0,0);
687
688 /**
689 * Text shadow data array.
690 * @since 5.9.174 (2012-07-25)
691 * @protected
692 */
693 protected $txtshadow = array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal');
694
695 /**
696 * Default number of columns for html table.
697 * @protected
698 */
699 protected $default_table_columns = 4;
700
701 // variables for html parser
702
703 /**
704 * HTML PARSER: array to store current link and rendering styles.
705 * @protected
706 */
707 protected $HREF = array();
708
709 /**
710 * List of available fonts on filesystem.
711 * @protected
712 */
713 protected $fontlist = array();
714
715 /**
716 * Current foreground color.
717 * @protected
718 */
719 protected $fgcolor;
720
721 /**
722 * HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
723 * @protected
724 */
725 protected $listordered = array();
726
727 /**
728 * HTML PARSER: array count list items on nested lists.
729 * @protected
730 */
731 protected $listcount = array();
732
733 /**
734 * HTML PARSER: current list nesting level.
735 * @protected
736 */
737 protected $listnum = 0;
738
739 /**
740 * HTML PARSER: indent amount for lists.
741 * @protected
742 */
743 protected $listindent = 0;
744
745 /**
746 * HTML PARSER: current list indententation level.
747 * @protected
748 */
749 protected $listindentlevel = 0;
750
751 /**
752 * Current background color.
753 * @protected
754 */
755 protected $bgcolor;
756
757 /**
758 * Temporary font size in points.
759 * @protected
760 */
761 protected $tempfontsize = 10;
762
763 /**
764 * Spacer string for LI tags.
765 * @protected
766 */
767 protected $lispacer = '';
768
769 /**
770 * Default encoding.
771 * @protected
772 * @since 1.53.0.TC010
773 */
774 protected $encoding = 'UTF-8';
775
776 /**
777 * PHP internal encoding.
778 * @protected
779 * @since 1.53.0.TC016
780 */
781 protected $internal_encoding;
782
783 /**
784 * Boolean flag to indicate if the document language is Right-To-Left.
785 * @protected
786 * @since 2.0.000
787 */
788 protected $rtl = false;
789
790 /**
791 * Boolean flag used to force RTL or LTR string direction.
792 * @protected
793 * @since 2.0.000
794 */
795 protected $tmprtl = false;
796
797 // --- Variables used for document encryption:
798
799 /**
800 * IBoolean flag indicating whether document is protected.
801 * @protected
802 * @since 2.0.000 (2008-01-02)
803 */
804 protected $encrypted;
805
806 /**
807 * Array containing encryption settings.
808 * @protected
809 * @since 5.0.005 (2010-05-11)
810 */
811 protected $encryptdata = array();
812
813 /**
814 * Last RC4 key encrypted (cached for optimisation).
815 * @protected
816 * @since 2.0.000 (2008-01-02)
817 */
818 protected $last_enc_key;
819
820 /**
821 * Last RC4 computed key.
822 * @protected
823 * @since 2.0.000 (2008-01-02)
824 */
825 protected $last_enc_key_c;
826
827 /**
828 * File ID (used on document trailer).
829 * @protected
830 * @since 5.0.005 (2010-05-12)
831 */
832 protected $file_id;
833
834 // --- bookmark ---
835
836 /**
837 * Outlines for bookmark.
838 * @protected
839 * @since 2.1.002 (2008-02-12)
840 */
841 protected $outlines = array();
842
843 /**
844 * Outline root for bookmark.
845 * @protected
846 * @since 2.1.002 (2008-02-12)
847 */
848 protected $OutlineRoot;
849
850 // --- javascript and form ---
851
852 /**
853 * Javascript code.
854 * @protected
855 * @since 2.1.002 (2008-02-12)
856 */
857 protected $javascript = '';
858
859 /**
860 * Javascript counter.
861 * @protected
862 * @since 2.1.002 (2008-02-12)
863 */
864 protected $n_js;
865
866 /**
867 * line through state
868 * @protected
869 * @since 2.8.000 (2008-03-19)
870 */
871 protected $linethrough;
872
873 /**
874 * Array with additional document-wide usage rights for the document.
875 * @protected
876 * @since 5.8.014 (2010-08-23)
877 */
878 protected $ur = array();
879
880 /**
881 * DPI (Dot Per Inch) Document Resolution (do not change).
882 * @protected
883 * @since 3.0.000 (2008-03-27)
884 */
885 protected $dpi = 72;
886
887 /**
888 * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
889 * @protected
890 * @since 3.0.000 (2008-03-27)
891 */
892 protected $newpagegroup = array();
893
894 /**
895 * Array that contains the number of pages in each page group.
896 * @protected
897 * @since 3.0.000 (2008-03-27)
898 */
899 protected $pagegroups = array();
900
901 /**
902 * Current page group number.
903 * @protected
904 * @since 3.0.000 (2008-03-27)
905 */
906 protected $currpagegroup = 0;
907
908 /**
909 * Array of transparency objects and parameters.
910 * @protected
911 * @since 3.0.000 (2008-03-27)
912 */
913 protected $extgstates;
914
915 /**
916 * Set the default JPEG compression quality (1-100).
917 * @protected
918 * @since 3.0.000 (2008-03-27)
919 */
920 protected $jpeg_quality;
921
922 /**
923 * Default cell height ratio.
924 * @protected
925 * @since 3.0.014 (2008-05-23)
926 */
927 protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
928
929 /**
930 * PDF viewer preferences.
931 * @protected
932 * @since 3.1.000 (2008-06-09)
933 */
934 protected $viewer_preferences;
935
936 /**
937 * A name object specifying how the document should be displayed when opened.
938 * @protected
939 * @since 3.1.000 (2008-06-09)
940 */
941 protected $PageMode;
942
943 /**
944 * Array for storing gradient information.
945 * @protected
946 * @since 3.1.000 (2008-06-09)
947 */
948 protected $gradients = array();
949
950 /**
951 * Array used to store positions inside the pages buffer (keys are the page numbers).
952 * @protected
953 * @since 3.2.000 (2008-06-26)
954 */
955 protected $intmrk = array();
956
957 /**
958 * Array used to store positions inside the pages buffer (keys are the page numbers).
959 * @protected
960 * @since 5.7.000 (2010-08-03)
961 */
962 protected $bordermrk = array();
963
964 /**
965 * Array used to store page positions to track empty pages (keys are the page numbers).
966 * @protected
967 * @since 5.8.007 (2010-08-18)
968 */
969 protected $emptypagemrk = array();
970
971 /**
972 * Array used to store content positions inside the pages buffer (keys are the page numbers).
973 * @protected
974 * @since 4.6.021 (2009-07-20)
975 */
976 protected $cntmrk = array();
977
978 /**
979 * Array used to store footer positions of each page.
980 * @protected
981 * @since 3.2.000 (2008-07-01)
982 */
983 protected $footerpos = array();
984
985 /**
986 * Array used to store footer length of each page.
987 * @protected
988 * @since 4.0.014 (2008-07-29)
989 */
990 protected $footerlen = array();
991
992 /**
993 * Boolean flag to indicate if a new line is created.
994 * @protected
995 * @since 3.2.000 (2008-07-01)
996 */
997 protected $newline = true;
998
999 /**
1000 * End position of the latest inserted line.
1001 * @protected
1002 * @since 3.2.000 (2008-07-01)
1003 */
1004 protected $endlinex = 0;
1005
1006 /**
1007 * PDF string for width value of the last line.
1008 * @protected
1009 * @since 4.0.006 (2008-07-16)
1010 */
1011 protected $linestyleWidth = '';
1012
1013 /**
1014 * PDF string for CAP value of the last line.
1015 * @protected
1016 * @since 4.0.006 (2008-07-16)
1017 */
1018 protected $linestyleCap = '0 J';
1019
1020 /**
1021 * PDF string for join value of the last line.
1022 * @protected
1023 * @since 4.0.006 (2008-07-16)
1024 */
1025 protected $linestyleJoin = '0 j';
1026
1027 /**
1028 * PDF string for dash value of the last line.
1029 * @protected
1030 * @since 4.0.006 (2008-07-16)
1031 */
1032 protected $linestyleDash = '[] 0 d';
1033
1034 /**
1035 * Boolean flag to indicate if marked-content sequence is open.
1036 * @protected
1037 * @since 4.0.013 (2008-07-28)
1038 */
1039 protected $openMarkedContent = false;
1040
1041 /**
1042 * Count the latest inserted vertical spaces on HTML.
1043 * @protected
1044 * @since 4.0.021 (2008-08-24)
1045 */
1046 protected $htmlvspace = 0;
1047
1048 /**
1049 * Array of Spot colors.
1050 * @protected
1051 * @since 4.0.024 (2008-09-12)
1052 */
1053 protected $spot_colors = array();
1054
1055 /**
1056 * Symbol used for HTML unordered list items.
1057 * @protected
1058 * @since 4.0.028 (2008-09-26)
1059 */
1060 protected $lisymbol = '';
1061
1062 /**
1063 * String used to mark the beginning and end of EPS image blocks.
1064 * @protected
1065 * @since 4.1.000 (2008-10-18)
1066 */
1067 protected $epsmarker = 'x#!#EPS#!#x';
1068
1069 /**
1070 * Array of transformation matrix.
1071 * @protected
1072 * @since 4.2.000 (2008-10-29)
1073 */
1074 protected $transfmatrix = array();
1075
1076 /**
1077 * Current key for transformation matrix.
1078 * @protected
1079 * @since 4.8.005 (2009-09-17)
1080 */
1081 protected $transfmatrix_key = 0;
1082
1083 /**
1084 * Booklet mode for double-sided pages.
1085 * @protected
1086 * @since 4.2.000 (2008-10-29)
1087 */
1088 protected $booklet = false;
1089
1090 /**
1091 * Epsilon value used for float calculations.
1092 * @protected
1093 * @since 4.2.000 (2008-10-29)
1094 */
1095 protected $feps = 0.005;
1096
1097 /**
1098 * Array used for custom vertical spaces for HTML tags.
1099 * @protected
1100 * @since 4.2.001 (2008-10-30)
1101 */
1102 protected $tagvspaces = array();
1103
1104 /**
1105 * HTML PARSER: custom indent amount for lists. Negative value means disabled.
1106 * @protected
1107 * @since 4.2.007 (2008-11-12)
1108 */
1109 protected $customlistindent = -1;
1110
1111 /**
1112 * Boolean flag to indicate if the border of the cell sides that cross the page should be removed.
1113 * @protected
1114 * @since 4.2.010 (2008-11-14)
1115 */
1116 protected $opencell = true;
1117
1118 /**
1119 * Array of files to embedd.
1120 * @protected
1121 * @since 4.4.000 (2008-12-07)
1122 */
1123 protected $embeddedfiles = array();
1124
1125 /**
1126 * Boolean flag to indicate if we are inside a PRE tag.
1127 * @protected
1128 * @since 4.4.001 (2008-12-08)
1129 */
1130 protected $premode = false;
1131
1132 /**
1133 * Array used to store positions of graphics transformation blocks inside the page buffer.
1134 * keys are the page numbers
1135 * @protected
1136 * @since 4.4.002 (2008-12-09)
1137 */
1138 protected $transfmrk = array();
1139
1140 /**
1141 * Default color for html links.
1142 * @protected
1143 * @since 4.4.003 (2008-12-09)
1144 */
1145 protected $htmlLinkColorArray = array(0, 0, 255);
1146
1147 /**
1148 * Default font style to add to html links.
1149 * @protected
1150 * @since 4.4.003 (2008-12-09)
1151 */
1152 protected $htmlLinkFontStyle = 'U';
1153
1154 /**
1155 * Counts the number of pages.
1156 * @protected
1157 * @since 4.5.000 (2008-12-31)
1158 */
1159 protected $numpages = 0;
1160
1161 /**
1162 * Array containing page lengths in bytes.
1163 * @protected
1164 * @since 4.5.000 (2008-12-31)
1165 */
1166 protected $pagelen = array();
1167
1168 /**
1169 * Counts the number of pages.
1170 * @protected
1171 * @since 4.5.000 (2008-12-31)
1172 */
1173 protected $numimages = 0;
1174
1175 /**
1176 * Store the image keys.
1177 * @protected
1178 * @since 4.5.000 (2008-12-31)
1179 */
1180 protected $imagekeys = array();
1181
1182 /**
1183 * Length of the buffer in bytes.
1184 * @protected
1185 * @since 4.5.000 (2008-12-31)
1186 */
1187 protected $bufferlen = 0;
1188
1189 /**
1190 * Counts the number of fonts.
1191 * @protected
1192 * @since 4.5.000 (2009-01-02)
1193 */
1194 protected $numfonts = 0;
1195
1196 /**
1197 * Store the font keys.
1198 * @protected
1199 * @since 4.5.000 (2009-01-02)
1200 */
1201 protected $fontkeys = array();
1202
1203 /**
1204 * Store the font object IDs.
1205 * @protected
1206 * @since 4.8.001 (2009-09-09)
1207 */
1208 protected $font_obj_ids = array();
1209
1210 /**
1211 * Store the fage status (true when opened, false when closed).
1212 * @protected
1213 * @since 4.5.000 (2009-01-02)
1214 */
1215 protected $pageopen = array();
1216
1217 /**
1218 * Default monospace font.
1219 * @protected
1220 * @since 4.5.025 (2009-03-10)
1221 */
1222 protected $default_monospaced_font = 'courier';
1223
1224 /**
1225 * Cloned copy of the current class object.
1226 * @protected
1227 * @since 4.5.029 (2009-03-19)
1228 */
1229 protected $objcopy;
1230
1231 /**
1232 * Array used to store the lengths of cache files.
1233 * @protected
1234 * @since 4.5.029 (2009-03-19)
1235 */
1236 protected $cache_file_length = array();
1237
1238 /**
1239 * Table header content to be repeated on each new page.
1240 * @protected
1241 * @since 4.5.030 (2009-03-20)
1242 */
1243 protected $thead = '';
1244
1245 /**
1246 * Margins used for table header.
1247 * @protected
1248 * @since 4.5.030 (2009-03-20)
1249 */
1250 protected $theadMargins = array();
1251
1252 /**
1253 * Boolean flag to enable document digital signature.
1254 * @protected
1255 * @since 4.6.005 (2009-04-24)
1256 */
1257 protected $sign = false;
1258
1259 /**
1260 * Digital signature data.
1261 * @protected
1262 * @since 4.6.005 (2009-04-24)
1263 */
1264 protected $signature_data = array();
1265
1266 /**
1267 * Digital signature max length.
1268 * @protected
1269 * @since 4.6.005 (2009-04-24)
1270 */
1271 protected $signature_max_length = 11742;
1272
1273 /**
1274 * Data for digital signature appearance.
1275 * @protected
1276 * @since 5.3.011 (2010-06-16)
1277 */
1278 protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1279
1280 /**
1281 * Array of empty digital signature appearances.
1282 * @protected
1283 * @since 5.9.101 (2011-07-06)
1284 */
1285 protected $empty_signature_appearance = array();
1286
1287 /**
1288 * Boolean flag to enable document timestamping with TSA.
1289 * @protected
1290 * @since 6.0.085 (2014-06-19)
1291 */
1292 protected $tsa_timestamp = false;
1293
1294 /**
1295 * Timestamping data.
1296 * @protected
1297 * @since 6.0.085 (2014-06-19)
1298 */
1299 protected $tsa_data = array();
1300
1301 /**
1302 * Regular expression used to find blank characters (required for word-wrapping).
1303 * @protected
1304 * @since 4.6.006 (2009-04-28)
1305 */
1306 protected $re_spaces = '/[^\S\xa0]/';
1307
1308 /**
1309 * Array of $re_spaces parts.
1310 * @protected
1311 * @since 5.5.011 (2010-07-09)
1312 */
1313 protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1314
1315 /**
1316 * Digital signature object ID.
1317 * @protected
1318 * @since 4.6.022 (2009-06-23)
1319 */
1320 protected $sig_obj_id = 0;
1321
1322 /**
1323 * ID of page objects.
1324 * @protected
1325 * @since 4.7.000 (2009-08-29)
1326 */
1327 protected $page_obj_id = array();
1328
1329 /**
1330 * List of form annotations IDs.
1331 * @protected
1332 * @since 4.8.000 (2009-09-07)
1333 */
1334 protected $form_obj_id = array();
1335
1336 /**
1337 * 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.
1338 * @protected
1339 * @since 4.8.000 (2009-09-07)
1340 */
1341 protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1342
1343 /**
1344 * Javascript objects array.
1345 * @protected
1346 * @since 4.8.000 (2009-09-07)
1347 */
1348 protected $js_objects = array();
1349
1350 /**
1351 * Current form action (used during XHTML rendering).
1352 * @protected
1353 * @since 4.8.000 (2009-09-07)
1354 */
1355 protected $form_action = '';
1356
1357 /**
1358 * Current form encryption type (used during XHTML rendering).
1359 * @protected
1360 * @since 4.8.000 (2009-09-07)
1361 */
1362 protected $form_enctype = 'application/x-www-form-urlencoded';
1363
1364 /**
1365 * Current method to submit forms.
1366 * @protected
1367 * @since 4.8.000 (2009-09-07)
1368 */
1369 protected $form_mode = 'post';
1370
1371 /**
1372 * List of fonts used on form fields (fontname => fontkey).
1373 * @protected
1374 * @since 4.8.001 (2009-09-09)
1375 */
1376 protected $annotation_fonts = array();
1377
1378 /**
1379 * List of radio buttons parent objects.
1380 * @protected
1381 * @since 4.8.001 (2009-09-09)
1382 */
1383 protected $radiobutton_groups = array();
1384
1385 /**
1386 * List of radio group objects IDs.
1387 * @protected
1388 * @since 4.8.001 (2009-09-09)
1389 */
1390 protected $radio_groups = array();
1391
1392 /**
1393 * Text indentation value (used for text-indent CSS attribute).
1394 * @protected
1395 * @since 4.8.006 (2009-09-23)
1396 */
1397 protected $textindent = 0;
1398
1399 /**
1400 * Store page number when startTransaction() is called.
1401 * @protected
1402 * @since 4.8.006 (2009-09-23)
1403 */
1404 protected $start_transaction_page = 0;
1405
1406 /**
1407 * Store Y position when startTransaction() is called.
1408 * @protected
1409 * @since 4.9.001 (2010-03-28)
1410 */
1411 protected $start_transaction_y = 0;
1412
1413 /**
1414 * True when we are printing the thead section on a new page.
1415 * @protected
1416 * @since 4.8.027 (2010-01-25)
1417 */
1418 protected $inthead = false;
1419
1420 /**
1421 * Array of column measures (width, space, starting Y position).
1422 * @protected
1423 * @since 4.9.001 (2010-03-28)
1424 */
1425 protected $columns = array();
1426
1427 /**
1428 * Number of colums.
1429 * @protected
1430 * @since 4.9.001 (2010-03-28)
1431 */
1432 protected $num_columns = 1;
1433
1434 /**
1435 * Current column number.
1436 * @protected
1437 * @since 4.9.001 (2010-03-28)
1438 */
1439 protected $current_column = 0;
1440
1441 /**
1442 * Starting page for columns.
1443 * @protected
1444 * @since 4.9.001 (2010-03-28)
1445 */
1446 protected $column_start_page = 0;
1447
1448 /**
1449 * Maximum page and column selected.
1450 * @protected
1451 * @since 5.8.000 (2010-08-11)
1452 */
1453 protected $maxselcol = array('page' => 0, 'column' => 0);
1454
1455 /**
1456 * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
1457 * @protected
1458 * @since 5.8.000 (2010-08-11)
1459 */
1460 protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1461
1462 /**
1463 * 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.
1464 * @protected
1465 * @since 4.9.008 (2010-04-03)
1466 */
1467 protected $textrendermode = 0;
1468
1469 /**
1470 * Text stroke width in doc units.
1471 * @protected
1472 * @since 4.9.008 (2010-04-03)
1473 */
1474 protected $textstrokewidth = 0;
1475
1476 /**
1477 * Current stroke color.
1478 * @protected
1479 * @since 4.9.008 (2010-04-03)
1480 */
1481 protected $strokecolor;
1482
1483 /**
1484 * Default unit of measure for document.
1485 * @protected
1486 * @since 5.0.000 (2010-04-22)
1487 */
1488 protected $pdfunit = 'mm';
1489
1490 /**
1491 * Boolean flag true when we are on TOC (Table Of Content) page.
1492 * @protected
1493 */
1494 protected $tocpage = false;
1495
1496 /**
1497 * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1498 * @protected
1499 * @since 5.0.000 (2010-04-26)
1500 */
1501 protected $rasterize_vector_images = false;
1502
1503 /**
1504 * Boolean flag: if true enables font subsetting by default.
1505 * @protected
1506 * @since 5.3.002 (2010-06-07)
1507 */
1508 protected $font_subsetting = true;
1509
1510 /**
1511 * Array of default graphic settings.
1512 * @protected
1513 * @since 5.5.008 (2010-07-02)
1514 */
1515 protected $default_graphic_vars = array();
1516
1517 /**
1518 * Array of XObjects.
1519 * @protected
1520 * @since 5.8.014 (2010-08-23)
1521 */
1522 protected $xobjects = array();
1523
1524 /**
1525 * Boolean value true when we are inside an XObject.
1526 * @protected
1527 * @since 5.8.017 (2010-08-24)
1528 */
1529 protected $inxobj = false;
1530
1531 /**
1532 * Current XObject ID.
1533 * @protected
1534 * @since 5.8.017 (2010-08-24)
1535 */
1536 protected $xobjid = '';
1537
1538 /**
1539 * Percentage of character stretching.
1540 * @protected
1541 * @since 5.9.000 (2010-09-29)
1542 */
1543 protected $font_stretching = 100;
1544
1545 /**
1546 * Increases or decreases the space between characters in a text by the specified amount (tracking).
1547 * @protected
1548 * @since 5.9.000 (2010-09-29)
1549 */
1550 protected $font_spacing = 0;
1551
1552 /**
1553 * Array of no-write regions.
1554 * ('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)
1555 * @protected
1556 * @since 5.9.003 (2010-10-14)
1557 */
1558 protected $page_regions = array();
1559
1560 /**
1561 * Boolean value true when page region check is active.
1562 * @protected
1563 */
1564 protected $check_page_regions = true;
1565
1566 /**
1567 * Array of PDF layers data.
1568 * @protected
1569 * @since 5.9.102 (2011-07-13)
1570 */
1571 protected $pdflayers = array();
1572
1573 /**
1574 * A dictionary of names and corresponding destinations (Dests key on document Catalog).
1575 * @protected
1576 * @since 5.9.097 (2011-06-23)
1577 */
1578 protected $dests = array();
1579
1580 /**
1581 * Object ID for Named Destinations
1582 * @protected
1583 * @since 5.9.097 (2011-06-23)
1584 */
1585 protected $n_dests;
1586
1587 /**
1588 * Embedded Files Names
1589 * @protected
1590 * @since 5.9.204 (2013-01-23)
1591 */
1592 protected $efnames = array();
1593
1594 /**
1595 * Directory used for the last SVG image.
1596 * @protected
1597 * @since 5.0.000 (2010-05-05)
1598 */
1599 protected $svgdir = '';
1600
1601 /**
1602 * Deafult unit of measure for SVG.
1603 * @protected
1604 * @since 5.0.000 (2010-05-02)
1605 */
1606 protected $svgunit = 'px';
1607
1608 /**
1609 * Array of SVG gradients.
1610 * @protected
1611 * @since 5.0.000 (2010-05-02)
1612 */
1613 protected $svggradients = array();
1614
1615 /**
1616 * ID of last SVG gradient.
1617 * @protected
1618 * @since 5.0.000 (2010-05-02)
1619 */
1620 protected $svggradientid = 0;
1621
1622 /**
1623 * Boolean value true when in SVG defs group.
1624 * @protected
1625 * @since 5.0.000 (2010-05-02)
1626 */
1627 protected $svgdefsmode = false;
1628
1629 /**
1630 * Array of SVG defs.
1631 * @protected
1632 * @since 5.0.000 (2010-05-02)
1633 */
1634 protected $svgdefs = array();
1635
1636 /**
1637 * Boolean value true when in SVG clipPath tag.
1638 * @protected
1639 * @since 5.0.000 (2010-04-26)
1640 */
1641 protected $svgclipmode = false;
1642
1643 /**
1644 * Array of SVG clipPath commands.
1645 * @protected
1646 * @since 5.0.000 (2010-05-02)
1647 */
1648 protected $svgclippaths = array();
1649
1650 /**
1651 * Array of SVG clipPath tranformation matrix.
1652 * @protected
1653 * @since 5.8.022 (2010-08-31)
1654 */
1655 protected $svgcliptm = array();
1656
1657 /**
1658 * ID of last SVG clipPath.
1659 * @protected
1660 * @since 5.0.000 (2010-05-02)
1661 */
1662 protected $svgclipid = 0;
1663
1664 /**
1665 * SVG text.
1666 * @protected
1667 * @since 5.0.000 (2010-05-02)
1668 */
1669 protected $svgtext = '';
1670
1671 /**
1672 * SVG text properties.
1673 * @protected
1674 * @since 5.8.013 (2010-08-23)
1675 */
1676 protected $svgtextmode = array();
1677
1678 /**
1679 * Array of SVG properties.
1680 * @protected
1681 * @since 5.0.000 (2010-05-02)
1682 */
1683 protected $svgstyles = array(array(
1684 'alignment-baseline' => 'auto',
1685 'baseline-shift' => 'baseline',
1686 'clip' => 'auto',
1687 'clip-path' => 'none',
1688 'clip-rule' => 'nonzero',
1689 'color' => 'black',
1690 'color-interpolation' => 'sRGB',
1691 'color-interpolation-filters' => 'linearRGB',
1692 'color-profile' => 'auto',
1693 'color-rendering' => 'auto',
1694 'cursor' => 'auto',
1695 'direction' => 'ltr',
1696 'display' => 'inline',
1697 'dominant-baseline' => 'auto',
1698 'enable-background' => 'accumulate',
1699 'fill' => 'black',
1700 'fill-opacity' => 1,
1701 'fill-rule' => 'nonzero',
1702 'filter' => 'none',
1703 'flood-color' => 'black',
1704 'flood-opacity' => 1,
1705 'font' => '',
1706 'font-family' => 'helvetica',
1707 'font-size' => 'medium',
1708 'font-size-adjust' => 'none',
1709 'font-stretch' => 'normal',
1710 'font-style' => 'normal',
1711 'font-variant' => 'normal',
1712 'font-weight' => 'normal',
1713 'glyph-orientation-horizontal' => '0deg',
1714 'glyph-orientation-vertical' => 'auto',
1715 'image-rendering' => 'auto',
1716 'kerning' => 'auto',
1717 'letter-spacing' => 'normal',
1718 'lighting-color' => 'white',
1719 'marker' => '',
1720 'marker-end' => 'none',
1721 'marker-mid' => 'none',
1722 'marker-start' => 'none',
1723 'mask' => 'none',
1724 'opacity' => 1,
1725 'overflow' => 'auto',
1726 'pointer-events' => 'visiblePainted',
1727 'shape-rendering' => 'auto',
1728 'stop-color' => 'black',
1729 'stop-opacity' => 1,
1730 'stroke' => 'none',
1731 'stroke-dasharray' => 'none',
1732 'stroke-dashoffset' => 0,
1733 'stroke-linecap' => 'butt',
1734 'stroke-linejoin' => 'miter',
1735 'stroke-miterlimit' => 4,
1736 'stroke-opacity' => 1,
1737 'stroke-width' => 1,
1738 'text-anchor' => 'start',
1739 'text-decoration' => 'none',
1740 'text-rendering' => 'auto',
1741 'unicode-bidi' => 'normal',
1742 'visibility' => 'visible',
1743 'word-spacing' => 'normal',
1744 'writing-mode' => 'lr-tb',
1745 'text-color' => 'black',
1746 'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1747 ));
1748
1749 /**
1750 * If true force sRGB color profile for all document.
1751 * @protected
1752 * @since 5.9.121 (2011-09-28)
1753 */
1754 protected $force_srgb = false;
1755
1756 /**
1757 * If true set the document to PDF/A mode.
1758 * @protected
1759 * @since 5.9.121 (2011-09-27)
1760 */
1761 protected $pdfa_mode = false;
1762
1763 /**
1764 * version of PDF/A mode (1 - 3).
1765 * @protected
1766 * @since 6.2.26 (2019-03-12)
1767 */
1768 protected $pdfa_version = 1;
1769
1770 /**
1771 * Document creation date-time
1772 * @protected
1773 * @since 5.9.152 (2012-03-22)
1774 */
1775 protected $doc_creation_timestamp;
1776
1777 /**
1778 * Document modification date-time
1779 * @protected
1780 * @since 5.9.152 (2012-03-22)
1781 */
1782 protected $doc_modification_timestamp;
1783
1784 /**
1785 * Custom XMP data.
1786 * @protected
1787 * @since 5.9.128 (2011-10-06)
1788 */
1789 protected $custom_xmp = '';
1790
1791 /**
1792 * Custom XMP RDF data.
1793 * @protected
1794 * @since 6.3.0 (2019-09-19)
1795 */
1796 protected $custom_xmp_rdf = '';
1797
1798 /**
1799 * Overprint mode array.
1800 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1801 * @protected
1802 * @since 5.9.152 (2012-03-23)
1803 */
1804 protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1805
1806 /**
1807 * Alpha mode array.
1808 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1809 * @protected
1810 * @since 5.9.152 (2012-03-23)
1811 */
1812 protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1813
1814 /**
1815 * Define the page boundaries boxes to be set on document.
1816 * @protected
1817 * @since 5.9.152 (2012-03-23)
1818 */
1819 protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1820
1821 /**
1822 * If true print TCPDF meta link.
1823 * @protected
1824 * @since 5.9.152 (2012-03-23)
1825 */
1826 protected $tcpdflink = true;
1827
1828 /**
1829 * Cache array for computed GD gamma values.
1830 * @protected
1831 * @since 5.9.1632 (2012-06-05)
1832 */
1833 protected $gdgammacache = array();
1834
1835 //------------------------------------------------------------
1836 // METHODS
1837 //------------------------------------------------------------
1838
1839 /**
1840 * This is the class constructor.
1841 * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1842 *
1843 * 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.
1844 *
1845 * @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>
1846 * @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.
1847 * @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().
1848 * @param $unicode (boolean) TRUE means that the input text is unicode (default = true)
1849 * @param $encoding (string) Charset encoding (used only when converting back html entities); default is UTF-8.
1850 * @param $diskcache (boolean) DEPRECATED FEATURE
1851 * @param $pdfa (integer) If not false, set the document to PDF/A mode and the good version (1 or 3).
1852 * @public
1853 * @see getPageSizeFromFormat(), setPageFormat()
1854 */
1855 public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
1856 /* Set internal character encoding to ASCII */
1857 if (function_exists('mb_internal_encoding') AND mb_internal_encoding()) {
1858 $this->internal_encoding = mb_internal_encoding();
1859 mb_internal_encoding('ASCII');
1860 }
1861 // set file ID for trailer
1862 $serformat = (is_array($format) ? json_encode($format) : $format);
1863 $this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
1864 $this->font_obj_ids = array();
1865 $this->page_obj_id = array();
1866 $this->form_obj_id = array();
1867
1868 // set pdf/a mode
1869 if ($pdfa != false) {
1870 $this->pdfa_mode = true;
1871 $this->pdfa_version = $pdfa; // 1 or 3
1872 } else
1873 $this->pdfa_mode = false;
1874
1875 $this->force_srgb = false;
1876 // set language direction
1877 $this->rtl = false;
1878 $this->tmprtl = false;
1879 // some checks
1880 $this->_dochecks();
1881 // initialization of properties
1882 $this->isunicode = $unicode;
1883 $this->page = 0;
1884 $this->transfmrk[0] = array();
1885 $this->pagedim = array();
1886 $this->n = 2;
1887 $this->buffer = '';
1888 $this->pages = array();
1889 $this->state = 0;
1890 $this->fonts = array();
1891 $this->FontFiles = array();
1892 $this->diffs = array();
1893 $this->images = array();
1894 $this->links = array();
1895 $this->gradients = array();
1896 $this->InFooter = false;
1897 $this->lasth = 0;
1898 $this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
1899 $this->FontStyle = '';
1900 $this->FontSizePt = 12;
1901 $this->underline = false;
1902 $this->overline = false;
1903 $this->linethrough = false;
1904 $this->DrawColor = '0 G';
1905 $this->FillColor = '0 g';
1906 $this->TextColor = '0 g';
1907 $this->ColorFlag = false;
1908 $this->pdflayers = array();
1909 // encryption values
1910 $this->encrypted = false;
1911 $this->last_enc_key = '';
1912 // standard Unicode fonts
1913 $this->CoreFonts = array(
1914 'courier'=>'Courier',
1915 'courierB'=>'Courier-Bold',
1916 'courierI'=>'Courier-Oblique',
1917 'courierBI'=>'Courier-BoldOblique',
1918 'helvetica'=>'Helvetica',
1919 'helveticaB'=>'Helvetica-Bold',
1920 'helveticaI'=>'Helvetica-Oblique',
1921 'helveticaBI'=>'Helvetica-BoldOblique',
1922 'times'=>'Times-Roman',
1923 'timesB'=>'Times-Bold',
1924 'timesI'=>'Times-Italic',
1925 'timesBI'=>'Times-BoldItalic',
1926 'symbol'=>'Symbol',
1927 'zapfdingbats'=>'ZapfDingbats'
1928 );
1929 // set scale factor
1930 $this->setPageUnit($unit);
1931 // set page format and orientation
1932 $this->setPageFormat($format, $orientation);
1933 // page margins (1 cm)
1934 $margin = 28.35 / $this->k;
1935 $this->SetMargins($margin, $margin);
1936 $this->clMargin = $this->lMargin;
1937 $this->crMargin = $this->rMargin;
1938 // internal cell padding
1939 $cpadding = $margin / 10;
1940 $this->setCellPaddings($cpadding, 0, $cpadding, 0);
1941 // cell margins
1942 $this->setCellMargins(0, 0, 0, 0);
1943 // line width (0.2 mm)
1944 $this->LineWidth = 0.57 / $this->k;
1945 $this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
1946 $this->linestyleCap = '0 J';
1947 $this->linestyleJoin = '0 j';
1948 $this->linestyleDash = '[] 0 d';
1949 // automatic page break
1950 $this->SetAutoPageBreak(true, (2 * $margin));
1951 // full width display mode
1952 $this->SetDisplayMode('fullwidth');
1953 // compression
1954 $this->SetCompression();
1955 // set default PDF version number
1956 $this->setPDFVersion();
1957 $this->tcpdflink = true;
1958 $this->encoding = $encoding;
1959 $this->HREF = array();
1960 $this->getFontsList();
1961 $this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
1962 $this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
1963 $this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
1964 $this->extgstates = array();
1965 $this->setTextShadow();
1966 // signature
1967 $this->sign = false;
1968 $this->tsa_timestamp = false;
1969 $this->tsa_data = array();
1970 $this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
1971 $this->empty_signature_appearance = array();
1972 // user's rights
1973 $this->ur['enabled'] = false;
1974 $this->ur['document'] = '/FullSave';
1975 $this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
1976 $this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
1977 $this->ur['signature'] = '/Modify';
1978 $this->ur['ef'] = '/Create/Delete/Modify/Import';
1979 $this->ur['formex'] = '';
1980 // set default JPEG quality
1981 $this->jpeg_quality = 75;
1982 // initialize some settings
1983 TCPDF_FONTS::utf8Bidi(array(), '', false, $this->isunicode, $this->CurrentFont);
1984 // set default font
1985 $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
1986 $this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1987 $this->setFooterFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1988 // check if PCRE Unicode support is enabled
1989 if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
1990 // PCRE unicode support is turned ON
1991 // \s : any whitespace character
1992 // \p{Z} : any separator
1993 // \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
1994 // \xa0 : Unicode Character 'NO-BREAK SPACE' (U+00A0)
1995 //$this->setSpacesRE('/(?!\xa0)[\s\p{Z}\p{Lo}]/u');
1996 $this->setSpacesRE('/(?!\xa0)[\s\p{Z}]/u');
1997 } else {
1998 // PCRE unicode support is turned OFF
1999 $this->setSpacesRE('/[^\S\xa0]/');
2000 }
2001 $this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
2002 // set document creation and modification timestamp
2003 $this->doc_creation_timestamp = time();
2004 $this->doc_modification_timestamp = $this->doc_creation_timestamp;
2005 // get default graphic vars
2006 $this->default_graphic_vars = $this->getGraphicVars();
2007 $this->header_xobj_autoreset = false;
2008 $this->custom_xmp = '';
2009 $this->custom_xmp_rdf = '';
2010 // Call cleanup method after script execution finishes or exit() is called.
2011 // NOTE: This will not be executed if the process is killed with a SIGTERM or SIGKILL signal.
2012 register_shutdown_function(array($this, '_destroy'), true);
2013 }
2014
2015 /**
2016 * Default destructor.
2017 * @public
2018 * @since 1.53.0.TC016
2019 */
2020 public function __destruct() {
2021 // cleanup
2022 $this->_destroy(true);
2023 }
2024
2025 /**
2026 * Set the units of measure for the document.
2027 * @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.
2028 * @public
2029 * @since 3.0.015 (2008-06-06)
2030 */
2031 public function setPageUnit($unit) {
2032 $unit = strtolower($unit);
2033 //Set scale factor
2034 switch ($unit) {
2035 // points
2036 case 'px':
2037 case 'pt': {
2038 $this->k = 1;
2039 break;
2040 }
2041 // millimeters
2042 case 'mm': {
2043 $this->k = $this->dpi / 25.4;
2044 break;
2045 }
2046 // centimeters
2047 case 'cm': {
2048 $this->k = $this->dpi / 2.54;
2049 break;
2050 }
2051 // inches
2052 case 'in': {
2053 $this->k = $this->dpi;
2054 break;
2055 }
2056 // unsupported unit
2057 default : {
2058 $this->Error('Incorrect unit: '.$unit);
2059 break;
2060 }
2061 }
2062 $this->pdfunit = $unit;
2063 if (isset($this->CurOrientation)) {
2064 $this->setPageOrientation($this->CurOrientation);
2065 }
2066 }
2067
2068 /**
2069 * Change the format of the current page
2070 * @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>
2071 * <li>['format'] = page format name (one of the above);</li>
2072 * <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>
2073 * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2074 * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2075 * <li>['MediaBox']['llx'] : lower-left x coordinate</li>
2076 * <li>['MediaBox']['lly'] : lower-left y coordinate</li>
2077 * <li>['MediaBox']['urx'] : upper-right x coordinate</li>
2078 * <li>['MediaBox']['ury'] : upper-right y coordinate</li>
2079 * <li>['CropBox'] : the visible region of default user space:</li>
2080 * <li>['CropBox']['llx'] : lower-left x coordinate</li>
2081 * <li>['CropBox']['lly'] : lower-left y coordinate</li>
2082 * <li>['CropBox']['urx'] : upper-right x coordinate</li>
2083 * <li>['CropBox']['ury'] : upper-right y coordinate</li>
2084 * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2085 * <li>['BleedBox']['llx'] : lower-left x coordinate</li>
2086 * <li>['BleedBox']['lly'] : lower-left y coordinate</li>
2087 * <li>['BleedBox']['urx'] : upper-right x coordinate</li>
2088 * <li>['BleedBox']['ury'] : upper-right y coordinate</li>
2089 * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2090 * <li>['TrimBox']['llx'] : lower-left x coordinate</li>
2091 * <li>['TrimBox']['lly'] : lower-left y coordinate</li>
2092 * <li>['TrimBox']['urx'] : upper-right x coordinate</li>
2093 * <li>['TrimBox']['ury'] : upper-right y coordinate</li>
2094 * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2095 * <li>['ArtBox']['llx'] : lower-left x coordinate</li>
2096 * <li>['ArtBox']['lly'] : lower-left y coordinate</li>
2097 * <li>['ArtBox']['urx'] : upper-right x coordinate</li>
2098 * <li>['ArtBox']['ury'] : upper-right y coordinate</li>
2099 * <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>
2100 * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2101 * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2102 * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2103 * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2104 * <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>
2105 * <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>
2106 * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2107 * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2108 * <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>
2109 * <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>
2110 * <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>
2111 * <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>
2112 * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2113 * </ul>
2114 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul>
2115 * <li>P or Portrait (default)</li>
2116 * <li>L or Landscape</li>
2117 * <li>'' (empty string) for automatic orientation</li>
2118 * </ul>
2119 * @protected
2120 * @since 3.0.015 (2008-06-06)
2121 * @see getPageSizeFromFormat()
2122 */
2123 protected function setPageFormat($format, $orientation='P') {
2124 if (!empty($format) AND isset($this->pagedim[$this->page])) {
2125 // remove inherited values
2126 unset($this->pagedim[$this->page]);
2127 }
2128 if (is_string($format)) {
2129 // get page measures from format name
2130 $pf = TCPDF_STATIC::getPageSizeFromFormat($format);
2131 $this->fwPt = $pf[0];
2132 $this->fhPt = $pf[1];
2133 } else {
2134 // the boundaries of the physical medium on which the page shall be displayed or printed
2135 if (isset($format['MediaBox'])) {
2136 $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);
2137 $this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2138 $this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2139 } else {
2140 if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2141 $pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2142 } else {
2143 if (!isset($format['format'])) {
2144 // default value
2145 $format['format'] = 'A4';
2146 }
2147 $pf = TCPDF_STATIC::getPageSizeFromFormat($format['format']);
2148 }
2149 $this->fwPt = $pf[0];
2150 $this->fhPt = $pf[1];
2151 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2152 }
2153 // the visible region of default user space
2154 if (isset($format['CropBox'])) {
2155 $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);
2156 }
2157 // the region to which the contents of the page shall be clipped when output in a production environment
2158 if (isset($format['BleedBox'])) {
2159 $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);
2160 }
2161 // the intended dimensions of the finished page after trimming
2162 if (isset($format['TrimBox'])) {
2163 $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);
2164 }
2165 // the page's meaningful content (including potential white space)
2166 if (isset($format['ArtBox'])) {
2167 $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);
2168 }
2169 // specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2170 if (isset($format['BoxColorInfo'])) {
2171 $this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2172 }
2173 if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2174 // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2175 $this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2176 }
2177 if (isset($format['PZ'])) {
2178 // The page's preferred zoom (magnification) factor
2179 $this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2180 }
2181 if (isset($format['trans'])) {
2182 // The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2183 if (isset($format['trans']['Dur'])) {
2184 // The page's display duration
2185 $this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2186 }
2187 $stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2188 if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2189 // The transition style that shall be used when moving to this page from another during a presentation
2190 $this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2191 $valid_effect = array('Split', 'Blinds');
2192 $valid_vals = array('H', 'V');
2193 if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2194 $this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2195 }
2196 $valid_effect = array('Split', 'Box', 'Fly');
2197 $valid_vals = array('I', 'O');
2198 if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2199 $this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2200 }
2201 $valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2202 if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2203 if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2204 OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2205 OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2206 $this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2207 }
2208 }
2209 if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2210 $this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2211 }
2212 if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2213 $this->pagedim[$this->page]['trans']['B'] = 'true';
2214 }
2215 } else {
2216 $this->pagedim[$this->page]['trans']['S'] = 'R';
2217 }
2218 if (isset($format['trans']['D'])) {
2219 // The duration of the transition effect, in seconds
2220 $this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2221 } else {
2222 $this->pagedim[$this->page]['trans']['D'] = 1;
2223 }
2224 }
2225 }
2226 $this->setPageOrientation($orientation);
2227 }
2228
2229 /**
2230 * Set page orientation.
2231 * @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>
2232 * @param $autopagebreak (boolean) Boolean indicating if auto-page-break mode should be on or off.
2233 * @param $bottommargin (float) bottom margin of the page.
2234 * @public
2235 * @since 3.0.015 (2008-06-06)
2236 */
2237 public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
2238 if (!isset($this->pagedim[$this->page]['MediaBox'])) {
2239 // the boundaries of the physical medium on which the page shall be displayed or printed
2240 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2241 }
2242 if (!isset($this->pagedim[$this->page]['CropBox'])) {
2243 // the visible region of default user space
2244 $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);
2245 }
2246 if (!isset($this->pagedim[$this->page]['BleedBox'])) {
2247 // the region to which the contents of the page shall be clipped when output in a production environment
2248 $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);
2249 }
2250 if (!isset($this->pagedim[$this->page]['TrimBox'])) {
2251 // the intended dimensions of the finished page after trimming
2252 $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);
2253 }
2254 if (!isset($this->pagedim[$this->page]['ArtBox'])) {
2255 // the page's meaningful content (including potential white space)
2256 $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);
2257 }
2258 if (!isset($this->pagedim[$this->page]['Rotate'])) {
2259 // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2260 $this->pagedim[$this->page]['Rotate'] = 0;
2261 }
2262 if (!isset($this->pagedim[$this->page]['PZ'])) {
2263 // The page's preferred zoom (magnification) factor
2264 $this->pagedim[$this->page]['PZ'] = 1;
2265 }
2266 if ($this->fwPt > $this->fhPt) {
2267 // landscape
2268 $default_orientation = 'L';
2269 } else {
2270 // portrait
2271 $default_orientation = 'P';
2272 }
2273 $valid_orientations = array('P', 'L');
2274 if (empty($orientation)) {
2275 $orientation = $default_orientation;
2276 } else {
2277 $orientation = strtoupper($orientation[0]);
2278 }
2279 if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2280 $this->CurOrientation = $orientation;
2281 $this->wPt = $this->fhPt;
2282 $this->hPt = $this->fwPt;
2283 } else {
2284 $this->CurOrientation = $default_orientation;
2285 $this->wPt = $this->fwPt;
2286 $this->hPt = $this->fhPt;
2287 }
2288 if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
2289 // swap X and Y coordinates (change page orientation)
2290 $this->pagedim = TCPDF_STATIC::swapPageBoxCoordinates($this->page, $this->pagedim);
2291 }
2292 $this->w = ($this->wPt / $this->k);
2293 $this->h = ($this->hPt / $this->k);
2294 if (TCPDF_STATIC::empty_string($autopagebreak)) {
2295 if (isset($this->AutoPageBreak)) {
2296 $autopagebreak = $this->AutoPageBreak;
2297 } else {
2298 $autopagebreak = true;
2299 }
2300 }
2301 if (TCPDF_STATIC::empty_string($bottommargin)) {
2302 if (isset($this->bMargin)) {
2303 $bottommargin = $this->bMargin;
2304 } else {
2305 // default value = 2 cm
2306 $bottommargin = 2 * 28.35 / $this->k;
2307 }
2308 }
2309 $this->SetAutoPageBreak($autopagebreak, $bottommargin);
2310 // store page dimensions
2311 $this->pagedim[$this->page]['w'] = $this->wPt;
2312 $this->pagedim[$this->page]['h'] = $this->hPt;
2313 $this->pagedim[$this->page]['wk'] = $this->w;
2314 $this->pagedim[$this->page]['hk'] = $this->h;
2315 $this->pagedim[$this->page]['tm'] = $this->tMargin;
2316 $this->pagedim[$this->page]['bm'] = $bottommargin;
2317 $this->pagedim[$this->page]['lm'] = $this->lMargin;
2318 $this->pagedim[$this->page]['rm'] = $this->rMargin;
2319 $this->pagedim[$this->page]['pb'] = $autopagebreak;
2320 $this->pagedim[$this->page]['or'] = $this->CurOrientation;
2321 $this->pagedim[$this->page]['olm'] = $this->original_lMargin;
2322 $this->pagedim[$this->page]['orm'] = $this->original_rMargin;
2323 }
2324
2325 /**
2326 * Set regular expression to detect withespaces or word separators.
2327 * The pattern delimiter must be the forward-slash character "/".
2328 * Some example patterns are:
2329 * <pre>
2330 * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
2331 * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
2332 * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
2333 * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
2334 * \s : any whitespace character
2335 * \p{Z} : any separator
2336 * \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2337 * \xa0 : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2338 * </pre>
2339 * @param $re (string) regular expression (leave empty for default).
2340 * @public
2341 * @since 4.6.016 (2009-06-15)
2342 */
2343 public function setSpacesRE($re='/[^\S\xa0]/') {
2344 $this->re_spaces = $re;
2345 $re_parts = explode('/', $re);
2346 // get pattern parts
2347 $this->re_space = array();
2348 if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2349 $this->re_space['p'] = $re_parts[1];
2350 } else {
2351 $this->re_space['p'] = '[\s]';
2352 }
2353 // set pattern modifiers
2354 if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2355 $this->re_space['m'] = $re_parts[2];
2356 } else {
2357 $this->re_space['m'] = '';
2358 }
2359 }
2360
2361 /**
2362 * Enable or disable Right-To-Left language mode
2363 * @param $enable (Boolean) if true enable Right-To-Left language mode.
2364 * @param $resetx (Boolean) if true reset the X position on direction change.
2365 * @public
2366 * @since 2.0.000 (2008-01-03)
2367 */
2368 public function setRTL($enable, $resetx=true) {
2369 $enable = $enable ? true : false;
2370 $resetx = ($resetx AND ($enable != $this->rtl));
2371 $this->rtl = $enable;
2372 $this->tmprtl = false;
2373 if ($resetx) {
2374 $this->Ln(0);
2375 }
2376 }
2377
2378 /**
2379 * Return the RTL status
2380 * @return boolean
2381 * @public
2382 * @since 4.0.012 (2008-07-24)
2383 */
2384 public function getRTL() {
2385 return $this->rtl;
2386 }
2387
2388 /**
2389 * Force temporary RTL language direction
2390 * @param $mode (mixed) can be false, 'L' for LTR or 'R' for RTL
2391 * @public
2392 * @since 2.1.000 (2008-01-09)
2393 */
2394 public function setTempRTL($mode) {
2395 $newmode = false;
2396 switch (strtoupper($mode)) {
2397 case 'LTR':
2398 case 'L': {
2399 if ($this->rtl) {
2400 $newmode = 'L';
2401 }
2402 break;
2403 }
2404 case 'RTL':
2405 case 'R': {
2406 if (!$this->rtl) {
2407 $newmode = 'R';
2408 }
2409 break;
2410 }
2411 case false:
2412 default: {
2413 $newmode = false;
2414 break;
2415 }
2416 }
2417 $this->tmprtl = $newmode;
2418 }
2419
2420 /**
2421 * Return the current temporary RTL status
2422 * @return boolean
2423 * @public
2424 * @since 4.8.014 (2009-11-04)
2425 */
2426 public function isRTLTextDir() {
2427 return ($this->rtl OR ($this->tmprtl == 'R'));
2428 }
2429
2430 /**
2431 * Set the last cell height.
2432 * @param $h (float) cell height.
2433 * @author Nicola Asuni
2434 * @public
2435 * @since 1.53.0.TC034
2436 */
2437 public function setLastH($h) {
2438 $this->lasth = $h;
2439 }
2440
2441 /**
2442 * Return the cell height
2443 * @param $fontsize (int) Font size in internal units
2444 * @param $padding (boolean) If true add cell padding
2445 * @public
2446 */
2447 public function getCellHeight($fontsize, $padding=TRUE) {
2448 $height = ($fontsize * $this->cell_height_ratio);
2449 if ($padding) {
2450 $height += ($this->cell_padding['T'] + $this->cell_padding['B']);
2451 }
2452 return round($height, 6);
2453 }
2454
2455 /**
2456 * Reset the last cell height.
2457 * @public
2458 * @since 5.9.000 (2010-10-03)
2459 */
2460 public function resetLastH() {
2461 $this->lasth = $this->getCellHeight($this->FontSize);
2462 }
2463
2464 /**
2465 * Get the last cell height.
2466 * @return last cell height
2467 * @public
2468 * @since 4.0.017 (2008-08-05)
2469 */
2470 public function getLastH() {
2471 return $this->lasth;
2472 }
2473
2474 /**
2475 * Set the adjusting factor to convert pixels to user units.
2476 * @param $scale (float) adjusting factor to convert pixels to user units.
2477 * @author Nicola Asuni
2478 * @public
2479 * @since 1.5.2
2480 */
2481 public function setImageScale($scale) {
2482 $this->imgscale = $scale;
2483 }
2484
2485 /**
2486 * Returns the adjusting factor to convert pixels to user units.
2487 * @return float adjusting factor to convert pixels to user units.
2488 * @author Nicola Asuni
2489 * @public
2490 * @since 1.5.2
2491 */
2492 public function getImageScale() {
2493 return $this->imgscale;
2494 }
2495
2496 /**
2497 * Returns an array of page dimensions:
2498 * <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>
2499 * @param $pagenum (int) page number (empty = current page)
2500 * @return array of page dimensions.
2501 * @author Nicola Asuni
2502 * @public
2503 * @since 4.5.027 (2009-03-16)
2504 */
2505 public function getPageDimensions($pagenum='') {
2506 if (empty($pagenum)) {
2507 $pagenum = $this->page;
2508 }
2509 return $this->pagedim[$pagenum];
2510 }
2511
2512 /**
2513 * Returns the page width in units.
2514 * @param $pagenum (int) page number (empty = current page)
2515 * @return int page width.
2516 * @author Nicola Asuni
2517 * @public
2518 * @since 1.5.2
2519 * @see getPageDimensions()
2520 */
2521 public function getPageWidth($pagenum='') {
2522 if (empty($pagenum)) {
2523 return $this->w;
2524 }
2525 return $this->pagedim[$pagenum]['w'];
2526 }
2527
2528 /**
2529 * Returns the page height in units.
2530 * @param $pagenum (int) page number (empty = current page)
2531 * @return int page height.
2532 * @author Nicola Asuni
2533 * @public
2534 * @since 1.5.2
2535 * @see getPageDimensions()
2536 */
2537 public function getPageHeight($pagenum='') {
2538 if (empty($pagenum)) {
2539 return $this->h;
2540 }
2541 return $this->pagedim[$pagenum]['h'];
2542 }
2543
2544 /**
2545 * Returns the page break margin.
2546 * @param $pagenum (int) page number (empty = current page)
2547 * @return int page break margin.
2548 * @author Nicola Asuni
2549 * @public
2550 * @since 1.5.2
2551 * @see getPageDimensions()
2552 */
2553 public function getBreakMargin($pagenum='') {
2554 if (empty($pagenum)) {
2555 return $this->bMargin;
2556 }
2557 return $this->pagedim[$pagenum]['bm'];
2558 }
2559
2560 /**
2561 * Returns the scale factor (number of points in user unit).
2562 * @return int scale factor.
2563 * @author Nicola Asuni
2564 * @public
2565 * @since 1.5.2
2566 */
2567 public function getScaleFactor() {
2568 return $this->k;
2569 }
2570
2571 /**
2572 * Defines the left, top and right margins.
2573 * @param $left (float) Left margin.
2574 * @param $top (float) Top margin.
2575 * @param $right (float) Right margin. Default value is the left one.
2576 * @param $keepmargins (boolean) if true overwrites the default page margins
2577 * @public
2578 * @since 1.0
2579 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
2580 */
2581 public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
2582 //Set left, top and right margins
2583 $this->lMargin = $left;
2584 $this->tMargin = $top;
2585 if ($right == -1) {
2586 $right = $left;
2587 }
2588 $this->rMargin = $right;
2589 if ($keepmargins) {
2590 // overwrite original values
2591 $this->original_lMargin = $this->lMargin;
2592 $this->original_rMargin = $this->rMargin;
2593 }
2594 }
2595
2596 /**
2597 * 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.
2598 * @param $margin (float) The margin.
2599 * @public
2600 * @since 1.4
2601 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2602 */
2603 public function SetLeftMargin($margin) {
2604 //Set left margin
2605 $this->lMargin = $margin;
2606 if (($this->page > 0) AND ($this->x < $margin)) {
2607 $this->x = $margin;
2608 }
2609 }
2610
2611 /**
2612 * Defines the top margin. The method can be called before creating the first page.
2613 * @param $margin (float) The margin.
2614 * @public
2615 * @since 1.5
2616 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2617 */
2618 public function SetTopMargin($margin) {
2619 //Set top margin
2620 $this->tMargin = $margin;
2621 if (($this->page > 0) AND ($this->y < $margin)) {
2622 $this->y = $margin;
2623 }
2624 }
2625
2626 /**
2627 * Defines the right margin. The method can be called before creating the first page.
2628 * @param $margin (float) The margin.
2629 * @public
2630 * @since 1.5
2631 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
2632 */
2633 public function SetRightMargin($margin) {
2634 $this->rMargin = $margin;
2635 if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
2636 $this->x = $this->w - $margin;
2637 }
2638 }
2639
2640 /**
2641 * Set the same internal Cell padding for top, right, bottom, left-
2642 * @param $pad (float) internal padding.
2643 * @public
2644 * @since 2.1.000 (2008-01-09)
2645 * @see getCellPaddings(), setCellPaddings()
2646 */
2647 public function SetCellPadding($pad) {
2648 if ($pad >= 0) {
2649 $this->cell_padding['L'] = $pad;
2650 $this->cell_padding['T'] = $pad;
2651 $this->cell_padding['R'] = $pad;
2652 $this->cell_padding['B'] = $pad;
2653 }
2654 }
2655
2656 /**
2657 * Set the internal Cell paddings.
2658 * @param $left (float) left padding
2659 * @param $top (float) top padding
2660 * @param $right (float) right padding
2661 * @param $bottom (float) bottom padding
2662 * @public
2663 * @since 5.9.000 (2010-10-03)
2664 * @see getCellPaddings(), SetCellPadding()
2665 */
2666 public function setCellPaddings($left='', $top='', $right='', $bottom='') {
2667 if (($left !== '') AND ($left >= 0)) {
2668 $this->cell_padding['L'] = $left;
2669 }
2670 if (($top !== '') AND ($top >= 0)) {
2671 $this->cell_padding['T'] = $top;
2672 }
2673 if (($right !== '') AND ($right >= 0)) {
2674 $this->cell_padding['R'] = $right;
2675 }
2676 if (($bottom !== '') AND ($bottom >= 0)) {
2677 $this->cell_padding['B'] = $bottom;
2678 }
2679 }
2680
2681 /**
2682 * Get the internal Cell padding array.
2683 * @return array of padding values
2684 * @public
2685 * @since 5.9.000 (2010-10-03)
2686 * @see setCellPaddings(), SetCellPadding()
2687 */
2688 public function getCellPaddings() {
2689 return $this->cell_padding;
2690 }
2691
2692 /**
2693 * Set the internal Cell margins.
2694 * @param $left (float) left margin
2695 * @param $top (float) top margin
2696 * @param $right (float) right margin
2697 * @param $bottom (float) bottom margin
2698 * @public
2699 * @since 5.9.000 (2010-10-03)
2700 * @see getCellMargins()
2701 */
2702 public function setCellMargins($left='', $top='', $right='', $bottom='') {
2703 if (($left !== '') AND ($left >= 0)) {
2704 $this->cell_margin['L'] = $left;
2705 }
2706 if (($top !== '') AND ($top >= 0)) {
2707 $this->cell_margin['T'] = $top;
2708 }
2709 if (($right !== '') AND ($right >= 0)) {
2710 $this->cell_margin['R'] = $right;
2711 }
2712 if (($bottom !== '') AND ($bottom >= 0)) {
2713 $this->cell_margin['B'] = $bottom;
2714 }
2715 }
2716
2717 /**
2718 * Get the internal Cell margin array.
2719 * @return array of margin values
2720 * @public
2721 * @since 5.9.000 (2010-10-03)
2722 * @see setCellMargins()
2723 */
2724 public function getCellMargins() {
2725 return $this->cell_margin;
2726 }
2727
2728 /**
2729 * Adjust the internal Cell padding array to take account of the line width.
2730 * @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)))
2731 * @return array of adjustments
2732 * @public
2733 * @since 5.9.000 (2010-10-03)
2734 */
2735 protected function adjustCellPadding($brd=0) {
2736 if (empty($brd)) {
2737 return;
2738 }
2739 if (is_string($brd)) {
2740 // convert string to array
2741 $slen = strlen($brd);
2742 $newbrd = array();
2743 for ($i = 0; $i < $slen; ++$i) {
2744 $newbrd[$brd[$i]] = true;
2745 }
2746 $brd = $newbrd;
2747 } elseif (($brd === 1) OR ($brd === true) OR (is_numeric($brd) AND (intval($brd) > 0))) {
2748 $brd = array('LRTB' => true);
2749 }
2750 if (!is_array($brd)) {
2751 return;
2752 }
2753 // store current cell padding
2754 $cp = $this->cell_padding;
2755 // select border mode
2756 if (isset($brd['mode'])) {
2757 $mode = $brd['mode'];
2758 unset($brd['mode']);
2759 } else {
2760 $mode = 'normal';
2761 }
2762 // process borders
2763 foreach ($brd as $border => $style) {
2764 $line_width = $this->LineWidth;
2765 if (is_array($style) AND isset($style['width'])) {
2766 // get border width
2767 $line_width = $style['width'];
2768 }
2769 $adj = 0; // line width inside the cell
2770 switch ($mode) {
2771 case 'ext': {
2772 $adj = 0;
2773 break;
2774 }
2775 case 'int': {
2776 $adj = $line_width;
2777 break;
2778 }
2779 case 'normal':
2780 default: {
2781 $adj = ($line_width / 2);
2782 break;
2783 }
2784 }
2785 // correct internal cell padding if required to avoid overlap between text and lines
2786 if ((strpos($border,'T') !== false) AND ($this->cell_padding['T'] < $adj)) {
2787 $this->cell_padding['T'] = $adj;
2788 }
2789 if ((strpos($border,'R') !== false) AND ($this->cell_padding['R'] < $adj)) {
2790 $this->cell_padding['R'] = $adj;
2791 }
2792 if ((strpos($border,'B') !== false) AND ($this->cell_padding['B'] < $adj)) {
2793 $this->cell_padding['B'] = $adj;
2794 }
2795 if ((strpos($border,'L') !== false) AND ($this->cell_padding['L'] < $adj)) {
2796 $this->cell_padding['L'] = $adj;
2797 }
2798 }
2799 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']));
2800 }
2801
2802 /**
2803 * 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.
2804 * @param $auto (boolean) Boolean indicating if mode should be on or off.
2805 * @param $margin (float) Distance from the bottom of the page.
2806 * @public
2807 * @since 1.0
2808 * @see Cell(), MultiCell(), AcceptPageBreak()
2809 */
2810 public function SetAutoPageBreak($auto, $margin=0) {
2811 $this->AutoPageBreak = $auto ? true : false;
2812 $this->bMargin = $margin;
2813 $this->PageBreakTrigger = $this->h - $margin;
2814 }
2815
2816 /**
2817 * Return the auto-page-break mode (true or false).
2818 * @return boolean auto-page-break mode
2819 * @public
2820 * @since 5.9.088
2821 */
2822 public function getAutoPageBreak() {
2823 return $this->AutoPageBreak;
2824 }
2825
2826 /**
2827 * Defines the way the document is to be displayed by the viewer.
2828 * @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>
2829 * @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>
2830 * @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>
2831 * @public
2832 * @since 1.2
2833 */
2834 public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
2835 if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
2836 $this->ZoomMode = $zoom;
2837 } else {
2838 $this->Error('Incorrect zoom display mode: '.$zoom);
2839 }
2840 $this->LayoutMode = TCPDF_STATIC::getPageLayoutMode($layout);
2841 $this->PageMode = TCPDF_STATIC::getPageMode($mode);
2842 }
2843
2844 /**
2845 * 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.
2846 * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
2847 * @param $compress (boolean) Boolean indicating if compression must be enabled.
2848 * @public
2849 * @since 1.4
2850 */
2851 public function SetCompression($compress=true) {
2852 $this->compress = false;
2853 if (function_exists('gzcompress')) {
2854 if ($compress) {
2855 if ( !$this->pdfa_mode) {
2856 $this->compress = true;
2857 }
2858 }
2859 }
2860 }
2861
2862 /**
2863 * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
2864 * @param $mode (boolean) If true force sRGB output intent.
2865 * @public
2866 * @since 5.9.121 (2011-09-28)
2867 */
2868 public function setSRGBmode($mode=false) {
2869 $this->force_srgb = $mode ? true : false;
2870 }
2871
2872 /**
2873 * Turn on/off Unicode mode for document information dictionary (meta tags).
2874 * This has effect only when unicode mode is set to false.
2875 * @param $unicode (boolean) if true set the meta information in Unicode
2876 * @since 5.9.027 (2010-12-01)
2877 * @public
2878 */
2879 public function SetDocInfoUnicode($unicode=true) {
2880 $this->docinfounicode = $unicode ? true : false;
2881 }
2882
2883 /**
2884 * Defines the title of the document.
2885 * @param $title (string) The title.
2886 * @public
2887 * @since 1.2
2888 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
2889 */
2890 public function SetTitle($title) {
2891 $this->title = $title;
2892 }
2893
2894 /**
2895 * Defines the subject of the document.
2896 * @param $subject (string) The subject.
2897 * @public
2898 * @since 1.2
2899 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
2900 */
2901 public function SetSubject($subject) {
2902 $this->subject = $subject;
2903 }
2904
2905 /**
2906 * Defines the author of the document.
2907 * @param $author (string) The name of the author.
2908 * @public
2909 * @since 1.2
2910 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
2911 */
2912 public function SetAuthor($author) {
2913 $this->author = $author;
2914 }
2915
2916 /**
2917 * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
2918 * @param $keywords (string) The list of keywords.
2919 * @public
2920 * @since 1.2
2921 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
2922 */
2923 public function SetKeywords($keywords) {
2924 $this->keywords = $keywords;
2925 }
2926
2927 /**
2928 * Defines the creator of the document. This is typically the name of the application that generates the PDF.
2929 * @param $creator (string) The name of the creator.
2930 * @public
2931 * @since 1.2
2932 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
2933 */
2934 public function SetCreator($creator) {
2935 $this->creator = $creator;
2936 }
2937
2938 /**
2939 * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
2940 * @param $msg (string) The error message
2941 * @public
2942 * @since 1.0
2943 */
2944 public function Error($msg) {
2945 // unset all class variables
2946 $this->_destroy(true);
2947 if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
2948 die('<strong>TCPDF ERROR: </strong>'.$msg);
2949 } else {
2950 throw new Exception('TCPDF ERROR: '.$msg);
2951 }
2952 }
2953
2954 /**
2955 * This method begins the generation of the PDF document.
2956 * It is not necessary to call it explicitly because AddPage() does it automatically.
2957 * Note: no page is created by this method
2958 * @public
2959 * @since 1.0
2960 * @see AddPage(), Close()
2961 */
2962 public function Open() {
2963 $this->state = 1;
2964 }
2965
2966 /**
2967 * Terminates the PDF document.
2968 * It is not necessary to call this method explicitly because Output() does it automatically.
2969 * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
2970 * @public
2971 * @since 1.0
2972 * @see Open(), Output()
2973 */
2974 public function Close() {
2975 if ($this->state == 3) {
2976 return;
2977 }
2978 if ($this->page == 0) {
2979 $this->AddPage();
2980 }
2981 $this->endLayer();
2982 if ($this->tcpdflink) {
2983 // save current graphic settings
2984 $gvars = $this->getGraphicVars();
2985 $this->setEqualColumns();
2986 $this->lastpage(true);
2987 $this->SetAutoPageBreak(false);
2988 $this->x = 0;
2989 $this->y = $this->h - (1 / $this->k);
2990 $this->lMargin = 0;
2991 $this->_outSaveGraphicsState();
2992 $font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
2993 $this->SetFont($font, '', 1);
2994 $this->setTextRenderingMode(0, false, false);
2995 $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";
2996 $lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
2997 $this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
2998 $this->_outRestoreGraphicsState();
2999 // restore graphic settings
3000 $this->setGraphicVars($gvars);
3001 }
3002 // close page
3003 $this->endPage();
3004 // close document
3005 $this->_enddoc();
3006 // unset all class variables (except critical ones)
3007 $this->_destroy(false);
3008 }
3009
3010 /**
3011 * Move pointer at the specified document page and update page dimensions.
3012 * @param $pnum (int) page number (1 ... numpages)
3013 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
3014 * @public
3015 * @since 2.1.000 (2008-01-07)
3016 * @see getPage(), lastpage(), getNumPages()
3017 */
3018 public function setPage($pnum, $resetmargins=false) {
3019 if (($pnum == $this->page) AND ($this->state == 2)) {
3020 return;
3021 }
3022 if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3023 $this->state = 2;
3024 // save current graphic settings
3025 //$gvars = $this->getGraphicVars();
3026 $oldpage = $this->page;
3027 $this->page = $pnum;
3028 $this->wPt = $this->pagedim[$this->page]['w'];
3029 $this->hPt = $this->pagedim[$this->page]['h'];
3030 $this->w = $this->pagedim[$this->page]['wk'];
3031 $this->h = $this->pagedim[$this->page]['hk'];
3032 $this->tMargin = $this->pagedim[$this->page]['tm'];
3033 $this->bMargin = $this->pagedim[$this->page]['bm'];
3034 $this->original_lMargin = $this->pagedim[$this->page]['olm'];
3035 $this->original_rMargin = $this->pagedim[$this->page]['orm'];
3036 $this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3037 $this->CurOrientation = $this->pagedim[$this->page]['or'];
3038 $this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3039 // restore graphic settings
3040 //$this->setGraphicVars($gvars);
3041 if ($resetmargins) {
3042 $this->lMargin = $this->pagedim[$this->page]['olm'];
3043 $this->rMargin = $this->pagedim[$this->page]['orm'];
3044 $this->SetY($this->tMargin);
3045 } else {
3046 // account for booklet mode
3047 if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3048 $deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3049 $this->lMargin += $deltam;
3050 $this->rMargin -= $deltam;
3051 }
3052 }
3053 } else {
3054 $this->Error('Wrong page number on setPage() function: '.$pnum);
3055 }
3056 }
3057
3058 /**
3059 * Reset pointer to the last document page.
3060 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
3061 * @public
3062 * @since 2.0.000 (2008-01-04)
3063 * @see setPage(), getPage(), getNumPages()
3064 */
3065 public function lastPage($resetmargins=false) {
3066 $this->setPage($this->getNumPages(), $resetmargins);
3067 }
3068
3069 /**
3070 * Get current document page number.
3071 * @return int page number
3072 * @public
3073 * @since 2.1.000 (2008-01-07)
3074 * @see setPage(), lastpage(), getNumPages()
3075 */
3076 public function getPage() {
3077 return $this->page;
3078 }
3079
3080 /**
3081 * Get the total number of insered pages.
3082 * @return int number of pages
3083 * @public
3084 * @since 2.1.000 (2008-01-07)
3085 * @see setPage(), getPage(), lastpage()
3086 */
3087 public function getNumPages() {
3088 return $this->numpages;
3089 }
3090
3091 /**
3092 * Adds a new TOC (Table Of Content) page to the document.
3093 * @param $orientation (string) page orientation.
3094 * @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().
3095 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3096 * @public
3097 * @since 5.0.001 (2010-05-06)
3098 * @see AddPage(), startPage(), endPage(), endTOCPage()
3099 */
3100 public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3101 $this->AddPage($orientation, $format, $keepmargins, true);
3102 }
3103
3104 /**
3105 * Terminate the current TOC (Table Of Content) page
3106 * @public
3107 * @since 5.0.001 (2010-05-06)
3108 * @see AddPage(), startPage(), endPage(), addTOCPage()
3109 */
3110 public function endTOCPage() {
3111 $this->endPage(true);
3112 }
3113
3114 /**
3115 * 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).
3116 * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3117 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3118 * @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().
3119 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3120 * @param $tocpage (boolean) if true set the tocpage state to true (the added page will be used to display Table Of Content).
3121 * @public
3122 * @since 1.0
3123 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3124 */
3125 public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3126 if ($this->inxobj) {
3127 // we are inside an XObject template
3128 return;
3129 }
3130 if (!isset($this->original_lMargin) OR $keepmargins) {
3131 $this->original_lMargin = $this->lMargin;
3132 }
3133 if (!isset($this->original_rMargin) OR $keepmargins) {
3134 $this->original_rMargin = $this->rMargin;
3135 }
3136 // terminate previous page
3137 $this->endPage();
3138 // start new page
3139 $this->startPage($orientation, $format, $tocpage);
3140 }
3141
3142 /**
3143 * Terminate the current page
3144 * @param $tocpage (boolean) if true set the tocpage state to false (end the page used to display Table Of Content).
3145 * @public
3146 * @since 4.2.010 (2008-11-14)
3147 * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3148 */
3149 public function endPage($tocpage=false) {
3150 // check if page is already closed
3151 if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3152 return;
3153 }
3154 // print page footer
3155 $this->setFooter();
3156 // close page
3157 $this->_endpage();
3158 // mark page as closed
3159 $this->pageopen[$this->page] = false;
3160 if ($tocpage) {
3161 $this->tocpage = false;
3162 }
3163 }
3164
3165 /**
3166 * Starts a new page to the document. The page must be closed using the endPage() function.
3167 * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3168 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3169 * @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().
3170 * @param $tocpage (boolean) if true the page is designated to contain the Table-Of-Content.
3171 * @since 4.2.010 (2008-11-14)
3172 * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3173 * @public
3174 */
3175 public function startPage($orientation='', $format='', $tocpage=false) {
3176 if ($tocpage) {
3177 $this->tocpage = true;
3178 }
3179 // move page numbers of documents to be attached
3180 if ($this->tocpage) {
3181 // move reference to unexistent pages (used for page attachments)
3182 // adjust outlines
3183 $tmpoutlines = $this->outlines;
3184 foreach ($tmpoutlines as $key => $outline) {
3185 if (!$outline['f'] AND ($outline['p'] > $this->numpages)) {
3186 $this->outlines[$key]['p'] = ($outline['p'] + 1);
3187 }
3188 }
3189 // adjust dests
3190 $tmpdests = $this->dests;
3191 foreach ($tmpdests as $key => $dest) {
3192 if (!$dest['f'] AND ($dest['p'] > $this->numpages)) {
3193 $this->dests[$key]['p'] = ($dest['p'] + 1);
3194 }
3195 }
3196 // adjust links
3197 $tmplinks = $this->links;
3198 foreach ($tmplinks as $key => $link) {
3199 if (!$link['f'] AND ($link['p'] > $this->numpages)) {
3200 $this->links[$key]['p'] = ($link['p'] + 1);
3201 }
3202 }
3203 }
3204 if ($this->numpages > $this->page) {
3205 // this page has been already added
3206 $this->setPage($this->page + 1);
3207 $this->SetY($this->tMargin);
3208 return;
3209 }
3210 // start a new page
3211 if ($this->state == 0) {
3212 $this->Open();
3213 }
3214 ++$this->numpages;
3215 $this->swapMargins($this->booklet);
3216 // save current graphic settings
3217 $gvars = $this->getGraphicVars();
3218 // start new page
3219 $this->_beginpage($orientation, $format);
3220 // mark page as open
3221 $this->pageopen[$this->page] = true;
3222 // restore graphic settings
3223 $this->setGraphicVars($gvars);
3224 // mark this point
3225 $this->setPageMark();
3226 // print page header
3227 $this->setHeader();
3228 // restore graphic settings
3229 $this->setGraphicVars($gvars);
3230 // mark this point
3231 $this->setPageMark();
3232 // print table header (if any)
3233 $this->setTableHeader();
3234 // set mark for empty page check
3235 $this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
3236 }
3237
3238 /**
3239 * Set start-writing mark on current page stream used to put borders and fills.
3240 * Borders and fills are always created after content and inserted on the position marked by this method.
3241 * This function must be called after calling Image() function for a background image.
3242 * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
3243 * @public
3244 * @since 4.0.016 (2008-07-30)
3245 */
3246 public function setPageMark() {
3247 $this->intmrk[$this->page] = $this->pagelen[$this->page];
3248 $this->bordermrk[$this->page] = $this->intmrk[$this->page];
3249 $this->setContentMark();
3250 }
3251
3252 /**
3253 * Set start-writing mark on selected page.
3254 * Borders and fills are always created after content and inserted on the position marked by this method.
3255 * @param $page (int) page number (default is the current page)
3256 * @protected
3257 * @since 4.6.021 (2009-07-20)
3258 */
3259 protected function setContentMark($page=0) {
3260 if ($page <= 0) {
3261 $page = $this->page;
3262 }
3263 if (isset($this->footerlen[$page])) {
3264 $this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3265 } else {
3266 $this->cntmrk[$page] = $this->pagelen[$page];
3267 }
3268 }
3269
3270 /**
3271 * Set header data.
3272 * @param $ln (string) header image logo
3273 * @param $lw (string) header image logo width in mm
3274 * @param $ht (string) string to print as title on document header
3275 * @param $hs (string) string to print on document header
3276 * @param $tc (array) RGB array color for text.
3277 * @param $lc (array) RGB array color for line.
3278 * @public
3279 */
3280 public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
3281 $this->header_logo = $ln;
3282 $this->header_logo_width = $lw;
3283 $this->header_title = $ht;
3284 $this->header_string = $hs;
3285 $this->header_text_color = $tc;
3286 $this->header_line_color = $lc;
3287 }
3288
3289 /**
3290 * Set footer data.
3291 * @param $tc (array) RGB array color for text.
3292 * @param $lc (array) RGB array color for line.
3293 * @public
3294 */
3295 public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
3296 $this->footer_text_color = $tc;
3297 $this->footer_line_color = $lc;
3298 }
3299
3300 /**
3301 * Returns header data:
3302 * <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>
3303 * @return array()
3304 * @public
3305 * @since 4.0.012 (2008-07-24)
3306 */
3307 public function getHeaderData() {
3308 $ret = array();
3309 $ret['logo'] = $this->header_logo;
3310 $ret['logo_width'] = $this->header_logo_width;
3311 $ret['title'] = $this->header_title;
3312 $ret['string'] = $this->header_string;
3313 $ret['text_color'] = $this->header_text_color;
3314 $ret['line_color'] = $this->header_line_color;
3315 return $ret;
3316 }
3317
3318 /**
3319 * Set header margin.
3320 * (minimum distance between header and top page margin)
3321 * @param $hm (int) distance in user units
3322 * @public
3323 */
3324 public function setHeaderMargin($hm=10) {
3325 $this->header_margin = $hm;
3326 }
3327
3328 /**
3329 * Returns header margin in user units.
3330 * @return float
3331 * @since 4.0.012 (2008-07-24)
3332 * @public
3333 */
3334 public function getHeaderMargin() {
3335 return $this->header_margin;
3336 }
3337
3338 /**
3339 * Set footer margin.
3340 * (minimum distance between footer and bottom page margin)
3341 * @param $fm (int) distance in user units
3342 * @public
3343 */
3344 public function setFooterMargin($fm=10) {
3345 $this->footer_margin = $fm;
3346 }
3347
3348 /**
3349 * Returns footer margin in user units.
3350 * @return float
3351 * @since 4.0.012 (2008-07-24)
3352 * @public
3353 */
3354 public function getFooterMargin() {
3355 return $this->footer_margin;
3356 }
3357 /**
3358 * Set a flag to print page header.
3359 * @param $val (boolean) set to true to print the page header (default), false otherwise.
3360 * @public
3361 */
3362 public function setPrintHeader($val=true) {
3363 $this->print_header = $val ? true : false;
3364 }
3365
3366 /**
3367 * Set a flag to print page footer.
3368 * @param $val (boolean) set to true to print the page footer (default), false otherwise.
3369 * @public
3370 */
3371 public function setPrintFooter($val=true) {
3372 $this->print_footer = $val ? true : false;
3373 }
3374
3375 /**
3376 * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
3377 * @return float
3378 * @public
3379 */
3380 public function getImageRBX() {
3381 return $this->img_rb_x;
3382 }
3383
3384 /**
3385 * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
3386 * @return float
3387 * @public
3388 */
3389 public function getImageRBY() {
3390 return $this->img_rb_y;
3391 }
3392
3393 /**
3394 * Reset the xobject template used by Header() method.
3395 * @public
3396 */
3397 public function resetHeaderTemplate() {
3398 $this->header_xobjid = false;
3399 }
3400
3401 /**
3402 * Set a flag to automatically reset the xobject template used by Header() method at each page.
3403 * @param $val (boolean) set to true to reset Header xobject template at each page, false otherwise.
3404 * @public
3405 */
3406 public function setHeaderTemplateAutoreset($val=true) {
3407 $this->header_xobj_autoreset = $val ? true : false;
3408 }
3409
3410 /**
3411 * This method is used to render the page header.
3412 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3413 * @public
3414 */
3415 public function Header() {
3416 if ($this->header_xobjid === false) {
3417 // start a new XObject Template
3418 $this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
3419 $headerfont = $this->getHeaderFont();
3420 $headerdata = $this->getHeaderData();
3421 $this->y = $this->header_margin;
3422 if ($this->rtl) {
3423 $this->x = $this->w - $this->original_rMargin;
3424 } else {
3425 $this->x = $this->original_lMargin;
3426 }
3427 if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
3428 $imgtype = TCPDF_IMAGES::getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
3429 if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
3430 $this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3431 } elseif ($imgtype == 'svg') {
3432 $this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3433 } else {
3434 $this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3435 }
3436 $imgy = $this->getImageRBY();
3437 } else {
3438 $imgy = $this->y;
3439 }
3440 $cell_height = $this->getCellHeight($headerfont[2] / $this->k);
3441 // set starting margin for text data cell
3442 if ($this->getRTL()) {
3443 $header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
3444 } else {
3445 $header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
3446 }
3447 $cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
3448 $this->SetTextColorArray($this->header_text_color);
3449 // header title
3450 $this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
3451 $this->SetX($header_x);
3452 $this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
3453 // header string
3454 $this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
3455 $this->SetX($header_x);
3456 $this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
3457 // print an ending header line
3458 $this->SetLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
3459 $this->SetY((2.835 / $this->k) + max($imgy, $this->y));
3460 if ($this->rtl) {
3461 $this->SetX($this->original_rMargin);
3462 } else {
3463 $this->SetX($this->original_lMargin);
3464 }
3465 $this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
3466 $this->endTemplate();
3467 }
3468 // print header template
3469 $x = 0;
3470 $dx = 0;
3471 if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
3472 // adjust margins for booklet mode
3473 $dx = ($this->original_lMargin - $this->original_rMargin);
3474 }
3475 if ($this->rtl) {
3476 $x = $this->w + $dx;
3477 } else {
3478 $x = 0 + $dx;
3479 }
3480 $this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
3481 if ($this->header_xobj_autoreset) {
3482 // reset header xobject template at each page
3483 $this->header_xobjid = false;
3484 }
3485 }
3486
3487 /**
3488 * This method is used to render the page footer.
3489 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3490 * @public
3491 */
3492 public function Footer() {
3493 $cur_y = $this->y;
3494 $this->SetTextColorArray($this->footer_text_color);
3495 //set style for cell border
3496 $line_width = (0.85 / $this->k);
3497 $this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
3498 //print document barcode
3499 $barcode = $this->getBarcode();
3500 if (!empty($barcode)) {
3501 $this->Ln($line_width);
3502 $barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
3503 $style = array(
3504 'position' => $this->rtl?'R':'L',
3505 'align' => $this->rtl?'R':'L',
3506 'stretch' => false,
3507 'fitwidth' => true,
3508 'cellfitalign' => '',
3509 'border' => false,
3510 'padding' => 0,
3511 'fgcolor' => array(0,0,0),
3512 'bgcolor' => false,
3513 'text' => false
3514 );
3515 $this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
3516 }
3517 $w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
3518 if (empty($this->pagegroups)) {
3519 $pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
3520 } else {
3521 $pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
3522 }
3523 $this->SetY($cur_y);
3524 //Print page number
3525 if ($this->getRTL()) {
3526 $this->SetX($this->original_rMargin);
3527 $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
3528 } else {
3529 $this->SetX($this->original_lMargin);
3530 $this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
3531 }
3532 }
3533
3534 /**
3535 * This method is used to render the page header.
3536 * @protected
3537 * @since 4.0.012 (2008-07-24)
3538 */
3539 protected function setHeader() {
3540 if (!$this->print_header OR ($this->state != 2)) {
3541 return;
3542 }
3543 $this->InHeader = true;
3544 $this->setGraphicVars($this->default_graphic_vars);
3545 $temp_thead = $this->thead;
3546 $temp_theadMargins = $this->theadMargins;
3547 $lasth = $this->lasth;
3548 $newline = $this->newline;
3549 $this->_outSaveGraphicsState();
3550 $this->rMargin = $this->original_rMargin;
3551 $this->lMargin = $this->original_lMargin;
3552 $this->SetCellPadding(0);
3553 //set current position
3554 if ($this->rtl) {
3555 $this->SetXY($this->original_rMargin, $this->header_margin);
3556 } else {
3557 $this->SetXY($this->original_lMargin, $this->header_margin);
3558 }
3559 $this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
3560 $this->Header();
3561 //restore position
3562 if ($this->rtl) {
3563 $this->SetXY($this->original_rMargin, $this->tMargin);
3564 } else {
3565 $this->SetXY($this->original_lMargin, $this->tMargin);
3566 }
3567 $this->_outRestoreGraphicsState();
3568 $this->lasth = $lasth;
3569 $this->thead = $temp_thead;
3570 $this->theadMargins = $temp_theadMargins;
3571 $this->newline = $newline;
3572 $this->InHeader = false;
3573 }
3574
3575 /**
3576 * This method is used to render the page footer.
3577 * @protected
3578 * @since 4.0.012 (2008-07-24)
3579 */
3580 protected function setFooter() {
3581 if ($this->state != 2) {
3582 return;
3583 }
3584 $this->InFooter = true;
3585 // save current graphic settings
3586 $gvars = $this->getGraphicVars();
3587 // mark this point
3588 $this->footerpos[$this->page] = $this->pagelen[$this->page];
3589 $this->_out("\n");
3590 if ($this->print_footer) {
3591 $this->setGraphicVars($this->default_graphic_vars);
3592 $this->current_column = 0;
3593 $this->num_columns = 1;
3594 $temp_thead = $this->thead;
3595 $temp_theadMargins = $this->theadMargins;
3596 $lasth = $this->lasth;
3597 $this->_outSaveGraphicsState();
3598 $this->rMargin = $this->original_rMargin;
3599 $this->lMargin = $this->original_lMargin;
3600 $this->SetCellPadding(0);
3601 //set current position
3602 $footer_y = $this->h - $this->footer_margin;
3603 if ($this->rtl) {
3604 $this->SetXY($this->original_rMargin, $footer_y);
3605 } else {
3606 $this->SetXY($this->original_lMargin, $footer_y);
3607 }
3608 $this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
3609 $this->Footer();
3610 //restore position
3611 if ($this->rtl) {
3612 $this->SetXY($this->original_rMargin, $this->tMargin);
3613 } else {
3614 $this->SetXY($this->original_lMargin, $this->tMargin);
3615 }
3616 $this->_outRestoreGraphicsState();
3617 $this->lasth = $lasth;
3618 $this->thead = $temp_thead;
3619 $this->theadMargins = $temp_theadMargins;
3620 }
3621 // restore graphic settings
3622 $this->setGraphicVars($gvars);
3623 $this->current_column = $gvars['current_column'];
3624 $this->num_columns = $gvars['num_columns'];
3625 // calculate footer length
3626 $this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
3627 $this->InFooter = false;
3628 }
3629
3630 /**
3631 * Check if we are on the page body (excluding page header and footer).
3632 * @return true if we are not in page header nor in page footer, false otherwise.
3633 * @protected
3634 * @since 5.9.091 (2011-06-15)
3635 */
3636 protected function inPageBody() {
3637 return (($this->InHeader === false) AND ($this->InFooter === false));
3638 }
3639
3640 /**
3641 * This method is used to render the table header on new page (if any).
3642 * @protected
3643 * @since 4.5.030 (2009-03-25)
3644 */
3645 protected function setTableHeader() {
3646 if ($this->num_columns > 1) {
3647 // multi column mode
3648 return;
3649 }
3650 if (isset($this->theadMargins['top'])) {
3651 // restore the original top-margin
3652 $this->tMargin = $this->theadMargins['top'];
3653 $this->pagedim[$this->page]['tm'] = $this->tMargin;
3654 $this->y = $this->tMargin;
3655 }
3656 if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
3657 // set margins
3658 $prev_lMargin = $this->lMargin;
3659 $prev_rMargin = $this->rMargin;
3660 $prev_cell_padding = $this->cell_padding;
3661 $this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
3662 $this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
3663 $this->cell_padding = $this->theadMargins['cell_padding'];
3664 if ($this->rtl) {
3665 $this->x = $this->w - $this->rMargin;
3666 } else {
3667 $this->x = $this->lMargin;
3668 }
3669 // account for special "cell" mode
3670 if ($this->theadMargins['cell']) {
3671 if ($this->rtl) {
3672 $this->x -= $this->cell_padding['R'];
3673 } else {
3674 $this->x += $this->cell_padding['L'];
3675 }
3676 }
3677 $gvars = $this->getGraphicVars();
3678 if (!empty($this->theadMargins['gvars'])) {
3679 // set the correct graphic style
3680 $this->setGraphicVars($this->theadMargins['gvars']);
3681 $this->rMargin = $gvars['rMargin'];
3682 $this->lMargin = $gvars['lMargin'];
3683 }
3684 // print table header
3685 $this->writeHTML($this->thead, false, false, false, false, '');
3686 $this->setGraphicVars($gvars);
3687 // set new top margin to skip the table headers
3688 if (!isset($this->theadMargins['top'])) {
3689 $this->theadMargins['top'] = $this->tMargin;
3690 }
3691 // store end of header position
3692 if (!isset($this->columns[0]['th'])) {
3693 $this->columns[0]['th'] = array();
3694 }
3695 $this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
3696 $this->tMargin = $this->y;
3697 $this->pagedim[$this->page]['tm'] = $this->tMargin;
3698 $this->lasth = 0;
3699 $this->lMargin = $prev_lMargin;
3700 $this->rMargin = $prev_rMargin;
3701 $this->cell_padding = $prev_cell_padding;
3702 }
3703 }
3704
3705 /**
3706 * Returns the current page number.
3707 * @return int page number
3708 * @public
3709 * @since 1.0
3710 * @see getAliasNbPages()
3711 */
3712 public function PageNo() {
3713 return $this->page;
3714 }
3715
3716 /**
3717 * Returns the array of spot colors.
3718 * @return (array) Spot colors array.
3719 * @public
3720 * @since 6.0.038 (2013-09-30)
3721 */
3722 public function getAllSpotColors() {
3723 return $this->spot_colors;
3724 }
3725
3726 /**
3727 * Defines a new spot color.
3728 * It can be expressed in RGB components or gray scale.
3729 * The method can be called before the first page is created and the value is retained from page to page.
3730 * @param $name (string) Full name of the spot color.
3731 * @param $c (float) Cyan color for CMYK. Value between 0 and 100.
3732 * @param $m (float) Magenta color for CMYK. Value between 0 and 100.
3733 * @param $y (float) Yellow color for CMYK. Value between 0 and 100.
3734 * @param $k (float) Key (Black) color for CMYK. Value between 0 and 100.
3735 * @public
3736 * @since 4.0.024 (2008-09-12)
3737 * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3738 */
3739 public function AddSpotColor($name, $c, $m, $y, $k) {
3740 if (!isset($this->spot_colors[$name])) {
3741 $i = (1 + count($this->spot_colors));
3742 $this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
3743 }
3744 }
3745
3746 /**
3747 * Set the spot color for the specified type ('draw', 'fill', 'text').
3748 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3749 * @param $name (string) Name of the spot color.
3750 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3751 * @return (string) PDF color command.
3752 * @public
3753 * @since 5.9.125 (2011-10-03)
3754 */
3755 public function setSpotColor($type, $name, $tint=100) {
3756 $spotcolor = TCPDF_COLORS::getSpotColor($name, $this->spot_colors);
3757 if ($spotcolor === false) {
3758 $this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
3759 }
3760 $tint = (max(0, min(100, $tint)) / 100);
3761 $pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
3762 switch ($type) {
3763 case 'draw': {
3764 $pdfcolor .= sprintf('CS %F SCN', $tint);
3765 $this->DrawColor = $pdfcolor;
3766 $this->strokecolor = $spotcolor;
3767 break;
3768 }
3769 case 'fill': {
3770 $pdfcolor .= sprintf('cs %F scn', $tint);
3771 $this->FillColor = $pdfcolor;
3772 $this->bgcolor = $spotcolor;
3773 break;
3774 }
3775 case 'text': {
3776 $pdfcolor .= sprintf('cs %F scn', $tint);
3777 $this->TextColor = $pdfcolor;
3778 $this->fgcolor = $spotcolor;
3779 break;
3780 }
3781 }
3782 $this->ColorFlag = ($this->FillColor != $this->TextColor);
3783 if ($this->state == 2) {
3784 $this->_out($pdfcolor);
3785 }
3786 if ($this->inxobj) {
3787 // we are inside an XObject template
3788 $this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
3789 }
3790 return $pdfcolor;
3791 }
3792
3793 /**
3794 * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
3795 * @param $name (string) Name of the spot color.
3796 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3797 * @public
3798 * @since 4.0.024 (2008-09-12)
3799 * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3800 */
3801 public function SetDrawSpotColor($name, $tint=100) {
3802 $this->setSpotColor('draw', $name, $tint);
3803 }
3804
3805 /**
3806 * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
3807 * @param $name (string) Name of the spot color.
3808 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3809 * @public
3810 * @since 4.0.024 (2008-09-12)
3811 * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
3812 */
3813 public function SetFillSpotColor($name, $tint=100) {
3814 $this->setSpotColor('fill', $name, $tint);
3815 }
3816
3817 /**
3818 * Defines the spot color used for text.
3819 * @param $name (string) Name of the spot color.
3820 * @param $tint (int) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3821 * @public
3822 * @since 4.0.024 (2008-09-12)
3823 * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
3824 */
3825 public function SetTextSpotColor($name, $tint=100) {
3826 $this->setSpotColor('text', $name, $tint);
3827 }
3828
3829 /**
3830 * Set the color array for the specified type ('draw', 'fill', 'text').
3831 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3832 * The method can be called before the first page is created and the value is retained from page to page.
3833 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3834 * @param $color (array) Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
3835 * @param $ret (boolean) If true do not send the PDF command.
3836 * @return (string) The PDF command or empty string.
3837 * @public
3838 * @since 3.1.000 (2008-06-11)
3839 */
3840 public function setColorArray($type, $color, $ret=false) {
3841 if (is_array($color)) {
3842 $color = array_values($color);
3843 // component: grey, RGB red or CMYK cyan
3844 $c = isset($color[0]) ? $color[0] : -1;
3845 // component: RGB green or CMYK magenta
3846 $m = isset($color[1]) ? $color[1] : -1;
3847 // component: RGB blue or CMYK yellow
3848 $y = isset($color[2]) ? $color[2] : -1;
3849 // component: CMYK black
3850 $k = isset($color[3]) ? $color[3] : -1;
3851 // color name
3852 $name = isset($color[4]) ? $color[4] : '';
3853 if ($c >= 0) {
3854 return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
3855 }
3856 }
3857 return '';
3858 }
3859
3860 /**
3861 * Defines the color used for all drawing operations (lines, rectangles and cell borders).
3862 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3863 * The method can be called before the first page is created and the value is retained from page to page.
3864 * @param $color (array) Array of colors (1, 3 or 4 values).
3865 * @param $ret (boolean) If true do not send the PDF command.
3866 * @return string the PDF command
3867 * @public
3868 * @since 3.1.000 (2008-06-11)
3869 * @see SetDrawColor()
3870 */
3871 public function SetDrawColorArray($color, $ret=false) {
3872 return $this->setColorArray('draw', $color, $ret);
3873 }
3874
3875 /**
3876 * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
3877 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3878 * The method can be called before the first page is created and the value is retained from page to page.
3879 * @param $color (array) Array of colors (1, 3 or 4 values).
3880 * @param $ret (boolean) If true do not send the PDF command.
3881 * @public
3882 * @since 3.1.000 (2008-6-11)
3883 * @see SetFillColor()
3884 */
3885 public function SetFillColorArray($color, $ret=false) {
3886 return $this->setColorArray('fill', $color, $ret);
3887 }
3888
3889 /**
3890 * Defines the color used for text. It can be expressed in RGB components or gray scale.
3891 * The method can be called before the first page is created and the value is retained from page to page.
3892 * @param $color (array) Array of colors (1, 3 or 4 values).
3893 * @param $ret (boolean) If true do not send the PDF command.
3894 * @public
3895 * @since 3.1.000 (2008-6-11)
3896 * @see SetFillColor()
3897 */
3898 public function SetTextColorArray($color, $ret=false) {
3899 return $this->setColorArray('text', $color, $ret);
3900 }
3901
3902 /**
3903 * Defines the color used by the specified type ('draw', 'fill', 'text').
3904 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3905 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3906 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3907 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3908 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3909 * @param $ret (boolean) If true do not send the command.
3910 * @param $name (string) spot color name (if any)
3911 * @return (string) The PDF command or empty string.
3912 * @public
3913 * @since 5.9.125 (2011-10-03)
3914 */
3915 public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3916 // set default values
3917 if (!is_numeric($col1)) {
3918 $col1 = 0;
3919 }
3920 if (!is_numeric($col2)) {
3921 $col2 = -1;
3922 }
3923 if (!is_numeric($col3)) {
3924 $col3 = -1;
3925 }
3926 if (!is_numeric($col4)) {
3927 $col4 = -1;
3928 }
3929 // set color by case
3930 $suffix = '';
3931 if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
3932 // Grey scale
3933 $col1 = max(0, min(255, $col1));
3934 $intcolor = array('G' => $col1);
3935 $pdfcolor = sprintf('%F ', ($col1 / 255));
3936 $suffix = 'g';
3937 } elseif ($col4 == -1) {
3938 // RGB
3939 $col1 = max(0, min(255, $col1));
3940 $col2 = max(0, min(255, $col2));
3941 $col3 = max(0, min(255, $col3));
3942 $intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
3943 $pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
3944 $suffix = 'rg';
3945 } else {
3946 $col1 = max(0, min(100, $col1));
3947 $col2 = max(0, min(100, $col2));
3948 $col3 = max(0, min(100, $col3));
3949 $col4 = max(0, min(100, $col4));
3950 if (empty($name)) {
3951 // CMYK
3952 $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
3953 $pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
3954 $suffix = 'k';
3955 } else {
3956 // SPOT COLOR
3957 $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
3958 $this->AddSpotColor($name, $col1, $col2, $col3, $col4);
3959 $pdfcolor = $this->setSpotColor($type, $name, 100);
3960 }
3961 }
3962 switch ($type) {
3963 case 'draw': {
3964 $pdfcolor .= strtoupper($suffix);
3965 $this->DrawColor = $pdfcolor;
3966 $this->strokecolor = $intcolor;
3967 break;
3968 }
3969 case 'fill': {
3970 $pdfcolor .= $suffix;
3971 $this->FillColor = $pdfcolor;
3972 $this->bgcolor = $intcolor;
3973 break;
3974 }
3975 case 'text': {
3976 $pdfcolor .= $suffix;
3977 $this->TextColor = $pdfcolor;
3978 $this->fgcolor = $intcolor;
3979 break;
3980 }
3981 }
3982 $this->ColorFlag = ($this->FillColor != $this->TextColor);
3983 if (($type != 'text') AND ($this->state == 2)) {
3984 if (!$ret) {
3985 $this->_out($pdfcolor);
3986 }
3987 return $pdfcolor;
3988 }
3989 return '';
3990 }
3991
3992 /**
3993 * 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.
3994 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3995 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3996 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3997 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3998 * @param $ret (boolean) If true do not send the command.
3999 * @param $name (string) spot color name (if any)
4000 * @return string the PDF command
4001 * @public
4002 * @since 1.3
4003 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
4004 */
4005 public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4006 return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
4007 }
4008
4009 /**
4010 * 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.
4011 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4012 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4013 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4014 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4015 * @param $ret (boolean) If true do not send the command.
4016 * @param $name (string) Spot color name (if any).
4017 * @return (string) The PDF command.
4018 * @public
4019 * @since 1.3
4020 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
4021 */
4022 public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4023 return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
4024 }
4025
4026 /**
4027 * 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.
4028 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4029 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4030 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4031 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4032 * @param $ret (boolean) If true do not send the command.
4033 * @param $name (string) Spot color name (if any).
4034 * @return (string) Empty string.
4035 * @public
4036 * @since 1.3
4037 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4038 */
4039 public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4040 return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4041 }
4042
4043 /**
4044 * Returns the length of a string in user unit. A font must be selected.<br>
4045 * @param $s (string) The string whose length is to be computed
4046 * @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.
4047 * @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.
4048 * @param $fontsize (float) Font size in points. The default value is the current size.
4049 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4050 * @return mixed int total string length or array of characted widths
4051 * @author Nicola Asuni
4052 * @public
4053 * @since 1.2
4054 */
4055 public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4056 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);
4057 }
4058
4059 /**
4060 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4061 * @param $sa (string) The array of chars whose total length is to be computed
4062 * @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.
4063 * @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.
4064 * @param $fontsize (float) Font size in points. The default value is the current size.
4065 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4066 * @return mixed int total string length or array of characted widths
4067 * @author Nicola Asuni
4068 * @public
4069 * @since 2.4.000 (2008-03-06)
4070 */
4071 public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4072 // store current values
4073 if (!TCPDF_STATIC::empty_string($fontname)) {
4074 $prev_FontFamily = $this->FontFamily;
4075 $prev_FontStyle = $this->FontStyle;
4076 $prev_FontSizePt = $this->FontSizePt;
4077 $this->SetFont($fontname, $fontstyle, $fontsize, '', 'default', false);
4078 }
4079 // convert UTF-8 array to Latin1 if required
4080 if ($this->isunicode AND (!$this->isUnicodeFont())) {
4081 $sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
4082 }
4083 $w = 0; // total width
4084 $wa = array(); // array of characters widths
4085 foreach ($sa as $ck => $char) {
4086 // character width
4087 $cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4088 $wa[] = $cw;
4089 $w += $cw;
4090 }
4091 // restore previous values
4092 if (!TCPDF_STATIC::empty_string($fontname)) {
4093 $this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4094 }
4095 if ($getarray) {
4096 return $wa;
4097 }
4098 return $w;
4099 }
4100
4101 /**
4102 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
4103 * @param $char (int) The char code whose length is to be returned
4104 * @param $notlast (boolean) If false ignore the font-spacing.
4105 * @return float char width
4106 * @author Nicola Asuni
4107 * @public
4108 * @since 2.4.000 (2008-03-06)
4109 */
4110 public function GetCharWidth($char, $notlast=true) {
4111 // get raw width
4112 $chw = $this->getRawCharWidth($char);
4113 if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
4114 // increase/decrease font spacing
4115 $chw += $this->font_spacing;
4116 }
4117 if ($this->font_stretching != 100) {
4118 // fixed stretching mode
4119 $chw *= ($this->font_stretching / 100);
4120 }
4121 return $chw;
4122 }
4123
4124 /**
4125 * Returns the length of the char in user unit for the current font.
4126 * @param $char (int) The char code whose length is to be returned
4127 * @return float char width
4128 * @author Nicola Asuni
4129 * @public
4130 * @since 5.9.000 (2010-09-28)
4131 */
4132 public function getRawCharWidth($char) {
4133 if ( is_float( $char ) ) {
4134 $char = intval( $char );
4135 }
4136 if ($char == 173) {
4137 // SHY character will not be printed
4138 return (0);
4139 }
4140 if (isset($this->CurrentFont['cw'][$char])) {
4141 $w = $this->CurrentFont['cw'][$char];
4142 } elseif (isset($this->CurrentFont['dw'])) {
4143 // default width
4144 $w = $this->CurrentFont['dw'];
4145 } elseif (isset($this->CurrentFont['cw'][32])) {
4146 // default width
4147 $w = $this->CurrentFont['cw'][32];
4148 } else {
4149 $w = 600;
4150 }
4151 return $this->getAbsFontMeasure($w);
4152 }
4153
4154 /**
4155 * Returns the numbero of characters in a string.
4156 * @param $s (string) The input string.
4157 * @return int number of characters
4158 * @public
4159 * @since 2.0.0001 (2008-01-07)
4160 */
4161 public function GetNumChars($s) {
4162 if ($this->isUnicodeFont()) {
4163 return count(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont));
4164 }
4165 return strlen($s);
4166 }
4167
4168 /**
4169 * Fill the list of available fonts ($this->fontlist).
4170 * @protected
4171 * @since 4.0.013 (2008-07-28)
4172 */
4173 protected function getFontsList() {
4174 if (($fontsdir = opendir(TCPDF_FONTS::_getfontpath())) !== false) {
4175 while (($file = readdir($fontsdir)) !== false) {
4176 if (substr($file, -4) == '.php') {
4177 array_push($this->fontlist, strtolower(basename($file, '.php')));
4178 }
4179 }
4180 closedir($fontsdir);
4181 }
4182 }
4183
4184 /**
4185 * Imports a TrueType, Type1, core, or CID0 font and makes it available.
4186 * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
4187 * 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.
4188 * @param $family (string) Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
4189 * @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>
4190 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4191 * @return array containing the font data, or false in case of error.
4192 * @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.
4193 * @public
4194 * @since 1.5
4195 * @see SetFont(), setFontSubsetting()
4196 */
4197 public function AddFont($family, $style='', $fontfile='', $subset='default') {
4198 if ($subset === 'default') {
4199 $subset = $this->font_subsetting;
4200 }
4201 if ($this->pdfa_mode) {
4202 $subset = false;
4203 }
4204 if (TCPDF_STATIC::empty_string($family)) {
4205 if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
4206 $family = $this->FontFamily;
4207 } else {
4208 $this->Error('Empty font family');
4209 }
4210 }
4211 // move embedded styles on $style
4212 if (substr($family, -1) == 'I') {
4213 $style .= 'I';
4214 $family = substr($family, 0, -1);
4215 }
4216 if (substr($family, -1) == 'B') {
4217 $style .= 'B';
4218 $family = substr($family, 0, -1);
4219 }
4220 // normalize family name
4221 $family = strtolower($family);
4222 if ((!$this->isunicode) AND ($family == 'arial')) {
4223 $family = 'helvetica';
4224 }
4225 if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4226 $style = '';
4227 }
4228 if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
4229 // all fonts must be embedded
4230 $family = 'pdfa'.$family;
4231 }
4232 $tempstyle = strtoupper($style);
4233 $style = '';
4234 // underline
4235 if (strpos($tempstyle, 'U') !== false) {
4236 $this->underline = true;
4237 } else {
4238 $this->underline = false;
4239 }
4240 // line-through (deleted)
4241 if (strpos($tempstyle, 'D') !== false) {
4242 $this->linethrough = true;
4243 } else {
4244 $this->linethrough = false;
4245 }
4246 // overline
4247 if (strpos($tempstyle, 'O') !== false) {
4248 $this->overline = true;
4249 } else {
4250 $this->overline = false;
4251 }
4252 // bold
4253 if (strpos($tempstyle, 'B') !== false) {
4254 $style .= 'B';
4255 }
4256 // oblique
4257 if (strpos($tempstyle, 'I') !== false) {
4258 $style .= 'I';
4259 }
4260 $bistyle = $style;
4261 $fontkey = $family.$style;
4262 $font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
4263 $fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4264 // check if the font has been already added
4265 $fb = $this->getFontBuffer($fontkey);
4266 if ($fb !== false) {
4267 if ($this->inxobj) {
4268 // we are inside an XObject template
4269 $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
4270 }
4271 return $fontdata;
4272 }
4273 // get specified font directory (if any)
4274 $fontdir = false;
4275 if (!TCPDF_STATIC::empty_string($fontfile)) {
4276 $fontdir = dirname($fontfile);
4277 if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
4278 $fontdir = '';
4279 } else {
4280 $fontdir .= '/';
4281 }
4282 }
4283 // true when the font style variation is missing
4284 $missing_style = false;
4285 // search and include font file
4286 if (TCPDF_STATIC::empty_string($fontfile) OR (!@TCPDF_STATIC::file_exists($fontfile))) {
4287 // build a standard filenames for specified font
4288 $tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
4289 $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4290 if (TCPDF_STATIC::empty_string($fontfile)) {
4291 $missing_style = true;
4292 // try to remove the style part
4293 $tmp_fontfile = str_replace(' ', '', $family).'.php';
4294 $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4295 }
4296 }
4297 // include font file
4298 if (!TCPDF_STATIC::empty_string($fontfile) AND (@TCPDF_STATIC::file_exists($fontfile))) {
4299 include($fontfile);
4300 } else {
4301 $this->Error('Could not include font definition file: '.$family.'');
4302 }
4303 // check font parameters
4304 if ((!isset($type)) OR (!isset($cw))) {
4305 $this->Error('The font definition file has a bad format: '.$fontfile.'');
4306 }
4307 // SET default parameters
4308 if (!isset($file) OR TCPDF_STATIC::empty_string($file)) {
4309 $file = '';
4310 }
4311 if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
4312 $enc = '';
4313 }
4314 if (!isset($cidinfo) OR TCPDF_STATIC::empty_string($cidinfo)) {
4315 $cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
4316 $cidinfo['uni2cid'] = array();
4317 }
4318 if (!isset($ctg) OR TCPDF_STATIC::empty_string($ctg)) {
4319 $ctg = '';
4320 }
4321 if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
4322 $desc = array();
4323 }
4324 if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
4325 $up = -100;
4326 }
4327 if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
4328 $ut = 50;
4329 }
4330 if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
4331 $cw = array();
4332 }
4333 if (!isset($dw) OR TCPDF_STATIC::empty_string($dw)) {
4334 // set default width
4335 if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4336 $dw = $desc['MissingWidth'];
4337 } elseif (isset($cw[32])) {
4338 $dw = $cw[32];
4339 } else {
4340 $dw = 600;
4341 }
4342 }
4343 ++$this->numfonts;
4344 if ($type == 'core') {
4345 $name = $this->CoreFonts[$fontkey];
4346 $subset = false;
4347 } elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4348 $subset = false;
4349 } elseif ($type == 'TrueTypeUnicode') {
4350 $enc = 'Identity-H';
4351 } elseif ($type == 'cidfont0') {
4352 if ($this->pdfa_mode) {
4353 $this->Error('All fonts must be embedded in PDF/A mode!');
4354 }
4355 } else {
4356 $this->Error('Unknow font type: '.$type.'');
4357 }
4358 // set name if unset
4359 if (!isset($name) OR empty($name)) {
4360 $name = $fontkey;
4361 }
4362 // create artificial font style variations if missing (only works with non-embedded fonts)
4363 if (($type != 'core') AND $missing_style) {
4364 // style variations
4365 $styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4366 $name .= $styles[$bistyle];
4367 // artificial bold
4368 if (strpos($bistyle, 'B') !== false) {
4369 if (isset($desc['StemV'])) {
4370 // from normal to bold
4371 $desc['StemV'] = round($desc['StemV'] * 1.75);
4372 } else {
4373 // bold
4374 $desc['StemV'] = 123;
4375 }
4376 }
4377 // artificial italic
4378 if (strpos($bistyle, 'I') !== false) {
4379 if (isset($desc['ItalicAngle'])) {
4380 $desc['ItalicAngle'] -= 11;
4381 } else {
4382 $desc['ItalicAngle'] = -11;
4383 }
4384 if (isset($desc['Flags'])) {
4385 $desc['Flags'] |= 64; //bit 7
4386 } else {
4387 $desc['Flags'] = 64;
4388 }
4389 }
4390 }
4391 // check if the array of characters bounding boxes is defined
4392 if (!isset($cbbox)) {
4393 $cbbox = array();
4394 }
4395 // initialize subsetchars
4396 $subsetchars = array_fill(0, 255, true);
4397 $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));
4398 if ($this->inxobj) {
4399 // we are inside an XObject template
4400 $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
4401 }
4402 if (isset($diff) AND (!empty($diff))) {
4403 //Search existing encodings
4404 $d = 0;
4405 $nb = count($this->diffs);
4406 for ($i=1; $i <= $nb; ++$i) {
4407 if ($this->diffs[$i] == $diff) {
4408 $d = $i;
4409 break;
4410 }
4411 }
4412 if ($d == 0) {
4413 $d = $nb + 1;
4414 $this->diffs[$d] = $diff;
4415 }
4416 $this->setFontSubBuffer($fontkey, 'diff', $d);
4417 }
4418 if (!TCPDF_STATIC::empty_string($file)) {
4419 if (!isset($this->FontFiles[$file])) {
4420 if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4421 $this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4422 } elseif ($type != 'core') {
4423 $this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4424 }
4425 } else {
4426 // update fontkeys that are sharing this font file
4427 $this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
4428 if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
4429 $this->FontFiles[$file]['fontkeys'][] = $fontkey;
4430 }
4431 }
4432 }
4433 return $fontdata;
4434 }
4435
4436 /**
4437 * Sets the font used to print character strings.
4438 * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
4439 * The method can be called before the first page is created and the font is retained from page to page.
4440 * If you just wish to change the current font size, it is simpler to call SetFontSize().
4441 * 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 />
4442 * @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.
4443 * @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.
4444 * @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
4445 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4446 * @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.
4447 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4448 * @author Nicola Asuni
4449 * @public
4450 * @since 1.0
4451 * @see AddFont(), SetFontSize()
4452 */
4453 public function SetFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
4454 //Select a font; size given in points
4455 if ($size === null) {
4456 $size = $this->FontSizePt;
4457 }
4458 if ($size < 0) {
4459 $size = 0;
4460 }
4461 // try to add font (if not already added)
4462 $fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4463 $this->FontFamily = $fontdata['family'];
4464 $this->FontStyle = $fontdata['style'];
4465 if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
4466 // save subset chars of the previous font
4467 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
4468 }
4469 $this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
4470 $this->SetFontSize($size, $out);
4471 }
4472
4473 /**
4474 * Defines the size of the current font.
4475 * @param $size (float) The font size in points.
4476 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4477 * @public
4478 * @since 1.0
4479 * @see SetFont()
4480 */
4481 public function SetFontSize($size, $out=true) {
4482 $size = (float)$size;
4483 // font size in points
4484 $this->FontSizePt = $size;
4485 // font size in user units
4486 $this->FontSize = $size / $this->k;
4487 // calculate some font metrics
4488 if (isset($this->CurrentFont['desc']['FontBBox'])) {
4489 $bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4490 $font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4491 } else {
4492 $font_height = $size * 1.219;
4493 }
4494 if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
4495 $font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
4496 }
4497 if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
4498 $font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
4499 }
4500 if (!isset($font_ascent) AND !isset($font_descent)) {
4501 // core font
4502 $font_ascent = 0.76 * $font_height;
4503 $font_descent = $font_height - $font_ascent;
4504 } elseif (!isset($font_descent)) {
4505 $font_descent = $font_height - $font_ascent;
4506 } elseif (!isset($font_ascent)) {
4507 $font_ascent = $font_height - $font_descent;
4508 }
4509 $this->FontAscent = ($font_ascent / $this->k);
4510 $this->FontDescent = ($font_descent / $this->k);
4511 if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
4512 $this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4513 }
4514 }
4515
4516 /**
4517 * Returns the bounding box of the current font in user units.
4518 * @return array
4519 * @public
4520 * @since 5.9.152 (2012-03-23)
4521 */
4522 public function getFontBBox() {
4523 $fbbox = array();
4524 if (isset($this->CurrentFont['desc']['FontBBox'])) {
4525 $tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4526 $fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
4527 } else {
4528 // Find max width
4529 if (isset($this->CurrentFont['desc']['MaxWidth'])) {
4530 $maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
4531 } else {
4532 $maxw = 0;
4533 if (isset($this->CurrentFont['desc']['MissingWidth'])) {
4534 $maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
4535 }
4536 if (isset($this->CurrentFont['desc']['AvgWidth'])) {
4537 $maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
4538 }
4539 if (isset($this->CurrentFont['dw'])) {
4540 $maxw = max($maxw, $this->CurrentFont['dw']);
4541 }
4542 foreach ($this->CurrentFont['cw'] as $char => $w) {
4543 $maxw = max($maxw, $w);
4544 }
4545 if ($maxw == 0) {
4546 $maxw = 600;
4547 }
4548 $maxw = $this->getAbsFontMeasure($maxw);
4549 }
4550 $fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
4551 }
4552 return $fbbox;
4553 }
4554
4555 /**
4556 * Convert a relative font measure into absolute value.
4557 * @param $s (int) Font measure.
4558 * @return float Absolute measure.
4559 * @since 5.9.186 (2012-09-13)
4560 */
4561 public function getAbsFontMeasure($s) {
4562 return ($s * $this->FontSize / 1000);
4563 }
4564
4565 /**
4566 * Returns the glyph bounding box of the specified character in the current font in user units.
4567 * @param $char (int) Input character code.
4568 * @return mixed array(xMin, yMin, xMax, yMax) or FALSE if not defined.
4569 * @since 5.9.186 (2012-09-13)
4570 */
4571 public function getCharBBox($char) {
4572 $c = intval($char);
4573 if (isset($this->CurrentFont['cw'][$c])) {
4574 // glyph is defined ... use zero width & height for glyphs without outlines
4575 $result = array(0,0,0,0);
4576 if (isset($this->CurrentFont['cbbox'][$c])) {
4577 $result = $this->CurrentFont['cbbox'][$c];
4578 }
4579 return array_map(array($this,'getAbsFontMeasure'), $result);
4580 }
4581 return false;
4582 }
4583
4584 /**
4585 * Return the font descent value
4586 * @param $font (string) font name
4587 * @param $style (string) font style
4588 * @param $size (float) The size (in points)
4589 * @return int font descent
4590 * @public
4591 * @author Nicola Asuni
4592 * @since 4.9.003 (2010-03-30)
4593 */
4594 public function getFontDescent($font, $style='', $size=0) {
4595 $fontdata = $this->AddFont($font, $style);
4596 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4597 if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
4598 $descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
4599 } else {
4600 $descent = (1.219 * 0.24 * $size);
4601 }
4602 return ($descent / $this->k);
4603 }
4604
4605 /**
4606 * Return the font ascent value.
4607 * @param $font (string) font name
4608 * @param $style (string) font style
4609 * @param $size (float) The size (in points)
4610 * @return int font ascent
4611 * @public
4612 * @author Nicola Asuni
4613 * @since 4.9.003 (2010-03-30)
4614 */
4615 public function getFontAscent($font, $style='', $size=0) {
4616 $fontdata = $this->AddFont($font, $style);
4617 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4618 if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
4619 $ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
4620 } else {
4621 $ascent = 1.219 * 0.76 * $size;
4622 }
4623 return ($ascent / $this->k);
4624 }
4625
4626 /**
4627 * Return true in the character is present in the specified font.
4628 * @param $char (mixed) Character to check (integer value or string)
4629 * @param $font (string) Font name (family name).
4630 * @param $style (string) Font style.
4631 * @return (boolean) true if the char is defined, false otherwise.
4632 * @public
4633 * @since 5.9.153 (2012-03-28)
4634 */
4635 public function isCharDefined($char, $font='', $style='') {
4636 if (is_string($char)) {
4637 // get character code
4638 $char = TCPDF_FONTS::UTF8StringToArray($char, $this->isunicode, $this->CurrentFont);
4639 $char = $char[0];
4640 }
4641 if (TCPDF_STATIC::empty_string($font)) {
4642 if (TCPDF_STATIC::empty_string($style)) {
4643 return (isset($this->CurrentFont['cw'][intval($char)]));
4644 }
4645 $font = $this->FontFamily;
4646 }
4647 $fontdata = $this->AddFont($font, $style);
4648 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4649 return (isset($fontinfo['cw'][intval($char)]));
4650 }
4651
4652 /**
4653 * Replace missing font characters on selected font with specified substitutions.
4654 * @param $text (string) Text to process.
4655 * @param $font (string) Font name (family name).
4656 * @param $style (string) Font style.
4657 * @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.
4658 * @return (string) Processed text.
4659 * @public
4660 * @since 5.9.153 (2012-03-28)
4661 */
4662 public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
4663 if (empty($subs)) {
4664 return $text;
4665 }
4666 if (TCPDF_STATIC::empty_string($font)) {
4667 $font = $this->FontFamily;
4668 }
4669 $fontdata = $this->AddFont($font, $style);
4670 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4671 $uniarr = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
4672 foreach ($uniarr as $k => $chr) {
4673 if (!isset($fontinfo['cw'][$chr])) {
4674 // this character is missing on the selected font
4675 if (isset($subs[$chr])) {
4676 // we have available substitutions
4677 if (is_array($subs[$chr])) {
4678 foreach($subs[$chr] as $s) {
4679 if (isset($fontinfo['cw'][$s])) {
4680 $uniarr[$k] = $s;
4681 break;
4682 }
4683 }
4684 } elseif (isset($fontinfo['cw'][$subs[$chr]])) {
4685 $uniarr[$k] = $subs[$chr];
4686 }
4687 }
4688 }
4689 }
4690 return TCPDF_FONTS::UniArrSubString(TCPDF_FONTS::UTF8ArrayToUniArray($uniarr, $this->isunicode));
4691 }
4692
4693 /**
4694 * Defines the default monospaced font.
4695 * @param $font (string) Font name.
4696 * @public
4697 * @since 4.5.025
4698 */
4699 public function SetDefaultMonospacedFont($font) {
4700 $this->default_monospaced_font = $font;
4701 }
4702
4703 /**
4704 * 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 />
4705 * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
4706 * @public
4707 * @since 1.5
4708 * @see Cell(), Write(), Image(), Link(), SetLink()
4709 */
4710 public function AddLink() {
4711 // create a new internal link
4712 $n = count($this->links) + 1;
4713 $this->links[$n] = array('p' => 0, 'y' => 0, 'f' => false);
4714 return $n;
4715 }
4716
4717 /**
4718 * Defines the page and position a link points to.
4719 * @param $link (int) The link identifier returned by AddLink()
4720 * @param $y (float) Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
4721 * @param $page (int|string) 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.
4722 * @public
4723 * @since 1.5
4724 * @see AddLink()
4725 */
4726 public function SetLink($link, $y=0, $page=-1) {
4727 $fixed = false;
4728 if (!empty($page) AND (substr($page, 0, 1) == '*')) {
4729 $page = intval(substr($page, 1));
4730 // this page number will not be changed when moving/add/deleting pages
4731 $fixed = true;
4732 }
4733 if ($page < 0) {
4734 $page = $this->page;
4735 }
4736 if ($y == -1) {
4737 $y = $this->y;
4738 }
4739 $this->links[$link] = array('p' => $page, 'y' => $y, 'f' => $fixed);
4740 }
4741
4742 /**
4743 * Puts a link on a rectangular area of the page.
4744 * 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.
4745 * @param $x (float) Abscissa of the upper-left corner of the rectangle
4746 * @param $y (float) Ordinate of the upper-left corner of the rectangle
4747 * @param $w (float) Width of the rectangle
4748 * @param $h (float) Height of the rectangle
4749 * @param $link (mixed) URL or identifier returned by AddLink()
4750 * @param $spaces (int) number of spaces on the text to link
4751 * @public
4752 * @since 1.5
4753 * @see AddLink(), Annotation(), Cell(), Write(), Image()
4754 */
4755 public function Link($x, $y, $w, $h, $link, $spaces=0) {
4756 $this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
4757 }
4758
4759 /**
4760 * Puts a markup annotation on a rectangular area of the page.
4761 * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
4762 * @param $x (float) Abscissa of the upper-left corner of the rectangle
4763 * @param $y (float) Ordinate of the upper-left corner of the rectangle
4764 * @param $w (float) Width of the rectangle
4765 * @param $h (float) Height of the rectangle
4766 * @param $text (string) annotation text or alternate content
4767 * @param $opt (array) array of options (see section 8.4 of PDF reference 1.7).
4768 * @param $spaces (int) number of spaces on the text to link
4769 * @public
4770 * @since 4.0.018 (2008-08-06)
4771 */
4772 public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
4773 if ($this->inxobj) {
4774 // store parameters for later use on template
4775 $this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
4776 return;
4777 }
4778 if ($x === '') {
4779 $x = $this->x;
4780 }
4781 if ($y === '') {
4782 $y = $this->y;
4783 }
4784 // check page for no-write regions and adapt page margins if necessary
4785 list($x, $y) = $this->checkPageRegions($h, $x, $y);
4786 // recalculate coordinates to account for graphic transformations
4787 if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
4788 for ($i=$this->transfmatrix_key; $i > 0; --$i) {
4789 $maxid = count($this->transfmatrix[$i]) - 1;
4790 for ($j=$maxid; $j >= 0; --$j) {
4791 $ctm = $this->transfmatrix[$i][$j];
4792 if (isset($ctm['a'])) {
4793 $x = $x * $this->k;
4794 $y = ($this->h - $y) * $this->k;
4795 $w = $w * $this->k;
4796 $h = $h * $this->k;
4797 // top left
4798 $xt = $x;
4799 $yt = $y;
4800 $x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4801 $y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4802 // top right
4803 $xt = $x + $w;
4804 $yt = $y;
4805 $x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4806 $y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4807 // bottom left
4808 $xt = $x;
4809 $yt = $y - $h;
4810 $x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4811 $y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4812 // bottom right
4813 $xt = $x + $w;
4814 $yt = $y - $h;
4815 $x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4816 $y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4817 // new coordinates (rectangle area)
4818 $x = min($x1, $x2, $x3, $x4);
4819 $y = max($y1, $y2, $y3, $y4);
4820 $w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
4821 $h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
4822 $x = $x / $this->k;
4823 $y = $this->h - ($y / $this->k);
4824 }
4825 }
4826 }
4827 }
4828 if ($this->page <= 0) {
4829 $page = 1;
4830 } else {
4831 $page = $this->page;
4832 }
4833 if (!isset($this->PageAnnots[$page])) {
4834 $this->PageAnnots[$page] = array();
4835 }
4836 $this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
4837 if (!$this->pdfa_mode || ($this->pdfa_mode && $this->pdfa_version == 3)) {
4838 if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
4839 AND (@TCPDF_STATIC::file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
4840 AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
4841 $this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
4842 }
4843 }
4844 // Add widgets annotation's icons
4845 if (isset($opt['mk']['i']) AND @TCPDF_STATIC::file_exists($opt['mk']['i'])) {
4846 $this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
4847 }
4848 if (isset($opt['mk']['ri']) AND @TCPDF_STATIC::file_exists($opt['mk']['ri'])) {
4849 $this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4850 }
4851 if (isset($opt['mk']['ix']) AND @TCPDF_STATIC::file_exists($opt['mk']['ix'])) {
4852 $this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4853 }
4854 }
4855
4856 /**
4857 * Embedd the attached files.
4858 * @since 4.4.000 (2008-12-07)
4859 * @protected
4860 * @see Annotation()
4861 */
4862 protected function _putEmbeddedFiles() {
4863 if ($this->pdfa_mode && $this->pdfa_version != 3) {
4864 // embedded files are not allowed in PDF/A mode version 1 and 2
4865 return;
4866 }
4867 reset($this->embeddedfiles);
4868 foreach ($this->embeddedfiles as $filename => $filedata) {
4869 $data = TCPDF_STATIC::fileGetContents($filedata['file']);
4870 if ($data !== FALSE) {
4871 $rawsize = strlen($data);
4872 if ($rawsize > 0) {
4873 // update name tree
4874 $this->efnames[$filename] = $filedata['f'].' 0 R';
4875 // embedded file specification object
4876 $out = $this->_getobj($filedata['f'])."\n";
4877 $out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']);
4878 $out .= ' /UF '.$this->_datastring($filename, $filedata['f']);
4879 $out .= ' /AFRelationship /Source';
4880 $out .= ' /EF <</F '.$filedata['n'].' 0 R>> >>';
4881 $out .= "\n".'endobj';
4882 $this->_out($out);
4883 // embedded file object
4884 $filter = '';
4885 if ($this->compress) {
4886 $data = gzcompress($data);
4887 $filter = ' /Filter /FlateDecode';
4888 }
4889
4890 if ($this->pdfa_version == 3) {
4891 $filter = ' /Subtype /text#2Fxml';
4892 }
4893
4894 $stream = $this->_getrawstream($data, $filedata['n']);
4895 $out = $this->_getobj($filedata['n'])."\n";
4896 $out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
4897 $out .= ' stream'."\n".$stream."\n".'endstream';
4898 $out .= "\n".'endobj';
4899 $this->_out($out);
4900 }
4901 }
4902 }
4903 }
4904
4905 /**
4906 * Prints a text cell at the specified position.
4907 * This method allows to place a string precisely on the page.
4908 * @param $x (float) Abscissa of the cell origin
4909 * @param $y (float) Ordinate of the cell origin
4910 * @param $txt (string) String to print
4911 * @param $fstroke (int) outline size in user units (false = disable)
4912 * @param $fclip (boolean) if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
4913 * @param $ffill (boolean) if true fills the text
4914 * @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)))
4915 * @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.
4916 * @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>
4917 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
4918 * @param $link (mixed) URL or identifier returned by AddLink().
4919 * @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.
4920 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
4921 * @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>
4922 * @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>
4923 * @param $rtloff (boolean) if true uses the page top-left corner as origin of axis for $x and $y initial position.
4924 * @public
4925 * @since 1.0
4926 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
4927 */
4928 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) {
4929 $textrendermode = $this->textrendermode;
4930 $textstrokewidth = $this->textstrokewidth;
4931 $this->setTextRenderingMode($fstroke, $ffill, $fclip);
4932 $this->SetXY($x, $y, $rtloff);
4933 $this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
4934 // restore previous rendering mode
4935 $this->textrendermode = $textrendermode;
4936 $this->textstrokewidth = $textstrokewidth;
4937 }
4938
4939 /**
4940 * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
4941 * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
4942 * This method is called automatically and should not be called directly by the application.
4943 * @return boolean
4944 * @public
4945 * @since 1.4
4946 * @see SetAutoPageBreak()
4947 */
4948 public function AcceptPageBreak() {
4949 if ($this->num_columns > 1) {
4950 // multi column mode
4951 if ($this->current_column < ($this->num_columns - 1)) {
4952 // go to next column
4953 $this->selectColumn($this->current_column + 1);
4954 } elseif ($this->AutoPageBreak) {
4955 // add a new page
4956 $this->AddPage();
4957 // set first column
4958 $this->selectColumn(0);
4959 }
4960 // avoid page breaking from checkPageBreak()
4961 return false;
4962 }
4963 return $this->AutoPageBreak;
4964 }
4965
4966 /**
4967 * Add page if needed.
4968 * @param $h (float) Cell height. Default value: 0.
4969 * @param $y (mixed) starting y position, leave empty for current position.
4970 * @param $addpage (boolean) if true add a page, otherwise only return the true/false state
4971 * @return boolean true in case of page break, false otherwise.
4972 * @since 3.2.000 (2008-07-01)
4973 * @protected
4974 */
4975 protected function checkPageBreak($h=0, $y='', $addpage=true) {
4976 if (TCPDF_STATIC::empty_string($y)) {
4977 $y = $this->y;
4978 }
4979 $current_page = $this->page;
4980 if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
4981 if ($addpage) {
4982 //Automatic page break
4983 $x = $this->x;
4984 $this->AddPage($this->CurOrientation);
4985 $this->y = $this->tMargin;
4986 $oldpage = $this->page - 1;
4987 if ($this->rtl) {
4988 if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
4989 $this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
4990 } else {
4991 $this->x = $x;
4992 }
4993 } else {
4994 if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
4995 $this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
4996 } else {
4997 $this->x = $x;
4998 }
4999 }
5000 }
5001 return true;
5002 }
5003 if ($current_page != $this->page) {
5004 // account for columns mode
5005 return true;
5006 }
5007 return false;
5008 }
5009
5010 /**
5011 * 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 />
5012 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5013 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
5014 * @param $h (float) Cell height. Default value: 0.
5015 * @param $txt (string) String to print. Default value: empty string.
5016 * @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)))
5017 * @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.
5018 * @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>
5019 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5020 * @param $link (mixed) URL or identifier returned by AddLink().
5021 * @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.
5022 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5023 * @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>
5024 * @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>
5025 * @public
5026 * @since 1.0
5027 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
5028 */
5029 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') {
5030 $prev_cell_margin = $this->cell_margin;
5031 $prev_cell_padding = $this->cell_padding;
5032 $this->adjustCellPadding($border);
5033 if (!$ignore_min_height) {
5034 $min_cell_height = $this->getCellHeight($this->FontSize);
5035 if ($h < $min_cell_height) {
5036 $h = $min_cell_height;
5037 }
5038 }
5039 $this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5040 // apply text shadow if enabled
5041 if ($this->txtshadow['enabled']) {
5042 // save data
5043 $x = $this->x;
5044 $y = $this->y;
5045 $bc = $this->bgcolor;
5046 $fc = $this->fgcolor;
5047 $sc = $this->strokecolor;
5048 $alpha = $this->alpha;
5049 // print shadow
5050 $this->x += $this->txtshadow['depth_w'];
5051 $this->y += $this->txtshadow['depth_h'];
5052 $this->SetFillColorArray($this->txtshadow['color']);
5053 $this->SetTextColorArray($this->txtshadow['color']);
5054 $this->SetDrawColorArray($this->txtshadow['color']);
5055 if ($this->txtshadow['opacity'] != $alpha['CA']) {
5056 $this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
5057 }
5058 if ($this->state == 2) {
5059 $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5060 }
5061 //restore data
5062 $this->x = $x;
5063 $this->y = $y;
5064 $this->SetFillColorArray($bc);
5065 $this->SetTextColorArray($fc);
5066 $this->SetDrawColorArray($sc);
5067 if ($this->txtshadow['opacity'] != $alpha['CA']) {
5068 $this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
5069 }
5070 }
5071 if ($this->state == 2) {
5072 $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5073 }
5074 $this->cell_padding = $prev_cell_padding;
5075 $this->cell_margin = $prev_cell_margin;
5076 }
5077
5078 /**
5079 * 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 />
5080 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5081 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
5082 * @param $h (float) Cell height. Default value: 0.
5083 * @param $txt (string) String to print. Default value: empty string.
5084 * @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)))
5085 * @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.
5086 * @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>
5087 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5088 * @param $link (mixed) URL or identifier returned by AddLink().
5089 * @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.
5090 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5091 * @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>
5092 * @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>
5093 * @return string containing cell code
5094 * @protected
5095 * @since 1.0
5096 * @see Cell()
5097 */
5098 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') {
5099 // replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
5100 $txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
5101 $prev_cell_margin = $this->cell_margin;
5102 $prev_cell_padding = $this->cell_padding;
5103 $txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
5104 $rs = ''; //string to be returned
5105 $this->adjustCellPadding($border);
5106 if (!$ignore_min_height) {
5107 $min_cell_height = $this->getCellHeight($this->FontSize);
5108 if ($h < $min_cell_height) {
5109 $h = $min_cell_height;
5110 }
5111 }
5112 $k = $this->k;
5113 // check page for no-write regions and adapt page margins if necessary
5114 list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
5115 if ($this->rtl) {
5116 $x = $this->x - $this->cell_margin['R'];
5117 } else {
5118 $x = $this->x + $this->cell_margin['L'];
5119 }
5120 $y = $this->y + $this->cell_margin['T'];
5121 $prev_font_stretching = $this->font_stretching;
5122 $prev_font_spacing = $this->font_spacing;
5123 // cell vertical alignment
5124 switch ($calign) {
5125 case 'A': {
5126 // font top
5127 switch ($valign) {
5128 case 'T': {
5129 // top
5130 $y -= $this->cell_padding['T'];
5131 break;
5132 }
5133 case 'B': {
5134 // bottom
5135 $y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5136 break;
5137 }
5138 default:
5139 case 'C':
5140 case 'M': {
5141 // center
5142 $y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5143 break;
5144 }
5145 }
5146 break;
5147 }
5148 case 'L': {
5149 // font baseline
5150 switch ($valign) {
5151 case 'T': {
5152 // top
5153 $y -= ($this->cell_padding['T'] + $this->FontAscent);
5154 break;
5155 }
5156 case 'B': {
5157 // bottom
5158 $y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5159 break;
5160 }
5161 default:
5162 case 'C':
5163 case 'M': {
5164 // center
5165 $y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5166 break;
5167 }
5168 }
5169 break;
5170 }
5171 case 'D': {
5172 // font bottom
5173 switch ($valign) {
5174 case 'T': {
5175 // top
5176 $y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5177 break;
5178 }
5179 case 'B': {
5180 // bottom
5181 $y -= ($h - $this->cell_padding['B']);
5182 break;
5183 }
5184 default:
5185 case 'C':
5186 case 'M': {
5187 // center
5188 $y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5189 break;
5190 }
5191 }
5192 break;
5193 }
5194 case 'B': {
5195 // cell bottom
5196 $y -= $h;
5197 break;
5198 }
5199 case 'C':
5200 case 'M': {
5201 // cell center
5202 $y -= ($h / 2);
5203 break;
5204 }
5205 default:
5206 case 'T': {
5207 // cell top
5208 break;
5209 }
5210 }
5211 // text vertical alignment
5212 switch ($valign) {
5213 case 'T': {
5214 // top
5215 $yt = $y + $this->cell_padding['T'];
5216 break;
5217 }
5218 case 'B': {
5219 // bottom
5220 $yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5221 break;
5222 }
5223 default:
5224 case 'C':
5225 case 'M': {
5226 // center
5227 $yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5228 break;
5229 }
5230 }
5231 $basefonty = $yt + $this->FontAscent;
5232 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5233 if ($this->rtl) {
5234 $w = $x - $this->lMargin;
5235 } else {
5236 $w = $this->w - $this->rMargin - $x;
5237 }
5238 }
5239 $s = '';
5240 // fill and borders
5241 if (is_string($border) AND (strlen($border) == 4)) {
5242 // full border
5243 $border = 1;
5244 }
5245 if ($fill OR ($border == 1)) {
5246 if ($fill) {
5247 $op = ($border == 1) ? 'B' : 'f';
5248 } else {
5249 $op = 'S';
5250 }
5251 if ($this->rtl) {
5252 $xk = (($x - $w) * $k);
5253 } else {
5254 $xk = ($x * $k);
5255 }
5256 $s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5257 }
5258 // draw borders
5259 $s .= $this->getCellBorder($x, $y, $w, $h, $border);
5260 if ($txt != '') {
5261 $txt2 = $txt;
5262 if ($this->isunicode) {
5263 if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
5264 $txt2 = TCPDF_FONTS::UTF8ToLatin1($txt2, $this->isunicode, $this->CurrentFont);
5265 } else {
5266 $unicode = TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont); // array of UTF-8 unicode values
5267 $unicode = TCPDF_FONTS::utf8Bidi($unicode, '', $this->tmprtl, $this->isunicode, $this->CurrentFont);
5268 // replace thai chars (if any)
5269 if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
5270 // number of chars
5271 $numchars = count($unicode);
5272 // po pla, for far, for fan
5273 $longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
5274 // do chada, to patak
5275 $lowtail = array(0x0e0e, 0x0e0f);
5276 // mai hun arkad, sara i, sara ii, sara ue, sara uee
5277 $upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
5278 // mai ek, mai tho, mai tri, mai chattawa, karan
5279 $tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
5280 // sara u, sara uu, pinthu
5281 $lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
5282 $output = array();
5283 for ($i = 0; $i < $numchars; $i++) {
5284 if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
5285 $ch0 = $unicode[$i];
5286 $ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
5287 $ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
5288 $chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
5289 if (in_array($ch0, $tonemark)) {
5290 if ($chn == 0x0e33) {
5291 // sara um
5292 if (in_array($ch1, $longtail)) {
5293 // tonemark at upper left
5294 $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5295 } else {
5296 // tonemark at upper right (normal position)
5297 $output[] = $ch0;
5298 }
5299 } elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
5300 // tonemark at lower left
5301 $output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
5302 } elseif (in_array($ch1, $upvowel)) {
5303 if (in_array($ch2, $longtail)) {
5304 // tonemark at upper left
5305 $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5306 } else {
5307 // tonemark at upper right (normal position)
5308 $output[] = $ch0;
5309 }
5310 } else {
5311 // tonemark at lower right
5312 $output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
5313 }
5314 } elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
5315 // add lower left nikhahit and sara aa
5316 if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
5317 $output[] = 0xf711;
5318 $this->CurrentFont['subsetchars'][0xf711] = true;
5319 $output[] = 0x0e32;
5320 $this->CurrentFont['subsetchars'][0x0e32] = true;
5321 } else {
5322 $output[] = $ch0;
5323 }
5324 } elseif (in_array($ch1, $longtail)) {
5325 if ($ch0 == 0x0e31) {
5326 // lower left mai hun arkad
5327 $output[] = $this->replaceChar($ch0, 0xf710);
5328 } elseif (in_array($ch0, $upvowel)) {
5329 // lower left
5330 $output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
5331 } elseif ($ch0 == 0x0e47) {
5332 // lower left mai tai koo
5333 $output[] = $this->replaceChar($ch0, 0xf712);
5334 } else {
5335 // normal character
5336 $output[] = $ch0;
5337 }
5338 } elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
5339 // lower vowel
5340 $output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
5341 } elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
5342 // yo ying without lower part
5343 $output[] = $this->replaceChar($ch0, 0xf70f);
5344 } elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
5345 // tho santan without lower part
5346 $output[] = $this->replaceChar($ch0, 0xf700);
5347 } else {
5348 $output[] = $ch0;
5349 }
5350 } else {
5351 // non-thai character
5352 $output[] = $unicode[$i];
5353 }
5354 }
5355 $unicode = $output;
5356 // update font subsetchars
5357 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
5358 } // end of K_THAI_TOPCHARS
5359 $txt2 = TCPDF_FONTS::arrUTF8ToUTF16BE($unicode, false);
5360 }
5361 }
5362 $txt2 = TCPDF_STATIC::_escape($txt2);
5363 // get current text width (considering general font stretching and spacing)
5364 $txwidth = $this->GetStringWidth($txt);
5365 $width = $txwidth;
5366 // check for stretch mode
5367 if ($stretch > 0) {
5368 // calculate ratio between cell width and text width
5369 if ($width <= 0) {
5370 $ratio = 1;
5371 } else {
5372 $ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
5373 }
5374 // check if stretching is required
5375 if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
5376 // the text will be stretched to fit cell width
5377 if ($stretch > 2) {
5378 // set new character spacing
5379 $this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
5380 } else {
5381 // set new horizontal stretching
5382 $this->font_stretching *= $ratio;
5383 }
5384 // recalculate text width (the text fills the entire cell)
5385 $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5386 // reset alignment
5387 $align = '';
5388 }
5389 }
5390 if ($this->font_stretching != 100) {
5391 // apply font stretching
5392 $rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
5393 }
5394 if ($this->font_spacing != 0) {
5395 // increase/decrease font spacing
5396 $rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
5397 }
5398 if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5399 $s .= 'q '.$this->TextColor.' ';
5400 }
5401 // rendering mode
5402 $s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
5403 // count number of spaces
5404 $ns = substr_count($txt, chr(32));
5405 // Justification
5406 $spacewidth = 0;
5407 if (($align == 'J') AND ($ns > 0)) {
5408 if ($this->isUnicodeFont()) {
5409 // get string width without spaces
5410 $width = $this->GetStringWidth(str_replace(' ', '', $txt));
5411 // calculate average space width
5412 $spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / ($this->FontSize?$this->FontSize:1);
5413 if ($this->font_stretching != 100) {
5414 // word spacing is affected by stretching
5415 $spacewidth /= ($this->font_stretching / 100);
5416 }
5417 // set word position to be used with TJ operator
5418 $txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
5419 $unicode_justification = true;
5420 } else {
5421 // get string width
5422 $width = $txwidth;
5423 // new space width
5424 $spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
5425 if ($this->font_stretching != 100) {
5426 // word spacing (Tw) is affected by stretching
5427 $spacewidth /= ($this->font_stretching / 100);
5428 }
5429 // set word spacing
5430 $rs .= sprintf('BT %F Tw ET ', $spacewidth);
5431 }
5432 $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5433 }
5434 // replace carriage return characters
5435 $txt2 = str_replace("\r", ' ', $txt2);
5436 switch ($align) {
5437 case 'C': {
5438 $dx = ($w - $width) / 2;
5439 break;
5440 }
5441 case 'R': {
5442 if ($this->rtl) {
5443 $dx = $this->cell_padding['R'];
5444 } else {
5445 $dx = $w - $width - $this->cell_padding['R'];
5446 }
5447 break;
5448 }
5449 case 'L': {
5450 if ($this->rtl) {
5451 $dx = $w - $width - $this->cell_padding['L'];
5452 } else {
5453 $dx = $this->cell_padding['L'];
5454 }
5455 break;
5456 }
5457 case 'J':
5458 default: {
5459 if ($this->rtl) {
5460 $dx = $this->cell_padding['R'];
5461 } else {
5462 $dx = $this->cell_padding['L'];
5463 }
5464 break;
5465 }
5466 }
5467 if ($this->rtl) {
5468 $xdx = $x - $dx - $width;
5469 } else {
5470 $xdx = $x + $dx;
5471 }
5472 $xdk = $xdx * $k;
5473 // print text
5474 $s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
5475 if (isset($uniblock)) {
5476 // print overlapping characters as separate string
5477 $xshift = 0; // horizontal shift
5478 $ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
5479 $spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
5480 foreach ($uniblock as $uk => $uniarr) {
5481 if (($uk % 2) == 0) {
5482 // x space to skip
5483 if ($spacewidth != 0) {
5484 // justification shift
5485 $xshift += (count(array_keys($uniarr, 32)) * $spw);
5486 }
5487 $xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5488 } else {
5489 // character to print
5490 $topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
5491 $topchr = TCPDF_STATIC::_escape($topchr);
5492 $s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
5493 }
5494 }
5495 }
5496 if ($this->underline) {
5497 $s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5498 }
5499 if ($this->linethrough) {
5500 $s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5501 }
5502 if ($this->overline) {
5503 $s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5504 }
5505 if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5506 $s .= ' Q';
5507 }
5508 if ($link) {
5509 $this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5510 }
5511 }
5512 // output cell
5513 if ($s) {
5514 // output cell
5515 $rs .= $s;
5516 if ($this->font_spacing != 0) {
5517 // reset font spacing mode
5518 $rs .= ' BT 0 Tc ET';
5519 }
5520 if ($this->font_stretching != 100) {
5521 // reset font stretching mode
5522 $rs .= ' BT 100 Tz ET';
5523 }
5524 }
5525 // reset word spacing
5526 if (!$this->isUnicodeFont() AND ($align == 'J')) {
5527 $rs .= ' BT 0 Tw ET';
5528 }
5529 // reset stretching and spacing
5530 $this->font_stretching = $prev_font_stretching;
5531 $this->font_spacing = $prev_font_spacing;
5532 $this->lasth = $h;
5533 if ($ln > 0) {
5534 //Go to the beginning of the next line
5535 $this->y = $y + $h + $this->cell_margin['B'];
5536 if ($ln == 1) {
5537 if ($this->rtl) {
5538 $this->x = $this->w - $this->rMargin;
5539 } else {
5540 $this->x = $this->lMargin;
5541 }
5542 }
5543 } else {
5544 // go left or right by case
5545 if ($this->rtl) {
5546 $this->x = $x - $w - $this->cell_margin['L'];
5547 } else {
5548 $this->x = $x + $w + $this->cell_margin['R'];
5549 }
5550 }
5551 $gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5552 $rs = $gstyles.$rs;
5553 $this->cell_padding = $prev_cell_padding;
5554 $this->cell_margin = $prev_cell_margin;
5555 return $rs;
5556 }
5557
5558 /**
5559 * Replace a char if is defined on the current font.
5560 * @param $oldchar (int) Integer code (unicode) of the character to replace.
5561 * @param $newchar (int) Integer code (unicode) of the new character.
5562 * @return int the replaced char or the old char in case the new char i not defined
5563 * @protected
5564 * @since 5.9.167 (2012-06-22)
5565 */
5566 protected function replaceChar($oldchar, $newchar) {
5567 if ($this->isCharDefined($newchar)) {
5568 // add the new char on the subset list
5569 $this->CurrentFont['subsetchars'][$newchar] = true;
5570 // return the new character
5571 return $newchar;
5572 }
5573 // return the old char
5574 return $oldchar;
5575 }
5576
5577 /**
5578 * Returns the code to draw the cell border
5579 * @param $x (float) X coordinate.
5580 * @param $y (float) Y coordinate.
5581 * @param $w (float) Cell width.
5582 * @param $h (float) Cell height.
5583 * @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)))
5584 * @return string containing cell border code
5585 * @protected
5586 * @see SetLineStyle()
5587 * @since 5.7.000 (2010-08-02)
5588 */
5589 protected function getCellBorder($x, $y, $w, $h, $brd) {
5590 $s = ''; // string to be returned
5591 if (empty($brd)) {
5592 return $s;
5593 }
5594 if ($brd == 1) {
5595 $brd = array('LRTB' => true);
5596 }
5597 // calculate coordinates for border
5598 $k = $this->k;
5599 if ($this->rtl) {
5600 $xeL = ($x - $w) * $k;
5601 $xeR = $x * $k;
5602 } else {
5603 $xeL = $x * $k;
5604 $xeR = ($x + $w) * $k;
5605 }
5606 $yeL = (($this->h - ($y + $h)) * $k);
5607 $yeT = (($this->h - $y) * $k);
5608 $xeT = $xeL;
5609 $xeB = $xeR;
5610 $yeR = $yeT;
5611 $yeB = $yeL;
5612 if (is_string($brd)) {
5613 // convert string to array
5614 $slen = strlen($brd);
5615 $newbrd = array();
5616 for ($i = 0; $i < $slen; ++$i) {
5617 $newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
5618 }
5619 $brd = $newbrd;
5620 }
5621 if (isset($brd['mode'])) {
5622 $mode = $brd['mode'];
5623 unset($brd['mode']);
5624 } else {
5625 $mode = 'normal';
5626 }
5627 foreach ($brd as $border => $style) {
5628 if (is_array($style) AND !empty($style)) {
5629 // apply border style
5630 $prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
5631 $s .= $this->SetLineStyle($style, true)."\n";
5632 }
5633 switch ($mode) {
5634 case 'ext': {
5635 $off = (($this->LineWidth / 2) * $k);
5636 $xL = $xeL - $off;
5637 $xR = $xeR + $off;
5638 $yT = $yeT + $off;
5639 $yL = $yeL - $off;
5640 $xT = $xL;
5641 $xB = $xR;
5642 $yR = $yT;
5643 $yB = $yL;
5644 $w += $this->LineWidth;
5645 $h += $this->LineWidth;
5646 break;
5647 }
5648 case 'int': {
5649 $off = ($this->LineWidth / 2) * $k;
5650 $xL = $xeL + $off;
5651 $xR = $xeR - $off;
5652 $yT = $yeT - $off;
5653 $yL = $yeL + $off;
5654 $xT = $xL;
5655 $xB = $xR;
5656 $yR = $yT;
5657 $yB = $yL;
5658 $w -= $this->LineWidth;
5659 $h -= $this->LineWidth;
5660 break;
5661 }
5662 case 'normal':
5663 default: {
5664 $xL = $xeL;
5665 $xT = $xeT;
5666 $xB = $xeB;
5667 $xR = $xeR;
5668 $yL = $yeL;
5669 $yT = $yeT;
5670 $yB = $yeB;
5671 $yR = $yeR;
5672 break;
5673 }
5674 }
5675 // draw borders by case
5676 if (strlen($border) == 4) {
5677 $s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5678 } elseif (strlen($border) == 3) {
5679 if (strpos($border,'B') === false) { // LTR
5680 $s .= sprintf('%F %F m ', $xL, $yL);
5681 $s .= sprintf('%F %F l ', $xT, $yT);
5682 $s .= sprintf('%F %F l ', $xR, $yR);
5683 $s .= sprintf('%F %F l ', $xB, $yB);
5684 $s .= 'S ';
5685 } elseif (strpos($border,'L') === false) { // TRB
5686 $s .= sprintf('%F %F m ', $xT, $yT);
5687 $s .= sprintf('%F %F l ', $xR, $yR);
5688 $s .= sprintf('%F %F l ', $xB, $yB);
5689 $s .= sprintf('%F %F l ', $xL, $yL);
5690 $s .= 'S ';
5691 } elseif (strpos($border,'T') === false) { // RBL
5692 $s .= sprintf('%F %F m ', $xR, $yR);
5693 $s .= sprintf('%F %F l ', $xB, $yB);
5694 $s .= sprintf('%F %F l ', $xL, $yL);
5695 $s .= sprintf('%F %F l ', $xT, $yT);
5696 $s .= 'S ';
5697 } elseif (strpos($border,'R') === false) { // BLT
5698 $s .= sprintf('%F %F m ', $xB, $yB);
5699 $s .= sprintf('%F %F l ', $xL, $yL);
5700 $s .= sprintf('%F %F l ', $xT, $yT);
5701 $s .= sprintf('%F %F l ', $xR, $yR);
5702 $s .= 'S ';
5703 }
5704 } elseif (strlen($border) == 2) {
5705 if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5706 $s .= sprintf('%F %F m ', $xL, $yL);
5707 $s .= sprintf('%F %F l ', $xT, $yT);
5708 $s .= sprintf('%F %F l ', $xR, $yR);
5709 $s .= 'S ';
5710 } elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5711 $s .= sprintf('%F %F m ', $xT, $yT);
5712 $s .= sprintf('%F %F l ', $xR, $yR);
5713 $s .= sprintf('%F %F l ', $xB, $yB);
5714 $s .= 'S ';
5715 } elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5716 $s .= sprintf('%F %F m ', $xR, $yR);
5717 $s .= sprintf('%F %F l ', $xB, $yB);
5718 $s .= sprintf('%F %F l ', $xL, $yL);
5719 $s .= 'S ';
5720 } elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5721 $s .= sprintf('%F %F m ', $xB, $yB);
5722 $s .= sprintf('%F %F l ', $xL, $yL);
5723 $s .= sprintf('%F %F l ', $xT, $yT);
5724 $s .= 'S ';
5725 } elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5726 $s .= sprintf('%F %F m ', $xL, $yL);
5727 $s .= sprintf('%F %F l ', $xT, $yT);
5728 $s .= 'S ';
5729 $s .= sprintf('%F %F m ', $xR, $yR);
5730 $s .= sprintf('%F %F l ', $xB, $yB);
5731 $s .= 'S ';
5732 } elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5733 $s .= sprintf('%F %F m ', $xT, $yT);
5734 $s .= sprintf('%F %F l ', $xR, $yR);
5735 $s .= 'S ';
5736 $s .= sprintf('%F %F m ', $xB, $yB);
5737 $s .= sprintf('%F %F l ', $xL, $yL);
5738 $s .= 'S ';
5739 }
5740 } else { // strlen($border) == 1
5741 if (strpos($border,'L') !== false) { // L
5742 $s .= sprintf('%F %F m ', $xL, $yL);
5743 $s .= sprintf('%F %F l ', $xT, $yT);
5744 $s .= 'S ';
5745 } elseif (strpos($border,'T') !== false) { // T
5746 $s .= sprintf('%F %F m ', $xT, $yT);
5747 $s .= sprintf('%F %F l ', $xR, $yR);
5748 $s .= 'S ';
5749 } elseif (strpos($border,'R') !== false) { // R
5750 $s .= sprintf('%F %F m ', $xR, $yR);
5751 $s .= sprintf('%F %F l ', $xB, $yB);
5752 $s .= 'S ';
5753 } elseif (strpos($border,'B') !== false) { // B
5754 $s .= sprintf('%F %F m ', $xB, $yB);
5755 $s .= sprintf('%F %F l ', $xL, $yL);
5756 $s .= 'S ';
5757 }
5758 }
5759 if (is_array($style) AND !empty($style)) {
5760 // reset border style to previous value
5761 $s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
5762 }
5763 }
5764 return $s;
5765 }
5766
5767 /**
5768 * This method allows printing text with line breaks.
5769 * 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 />
5770 * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
5771 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
5772 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
5773 * @param $txt (string) String to print
5774 * @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)))
5775 * @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>
5776 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5777 * @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>
5778 * @param $x (float) x position in user units
5779 * @param $y (float) y position in user units
5780 * @param $reseth (boolean) if true reset the last cell height (default true).
5781 * @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.
5782 * @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.
5783 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
5784 * @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.
5785 * @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.
5786 * @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.
5787 * @return int Return the number of cells or 1 for html mode.
5788 * @public
5789 * @since 1.3
5790 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
5791 */
5792 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) {
5793 $prev_cell_margin = $this->cell_margin;
5794 $prev_cell_padding = $this->cell_padding;
5795 // adjust internal padding
5796 $this->adjustCellPadding($border);
5797 $mc_padding = $this->cell_padding;
5798 $mc_margin = $this->cell_margin;
5799 $this->cell_padding['T'] = 0;
5800 $this->cell_padding['B'] = 0;
5801 $this->setCellMargins(0, 0, 0, 0);
5802 if (TCPDF_STATIC::empty_string($this->lasth) OR $reseth) {
5803 // reset row height
5804 $this->resetLastH();
5805 }
5806 if (!TCPDF_STATIC::empty_string($y)) {
5807 $this->SetY($y); // set y in order to convert negative y values to positive ones
5808 }
5809 $y = $this->GetY();
5810 $resth = 0;
5811 if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
5812 // spit cell in more pages/columns
5813 $newh = ($this->PageBreakTrigger - $y);
5814 $resth = ($h - $newh); // cell to be printed on the next page/column
5815 $h = $newh;
5816 }
5817 // get current page number
5818 $startpage = $this->page;
5819 // get current column
5820 $startcolumn = $this->current_column;
5821 if (!TCPDF_STATIC::empty_string($x)) {
5822 $this->SetX($x);
5823 } else {
5824 $x = $this->GetX();
5825 }
5826 // check page for no-write regions and adapt page margins if necessary
5827 list($x, $y) = $this->checkPageRegions(0, $x, $y);
5828 // apply margins
5829 $oy = $y + $mc_margin['T'];
5830 if ($this->rtl) {
5831 $ox = ($this->w - $x - $mc_margin['R']);
5832 } else {
5833 $ox = ($x + $mc_margin['L']);
5834 }
5835 $this->x = $ox;
5836 $this->y = $oy;
5837 // set width
5838 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5839 if ($this->rtl) {
5840 $w = ($this->x - $this->lMargin - $mc_margin['L']);
5841 } else {
5842 $w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
5843 }
5844 }
5845 // store original margin values
5846 $lMargin = $this->lMargin;
5847 $rMargin = $this->rMargin;
5848 if ($this->rtl) {
5849 $this->rMargin = ($this->w - $this->x);
5850 $this->lMargin = ($this->x - $w);
5851 } else {
5852 $this->lMargin = ($this->x);
5853 $this->rMargin = ($this->w - $this->x - $w);
5854 }
5855 $this->clMargin = $this->lMargin;
5856 $this->crMargin = $this->rMargin;
5857 if ($autopadding) {
5858 // add top padding
5859 $this->y += $mc_padding['T'];
5860 }
5861 if ($ishtml) { // ******* Write HTML text
5862 $this->writeHTML($txt, true, false, $reseth, true, $align);
5863 $nl = 1;
5864 } else { // ******* Write simple text
5865 $prev_FontSizePt = $this->FontSizePt;
5866 if ($fitcell) {
5867 // ajust height values
5868 $tobottom = ($this->h - $this->y - $this->bMargin - $this->cell_padding['T'] - $this->cell_padding['B']);
5869 $h = $maxh = max(min($h, $tobottom), min($maxh, $tobottom));
5870 }
5871 // vertical alignment
5872 if ($maxh > 0) {
5873 // get text height
5874 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5875 if ($fitcell AND ($text_height > $maxh) AND ($this->FontSizePt > 1)) {
5876 // try to reduce font size to fit text on cell (use a quick search algorithm)
5877 $fmin = 1;
5878 $fmax = $this->FontSizePt;
5879 $diff_epsilon = (1 / $this->k); // one point (min resolution)
5880 $maxit = (2 * min(100, max(10, intval($fmax)))); // max number of iterations
5881 while ($maxit >= 0) {
5882 $fmid = (($fmax + $fmin) / 2);
5883 $this->SetFontSize($fmid, false);
5884 $this->resetLastH();
5885 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5886 $diff = ($maxh - $text_height);
5887 if ($diff >= 0) {
5888 if ($diff <= $diff_epsilon) {
5889 break;
5890 }
5891 $fmin = $fmid;
5892 } else {
5893 $fmax = $fmid;
5894 }
5895 --$maxit;
5896 }
5897 if ($maxit < 0) {
5898 // premature exit, we get the minimum font value to fit the cell
5899 $this->SetFontSize($fmin);
5900 $this->resetLastH();
5901 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5902 } else {
5903 $this->SetFontSize($fmid);
5904 $this->resetLastH();
5905 }
5906 }
5907 if ($text_height < $maxh) {
5908 if ($valign == 'M') {
5909 // text vertically centered
5910 $this->y += (($maxh - $text_height) / 2);
5911 } elseif ($valign == 'B') {
5912 // text vertically aligned on bottom
5913 $this->y += ($maxh - $text_height);
5914 }
5915 }
5916 }
5917 $nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
5918 if ($fitcell) {
5919 // restore font size
5920 $this->SetFontSize($prev_FontSizePt);
5921 }
5922 }
5923 if ($autopadding) {
5924 // add bottom padding
5925 $this->y += $mc_padding['B'];
5926 }
5927 // Get end-of-text Y position
5928 $currentY = $this->y;
5929 // get latest page number
5930 $endpage = $this->page;
5931 if ($resth > 0) {
5932 $skip = ($endpage - $startpage);
5933 $tmpresth = $resth;
5934 while ($tmpresth > 0) {
5935 if ($skip <= 0) {
5936 // add a page (or trig AcceptPageBreak() for multicolumn mode)
5937 $this->checkPageBreak($this->PageBreakTrigger + 1);
5938 }
5939 if ($this->num_columns > 1) {
5940 $tmpresth -= ($this->h - $this->y - $this->bMargin);
5941 } else {
5942 $tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
5943 }
5944 --$skip;
5945 }
5946 $currentY = $this->y;
5947 $endpage = $this->page;
5948 }
5949 // get latest column
5950 $endcolumn = $this->current_column;
5951 if ($this->num_columns == 0) {
5952 $this->num_columns = 1;
5953 }
5954 // disable page regions check
5955 $check_page_regions = $this->check_page_regions;
5956 $this->check_page_regions = false;
5957 // get border modes
5958 $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
5959 $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
5960 $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
5961 // design borders around HTML cells.
5962 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
5963 $ccode = '';
5964 $this->setPage($page);
5965 if ($this->num_columns < 2) {
5966 // single-column mode
5967 $this->SetX($x);
5968 $this->y = $this->tMargin;
5969 }
5970 // account for margin changes
5971 if ($page > $startpage) {
5972 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
5973 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
5974 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
5975 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
5976 }
5977 }
5978 if ($startpage == $endpage) {
5979 // single page
5980 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
5981 if ($column != $this->current_column) {
5982 $this->selectColumn($column);
5983 }
5984 if ($this->rtl) {
5985 $this->x -= $mc_margin['R'];
5986 } else {
5987 $this->x += $mc_margin['L'];
5988 }
5989 if ($startcolumn == $endcolumn) { // single column
5990 $cborder = $border;
5991 $h = max($h, ($currentY - $oy));
5992 $this->y = $oy;
5993 } elseif ($column == $startcolumn) { // first column
5994 $cborder = $border_start;
5995 $this->y = $oy;
5996 $h = $this->h - $this->y - $this->bMargin;
5997 } elseif ($column == $endcolumn) { // end column
5998 $cborder = $border_end;
5999 $h = $currentY - $this->y;
6000 if ($resth > $h) {
6001 $h = $resth;
6002 }
6003 } else { // middle column
6004 $cborder = $border_middle;
6005 $h = $this->h - $this->y - $this->bMargin;
6006 $resth -= $h;
6007 }
6008 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6009 } // end for each column
6010 } elseif ($page == $startpage) { // first page
6011 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
6012 if ($column != $this->current_column) {
6013 $this->selectColumn($column);
6014 }
6015 if ($this->rtl) {
6016 $this->x -= $mc_margin['R'];
6017 } else {
6018 $this->x += $mc_margin['L'];
6019 }
6020 if ($column == $startcolumn) { // first column
6021 $cborder = $border_start;
6022 $this->y = $oy;
6023 $h = $this->h - $this->y - $this->bMargin;
6024 } else { // middle column
6025 $cborder = $border_middle;
6026 $h = $this->h - $this->y - $this->bMargin;
6027 $resth -= $h;
6028 }
6029 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6030 } // end for each column
6031 } elseif ($page == $endpage) { // last page
6032 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
6033 if ($column != $this->current_column) {
6034 $this->selectColumn($column);
6035 }
6036 if ($this->rtl) {
6037 $this->x -= $mc_margin['R'];
6038 } else {
6039 $this->x += $mc_margin['L'];
6040 }
6041 if ($column == $endcolumn) {
6042 // end column
6043 $cborder = $border_end;
6044 $h = $currentY - $this->y;
6045 if ($resth > $h) {
6046 $h = $resth;
6047 }
6048 } else {
6049 // middle column
6050 $cborder = $border_middle;
6051 $h = $this->h - $this->y - $this->bMargin;
6052 $resth -= $h;
6053 }
6054 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6055 } // end for each column
6056 } else { // middle page
6057 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6058 $this->selectColumn($column);
6059 if ($this->rtl) {
6060 $this->x -= $mc_margin['R'];
6061 } else {
6062 $this->x += $mc_margin['L'];
6063 }
6064 $cborder = $border_middle;
6065 $h = $this->h - $this->y - $this->bMargin;
6066 $resth -= $h;
6067 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6068 } // end for each column
6069 }
6070 if ($cborder OR $fill) {
6071 $offsetlen = strlen($ccode);
6072 // draw border and fill
6073 if ($this->inxobj) {
6074 // we are inside an XObject template
6075 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6076 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6077 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6078 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
6079 } else {
6080 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
6081 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
6082 }
6083 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6084 $pstart = substr($pagebuff, 0, $pagemark);
6085 $pend = substr($pagebuff, $pagemark);
6086 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6087 } else {
6088 if (end($this->transfmrk[$this->page]) !== false) {
6089 $pagemarkkey = key($this->transfmrk[$this->page]);
6090 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
6091 $this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
6092 } elseif ($this->InFooter) {
6093 $pagemark = $this->footerpos[$this->page];
6094 $this->footerpos[$this->page] += $offsetlen;
6095 } else {
6096 $pagemark = $this->intmrk[$this->page];
6097 $this->intmrk[$this->page] += $offsetlen;
6098 }
6099 $pagebuff = $this->getPageBuffer($this->page);
6100 $pstart = substr($pagebuff, 0, $pagemark);
6101 $pend = substr($pagebuff, $pagemark);
6102 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6103 }
6104 }
6105 } // end for each page
6106 // restore page regions check
6107 $this->check_page_regions = $check_page_regions;
6108 // Get end-of-cell Y position
6109 $currentY = $this->GetY();
6110 // restore previous values
6111 if ($this->num_columns > 1) {
6112 $this->selectColumn();
6113 } else {
6114 // restore original margins
6115 $this->lMargin = $lMargin;
6116 $this->rMargin = $rMargin;
6117 if ($this->page > $startpage) {
6118 // check for margin variations between pages (i.e. booklet mode)
6119 $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
6120 $dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
6121 if (($dl != 0) OR ($dr != 0)) {
6122 $this->lMargin += $dl;
6123 $this->rMargin += $dr;
6124 }
6125 }
6126 }
6127 if ($ln > 0) {
6128 //Go to the beginning of the next line
6129 $this->SetY($currentY + $mc_margin['B']);
6130 if ($ln == 2) {
6131 $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6132 }
6133 } else {
6134 // go left or right by case
6135 $this->setPage($startpage);
6136 $this->y = $y;
6137 $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6138 }
6139 $this->setContentMark();
6140 $this->cell_padding = $prev_cell_padding;
6141 $this->cell_margin = $prev_cell_margin;
6142 $this->clMargin = $this->lMargin;
6143 $this->crMargin = $this->rMargin;
6144 return $nl;
6145 }
6146
6147 /**
6148 * This method return the estimated number of lines for print a simple text string using Multicell() method.
6149 * @param $txt (string) String for calculating his height
6150 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6151 * @param $reseth (boolean) if true reset the last cell height (default false).
6152 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6153 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6154 * @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)))
6155 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6156 * @author Alexander Escalona Fern\E1ndez, Nicola Asuni
6157 * @public
6158 * @since 4.5.011
6159 */
6160 public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6161 if ($txt === NULL) {
6162 return 0;
6163 }
6164 if ($txt === '') {
6165 // empty string
6166 return 1;
6167 }
6168 // adjust internal padding
6169 $prev_cell_padding = $this->cell_padding;
6170 $prev_lasth = $this->lasth;
6171 if (is_array($cellpadding)) {
6172 $this->cell_padding = $cellpadding;
6173 }
6174 $this->adjustCellPadding($border);
6175 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
6176 if ($this->rtl) {
6177 $w = $this->x - $this->lMargin;
6178 } else {
6179 $w = $this->w - $this->rMargin - $this->x;
6180 }
6181 }
6182 $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6183 if ($reseth) {
6184 // reset row height
6185 $this->resetLastH();
6186 }
6187 $lines = 1;
6188 $sum = 0;
6189 $chars = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont), $txt, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6190 $charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6191 $length = count($chars);
6192 $lastSeparator = -1;
6193 for ($i = 0; $i < $length; ++$i) {
6194 $c = $chars[$i];
6195 $charWidth = $charsWidth[$i];
6196 if (($c != 160)
6197 AND (($c == 173)
6198 OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6199 OR (($c == 45)
6200 AND ($i > 0) AND ($i < ($length - 1))
6201 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i - 1)], $this->isunicode))
6202 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6203 )
6204 )
6205 ) {
6206 $lastSeparator = $i;
6207 }
6208 if ((($sum + $charWidth) > $wmax) OR ($c == 10)) {
6209 ++$lines;
6210 if ($c == 10) {
6211 $lastSeparator = -1;
6212 $sum = 0;
6213 } elseif ($lastSeparator != -1) {
6214 $i = $lastSeparator;
6215 $lastSeparator = -1;
6216 $sum = 0;
6217 } else {
6218 $sum = $charWidth;
6219 }
6220 } else {
6221 $sum += $charWidth;
6222 }
6223 }
6224 if ($chars[($length - 1)] == 10) {
6225 --$lines;
6226 }
6227 $this->cell_padding = $prev_cell_padding;
6228 $this->lasth = $prev_lasth;
6229 return $lines;
6230 }
6231
6232 /**
6233 * This method return the estimated height needed for printing a simple text string using the Multicell() method.
6234 * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6235 * @pre
6236 * // store current object
6237 * $pdf->startTransaction();
6238 * // store starting values
6239 * $start_y = $pdf->GetY();
6240 * $start_page = $pdf->getPage();
6241 * // call your printing functions with your parameters
6242 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6243 * $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);
6244 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6245 * // get the new Y
6246 * $end_y = $pdf->GetY();
6247 * $end_page = $pdf->getPage();
6248 * // calculate height
6249 * $height = 0;
6250 * if ($end_page == $start_page) {
6251 * $height = $end_y - $start_y;
6252 * } else {
6253 * for ($page=$start_page; $page <= $end_page; ++$page) {
6254 * $this->setPage($page);
6255 * if ($page == $start_page) {
6256 * // first page
6257 * $height += $this->h - $start_y - $this->bMargin;
6258 * } elseif ($page == $end_page) {
6259 * // last page
6260 * $height += $end_y - $this->tMargin;
6261 * } else {
6262 * $height += $this->h - $this->tMargin - $this->bMargin;
6263 * }
6264 * }
6265 * }
6266 * // restore previous object
6267 * $pdf = $pdf->rollbackTransaction();
6268 *
6269 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6270 * @param $txt (string) String for calculating his height
6271 * @param $reseth (boolean) if true reset the last cell height (default false).
6272 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6273 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6274 * @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)))
6275 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6276 * @author Nicola Asuni, Alexander Escalona Fern\E1ndez
6277 * @public
6278 */
6279 public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6280 // adjust internal padding
6281 $prev_cell_padding = $this->cell_padding;
6282 $prev_lasth = $this->lasth;
6283 if (is_array($cellpadding)) {
6284 $this->cell_padding = $cellpadding;
6285 }
6286 $this->adjustCellPadding($border);
6287 $lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6288 $height = $this->getCellHeight(($lines * $this->FontSize), $autopadding);
6289 $this->cell_padding = $prev_cell_padding;
6290 $this->lasth = $prev_lasth;
6291 return $height;
6292 }
6293
6294 /**
6295 * This method prints text from the current position.<br />
6296 * @param $h (float) Line height
6297 * @param $txt (string) String to print
6298 * @param $link (mixed) URL or identifier returned by AddLink()
6299 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
6300 * @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>
6301 * @param $ln (boolean) if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
6302 * @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.
6303 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
6304 * @param $firstblock (boolean) if true the string is the starting of a line.
6305 * @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.
6306 * @param $wadj (float) first line width will be reduced by this amount (used in HTML mode).
6307 * @param $margin (array) margin array of the parent container
6308 * @return mixed Return the number of cells or the remaining string if $firstline = true.
6309 * @public
6310 * @since 1.5
6311 */
6312 public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
6313 // check page for no-write regions and adapt page margins if necessary
6314 list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
6315 if (strlen($txt) == 0) {
6316 // fix empty text
6317 $txt = ' ';
6318 }
6319 if ($margin === '') {
6320 // set default margins
6321 $margin = $this->cell_margin;
6322 }
6323 // remove carriage returns
6324 $s = str_replace("\r", '', $txt);
6325 // check if string contains arabic text
6326 if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $s)) {
6327 $arabic = true;
6328 } else {
6329 $arabic = false;
6330 }
6331 // check if string contains RTL text
6332 if ($arabic OR ($this->tmprtl == 'R') OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $s)) {
6333 $rtlmode = true;
6334 } else {
6335 $rtlmode = false;
6336 }
6337 // get a char width
6338 $chrwidth = $this->GetCharWidth(46); // dot character
6339 // get array of unicode values
6340 $chars = TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont);
6341 // calculate maximum width for a single character on string
6342 $chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
6343 array_walk($chrw, array($this, 'getRawCharWidth'));
6344 $maxchwidth = max($chrw);
6345 // get array of chars
6346 $uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
6347 // get the number of characters
6348 $nb = count($chars);
6349 // replacement for SHY character (minus symbol)
6350 $shy_replacement = 45;
6351 $shy_replacement_char = TCPDF_FONTS::unichr($shy_replacement, $this->isunicode);
6352 // widht for SHY replacement
6353 $shy_replacement_width = $this->GetCharWidth($shy_replacement);
6354 // page width
6355 $pw = $w = $this->w - $this->lMargin - $this->rMargin;
6356 // calculate remaining line width ($w)
6357 if ($this->rtl) {
6358 $w = $this->x - $this->lMargin;
6359 } else {
6360 $w = $this->w - $this->rMargin - $this->x;
6361 }
6362 // max column width
6363 $wmax = ($w - $wadj);
6364 if (!$firstline) {
6365 $wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
6366 }
6367 if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
6368 // the maximum width character do not fit on column
6369 return '';
6370 }
6371 // minimum row height
6372 $row_height = max($h, $this->getCellHeight($this->FontSize));
6373 // max Y
6374 $maxy = $this->y + $maxh - max($row_height, $h);
6375 $start_page = $this->page;
6376 $i = 0; // character position
6377 $j = 0; // current starting position
6378 $sep = -1; // position of the last blank space
6379 $prevsep = $sep; // previous separator
6380 $shy = false; // true if the last blank is a soft hypen (SHY)
6381 $prevshy = $shy; // previous shy mode
6382 $l = 0; // current string length
6383 $nl = 0; //number of lines
6384 $linebreak = false;
6385 $pc = 0; // previous character
6386 // for each character
6387 while ($i < $nb) {
6388 if (($maxh > 0) AND ($this->y > $maxy) ) {
6389 break;
6390 }
6391 //Get the current character
6392 $c = $chars[$i];
6393 if ($c == 10) { // 10 = "\n" = new line
6394 //Explicit line break
6395 if ($align == 'J') {
6396 if ($this->rtl) {
6397 $talign = 'R';
6398 } else {
6399 $talign = 'L';
6400 }
6401 } else {
6402 $talign = $align;
6403 }
6404 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6405 if ($firstline) {
6406 $startx = $this->x;
6407 $tmparr = array_slice($chars, $j, ($i - $j));
6408 if ($rtlmode) {
6409 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6410 }
6411 $linew = $this->GetArrStringWidth($tmparr);
6412 unset($tmparr);
6413 if ($this->rtl) {
6414 $this->endlinex = $startx - $linew;
6415 } else {
6416 $this->endlinex = $startx + $linew;
6417 }
6418 $w = $linew;
6419 $tmpcellpadding = $this->cell_padding;
6420 if ($maxh == 0) {
6421 $this->SetCellPadding(0);
6422 }
6423 }
6424 if ($firstblock AND $this->isRTLTextDir()) {
6425 $tmpstr = $this->stringRightTrim($tmpstr);
6426 }
6427 // Skip newlines at the beginning of a page or column
6428 if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
6429 $this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6430 }
6431 unset($tmpstr);
6432 if ($firstline) {
6433 $this->cell_padding = $tmpcellpadding;
6434 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6435 }
6436 ++$nl;
6437 $j = $i + 1;
6438 $l = 0;
6439 $sep = -1;
6440 $prevsep = $sep;
6441 $shy = false;
6442 // account for margin changes
6443 if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6444 $this->AcceptPageBreak();
6445 if ($this->rtl) {
6446 $this->x -= $margin['R'];
6447 } else {
6448 $this->x += $margin['L'];
6449 }
6450 $this->lMargin += $margin['L'];
6451 $this->rMargin += $margin['R'];
6452 }
6453 $w = $this->getRemainingWidth();
6454 $wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
6455 } else {
6456 // 160 is the non-breaking space.
6457 // 173 is SHY (Soft Hypen).
6458 // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6459 // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6460 // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6461 if (($c != 160)
6462 AND (($c == 173)
6463 OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6464 OR (($c == 45)
6465 AND ($i < ($nb - 1))
6466 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($pc, $this->isunicode))
6467 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6468 )
6469 )
6470 ) {
6471 // update last blank space position
6472 $prevsep = $sep;
6473 $sep = $i;
6474 // check if is a SHY
6475 if (($c == 173) OR ($c == 45)) {
6476 $prevshy = $shy;
6477 $shy = true;
6478 if ($pc == 45) {
6479 $tmp_shy_replacement_width = 0;
6480 $tmp_shy_replacement_char = '';
6481 } else {
6482 $tmp_shy_replacement_width = $shy_replacement_width;
6483 $tmp_shy_replacement_char = $shy_replacement_char;
6484 }
6485 } else {
6486 $shy = false;
6487 }
6488 }
6489 // update string length
6490 if ($this->isUnicodeFont() AND ($arabic)) {
6491 // with bidirectional algorithm some chars may be changed affecting the line length
6492 // *** very slow ***
6493 $l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont));
6494 } else {
6495 $l += $this->GetCharWidth($c);
6496 }
6497 if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) >= $wmax))) {
6498 if (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) {
6499 $sep = $prevsep;
6500 $shy = $prevshy;
6501 }
6502 // we have reached the end of column
6503 if ($sep == -1) {
6504 // check if the line was already started
6505 if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
6506 OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
6507 // print a void cell and go to next line
6508 $this->Cell($w, $h, '', 0, 1);
6509 $linebreak = true;
6510 if ($firstline) {
6511 return (TCPDF_FONTS::UniArrSubString($uchars, $j));
6512 }
6513 } else {
6514 // truncate the word because do not fit on column
6515 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6516 if ($firstline) {
6517 $startx = $this->x;
6518 $tmparr = array_slice($chars, $j, ($i - $j));
6519 if ($rtlmode) {
6520 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6521 }
6522 $linew = $this->GetArrStringWidth($tmparr);
6523 unset($tmparr);
6524 if ($this->rtl) {
6525 $this->endlinex = $startx - $linew;
6526 } else {
6527 $this->endlinex = $startx + $linew;
6528 }
6529 $w = $linew;
6530 $tmpcellpadding = $this->cell_padding;
6531 if ($maxh == 0) {
6532 $this->SetCellPadding(0);
6533 }
6534 }
6535 if ($firstblock AND $this->isRTLTextDir()) {
6536 $tmpstr = $this->stringRightTrim($tmpstr);
6537 }
6538 $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6539 unset($tmpstr);
6540 if ($firstline) {
6541 $this->cell_padding = $tmpcellpadding;
6542 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6543 }
6544 $j = $i;
6545 --$i;
6546 }
6547 } else {
6548 // word wrapping
6549 if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6550 $endspace = 1;
6551 } else {
6552 $endspace = 0;
6553 }
6554 // check the length of the next string
6555 $strrest = TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace));
6556 $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $this->stringTrim($strrest));
6557 if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
6558 // truncate the word because do not fit on a full page width
6559 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6560 if ($firstline) {
6561 $startx = $this->x;
6562 $tmparr = array_slice($chars, $j, ($i - $j));
6563 if ($rtlmode) {
6564 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6565 }
6566 $linew = $this->GetArrStringWidth($tmparr);
6567 unset($tmparr);
6568 if ($this->rtl) {
6569 $this->endlinex = ($startx - $linew);
6570 } else {
6571 $this->endlinex = ($startx + $linew);
6572 }
6573 $w = $linew;
6574 $tmpcellpadding = $this->cell_padding;
6575 if ($maxh == 0) {
6576 $this->SetCellPadding(0);
6577 }
6578 }
6579 if ($firstblock AND $this->isRTLTextDir()) {
6580 $tmpstr = $this->stringRightTrim($tmpstr);
6581 }
6582 $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6583 unset($tmpstr);
6584 if ($firstline) {
6585 $this->cell_padding = $tmpcellpadding;
6586 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6587 }
6588 $j = $i;
6589 --$i;
6590 } else {
6591 // word wrapping
6592 if ($shy) {
6593 // add hypen (minus symbol) at the end of the line
6594 $shy_width = $tmp_shy_replacement_width;
6595 if ($this->rtl) {
6596 $shy_char_left = $tmp_shy_replacement_char;
6597 $shy_char_right = '';
6598 } else {
6599 $shy_char_left = '';
6600 $shy_char_right = $tmp_shy_replacement_char;
6601 }
6602 } else {
6603 $shy_width = 0;
6604 $shy_char_left = '';
6605 $shy_char_right = '';
6606 }
6607 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
6608 if ($firstline) {
6609 $startx = $this->x;
6610 $tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6611 if ($rtlmode) {
6612 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6613 }
6614 $linew = $this->GetArrStringWidth($tmparr);
6615 unset($tmparr);
6616 if ($this->rtl) {
6617 $this->endlinex = $startx - $linew - $shy_width;
6618 } else {
6619 $this->endlinex = $startx + $linew + $shy_width;
6620 }
6621 $w = $linew;
6622 $tmpcellpadding = $this->cell_padding;
6623 if ($maxh == 0) {
6624 $this->SetCellPadding(0);
6625 }
6626 }
6627 // print the line
6628 if ($firstblock AND $this->isRTLTextDir()) {
6629 $tmpstr = $this->stringRightTrim($tmpstr);
6630 }
6631 $this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6632 unset($tmpstr);
6633 if ($firstline) {
6634 if ($chars[$sep] == 45) {
6635 $endspace += 1;
6636 }
6637 // return the remaining text
6638 $this->cell_padding = $tmpcellpadding;
6639 return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
6640 }
6641 $i = $sep;
6642 $sep = -1;
6643 $shy = false;
6644 $j = ($i + 1);
6645 }
6646 }
6647 // account for margin changes
6648 if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6649 $this->AcceptPageBreak();
6650 if ($this->rtl) {
6651 $this->x -= $margin['R'];
6652 } else {
6653 $this->x += $margin['L'];
6654 }
6655 $this->lMargin += $margin['L'];
6656 $this->rMargin += $margin['R'];
6657 }
6658 $w = $this->getRemainingWidth();
6659 $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6660 if ($linebreak) {
6661 $linebreak = false;
6662 } else {
6663 ++$nl;
6664 $l = 0;
6665 }
6666 }
6667 }
6668 // save last character
6669 $pc = $c;
6670 ++$i;
6671 } // end while i < nb
6672 // print last substring (if any)
6673 if ($l > 0) {
6674 switch ($align) {
6675 case 'J':
6676 case 'C': {
6677 $w = $w;
6678 break;
6679 }
6680 case 'L': {
6681 if ($this->rtl) {
6682 $w = $w;
6683 } else {
6684 $w = $l;
6685 }
6686 break;
6687 }
6688 case 'R': {
6689 if ($this->rtl) {
6690 $w = $l;
6691 } else {
6692 $w = $w;
6693 }
6694 break;
6695 }
6696 default: {
6697 $w = $l;
6698 break;
6699 }
6700 }
6701 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
6702 if ($firstline) {
6703 $startx = $this->x;
6704 $tmparr = array_slice($chars, $j, ($nb - $j));
6705 if ($rtlmode) {
6706 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6707 }
6708 $linew = $this->GetArrStringWidth($tmparr);
6709 unset($tmparr);
6710 if ($this->rtl) {
6711 $this->endlinex = $startx - $linew;
6712 } else {
6713 $this->endlinex = $startx + $linew;
6714 }
6715 $w = $linew;
6716 $tmpcellpadding = $this->cell_padding;
6717 if ($maxh == 0) {
6718 $this->SetCellPadding(0);
6719 }
6720 }
6721 if ($firstblock AND $this->isRTLTextDir()) {
6722 $tmpstr = $this->stringRightTrim($tmpstr);
6723 }
6724 $this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6725 unset($tmpstr);
6726 if ($firstline) {
6727 $this->cell_padding = $tmpcellpadding;
6728 return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
6729 }
6730 ++$nl;
6731 }
6732 if ($firstline) {
6733 return '';
6734 }
6735 return $nl;
6736 }
6737
6738 /**
6739 * Returns the remaining width between the current position and margins.
6740 * @return int Return the remaining width
6741 * @protected
6742 */
6743 protected function getRemainingWidth() {
6744 list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
6745 if ($this->rtl) {
6746 return ($this->x - $this->lMargin);
6747 } else {
6748 return ($this->w - $this->rMargin - $this->x);
6749 }
6750 }
6751
6752 /**
6753 * Set the block dimensions accounting for page breaks and page/column fitting
6754 * @param $w (float) width
6755 * @param $h (float) height
6756 * @param $x (float) X coordinate
6757 * @param $y (float) Y coodiante
6758 * @param $fitonpage (boolean) if true the block is resized to not exceed page dimensions.
6759 * @return array($w, $h, $x, $y)
6760 * @protected
6761 * @since 5.5.009 (2010-07-05)
6762 */
6763 protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
6764 if ($w <= 0) {
6765 // set maximum width
6766 $w = ($this->w - $this->lMargin - $this->rMargin);
6767 if ($w <= 0) {
6768 $w = 1;
6769 }
6770 }
6771 if ($h <= 0) {
6772 // set maximum height
6773 $h = ($this->PageBreakTrigger - $this->tMargin);
6774 if ($h <= 0) {
6775 $h = 1;
6776 }
6777 }
6778 // resize the block to be vertically contained on a single page or single column
6779 if ($fitonpage OR $this->AutoPageBreak) {
6780 $ratio_wh = ($w / $h);
6781 if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
6782 $h = $this->PageBreakTrigger - $this->tMargin;
6783 $w = ($h * $ratio_wh);
6784 }
6785 // resize the block to be horizontally contained on a single page or single column
6786 if ($fitonpage) {
6787 $maxw = ($this->w - $this->lMargin - $this->rMargin);
6788 if ($w > $maxw) {
6789 $w = $maxw;
6790 $h = ($w / $ratio_wh);
6791 }
6792 }
6793 }
6794 // Check whether we need a new page or new column first as this does not fit
6795 $prev_x = $this->x;
6796 $prev_y = $this->y;
6797 if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
6798 $y = $this->y;
6799 if ($this->rtl) {
6800 $x += ($prev_x - $this->x);
6801 } else {
6802 $x += ($this->x - $prev_x);
6803 }
6804 $this->newline = true;
6805 }
6806 // resize the block to be contained on the remaining available page or column space
6807 if ($fitonpage) {
6808 $ratio_wh = ($w / $h);
6809 if (($y + $h) > $this->PageBreakTrigger) {
6810 $h = $this->PageBreakTrigger - $y;
6811 $w = ($h * $ratio_wh);
6812 }
6813 if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
6814 $w = $this->w - $this->rMargin - $x;
6815 $h = ($w / $ratio_wh);
6816 } elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
6817 $w = $x - $this->lMargin;
6818 $h = ($w / $ratio_wh);
6819 }
6820 }
6821 return array($w, $h, $x, $y);
6822 }
6823
6824 /**
6825 * Puts an image in the page.
6826 * The upper-left corner must be given.
6827 * The dimensions can be specified in different ways:<ul>
6828 * <li>explicit width and height (expressed in user unit)</li>
6829 * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
6830 * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
6831 * 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;
6832 * The format can be specified explicitly or inferred from the file extension.<br />
6833 * It is possible to put a link on the image.<br />
6834 * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
6835 * @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').
6836 * @param $x (float) Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
6837 * @param $y (float) Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
6838 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
6839 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
6840 * @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.
6841 * @param $link (mixed) URL or identifier returned by AddLink().
6842 * @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>
6843 * @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).
6844 * @param $dpi (int) dot-per-inch resolution used on resize
6845 * @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>
6846 * @param $ismask (boolean) true if this image is a mask, false otherwise
6847 * @param $imgmask (mixed) image object returned by this function or false
6848 * @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)))
6849 * @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).
6850 * @param $hidden (boolean) If true do not display the image.
6851 * @param $fitonpage (boolean) If true the image is resized to not exceed page dimensions.
6852 * @param $alt (boolean) If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
6853 * @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.
6854 * @return image information
6855 * @public
6856 * @since 1.1
6857 */
6858 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()) {
6859 if ($this->state != 2) {
6860 return;
6861 }
6862 if (strcmp($x, '') === 0) {
6863 $x = $this->x;
6864 }
6865 if (strcmp($y, '') === 0) {
6866 $y = $this->y;
6867 }
6868 // check page for no-write regions and adapt page margins if necessary
6869 list($x, $y) = $this->checkPageRegions($h, $x, $y);
6870 $exurl = ''; // external streams
6871 $imsize = FALSE;
6872 // check if we are passing an image as file or string
6873 if ($file[0] === '@') {
6874 // image from string
6875 $imgdata = substr($file, 1);
6876 } else { // image file
6877 if ($file[0] === '*') {
6878 // image as external stream
6879 $file = substr($file, 1);
6880 $exurl = $file;
6881 }
6882 // check if file exist and it is valid
6883 if (!@TCPDF_STATIC::file_exists($file)) {
6884 return false;
6885 }
6886 if (($imsize = @getimagesize($file)) === FALSE) {
6887 if (in_array($file, $this->imagekeys)) {
6888 // get existing image data
6889 $info = $this->getImageBuffer($file);
6890 $imsize = array($info['w'], $info['h']);
6891 } elseif (strpos($file, '__tcpdf_'.$this->file_id.'_img') === FALSE) {
6892 $imgdata = TCPDF_STATIC::fileGetContents($file);
6893 }
6894 }
6895 }
6896 if (!empty($imgdata)) {
6897 // copy image to cache
6898 $original_file = $file;
6899 $file = TCPDF_STATIC::getObjFilename('img', $this->file_id);
6900 $fp = TCPDF_STATIC::fopenLocal($file, 'w');
6901 if (!$fp) {
6902 $this->Error('Unable to write file: '.$file);
6903 }
6904 fwrite($fp, $imgdata);
6905 fclose($fp);
6906 unset($imgdata);
6907 $imsize = @getimagesize($file);
6908 if ($imsize === FALSE) {
6909 unlink($file);
6910 $file = $original_file;
6911 }
6912 }
6913 if ($imsize === FALSE) {
6914 if (($w > 0) AND ($h > 0)) {
6915 // get measures from specified data
6916 $pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6917 $ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6918 $imsize = array($pw, $ph);
6919 } else {
6920 $this->Error('[Image] Unable to get the size of the image: '.$file);
6921 }
6922 }
6923 // file hash
6924 $filehash = md5($file);
6925 // get original image width and height in pixels
6926 list($pixw, $pixh) = $imsize;
6927 // calculate image width and height on document
6928 if (($w <= 0) AND ($h <= 0)) {
6929 // convert image size to document unit
6930 $w = $this->pixelsToUnits($pixw);
6931 $h = $this->pixelsToUnits($pixh);
6932 } elseif ($w <= 0) {
6933 $w = $h * $pixw / $pixh;
6934 } elseif ($h <= 0) {
6935 $h = $w * $pixh / $pixw;
6936 } elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
6937 if (strlen($fitbox) !== 2) {
6938 // set default alignment
6939 $fitbox = '--';
6940 }
6941 // scale image dimensions proportionally to fit within the ($w, $h) box
6942 if ((($w * $pixh) / ($h * $pixw)) < 1) {
6943 // store current height
6944 $oldh = $h;
6945 // calculate new height
6946 $h = $w * $pixh / $pixw;
6947 // height difference
6948 $hdiff = ($oldh - $h);
6949 // vertical alignment
6950 switch (strtoupper($fitbox[1])) {
6951 case 'T': {
6952 break;
6953 }
6954 case 'M': {
6955 $y += ($hdiff / 2);
6956 break;
6957 }
6958 case 'B': {
6959 $y += $hdiff;
6960 break;
6961 }
6962 }
6963 } else {
6964 // store current width
6965 $oldw = $w;
6966 // calculate new width
6967 $w = $h * $pixw / $pixh;
6968 // width difference
6969 $wdiff = ($oldw - $w);
6970 // horizontal alignment
6971 switch (strtoupper($fitbox[0])) {
6972 case 'L': {
6973 if ($this->rtl) {
6974 $x -= $wdiff;
6975 }
6976 break;
6977 }
6978 case 'C': {
6979 if ($this->rtl) {
6980 $x -= ($wdiff / 2);
6981 } else {
6982 $x += ($wdiff / 2);
6983 }
6984 break;
6985 }
6986 case 'R': {
6987 if (!$this->rtl) {
6988 $x += $wdiff;
6989 }
6990 break;
6991 }
6992 }
6993 }
6994 }
6995 // fit the image on available space
6996 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
6997 // calculate new minimum dimensions in pixels
6998 $neww = round($w * $this->k * $dpi / $this->dpi);
6999 $newh = round($h * $this->k * $dpi / $this->dpi);
7000 // check if resize is necessary (resize is used only to reduce the image)
7001 $newsize = ($neww * $newh);
7002 $pixsize = ($pixw * $pixh);
7003 if (intval($resize) == 2) {
7004 $resize = true;
7005 } elseif ($newsize >= $pixsize) {
7006 $resize = false;
7007 }
7008 // check if image has been already added on document
7009 $newimage = true;
7010 if (in_array($file, $this->imagekeys)) {
7011 $newimage = false;
7012 // get existing image data
7013 $info = $this->getImageBuffer($file);
7014 if (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE) {
7015 // check if the newer image is larger
7016 $oldsize = ($info['w'] * $info['h']);
7017 if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7018 $newimage = true;
7019 }
7020 }
7021 } elseif (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)) {
7022 // create temp image file (without alpha channel)
7023 $tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7024 // create temp alpha file
7025 $tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7026 // check for cached images
7027 if (in_array($tempfile_plain, $this->imagekeys)) {
7028 // get existing image data
7029 $info = $this->getImageBuffer($tempfile_plain);
7030 // check if the newer image is larger
7031 $oldsize = ($info['w'] * $info['h']);
7032 if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7033 $newimage = true;
7034 } else {
7035 $newimage = false;
7036 // embed mask image
7037 $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7038 // embed image, masked with previously embedded mask
7039 return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7040 }
7041 }
7042 }
7043 if ($newimage) {
7044 //First use of image, get info
7045 $type = strtolower($type);
7046 if ($type == '') {
7047 $type = TCPDF_IMAGES::getImageFileType($file, $imsize);
7048 } elseif ($type == 'jpg') {
7049 $type = 'jpeg';
7050 }
7051 $mqr = TCPDF_STATIC::get_mqr();
7052 TCPDF_STATIC::set_mqr(false);
7053 // Specific image handlers (defined on TCPDF_IMAGES CLASS)
7054 $mtd = '_parse'.$type;
7055 // GD image handler function
7056 $gdfunction = 'imagecreatefrom'.$type;
7057 $info = false;
7058 if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
7059 // TCPDF image functions
7060 $info = TCPDF_IMAGES::$mtd($file);
7061 if (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)
7062 AND (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns'])))) {
7063 return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
7064 }
7065 }
7066 if (($info === false) AND function_exists($gdfunction)) {
7067 try {
7068 // GD library
7069 $img = $gdfunction($file);
7070 if ($img !== false) {
7071 if ($resize) {
7072 $imgr = imagecreatetruecolor($neww, $newh);
7073 if (($type == 'gif') OR ($type == 'png')) {
7074 $imgr = TCPDF_IMAGES::setGDImageTransparency($imgr, $img);
7075 }
7076 imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7077 $img = $imgr;
7078 }
7079 if (($type == 'gif') OR ($type == 'png')) {
7080 $info = TCPDF_IMAGES::_toPNG($img, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7081 } else {
7082 $info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7083 }
7084 }
7085 } catch(Exception $e) {
7086 $info = false;
7087 }
7088 }
7089 if (($info === false) AND extension_loaded('imagick')) {
7090 try {
7091 // ImageMagick library
7092 $img = new Imagick();
7093 if ($type == 'svg') {
7094 if ($file[0] === '@') {
7095 // image from string
7096 $svgimg = substr($file, 1);
7097 } else {
7098 // get SVG file content
7099 $svgimg = TCPDF_STATIC::fileGetContents($file);
7100 }
7101 if ($svgimg !== FALSE) {
7102 // get width and height
7103 $regs = array();
7104 if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7105 $svgtag = $regs[1];
7106 $tmp = array();
7107 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7108 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7109 $owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
7110 $svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7111 } else {
7112 $ow = $w;
7113 }
7114 $tmp = array();
7115 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7116 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7117 $ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
7118 $svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7119 } else {
7120 $oh = $h;
7121 }
7122 $tmp = array();
7123 if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7124 $vbw = ($ow * $this->imgscale * $this->k);
7125 $vbh = ($oh * $this->imgscale * $this->k);
7126 $vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
7127 $svgtag = $vbox.$svgtag;
7128 }
7129 $svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7130 }
7131 $img->readImageBlob($svgimg);
7132 }
7133 } else {
7134 $img->readImage($file);
7135 }
7136 if ($resize) {
7137 $img->resizeImage($neww, $newh, 10, 1, false);
7138 }
7139 $img->setCompressionQuality($this->jpeg_quality);
7140 $img->setImageFormat('jpeg');
7141 $tempname = TCPDF_STATIC::getObjFilename('img', $this->file_id);
7142 $img->writeImage($tempname);
7143 $info = TCPDF_IMAGES::_parsejpeg($tempname);
7144 unlink($tempname);
7145 $img->destroy();
7146 } catch(Exception $e) {
7147 $info = false;
7148 }
7149 }
7150 if ($info === false) {
7151 // unable to process image
7152 return;
7153 }
7154 TCPDF_STATIC::set_mqr($mqr);
7155 if ($ismask) {
7156 // force grayscale
7157 $info['cs'] = 'DeviceGray';
7158 }
7159 if ($imgmask !== false) {
7160 $info['masked'] = $imgmask;
7161 }
7162 if (!empty($exurl)) {
7163 $info['exurl'] = $exurl;
7164 }
7165 // array of alternative images
7166 $info['altimgs'] = $altimgs;
7167 // add image to document
7168 $info['i'] = $this->setImageBuffer($file, $info);
7169 }
7170 // set alignment
7171 $this->img_rb_x = $x + $w;
7172 $this->img_rb_y = $y + $h;
7173
7174 // set alignment
7175 if ($palign == 'L') {
7176 $ximg = $this->lMargin;
7177 } elseif ($palign == 'C') {
7178 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7179 } elseif ($palign == 'R') {
7180 $ximg = $this->w - $this->rMargin - $w;
7181 } else {
7182 $ximg = $x;
7183 }
7184
7185 if ($ismask OR $hidden) {
7186 // image is not displayed
7187 return $info['i'];
7188 }
7189 $xkimg = $ximg * $this->k;
7190 if (!$alt) {
7191 // only non-alternative immages will be set
7192 $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']));
7193 }
7194 if (!empty($border)) {
7195 $bx = $this->x;
7196 $by = $this->y;
7197 $this->x = $ximg;
7198 if ($this->rtl) {
7199 $this->x += $w;
7200 }
7201 $this->y = $y;
7202 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7203 $this->x = $bx;
7204 $this->y = $by;
7205 }
7206 if ($link) {
7207 $this->Link($ximg, $y, $w, $h, $link, 0);
7208 }
7209 // set pointer to align the next text/objects
7210 switch($align) {
7211 case 'T': {
7212 $this->y = $y;
7213 $this->x = $this->img_rb_x;
7214 break;
7215 }
7216 case 'M': {
7217 $this->y = $y + round($h/2);
7218 $this->x = $this->img_rb_x;
7219 break;
7220 }
7221 case 'B': {
7222 $this->y = $this->img_rb_y;
7223 $this->x = $this->img_rb_x;
7224 break;
7225 }
7226 case 'N': {
7227 $this->SetY($this->img_rb_y);
7228 break;
7229 }
7230 default:{
7231 break;
7232 }
7233 }
7234 $this->endlinex = $this->img_rb_x;
7235 if ($this->inxobj) {
7236 // we are inside an XObject template
7237 $this->xobjects[$this->xobjid]['images'][] = $info['i'];
7238 }
7239 return $info['i'];
7240 }
7241
7242 /**
7243 * Extract info from a PNG image with alpha channel using the Imagick or GD library.
7244 * @param $file (string) Name of the file containing the image.
7245 * @param $x (float) Abscissa of the upper-left corner.
7246 * @param $y (float) Ordinate of the upper-left corner.
7247 * @param $wpx (float) Original width of the image in pixels.
7248 * @param $hpx (float) original height of the image in pixels.
7249 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7250 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7251 * @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.
7252 * @param $link (mixed) URL or identifier returned by AddLink().
7253 * @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>
7254 * @param $resize (boolean) If true resize (reduce) the image to fit $w and $h (requires GD library).
7255 * @param $dpi (int) dot-per-inch resolution used on resize
7256 * @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>
7257 * @param $filehash (string) File hash used to build unique file names.
7258 * @author Nicola Asuni
7259 * @protected
7260 * @since 4.3.007 (2008-12-04)
7261 * @see Image()
7262 */
7263 protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
7264 // create temp images
7265 if (empty($filehash)) {
7266 $filehash = md5($file);
7267 }
7268 // create temp image file (without alpha channel)
7269 $tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7270 // create temp alpha file
7271 $tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7272 $parsed = false;
7273 $parse_error = '';
7274 // ImageMagick extension
7275 if (($parsed === false) AND extension_loaded('imagick')) {
7276 try {
7277 // ImageMagick library
7278 $img = new Imagick();
7279 $img->readImage($file);
7280 // clone image object
7281 $imga = TCPDF_STATIC::objclone($img);
7282 // extract alpha channel
7283 if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
7284 $img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
7285 } else {
7286 $img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7287 $img->negateImage(true);
7288 }
7289 $img->setImageFormat('png');
7290 $img->writeImage($tempfile_alpha);
7291 // remove alpha channel
7292 if (method_exists($imga, 'setImageMatte')) {
7293 $imga->setImageMatte(false);
7294 } else {
7295 $imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7296 }
7297 $imga->setImageFormat('png');
7298 $imga->writeImage($tempfile_plain);
7299 $parsed = true;
7300 } catch (Exception $e) {
7301 // Imagemagick fails, try with GD
7302 $parse_error = 'Imagick library error: '.$e->getMessage();
7303 }
7304 }
7305 // GD extension
7306 if (($parsed === false) AND function_exists('imagecreatefrompng')) {
7307 try {
7308 // generate images
7309 $img = imagecreatefrompng($file);
7310 $imgalpha = imagecreate($wpx, $hpx);
7311 // generate gray scale palette (0 -> 255)
7312 for ($c = 0; $c < 256; ++$c) {
7313 ImageColorAllocate($imgalpha, $c, $c, $c);
7314 }
7315 // extract alpha channel
7316 for ($xpx = 0; $xpx < $wpx; ++$xpx) {
7317 for ($ypx = 0; $ypx < $hpx; ++$ypx) {
7318 $color = imagecolorat($img, $xpx, $ypx);
7319 // get and correct gamma color
7320 $alpha = $this->getGDgamma($img, $color);
7321 imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
7322 }
7323 }
7324 imagepng($imgalpha, $tempfile_alpha);
7325 unset( $imgalpha );
7326 // extract image without alpha channel
7327 $imgplain = imagecreatetruecolor($wpx, $hpx);
7328 imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7329 imagepng($imgplain, $tempfile_plain);
7330 unset( $imgplain );
7331 $parsed = true;
7332 } catch (Exception $e) {
7333 // GD fails
7334 $parse_error = 'GD library error: '.$e->getMessage();
7335 }
7336 }
7337 if ($parsed === false) {
7338 if (empty($parse_error)) {
7339 $this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
7340 } else {
7341 $this->Error($parse_error);
7342 }
7343 }
7344 // embed mask image
7345 $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7346 // embed image, masked with previously embedded mask
7347 $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7348 }
7349
7350 /**
7351 * Get the GD-corrected PNG gamma value from alpha color
7352 * @param $img (int) GD image Resource ID.
7353 * @param $c (int) alpha color
7354 * @protected
7355 * @since 4.3.007 (2008-12-04)
7356 */
7357 protected function getGDgamma($img, $c) {
7358 if (!isset($this->gdgammacache['#'.$c])) {
7359 $colors = imagecolorsforindex($img, $c);
7360 // GD alpha is only 7 bit (0 -> 127)
7361 $this->gdgammacache['#'.$c] = (((127 - $colors['alpha']) / 127) * 255);
7362 // correct gamma
7363 $this->gdgammacache['#'.$c] = (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255);
7364 // store the latest values on cache to improve performances
7365 if (count($this->gdgammacache) > 8) {
7366 // remove one element from the cache array
7367 array_shift($this->gdgammacache);
7368 }
7369 }
7370 return $this->gdgammacache['#'.$c];
7371 }
7372
7373 /**
7374 * Performs a line break.
7375 * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
7376 * @param $h (float) The height of the break. By default, the value equals the height of the last printed cell.
7377 * @param $cell (boolean) if true add the current left (or right o for RTL) padding to the X coordinate
7378 * @public
7379 * @since 1.0
7380 * @see Cell()
7381 */
7382 public function Ln($h='', $cell=false) {
7383 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'])) {
7384 // revove vertical space from the top of the column
7385 return;
7386 }
7387 if ($cell) {
7388 if ($this->rtl) {
7389 $cellpadding = $this->cell_padding['R'];
7390 } else {
7391 $cellpadding = $this->cell_padding['L'];
7392 }
7393 } else {
7394 $cellpadding = 0;
7395 }
7396 if ($this->rtl) {
7397 $this->x = $this->w - $this->rMargin - $cellpadding;
7398 } else {
7399 $this->x = $this->lMargin + $cellpadding;
7400 }
7401 if (is_string($h)) {
7402 $h = $this->lasth;
7403 }
7404 $this->y += $h;
7405 $this->newline = true;
7406 }
7407
7408 /**
7409 * Returns the relative X value of current position.
7410 * The value is relative to the left border for LTR languages and to the right border for RTL languages.
7411 * @return float
7412 * @public
7413 * @since 1.2
7414 * @see SetX(), GetY(), SetY()
7415 */
7416 public function GetX() {
7417 //Get x position
7418 if ($this->rtl) {
7419 return ($this->w - $this->x);
7420 } else {
7421 return $this->x;
7422 }
7423 }
7424
7425 /**
7426 * Returns the absolute X value of current position.
7427 * @return float
7428 * @public
7429 * @since 1.2
7430 * @see SetX(), GetY(), SetY()
7431 */
7432 public function GetAbsX() {
7433 return $this->x;
7434 }
7435
7436 /**
7437 * Returns the ordinate of the current position.
7438 * @return float
7439 * @public
7440 * @since 1.0
7441 * @see SetY(), GetX(), SetX()
7442 */
7443 public function GetY() {
7444 return $this->y;
7445 }
7446
7447 /**
7448 * Defines the abscissa of the current position.
7449 * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
7450 * @param $x (float) The value of the abscissa in user units.
7451 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7452 * @public
7453 * @since 1.2
7454 * @see GetX(), GetY(), SetY(), SetXY()
7455 */
7456 public function SetX($x, $rtloff=false) {
7457 $x = floatval($x);
7458 if (!$rtloff AND $this->rtl) {
7459 if ($x >= 0) {
7460 $this->x = $this->w - $x;
7461 } else {
7462 $this->x = abs($x);
7463 }
7464 } else {
7465 if ($x >= 0) {
7466 $this->x = $x;
7467 } else {
7468 $this->x = $this->w + $x;
7469 }
7470 }
7471 if ($this->x < 0) {
7472 $this->x = 0;
7473 }
7474 if ($this->x > $this->w) {
7475 $this->x = $this->w;
7476 }
7477 }
7478
7479 /**
7480 * Moves the current abscissa back to the left margin and sets the ordinate.
7481 * If the passed value is negative, it is relative to the bottom of the page.
7482 * @param $y (float) The value of the ordinate in user units.
7483 * @param $resetx (bool) if true (default) reset the X position.
7484 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7485 * @public
7486 * @since 1.0
7487 * @see GetX(), GetY(), SetY(), SetXY()
7488 */
7489 public function SetY($y, $resetx=true, $rtloff=false) {
7490 $y = floatval($y);
7491 if ($resetx) {
7492 //reset x
7493 if (!$rtloff AND $this->rtl) {
7494 $this->x = $this->w - $this->rMargin;
7495 } else {
7496 $this->x = $this->lMargin;
7497 }
7498 }
7499 if ($y >= 0) {
7500 $this->y = $y;
7501 } else {
7502 $this->y = $this->h + $y;
7503 }
7504 if ($this->y < 0) {
7505 $this->y = 0;
7506 }
7507 if ($this->y > $this->h) {
7508 $this->y = $this->h;
7509 }
7510 }
7511
7512 /**
7513 * Defines the abscissa and ordinate of the current position.
7514 * If the passed values are negative, they are relative respectively to the right and bottom of the page.
7515 * @param $x (float) The value of the abscissa.
7516 * @param $y (float) The value of the ordinate.
7517 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7518 * @public
7519 * @since 1.2
7520 * @see SetX(), SetY()
7521 */
7522 public function SetXY($x, $y, $rtloff=false) {
7523 $this->SetY($y, false, $rtloff);
7524 $this->SetX($x, $rtloff);
7525 }
7526
7527 /**
7528 * Set the absolute X coordinate of the current pointer.
7529 * @param $x (float) The value of the abscissa in user units.
7530 * @public
7531 * @since 5.9.186 (2012-09-13)
7532 * @see setAbsX(), setAbsY(), SetAbsXY()
7533 */
7534 public function SetAbsX($x) {
7535 $this->x = floatval($x);
7536 }
7537
7538 /**
7539 * Set the absolute Y coordinate of the current pointer.
7540 * @param $y (float) (float) The value of the ordinate in user units.
7541 * @public
7542 * @since 5.9.186 (2012-09-13)
7543 * @see setAbsX(), setAbsY(), SetAbsXY()
7544 */
7545 public function SetAbsY($y) {
7546 $this->y = floatval($y);
7547 }
7548
7549 /**
7550 * Set the absolute X and Y coordinates of the current pointer.
7551 * @param $x (float) The value of the abscissa in user units.
7552 * @param $y (float) (float) The value of the ordinate in user units.
7553 * @public
7554 * @since 5.9.186 (2012-09-13)
7555 * @see setAbsX(), setAbsY(), SetAbsXY()
7556 */
7557 public function SetAbsXY($x, $y) {
7558 $this->SetAbsX($x);
7559 $this->SetAbsY($y);
7560 }
7561
7562 /**
7563 * Send the document to a given destination: string, local file or browser.
7564 * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
7565 * The method first calls Close() if necessary to terminate the document.
7566 * @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.
7567 * @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>
7568 * @return string
7569 * @public
7570 * @since 1.0
7571 * @see Close()
7572 */
7573 public function Output($name='doc.pdf', $dest='I') {
7574 //Output PDF to some destination
7575 //Finish document if necessary
7576 if ($this->state < 3) {
7577 $this->Close();
7578 }
7579 //Normalize parameters
7580 if (is_bool($dest)) {
7581 $dest = $dest ? 'D' : 'F';
7582 }
7583 $dest = strtoupper($dest);
7584 if ($dest[0] != 'F') {
7585 $name = preg_replace('/[\s]+/', '_', $name);
7586 $name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
7587 }
7588 if ($this->sign) {
7589 // *** apply digital signature to the document ***
7590 // get the document content
7591 $pdfdoc = $this->getBuffer();
7592 // remove last newline
7593 $pdfdoc = substr($pdfdoc, 0, -1);
7594 // remove filler space
7595 $byterange_string_len = strlen(TCPDF_STATIC::$byterange_string);
7596 // define the ByteRange
7597 $byte_range = array();
7598 $byte_range[0] = 0;
7599 $byte_range[1] = strpos($pdfdoc, TCPDF_STATIC::$byterange_string) + $byterange_string_len + 10;
7600 $byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
7601 $byte_range[3] = strlen($pdfdoc) - $byte_range[2];
7602 $pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
7603 // replace the ByteRange
7604 $byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
7605 $byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
7606 $pdfdoc = str_replace(TCPDF_STATIC::$byterange_string, $byterange, $pdfdoc);
7607 // write the document to a temporary folder
7608 $tempdoc = TCPDF_STATIC::getObjFilename('doc', $this->file_id);
7609 $f = TCPDF_STATIC::fopenLocal($tempdoc, 'wb');
7610 if (!$f) {
7611 $this->Error('Unable to create temporary file: '.$tempdoc);
7612 }
7613 $pdfdoc_length = strlen($pdfdoc);
7614 fwrite($f, $pdfdoc, $pdfdoc_length);
7615 fclose($f);
7616 // get digital signature via openssl library
7617 $tempsign = TCPDF_STATIC::getObjFilename('sig', $this->file_id);
7618 if (empty($this->signature_data['extracerts'])) {
7619 openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
7620 } else {
7621 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']);
7622 }
7623 // read signature
7624 $signature = file_get_contents($tempsign);
7625 // extract signature
7626 $signature = substr($signature, $pdfdoc_length);
7627 $signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
7628 $tmparr = explode("\n\n", $signature);
7629 $signature = $tmparr[1];
7630 // decode signature
7631 $signature = base64_decode(trim($signature));
7632 // add TSA timestamp to signature
7633 $signature = $this->applyTSA($signature);
7634 // convert signature to hex
7635 $signature = current(unpack('H*', $signature));
7636 $signature = str_pad($signature, $this->signature_max_length, '0');
7637 // Add signature to the document
7638 $this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
7639 $this->bufferlen = strlen($this->buffer);
7640 }
7641 switch($dest) {
7642 case 'I': {
7643 // Send PDF to the standard output
7644 if (ob_get_contents()) {
7645 $this->Error('Some data has already been output, can\'t send PDF file');
7646 }
7647 if (php_sapi_name() != 'cli') {
7648 // send output to a browser
7649 header('Content-Type: application/pdf');
7650 if (headers_sent()) {
7651 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7652 }
7653 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7654 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7655 header('Pragma: public');
7656 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7657 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7658 header('Content-Disposition: inline; filename="'.basename($name).'"');
7659 TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7660 } else {
7661 echo $this->getBuffer();
7662 }
7663 break;
7664 }
7665 case 'D': {
7666 // download PDF as file
7667 if (ob_get_contents()) {
7668 $this->Error('Some data has already been output, can\'t send PDF file');
7669 }
7670 header('Content-Description: File Transfer');
7671 if (headers_sent()) {
7672 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7673 }
7674 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7675 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7676 header('Pragma: public');
7677 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7678 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7679 // force download dialog
7680 if (strpos(php_sapi_name(), 'cgi') === false) {
7681 header('Content-Type: application/force-download');
7682 header('Content-Type: application/octet-stream', false);
7683 header('Content-Type: application/download', false);
7684 header('Content-Type: application/pdf', false);
7685 } else {
7686 header('Content-Type: application/pdf');
7687 }
7688 // use the Content-Disposition header to supply a recommended filename
7689 header('Content-Disposition: attachment; filename="'.basename($name).'"');
7690 header('Content-Transfer-Encoding: binary');
7691 TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7692 break;
7693 }
7694 case 'F':
7695 case 'FI':
7696 case 'FD': {
7697 // save PDF to a local file
7698 $f = TCPDF_STATIC::fopenLocal($name, 'wb');
7699 if (!$f) {
7700 $this->Error('Unable to create output file: '.$name);
7701 }
7702 fwrite($f, $this->getBuffer(), $this->bufferlen);
7703 fclose($f);
7704 if ($dest == 'FI') {
7705 // send headers to browser
7706 header('Content-Type: application/pdf');
7707 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7708 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7709 header('Pragma: public');
7710 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7711 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7712 header('Content-Disposition: inline; filename="'.basename($name).'"');
7713 TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7714 } elseif ($dest == 'FD') {
7715 // send headers to browser
7716 if (ob_get_contents()) {
7717 $this->Error('Some data has already been output, can\'t send PDF file');
7718 }
7719 header('Content-Description: File Transfer');
7720 if (headers_sent()) {
7721 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7722 }
7723 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7724 header('Pragma: public');
7725 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7726 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7727 // force download dialog
7728 if (strpos(php_sapi_name(), 'cgi') === false) {
7729 header('Content-Type: application/force-download');
7730 header('Content-Type: application/octet-stream', false);
7731 header('Content-Type: application/download', false);
7732 header('Content-Type: application/pdf', false);
7733 } else {
7734 header('Content-Type: application/pdf');
7735 }
7736 // use the Content-Disposition header to supply a recommended filename
7737 header('Content-Disposition: attachment; filename="'.basename($name).'"');
7738 header('Content-Transfer-Encoding: binary');
7739 TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7740 }
7741 break;
7742 }
7743 case 'E': {
7744 // return PDF as base64 mime multi-part email attachment (RFC 2045)
7745 $retval = 'Content-Type: application/pdf;'."\r\n";
7746 $retval .= ' name="'.$name.'"'."\r\n";
7747 $retval .= 'Content-Transfer-Encoding: base64'."\r\n";
7748 $retval .= 'Content-Disposition: attachment;'."\r\n";
7749 $retval .= ' filename="'.$name.'"'."\r\n\r\n";
7750 $retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
7751 return $retval;
7752 }
7753 case 'S': {
7754 // returns PDF as a string
7755 return $this->getBuffer();
7756 }
7757 default: {
7758 $this->Error('Incorrect output destination: '.$dest);
7759 }
7760 }
7761 return '';
7762 }
7763
7764 protected static $cleaned_ids = array();
7765 /**
7766 * Unset all class variables except the following critical variables.
7767 * @param $destroyall (boolean) if true destroys all class variables, otherwise preserves critical variables.
7768 * @param $preserve_objcopy (boolean) if true preserves the objcopy variable
7769 * @public
7770 * @since 4.5.016 (2009-02-24)
7771 */
7772 public function _destroy($destroyall=false, $preserve_objcopy=false) {
7773 // restore internal encoding
7774 if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) {
7775 mb_internal_encoding($this->internal_encoding);
7776 }
7777 if (isset(self::$cleaned_ids[$this->file_id])) {
7778 $destroyall = false;
7779 }
7780 if ($destroyall AND !$preserve_objcopy) {
7781 self::$cleaned_ids[$this->file_id] = true;
7782 // remove all temporary files
7783 if ($handle = @opendir(K_PATH_CACHE)) {
7784 while ( false !== ( $file_name = readdir( $handle ) ) ) {
7785 if (strpos($file_name, '__tcpdf_'.$this->file_id.'_') === 0) {
7786 unlink(K_PATH_CACHE.$file_name);
7787 }
7788 }
7789 closedir($handle);
7790 }
7791 if (isset($this->imagekeys)) {
7792 $real_cache_path = realpath(K_PATH_CACHE);
7793 foreach($this->imagekeys as $file) {
7794 if (!is_string($file)) {
7795 continue;
7796 }
7797 $real_file = realpath($file);
7798 // Resolve both paths before comparing, otherwise a value like K_PATH_CACHE.'../../wp-config.php'
7799 // passes a naive strpos() prefix check while still pointing outside the cache directory.
7800 if ($real_file !== false && $real_cache_path !== false && strpos($real_file, $real_cache_path . DIRECTORY_SEPARATOR) === 0) {
7801 @unlink($file);
7802 }
7803 }
7804 }
7805 }
7806 $preserve = array(
7807 'file_id',
7808 'internal_encoding',
7809 'state',
7810 'bufferlen',
7811 'buffer',
7812 'cached_files',
7813 'imagekeys',
7814 'sign',
7815 'signature_data',
7816 'signature_max_length',
7817 'byterange_string',
7818 'tsa_timestamp',
7819 'tsa_data'
7820 );
7821 foreach (array_keys(get_object_vars($this)) as $val) {
7822 if ($destroyall OR !in_array($val, $preserve)) {
7823 if ((!$preserve_objcopy OR ($val != 'objcopy')) AND ($val != 'file_id') AND isset($this->$val)) {
7824 unset($this->$val);
7825 }
7826 }
7827 }
7828 }
7829
7830 /**
7831 * Check for locale-related bug
7832 * @protected
7833 */
7834 protected function _dochecks() {
7835 //Check for locale-related bug
7836 if (1.1 == 1) {
7837 $this->Error('Don\'t alter the locale before including class file');
7838 }
7839 //Check for decimal separator
7840 if (sprintf('%.1F', 1.0) != '1.0') {
7841 setlocale(LC_NUMERIC, 'C');
7842 }
7843 }
7844
7845 /**
7846 * Return an array containing variations for the basic page number alias.
7847 * @param $a (string) Base alias.
7848 * @return array of page number aliases
7849 * @protected
7850 */
7851 protected function getInternalPageNumberAliases($a= '') {
7852 $alias = array();
7853 // build array of Unicode + ASCII variants (the order is important)
7854 $alias = array('u' => array(), 'a' => array());
7855 $u = '{'.$a.'}';
7856 $alias['u'][] = TCPDF_STATIC::_escape($u);
7857 if ($this->isunicode) {
7858 $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($u, $this->isunicode, $this->CurrentFont));
7859 $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($u, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7860 $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($a, $this->isunicode, $this->CurrentFont));
7861 $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($a, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7862 }
7863 $alias['a'][] = TCPDF_STATIC::_escape($a);
7864 return $alias;
7865 }
7866
7867 /**
7868 * Return an array containing all internal page aliases.
7869 * @return array of page number aliases
7870 * @protected
7871 */
7872 protected function getAllInternalPageNumberAliases() {
7873 $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);
7874 $pnalias = array();
7875 foreach($basic_alias as $k => $a) {
7876 $pnalias[$k] = $this->getInternalPageNumberAliases($a);
7877 }
7878 return $pnalias;
7879 }
7880
7881 /**
7882 * Replace right shift page number aliases with spaces to correct right alignment.
7883 * This works perfectly only when using monospaced fonts.
7884 * @param $page (string) Page content.
7885 * @param $aliases (array) Array of page aliases.
7886 * @param $diff (int) initial difference to add.
7887 * @return replaced page content.
7888 * @protected
7889 */
7890 protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
7891 foreach ($aliases as $type => $alias) {
7892 foreach ($alias as $a) {
7893 // find position of compensation factor
7894 $startnum = (strpos($a, ':') + 1);
7895 $a = substr($a, 0, $startnum);
7896 if (($pos = strpos($page, $a)) !== false) {
7897 // end of alias
7898 $endnum = strpos($page, '}', $pos);
7899 // string to be replaced
7900 $aa = substr($page, $pos, ($endnum - $pos + 1));
7901 // get compensation factor
7902 $ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
7903 $ratio = preg_replace('/[^0-9\.]/', '', $ratio);
7904 $ratio = floatval($ratio);
7905 if ($type == 'u') {
7906 $chrdiff = floor(($diff + 12) * $ratio);
7907 $shift = str_repeat(' ', $chrdiff);
7908 $shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
7909 } else {
7910 $chrdiff = floor(($diff + 11) * $ratio);
7911 $shift = str_repeat(' ', $chrdiff);
7912 }
7913 $page = str_replace($aa, $shift, $page);
7914 }
7915 }
7916 }
7917 return $page;
7918 }
7919
7920 /**
7921 * Set page boxes to be included on page descriptions.
7922 * @param $boxes (array) Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
7923 * @protected
7924 */
7925 protected function setPageBoxTypes($boxes) {
7926 $this->page_boxes = array();
7927 foreach ($boxes as $box) {
7928 if (in_array($box, TCPDF_STATIC::$pageboxes)) {
7929 $this->page_boxes[] = $box;
7930 }
7931 }
7932 }
7933
7934 /**
7935 * Output pages (and replace page number aliases).
7936 * @protected
7937 */
7938 protected function _putpages() {
7939 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
7940 // get internal aliases for page numbers
7941 $pnalias = $this->getAllInternalPageNumberAliases();
7942 $num_pages = $this->numpages;
7943 $ptpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $num_pages - 1));
7944 $ptpu = TCPDF_FONTS::UTF8ToUTF16BE($ptpa, false, $this->isunicode, $this->CurrentFont);
7945 $ptp_num_chars = $this->GetNumChars($ptpa);
7946 $pagegroupnum = 0;
7947 $groupnum = 0;
7948 $ptgu = 1;
7949 $ptga = 1;
7950 $ptg_num_chars = 1;
7951 for ($n = 1; $n <= $num_pages; ++$n) {
7952 // get current page
7953 $temppage = $this->getPageBuffer($n);
7954 $pagelen = strlen($temppage);
7955 // set replacements for total pages number
7956 $pnpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $n - 1));
7957 $pnpu = TCPDF_FONTS::UTF8ToUTF16BE($pnpa, false, $this->isunicode, $this->CurrentFont);
7958 $pnp_num_chars = $this->GetNumChars($pnpa);
7959 $pdiff = 0; // difference used for right shift alignment of page numbers
7960 $gdiff = 0; // difference used for right shift alignment of page group numbers
7961 if (!empty($this->pagegroups)) {
7962 if (isset($this->newpagegroup[$n])) {
7963 $pagegroupnum = 0;
7964 ++$groupnum;
7965 $ptga = TCPDF_STATIC::formatPageNumber($this->pagegroups[$groupnum]);
7966 $ptgu = TCPDF_FONTS::UTF8ToUTF16BE($ptga, false, $this->isunicode, $this->CurrentFont);
7967 $ptg_num_chars = $this->GetNumChars($ptga);
7968 }
7969 ++$pagegroupnum;
7970 $pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
7971 $pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
7972 $png_num_chars = $this->GetNumChars($pnga);
7973 // replace page numbers
7974 $replace = array();
7975 $replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
7976 $replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
7977 $replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
7978 $replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
7979 list($temppage, $gdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $gdiff);
7980 }
7981 // replace page numbers
7982 $replace = array();
7983 $replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
7984 $replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
7985 $replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
7986 $replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
7987 list($temppage, $pdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $pdiff);
7988 // replace right shift alias
7989 $temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
7990 // replace EPS marker
7991 $temppage = str_replace($this->epsmarker, '', $temppage);
7992 //Page
7993 $this->page_obj_id[$n] = $this->_newobj();
7994 $out = '<<';
7995 $out .= ' /Type /Page';
7996 $out .= ' /Parent 1 0 R';
7997 if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
7998 $out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
7999 }
8000 $out .= ' /Resources 2 0 R';
8001 foreach ($this->page_boxes as $box) {
8002 $out .= ' /'.$box;
8003 $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']);
8004 }
8005 if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
8006 $out .= ' /BoxColorInfo <<';
8007 foreach ($this->page_boxes as $box) {
8008 if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
8009 $out .= ' /'.$box.' <<';
8010 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
8011 $color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
8012 $out .= ' /C [';
8013 $out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
8014 $out .= ' ]';
8015 }
8016 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
8017 $out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
8018 }
8019 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
8020 $out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
8021 }
8022 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
8023 $dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
8024 $out .= ' /D [';
8025 foreach ($dashes as $dash) {
8026 $out .= sprintf(' %F', ($dash * $this->k));
8027 }
8028 $out .= ' ]';
8029 }
8030 $out .= ' >>';
8031 }
8032 }
8033 $out .= ' >>';
8034 }
8035 $out .= ' /Contents '.($this->n + 1).' 0 R';
8036 $out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
8037 if (!$this->pdfa_mode) {
8038 $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
8039 }
8040 if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
8041 // page transitions
8042 if (isset($this->pagedim[$n]['trans']['Dur'])) {
8043 $out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
8044 }
8045 $out .= ' /Trans <<';
8046 $out .= ' /Type /Trans';
8047 if (isset($this->pagedim[$n]['trans']['S'])) {
8048 $out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
8049 }
8050 if (isset($this->pagedim[$n]['trans']['D'])) {
8051 $out .= ' /D '.$this->pagedim[$n]['trans']['D'];
8052 }
8053 if (isset($this->pagedim[$n]['trans']['Dm'])) {
8054 $out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
8055 }
8056 if (isset($this->pagedim[$n]['trans']['M'])) {
8057 $out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
8058 }
8059 if (isset($this->pagedim[$n]['trans']['Di'])) {
8060 $out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
8061 }
8062 if (isset($this->pagedim[$n]['trans']['SS'])) {
8063 $out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
8064 }
8065 if (isset($this->pagedim[$n]['trans']['B'])) {
8066 $out .= ' /B '.$this->pagedim[$n]['trans']['B'];
8067 }
8068 $out .= ' >>';
8069 }
8070 $out .= $this->_getannotsrefs($n);
8071 $out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8072 $out .= ' >>';
8073 $out .= "\n".'endobj';
8074 $this->_out($out);
8075 //Page content
8076 $p = ($this->compress) ? gzcompress($temppage) : $temppage;
8077 $this->_newobj();
8078 $p = $this->_getrawstream($p);
8079 $this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8080 }
8081 //Pages root
8082 $out = $this->_getobj(1)."\n";
8083 $out .= '<< /Type /Pages /Kids [';
8084 foreach($this->page_obj_id as $page_obj) {
8085 $out .= ' '.$page_obj.' 0 R';
8086 }
8087 $out .= ' ] /Count '.$num_pages.' >>';
8088 $out .= "\n".'endobj';
8089 $this->_out($out);
8090 }
8091
8092 /**
8093 * Get references to page annotations.
8094 * @param $n (int) page number
8095 * @return string
8096 * @protected
8097 * @author Nicola Asuni
8098 * @since 5.0.010 (2010-05-17)
8099 */
8100 protected function _getannotsrefs($n) {
8101 if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
8102 return '';
8103 }
8104 $out = ' /Annots [';
8105 if (isset($this->PageAnnots[$n])) {
8106 foreach ($this->PageAnnots[$n] as $key => $val) {
8107 if (!in_array($val['n'], $this->radio_groups)) {
8108 $out .= ' '.$val['n'].' 0 R';
8109 }
8110 }
8111 // add radiobutton groups
8112 if (isset($this->radiobutton_groups[$n])) {
8113 foreach ($this->radiobutton_groups[$n] as $key => $data) {
8114 if (isset($data['n'])) {
8115 $out .= ' '.$data['n'].' 0 R';
8116 }
8117 }
8118 }
8119 }
8120 if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
8121 // set reference for signature object
8122 $out .= ' '.$this->sig_obj_id.' 0 R';
8123 }
8124 if (!empty($this->empty_signature_appearance)) {
8125 foreach ($this->empty_signature_appearance as $esa) {
8126 if ($esa['page'] == $n) {
8127 // set reference for empty signature objects
8128 $out .= ' '.$esa['objid'].' 0 R';
8129 }
8130 }
8131 }
8132 $out .= ' ]';
8133 return $out;
8134 }
8135
8136 /**
8137 * Output annotations objects for all pages.
8138 * !!! THIS METHOD IS NOT YET COMPLETED !!!
8139 * See section 12.5 of PDF 32000_2008 reference.
8140 * @protected
8141 * @author Nicola Asuni
8142 * @since 4.0.018 (2008-08-06)
8143 */
8144 protected function _putannotsobjs() {
8145 // reset object counter
8146 for ($n=1; $n <= $this->numpages; ++$n) {
8147 if (isset($this->PageAnnots[$n])) {
8148 // set page annotations
8149 foreach ($this->PageAnnots[$n] as $key => $pl) {
8150 $annot_obj_id = $this->PageAnnots[$n][$key]['n'];
8151 // create annotation object for grouping radiobuttons
8152 if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
8153 $radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
8154 $annots = '<<';
8155 $annots .= ' /Type /Annot';
8156 $annots .= ' /Subtype /Widget';
8157 $annots .= ' /Rect [0 0 0 0]';
8158 if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
8159 // read only
8160 $annots .= ' /F 68';
8161 $annots .= ' /Ff 49153';
8162 } else {
8163 $annots .= ' /F 4'; // default print for PDF/A
8164 $annots .= ' /Ff 49152';
8165 }
8166 $annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8167 if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8168 $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
8169 }
8170 $annots .= ' /FT /Btn';
8171 $annots .= ' /Kids [';
8172 $defval = '';
8173 foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
8174 if (isset($data['kid'])) {
8175 $annots .= ' '.$data['kid'].' 0 R';
8176 if ($data['def'] !== 'Off') {
8177 $defval = $data['def'];
8178 }
8179 }
8180 }
8181 $annots .= ' ]';
8182 if (!empty($defval)) {
8183 $annots .= ' /V /'.$defval;
8184 }
8185 $annots .= ' >>';
8186 $this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8187 $this->form_obj_id[] = $radio_button_obj_id;
8188 // store object id to be used on Parent entry of Kids
8189 $this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
8190 }
8191 $formfield = false;
8192 $pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
8193 $a = $pl['x'] * $this->k;
8194 $b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
8195 $c = $pl['w'] * $this->k;
8196 $d = $pl['h'] * $this->k;
8197 $rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
8198 // create new annotation object
8199 $annots = '<</Type /Annot';
8200 $annots .= ' /Subtype /'.$pl['opt']['subtype'];
8201 $annots .= ' /Rect ['.$rect.']';
8202 $ft = array('Btn', 'Tx', 'Ch', 'Sig');
8203 if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8204 $annots .= ' /FT /'.$pl['opt']['ft'];
8205 $formfield = true;
8206 }
8207 if ($pl['opt']['subtype'] !== 'Link') {
8208 $annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8209 }
8210 $annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
8211 $annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8212 $annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
8213 if (isset($pl['opt']['f'])) {
8214 $fval = 0;
8215 if (is_array($pl['opt']['f'])) {
8216 foreach ($pl['opt']['f'] as $f) {
8217 switch (strtolower($f)) {
8218 case 'invisible': {
8219 $fval += 1 << 0;
8220 break;
8221 }
8222 case 'hidden': {
8223 $fval += 1 << 1;
8224 break;
8225 }
8226 case 'print': {
8227 $fval += 1 << 2;
8228 break;
8229 }
8230 case 'nozoom': {
8231 $fval += 1 << 3;
8232 break;
8233 }
8234 case 'norotate': {
8235 $fval += 1 << 4;
8236 break;
8237 }
8238 case 'noview': {
8239 $fval += 1 << 5;
8240 break;
8241 }
8242 case 'readonly': {
8243 $fval += 1 << 6;
8244 break;
8245 }
8246 case 'locked': {
8247 $fval += 1 << 8;
8248 break;
8249 }
8250 case 'togglenoview': {
8251 $fval += 1 << 9;
8252 break;
8253 }
8254 case 'lockedcontents': {
8255 $fval += 1 << 10;
8256 break;
8257 }
8258 default: {
8259 break;
8260 }
8261 }
8262 }
8263 } else {
8264 $fval = intval($pl['opt']['f']);
8265 }
8266 } else {
8267 $fval = 4;
8268 }
8269 if ($this->pdfa_mode) {
8270 // force print flag for PDF/A mode
8271 $fval |= 4;
8272 }
8273 $annots .= ' /F '.intval($fval);
8274 if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8275 $annots .= ' /AS /'.$pl['opt']['as'];
8276 }
8277 if (isset($pl['opt']['ap'])) {
8278 // appearance stream
8279 $annots .= ' /AP <<';
8280 if (is_array($pl['opt']['ap'])) {
8281 foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8282 // $apmode can be: n = normal; r = rollover; d = down;
8283 $annots .= ' /'.strtoupper($apmode);
8284 if (is_array($apdef)) {
8285 $annots .= ' <<';
8286 foreach ($apdef as $apstate => $stream) {
8287 // reference to XObject that define the appearance for this mode-state
8288 $apsobjid = $this->_putAPXObject($c, $d, $stream);
8289 $annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8290 }
8291 $annots .= ' >>';
8292 } else {
8293 // reference to XObject that define the appearance for this mode
8294 $apsobjid = $this->_putAPXObject($c, $d, $apdef);
8295 $annots .= ' '.$apsobjid.' 0 R';
8296 }
8297 }
8298 } else {
8299 $annots .= $pl['opt']['ap'];
8300 }
8301 $annots .= ' >>';
8302 }
8303 if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8304 $annots .= ' /BS <<';
8305 $annots .= ' /Type /Border';
8306 if (isset($pl['opt']['bs']['w'])) {
8307 $annots .= ' /W '.intval($pl['opt']['bs']['w']);
8308 }
8309 $bstyles = array('S', 'D', 'B', 'I', 'U');
8310 if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8311 $annots .= ' /S /'.$pl['opt']['bs']['s'];
8312 }
8313 if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8314 $annots .= ' /D [';
8315 foreach ($pl['opt']['bs']['d'] as $cord) {
8316 $annots .= ' '.intval($cord);
8317 }
8318 $annots .= ']';
8319 }
8320 $annots .= ' >>';
8321 } else {
8322 $annots .= ' /Border [';
8323 if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8324 $annots .= intval($pl['opt']['border'][0]).' ';
8325 $annots .= intval($pl['opt']['border'][1]).' ';
8326 $annots .= intval($pl['opt']['border'][2]);
8327 if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8328 $annots .= ' [';
8329 foreach ($pl['opt']['border'][3] as $dash) {
8330 $annots .= intval($dash).' ';
8331 }
8332 $annots .= ']';
8333 }
8334 } else {
8335 $annots .= '0 0 0';
8336 }
8337 $annots .= ']';
8338 }
8339 if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8340 $annots .= ' /BE <<';
8341 $bstyles = array('S', 'C');
8342 if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
8343 $annots .= ' /S /'.$pl['opt']['bs']['s'];
8344 } else {
8345 $annots .= ' /S /S';
8346 }
8347 if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8348 $annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
8349 }
8350 $annots .= '>>';
8351 }
8352 if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8353 $annots .= ' /C '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['c']);
8354 }
8355 //$annots .= ' /StructParent ';
8356 //$annots .= ' /OC ';
8357 $markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8358 if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8359 // this is a markup type
8360 if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8361 $annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8362 }
8363 //$annots .= ' /Popup ';
8364 if (isset($pl['opt']['ca'])) {
8365 $annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
8366 }
8367 if (isset($pl['opt']['rc'])) {
8368 $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8369 }
8370 $annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
8371 //$annots .= ' /IRT ';
8372 if (isset($pl['opt']['subj'])) {
8373 $annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8374 }
8375 //$annots .= ' /RT ';
8376 //$annots .= ' /IT ';
8377 //$annots .= ' /ExData ';
8378 }
8379 $lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8380 // Annotation types
8381 switch (strtolower($pl['opt']['subtype'])) {
8382 case 'text': {
8383 if (isset($pl['opt']['open'])) {
8384 $annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
8385 }
8386 $iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8387 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8388 $annots .= ' /Name /'.$pl['opt']['name'];
8389 } else {
8390 $annots .= ' /Name /Note';
8391 }
8392 $statemodels = array('Marked', 'Review');
8393 if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
8394 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8395 } else {
8396 $pl['opt']['statemodel'] = 'Marked';
8397 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8398 }
8399 if ($pl['opt']['statemodel'] == 'Marked') {
8400 $states = array('Accepted', 'Unmarked');
8401 } else {
8402 $states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8403 }
8404 if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
8405 $annots .= ' /State /'.$pl['opt']['state'];
8406 } else {
8407 if ($pl['opt']['statemodel'] == 'Marked') {
8408 $annots .= ' /State /Unmarked';
8409 } else {
8410 $annots .= ' /State /None';
8411 }
8412 }
8413 break;
8414 }
8415 case 'link': {
8416 if (is_string($pl['txt']) && !empty($pl['txt'])) {
8417 if ($pl['txt'][0] == '#') {
8418 // internal destination
8419 $annots .= ' /A <</S /GoTo /D '.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1)).'>>';
8420 } elseif ($pl['txt'][0] == '%') {
8421 // embedded PDF file
8422 $filename = basename(substr($pl['txt'], 1));
8423 $annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
8424 } elseif ($pl['txt'][0] == '*') {
8425 // embedded generic file
8426 $filename = basename(substr($pl['txt'], 1));
8427 $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});';
8428 $annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
8429 } else {
8430 $parsedUrl = parse_url($pl['txt']);
8431 if (empty($parsedUrl['scheme']) AND (!empty($parsedUrl['path']) && strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
8432 // relative link to a PDF file
8433 $dest = '[0 /Fit]'; // default page 0
8434 if (!empty($parsedUrl['fragment'])) {
8435 // check for named destination
8436 $tmp = explode('=', $parsedUrl['fragment']);
8437 $dest = '('.((count($tmp) == 2) ? $tmp[1] : $tmp[0]).')';
8438 }
8439 $annots .= ' /A <</S /GoToR /D '.$dest.' /F '.$this->_datastring($this->unhtmlentities($parsedUrl['path']), $annot_obj_id).' /NewWindow true>>';
8440 } else {
8441 // external URI link
8442 $annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8443 }
8444 }
8445 } elseif (isset($this->links[$pl['txt']])) {
8446 // internal link ID
8447 $l = $this->links[$pl['txt']];
8448 if (isset($this->page_obj_id[($l['p'])])) {
8449 $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)));
8450 }
8451 }
8452 $hmodes = array('N', 'I', 'O', 'P');
8453 if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8454 $annots .= ' /H /'.$pl['opt']['h'];
8455 } else {
8456 $annots .= ' /H /I';
8457 }
8458 //$annots .= ' /PA ';
8459 //$annots .= ' /Quadpoints ';
8460 break;
8461 }
8462 case 'freetext': {
8463 if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8464 $annots .= ' /DA ('.$pl['opt']['da'].')';
8465 }
8466 if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8467 $annots .= ' /Q '.intval($pl['opt']['q']);
8468 }
8469 if (isset($pl['opt']['rc'])) {
8470 $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8471 }
8472 if (isset($pl['opt']['ds'])) {
8473 $annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8474 }
8475 if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8476 $annots .= ' /CL [';
8477 foreach ($pl['opt']['cl'] as $cl) {
8478 $annots .= sprintf('%F ', $cl * $this->k);
8479 }
8480 $annots .= ']';
8481 }
8482 $tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8483 if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8484 $annots .= ' /IT /'.$pl['opt']['it'];
8485 }
8486 if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8487 $l = $pl['opt']['rd'][0] * $this->k;
8488 $r = $pl['opt']['rd'][1] * $this->k;
8489 $t = $pl['opt']['rd'][2] * $this->k;
8490 $b = $pl['opt']['rd'][3] * $this->k;
8491 $annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
8492 }
8493 if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8494 $annots .= ' /LE /'.$pl['opt']['le'];
8495 }
8496 break;
8497 }
8498 case 'line': {
8499 break;
8500 }
8501 case 'square': {
8502 break;
8503 }
8504 case 'circle': {
8505 break;
8506 }
8507 case 'polygon': {
8508 break;
8509 }
8510 case 'polyline': {
8511 break;
8512 }
8513 case 'highlight': {
8514 break;
8515 }
8516 case 'underline': {
8517 break;
8518 }
8519 case 'squiggly': {
8520 break;
8521 }
8522 case 'strikeout': {
8523 break;
8524 }
8525 case 'stamp': {
8526 break;
8527 }
8528 case 'caret': {
8529 break;
8530 }
8531 case 'ink': {
8532 break;
8533 }
8534 case 'popup': {
8535 break;
8536 }
8537 case 'fileattachment': {
8538 if ($this->pdfa_mode && $this->pdfa_version != 3) {
8539 // embedded files are not allowed in PDF/A mode version 1 and 2
8540 break;
8541 }
8542 if (!isset($pl['opt']['fs'])) {
8543 break;
8544 }
8545 $filename = basename($pl['opt']['fs']);
8546 if (isset($this->embeddedfiles[$filename]['f'])) {
8547 $annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
8548 $iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8549 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8550 $annots .= ' /Name /'.$pl['opt']['name'];
8551 } else {
8552 $annots .= ' /Name /PushPin';
8553 }
8554 // index (zero-based) of the annotation in the Annots array of this page
8555 $this->embeddedfiles[$filename]['a'] = $key;
8556 }
8557 break;
8558 }
8559 case 'sound': {
8560 if (!isset($pl['opt']['fs'])) {
8561 break;
8562 }
8563 $filename = basename($pl['opt']['fs']);
8564 if (isset($this->embeddedfiles[$filename]['f'])) {
8565 // ... TO BE COMPLETED ...
8566 // /R /C /B /E /CO /CP
8567 $annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
8568 $iconsapp = array('Speaker', 'Mic');
8569 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8570 $annots .= ' /Name /'.$pl['opt']['name'];
8571 } else {
8572 $annots .= ' /Name /Speaker';
8573 }
8574 }
8575 break;
8576 }
8577 case 'movie': {
8578 break;
8579 }
8580 case 'widget': {
8581 $hmode = array('N', 'I', 'O', 'P', 'T');
8582 if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8583 $annots .= ' /H /'.$pl['opt']['h'];
8584 }
8585 if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8586 $annots .= ' /MK <<';
8587 if (isset($pl['opt']['mk']['r'])) {
8588 $annots .= ' /R '.$pl['opt']['mk']['r'];
8589 }
8590 if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8591 $annots .= ' /BC '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bc']);
8592 }
8593 if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8594 $annots .= ' /BG '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bg']);
8595 }
8596 if (isset($pl['opt']['mk']['ca'])) {
8597 $annots .= ' /CA '.$pl['opt']['mk']['ca'];
8598 }
8599 if (isset($pl['opt']['mk']['rc'])) {
8600 $annots .= ' /RC '.$pl['opt']['mk']['rc'];
8601 }
8602 if (isset($pl['opt']['mk']['ac'])) {
8603 $annots .= ' /AC '.$pl['opt']['mk']['ac'];
8604 }
8605 if (isset($pl['opt']['mk']['i'])) {
8606 $info = $this->getImageBuffer($pl['opt']['mk']['i']);
8607 if ($info !== false) {
8608 $annots .= ' /I '.$info['n'].' 0 R';
8609 }
8610 }
8611 if (isset($pl['opt']['mk']['ri'])) {
8612 $info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8613 if ($info !== false) {
8614 $annots .= ' /RI '.$info['n'].' 0 R';
8615 }
8616 }
8617 if (isset($pl['opt']['mk']['ix'])) {
8618 $info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8619 if ($info !== false) {
8620 $annots .= ' /IX '.$info['n'].' 0 R';
8621 }
8622 }
8623 if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8624 $annots .= ' /IF <<';
8625 $if_sw = array('A', 'B', 'S', 'N');
8626 if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8627 $annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8628 }
8629 $if_s = array('A', 'P');
8630 if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8631 $annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8632 }
8633 if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8634 $annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8635 }
8636 if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8637 $annots .= ' /FB true';
8638 }
8639 $annots .= '>>';
8640 }
8641 if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8642 $annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8643 }
8644 $annots .= '>>';
8645 } // end MK
8646 // --- Entries for field dictionaries ---
8647 if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8648 // set parent
8649 $annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
8650 }
8651 if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8652 $annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8653 }
8654 if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8655 $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8656 }
8657 if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8658 $annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8659 }
8660 if (isset($pl['opt']['ff'])) {
8661 if (is_array($pl['opt']['ff'])) {
8662 // array of bit settings
8663 $flag = 0;
8664 foreach($pl['opt']['ff'] as $val) {
8665 $flag += 1 << ($val - 1);
8666 }
8667 } else {
8668 $flag = intval($pl['opt']['ff']);
8669 }
8670 $annots .= ' /Ff '.$flag;
8671 }
8672 if (isset($pl['opt']['maxlen'])) {
8673 $annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
8674 }
8675 if (isset($pl['opt']['v'])) {
8676 $annots .= ' /V';
8677 if (is_array($pl['opt']['v'])) {
8678 foreach ($pl['opt']['v'] AS $optval) {
8679 if (is_float($optval)) {
8680 $optval = sprintf('%F', $optval);
8681 }
8682 $annots .= ' '.$optval;
8683 }
8684 } else {
8685 $annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8686 }
8687 }
8688 if (isset($pl['opt']['dv'])) {
8689 $annots .= ' /DV';
8690 if (is_array($pl['opt']['dv'])) {
8691 foreach ($pl['opt']['dv'] AS $optval) {
8692 if (is_float($optval)) {
8693 $optval = sprintf('%F', $optval);
8694 }
8695 $annots .= ' '.$optval;
8696 }
8697 } else {
8698 $annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8699 }
8700 }
8701 if (isset($pl['opt']['rv'])) {
8702 $annots .= ' /RV';
8703 if (is_array($pl['opt']['rv'])) {
8704 foreach ($pl['opt']['rv'] AS $optval) {
8705 if (is_float($optval)) {
8706 $optval = sprintf('%F', $optval);
8707 }
8708 $annots .= ' '.$optval;
8709 }
8710 } else {
8711 $annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
8712 }
8713 }
8714 if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
8715 $annots .= ' /A << '.$pl['opt']['a'].' >>';
8716 }
8717 if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
8718 $annots .= ' /AA << '.$pl['opt']['aa'].' >>';
8719 }
8720 if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8721 $annots .= ' /DA ('.$pl['opt']['da'].')';
8722 }
8723 if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8724 $annots .= ' /Q '.intval($pl['opt']['q']);
8725 }
8726 if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
8727 $annots .= ' /Opt [';
8728 foreach($pl['opt']['opt'] AS $copt) {
8729 if (is_array($copt)) {
8730 $annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
8731 } else {
8732 $annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8733 }
8734 }
8735 $annots .= ']';
8736 }
8737 if (isset($pl['opt']['ti'])) {
8738 $annots .= ' /TI '.intval($pl['opt']['ti']);
8739 }
8740 if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
8741 $annots .= ' /I [';
8742 foreach($pl['opt']['i'] AS $copt) {
8743 $annots .= intval($copt).' ';
8744 }
8745 $annots .= ']';
8746 }
8747 break;
8748 }
8749 case 'screen': {
8750 break;
8751 }
8752 case 'printermark': {
8753 break;
8754 }
8755 case 'trapnet': {
8756 break;
8757 }
8758 case 'watermark': {
8759 break;
8760 }
8761 case '3d': {
8762 break;
8763 }
8764 default: {
8765 break;
8766 }
8767 }
8768 $annots .= '>>';
8769 // create new annotation object
8770 $this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
8771 if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
8772 // store reference of form object
8773 $this->form_obj_id[] = $annot_obj_id;
8774 }
8775 }
8776 }
8777 } // end for each page
8778 }
8779
8780 /**
8781 * Put appearance streams XObject used to define annotation's appearance states.
8782 * @param $w (int) annotation width
8783 * @param $h (int) annotation height
8784 * @param $stream (string) appearance stream
8785 * @return int object ID
8786 * @protected
8787 * @since 4.8.001 (2009-09-09)
8788 */
8789 protected function _putAPXObject($w=0, $h=0, $stream='') {
8790 $stream = trim($stream);
8791 $out = $this->_getobj()."\n";
8792 $this->xobjects['AX'.$this->n] = array('n' => $this->n);
8793 $out .= '<<';
8794 $out .= ' /Type /XObject';
8795 $out .= ' /Subtype /Form';
8796 $out .= ' /FormType 1';
8797 if ($this->compress) {
8798 $stream = gzcompress($stream);
8799 $out .= ' /Filter /FlateDecode';
8800 }
8801 $rect = sprintf('%F %F', $w, $h);
8802 $out .= ' /BBox [0 0 '.$rect.']';
8803 $out .= ' /Matrix [1 0 0 1 0 0]';
8804 $out .= ' /Resources 2 0 R';
8805 $stream = $this->_getrawstream($stream);
8806 $out .= ' /Length '.strlen($stream);
8807 $out .= ' >>';
8808 $out .= ' stream'."\n".$stream."\n".'endstream';
8809 $out .= "\n".'endobj';
8810 $this->_out($out);
8811 return $this->n;
8812 }
8813
8814 /**
8815 * Output fonts.
8816 * @author Nicola Asuni
8817 * @protected
8818 */
8819 protected function _putfonts() {
8820 $nf = $this->n;
8821 foreach ($this->diffs as $diff) {
8822 //Encodings
8823 $this->_newobj();
8824 $this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
8825 }
8826 $mqr = TCPDF_STATIC::get_mqr();
8827 TCPDF_STATIC::set_mqr(false);
8828 foreach ($this->FontFiles as $file => $info) {
8829 // search and get font file to embedd
8830 $fontfile = TCPDF_FONTS::getFontFullPath($file, $info['fontdir']);
8831 if (!TCPDF_STATIC::empty_string($fontfile)) {
8832 $font = file_get_contents($fontfile);
8833 $compressed = (substr($file, -2) == '.z');
8834 if ((!$compressed) AND (isset($info['length2']))) {
8835 $header = (ord($font[0]) == 128);
8836 if ($header) {
8837 // strip first binary header
8838 $font = substr($font, 6);
8839 }
8840 if ($header AND (ord($font[$info['length1']]) == 128)) {
8841 // strip second binary header
8842 $font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
8843 }
8844 } elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
8845 if ($compressed) {
8846 // uncompress font
8847 $font = gzuncompress($font);
8848 }
8849 // merge subset characters
8850 $subsetchars = array(); // used chars
8851 foreach ($info['fontkeys'] as $fontkey) {
8852 $fontinfo = $this->getFontBuffer($fontkey);
8853 $subsetchars += $fontinfo['subsetchars'];
8854 }
8855 // rebuild a font subset
8856 $font = TCPDF_FONTS::_getTrueTypeFontSubset($font, $subsetchars);
8857 // calculate new font length
8858 $info['length1'] = strlen($font);
8859 if ($compressed) {
8860 // recompress font
8861 $font = gzcompress($font);
8862 }
8863 }
8864 $this->_newobj();
8865 $this->FontFiles[$file]['n'] = $this->n;
8866 $stream = $this->_getrawstream($font);
8867 $out = '<< /Length '.strlen($stream);
8868 if ($compressed) {
8869 $out .= ' /Filter /FlateDecode';
8870 }
8871 $out .= ' /Length1 '.$info['length1'];
8872 if (isset($info['length2'])) {
8873 $out .= ' /Length2 '.$info['length2'].' /Length3 0';
8874 }
8875 $out .= ' >>';
8876 $out .= ' stream'."\n".$stream."\n".'endstream';
8877 $out .= "\n".'endobj';
8878 $this->_out($out);
8879 }
8880 }
8881 TCPDF_STATIC::set_mqr($mqr);
8882 foreach ($this->fontkeys as $k) {
8883 //Font objects
8884 $font = $this->getFontBuffer($k);
8885 $type = $font['type'];
8886 $name = $font['name'];
8887 if ($type == 'core') {
8888 // standard core font
8889 $out = $this->_getobj($this->font_obj_ids[$k])."\n";
8890 $out .= '<</Type /Font';
8891 $out .= ' /Subtype /Type1';
8892 $out .= ' /BaseFont /'.$name;
8893 $out .= ' /Name /F'.$font['i'];
8894 if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
8895 $out .= ' /Encoding /WinAnsiEncoding';
8896 }
8897 if ($k == 'helvetica') {
8898 // add default font for annotations
8899 $this->annotation_fonts[$k] = $font['i'];
8900 }
8901 $out .= ' >>';
8902 $out .= "\n".'endobj';
8903 $this->_out($out);
8904 } elseif (($type == 'Type1') OR ($type == 'TrueType')) {
8905 // additional Type1 or TrueType font
8906 $out = $this->_getobj($this->font_obj_ids[$k])."\n";
8907 $out .= '<</Type /Font';
8908 $out .= ' /Subtype /'.$type;
8909 $out .= ' /BaseFont /'.$name;
8910 $out .= ' /Name /F'.$font['i'];
8911 $out .= ' /FirstChar 32 /LastChar 255';
8912 $out .= ' /Widths '.($this->n + 1).' 0 R';
8913 $out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
8914 if ($font['enc']) {
8915 if (isset($font['diff'])) {
8916 $out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
8917 } else {
8918 $out .= ' /Encoding /WinAnsiEncoding';
8919 }
8920 }
8921 $out .= ' >>';
8922 $out .= "\n".'endobj';
8923 $this->_out($out);
8924 // Widths
8925 $this->_newobj();
8926 $s = '[';
8927 for ($i = 32; $i < 256; ++$i) {
8928 if (isset($font['cw'][$i])) {
8929 $s .= $font['cw'][$i].' ';
8930 } else {
8931 $s .= $font['dw'].' ';
8932 }
8933 }
8934 $s .= ']';
8935 $s .= "\n".'endobj';
8936 $this->_out($s);
8937 //Descriptor
8938 $this->_newobj();
8939 $s = '<</Type /FontDescriptor /FontName /'.$name;
8940 foreach ($font['desc'] as $fdk => $fdv) {
8941 if (is_float($fdv)) {
8942 $fdv = sprintf('%F', $fdv);
8943 }
8944 $s .= ' /'.$fdk.' '.$fdv.'';
8945 }
8946 if (!TCPDF_STATIC::empty_string($font['file'])) {
8947 $s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
8948 }
8949 $s .= '>>';
8950 $s .= "\n".'endobj';
8951 $this->_out($s);
8952 } else {
8953 // additional types
8954 $mtd = '_put'.strtolower($type);
8955 if (!method_exists($this, $mtd)) {
8956 $this->Error('Unsupported font type: '.$type);
8957 }
8958 $this->$mtd($font);
8959 }
8960 }
8961 }
8962
8963 /**
8964 * Adds unicode fonts.<br>
8965 * Based on PDF Reference 1.3 (section 5)
8966 * @param $font (array) font data
8967 * @protected
8968 * @author Nicola Asuni
8969 * @since 1.52.0.TC005 (2005-01-05)
8970 */
8971 protected function _puttruetypeunicode($font) {
8972 $fontname = '';
8973 if ($font['subset']) {
8974 // change name for font subsetting
8975 $subtag = sprintf('%06u', $font['i']);
8976 $subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
8977 $fontname .= $subtag.'+';
8978 }
8979 $fontname .= $font['name'];
8980 // Type0 Font
8981 // A composite font composed of other fonts, organized hierarchically
8982 $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
8983 $out .= '<< /Type /Font';
8984 $out .= ' /Subtype /Type0';
8985 $out .= ' /BaseFont /'.$fontname;
8986 $out .= ' /Name /F'.$font['i'];
8987 $out .= ' /Encoding /'.$font['enc'];
8988 $out .= ' /ToUnicode '.($this->n + 1).' 0 R';
8989 $out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
8990 $out .= ' >>';
8991 $out .= "\n".'endobj';
8992 $this->_out($out);
8993 // ToUnicode map for Identity-H
8994 $stream = TCPDF_FONT_DATA::$uni_identity_h;
8995 // ToUnicode Object
8996 $this->_newobj();
8997 $stream = ($this->compress) ? gzcompress($stream) : $stream;
8998 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
8999 $stream = $this->_getrawstream($stream);
9000 $this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
9001 // CIDFontType2
9002 // A CIDFont whose glyph descriptions are based on TrueType font technology
9003 $oid = $this->_newobj();
9004 $out = '<< /Type /Font';
9005 $out .= ' /Subtype /CIDFontType2';
9006 $out .= ' /BaseFont /'.$fontname;
9007 // A dictionary containing entries that define the character collection of the CIDFont.
9008 $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9009 $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9010 $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9011 $out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
9012 $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9013 $out .= ' /DW '.$font['dw']; // default width
9014 $out .= "\n".TCPDF_FONTS::_putfontwidths($font, 0);
9015 if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9016 $out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
9017 }
9018 $out .= ' >>';
9019 $out .= "\n".'endobj';
9020 $this->_out($out);
9021 // Font descriptor
9022 // A font descriptor describing the CIDFont default metrics other than its glyph widths
9023 $this->_newobj();
9024 $out = '<< /Type /FontDescriptor';
9025 $out .= ' /FontName /'.$fontname;
9026 foreach ($font['desc'] as $key => $value) {
9027 if (is_float($value)) {
9028 $value = sprintf('%F', $value);
9029 }
9030 $out .= ' /'.$key.' '.$value;
9031 }
9032 $fontdir = false;
9033 if (!TCPDF_STATIC::empty_string($font['file'])) {
9034 // A stream containing a TrueType font
9035 $out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
9036 $fontdir = $this->FontFiles[$font['file']]['fontdir'];
9037 }
9038 $out .= ' >>';
9039 $out .= "\n".'endobj';
9040 $this->_out($out);
9041 if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9042 $this->_newobj();
9043 // Embed CIDToGIDMap
9044 // A specification of the mapping from CIDs to glyph indices
9045 // search and get CTG font file to embedd
9046 $ctgfile = strtolower($font['ctg']);
9047 // search and get ctg font file to embedd
9048 $fontfile = TCPDF_FONTS::getFontFullPath($ctgfile, $fontdir);
9049 if (TCPDF_STATIC::empty_string($fontfile)) {
9050 $this->Error('Font file not found: '.$ctgfile);
9051 }
9052 $stream = $this->_getrawstream(file_get_contents($fontfile));
9053 $out = '<< /Length '.strlen($stream).'';
9054 if (substr($fontfile, -2) == '.z') { // check file extension
9055 // Decompresses data encoded using the public-domain
9056 // zlib/deflate compression method, reproducing the
9057 // original text or binary data
9058 $out .= ' /Filter /FlateDecode';
9059 }
9060 $out .= ' >>';
9061 $out .= ' stream'."\n".$stream."\n".'endstream';
9062 $out .= "\n".'endobj';
9063 $this->_out($out);
9064 }
9065 }
9066
9067 /**
9068 * Output CID-0 fonts.
9069 * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
9070 * @param $font (array) font data
9071 * @protected
9072 * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
9073 * @since 3.2.000 (2008-06-23)
9074 */
9075 protected function _putcidfont0($font) {
9076 $cidoffset = 0;
9077 if (!isset($font['cw'][1])) {
9078 $cidoffset = 31;
9079 }
9080 if (isset($font['cidinfo']['uni2cid'])) {
9081 // convert unicode to cid.
9082 $uni2cid = $font['cidinfo']['uni2cid'];
9083 $cw = array();
9084 foreach ($font['cw'] as $uni => $width) {
9085 if (isset($uni2cid[$uni])) {
9086 $cw[($uni2cid[$uni] + $cidoffset)] = $width;
9087 } elseif ($uni < 256) {
9088 $cw[$uni] = $width;
9089 } // else unknown character
9090 }
9091 $font = array_merge($font, array('cw' => $cw));
9092 }
9093 $name = $font['name'];
9094 $enc = $font['enc'];
9095 if ($enc) {
9096 $longname = $name.'-'.$enc;
9097 } else {
9098 $longname = $name;
9099 }
9100 $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9101 $out .= '<</Type /Font';
9102 $out .= ' /Subtype /Type0';
9103 $out .= ' /BaseFont /'.$longname;
9104 $out .= ' /Name /F'.$font['i'];
9105 if ($enc) {
9106 $out .= ' /Encoding /'.$enc;
9107 }
9108 $out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
9109 $out .= ' >>';
9110 $out .= "\n".'endobj';
9111 $this->_out($out);
9112 $oid = $this->_newobj();
9113 $out = '<</Type /Font';
9114 $out .= ' /Subtype /CIDFontType0';
9115 $out .= ' /BaseFont /'.$name;
9116 $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9117 $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9118 $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9119 $out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
9120 $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9121 $out .= ' /DW '.$font['dw'];
9122 $out .= "\n".TCPDF_FONTS::_putfontwidths($font, $cidoffset);
9123 $out .= ' >>';
9124 $out .= "\n".'endobj';
9125 $this->_out($out);
9126 $this->_newobj();
9127 $s = '<</Type /FontDescriptor /FontName /'.$name;
9128 foreach ($font['desc'] as $k => $v) {
9129 if ($k != 'Style') {
9130 if (is_float($v)) {
9131 $v = sprintf('%F', $v);
9132 }
9133 $s .= ' /'.$k.' '.$v.'';
9134 }
9135 }
9136 $s .= '>>';
9137 $s .= "\n".'endobj';
9138 $this->_out($s);
9139 }
9140
9141 /**
9142 * Output images.
9143 * @protected
9144 */
9145 protected function _putimages() {
9146 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9147 foreach ($this->imagekeys as $file) {
9148 $info = $this->getImageBuffer($file);
9149 // set object for alternate images array
9150 if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
9151 $altoid = $this->_newobj();
9152 $out = '[';
9153 foreach ($info['altimgs'] as $altimage) {
9154 if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
9155 $out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
9156 $out .= ' /DefaultForPrinting';
9157 if ($altimage[1] === true) {
9158 $out .= ' true';
9159 } else {
9160 $out .= ' false';
9161 }
9162 $out .= ' >>';
9163 }
9164 }
9165 $out .= ' ]';
9166 $out .= "\n".'endobj';
9167 $this->_out($out);
9168 }
9169 // set image object
9170 $oid = $this->_newobj();
9171 $this->xobjects['I'.$info['i']] = array('n' => $oid);
9172 $this->setImageSubBuffer($file, 'n', $this->n);
9173 $out = '<</Type /XObject';
9174 $out .= ' /Subtype /Image';
9175 $out .= ' /Width '.$info['w'];
9176 $out .= ' /Height '.$info['h'];
9177 if (array_key_exists('masked', $info)) {
9178 $out .= ' /SMask '.($this->n - 1).' 0 R';
9179 }
9180 // set color space
9181 $icc = false;
9182 if (isset($info['icc']) AND ($info['icc'] !== false)) {
9183 // ICC Colour Space
9184 $icc = true;
9185 $out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
9186 } elseif ($info['cs'] == 'Indexed') {
9187 // Indexed Colour Space
9188 $out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
9189 } else {
9190 // Device Colour Space
9191 $out .= ' /ColorSpace /'.$info['cs'];
9192 }
9193 if ($info['cs'] == 'DeviceCMYK') {
9194 $out .= ' /Decode [1 0 1 0 1 0 1 0]';
9195 }
9196 $out .= ' /BitsPerComponent '.$info['bpc'];
9197 if (isset($altoid) AND ($altoid > 0)) {
9198 // reference to alternate images dictionary
9199 $out .= ' /Alternates '.$altoid.' 0 R';
9200 }
9201 if (isset($info['exurl']) AND !empty($info['exurl'])) {
9202 // external stream
9203 $out .= ' /Length 0';
9204 $out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
9205 if (isset($info['f'])) {
9206 $out .= ' /FFilter /'.$info['f'];
9207 }
9208 $out .= ' >>';
9209 $out .= ' stream'."\n".'endstream';
9210 } else {
9211 if (isset($info['f'])) {
9212 $out .= ' /Filter /'.$info['f'];
9213 }
9214 if (isset($info['parms'])) {
9215 $out .= ' '.$info['parms'];
9216 }
9217 if (isset($info['trns']) AND is_array($info['trns'])) {
9218 $trns = '';
9219 $count_info = count($info['trns']);
9220 if ($info['cs'] == 'Indexed') {
9221 $maxval =(pow(2, $info['bpc']) - 1);
9222 for ($i = 0; $i < $count_info; ++$i) {
9223 if (($info['trns'][$i] != 0) AND ($info['trns'][$i] != $maxval)) {
9224 // this is not a binary type mask @TODO: create a SMask
9225 $trns = '';
9226 break;
9227 } elseif (empty($trns) AND ($info['trns'][$i] == 0)) {
9228 // store the first fully transparent value
9229 $trns .= $i.' '.$i.' ';
9230 }
9231 }
9232 } else {
9233 // grayscale or RGB
9234 for ($i = 0; $i < $count_info; ++$i) {
9235 if ($info['trns'][$i] == 0) {
9236 $trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
9237 }
9238 }
9239 }
9240 // Colour Key Masking
9241 if (!empty($trns)) {
9242 $out .= ' /Mask ['.$trns.']';
9243 }
9244 }
9245 $stream = $this->_getrawstream($info['data']);
9246 $out .= ' /Length '.strlen($stream).' >>';
9247 $out .= ' stream'."\n".$stream."\n".'endstream';
9248 }
9249 $out .= "\n".'endobj';
9250 $this->_out($out);
9251 if ($icc) {
9252 // ICC colour profile
9253 $this->_newobj();
9254 $icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
9255 $icc = $this->_getrawstream($icc);
9256 $this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9257 } elseif ($info['cs'] == 'Indexed') {
9258 // colour palette
9259 $this->_newobj();
9260 $pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
9261 $pal = $this->_getrawstream($pal);
9262 $this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
9263 }
9264 }
9265 }
9266
9267 /**
9268 * Output Form XObjects Templates.
9269 * @author Nicola Asuni
9270 * @since 5.8.017 (2010-08-24)
9271 * @protected
9272 * @see startTemplate(), endTemplate(), printTemplate()
9273 */
9274 protected function _putxobjects() {
9275 foreach ($this->xobjects as $key => $data) {
9276 if (isset($data['outdata'])) {
9277 $stream = str_replace($this->epsmarker, '', trim($data['outdata']));
9278 $out = $this->_getobj($data['n'])."\n";
9279 $out .= '<<';
9280 $out .= ' /Type /XObject';
9281 $out .= ' /Subtype /Form';
9282 $out .= ' /FormType 1';
9283 if ($this->compress) {
9284 $stream = gzcompress($stream);
9285 $out .= ' /Filter /FlateDecode';
9286 }
9287 $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));
9288 $out .= ' /Matrix [1 0 0 1 0 0]';
9289 $out .= ' /Resources <<';
9290 $out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9291 if (!$this->pdfa_mode) {
9292 // transparency
9293 if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
9294 $out .= ' /ExtGState <<';
9295 foreach ($data['extgstates'] as $k => $extgstate) {
9296 if (isset($this->extgstates[$k]['name'])) {
9297 $out .= ' /'.$this->extgstates[$k]['name'];
9298 } else {
9299 $out .= ' /GS'.$k;
9300 }
9301 $out .= ' '.$this->extgstates[$k]['n'].' 0 R';
9302 }
9303 $out .= ' >>';
9304 }
9305 if (isset($data['gradients']) AND !empty($data['gradients'])) {
9306 $gp = '';
9307 $gs = '';
9308 foreach ($data['gradients'] as $id => $grad) {
9309 // gradient patterns
9310 $gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
9311 // gradient shadings
9312 $gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
9313 }
9314 $out .= ' /Pattern <<'.$gp.' >>';
9315 $out .= ' /Shading <<'.$gs.' >>';
9316 }
9317 }
9318 // spot colors
9319 if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
9320 $out .= ' /ColorSpace <<';
9321 foreach ($data['spot_colors'] as $name => $color) {
9322 $out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
9323 }
9324 $out .= ' >>';
9325 }
9326 // fonts
9327 if (!empty($data['fonts'])) {
9328 $out .= ' /Font <<';
9329 foreach ($data['fonts'] as $fontkey => $fontid) {
9330 $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9331 }
9332 $out .= ' >>';
9333 }
9334 // images or nested xobjects
9335 if (!empty($data['images']) OR !empty($data['xobjects'])) {
9336 $out .= ' /XObject <<';
9337 foreach ($data['images'] as $imgid) {
9338 $out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
9339 }
9340 foreach ($data['xobjects'] as $sub_id => $sub_objid) {
9341 $out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
9342 }
9343 $out .= ' >>';
9344 }
9345 $out .= ' >>'; //end resources
9346 if (isset($data['group']) AND ($data['group'] !== false)) {
9347 // set transparency group
9348 $out .= ' /Group << /Type /Group /S /Transparency';
9349 if (is_array($data['group'])) {
9350 if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
9351 $out .= ' /CS /'.$data['group']['CS'];
9352 }
9353 if (isset($data['group']['I'])) {
9354 $out .= ' /I /'.($data['group']['I']===true?'true':'false');
9355 }
9356 if (isset($data['group']['K'])) {
9357 $out .= ' /K /'.($data['group']['K']===true?'true':'false');
9358 }
9359 }
9360 $out .= ' >>';
9361 }
9362 $stream = $this->_getrawstream($stream, $data['n']);
9363 $out .= ' /Length '.strlen($stream);
9364 $out .= ' >>';
9365 $out .= ' stream'."\n".$stream."\n".'endstream';
9366 $out .= "\n".'endobj';
9367 $this->_out($out);
9368 }
9369 }
9370 }
9371
9372 /**
9373 * Output Spot Colors Resources.
9374 * @protected
9375 * @since 4.0.024 (2008-09-12)
9376 */
9377 protected function _putspotcolors() {
9378 foreach ($this->spot_colors as $name => $color) {
9379 $this->_newobj();
9380 $this->spot_colors[$name]['n'] = $this->n;
9381 $out = '[/Separation /'.str_replace(' ', '#20', $name);
9382 $out .= ' /DeviceCMYK <<';
9383 $out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
9384 $out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
9385 $out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
9386 $out .= "\n".'endobj';
9387 $this->_out($out);
9388 }
9389 }
9390
9391 /**
9392 * Return XObjects Dictionary.
9393 * @return string XObjects dictionary
9394 * @protected
9395 * @since 5.8.014 (2010-08-23)
9396 */
9397 protected function _getxobjectdict() {
9398 $out = '';
9399 foreach ($this->xobjects as $id => $objid) {
9400 $out .= ' /'.$id.' '.$objid['n'].' 0 R';
9401 }
9402 return $out;
9403 }
9404
9405 /**
9406 * Output Resources Dictionary.
9407 * @protected
9408 */
9409 protected function _putresourcedict() {
9410 $out = $this->_getobj(2)."\n";
9411 $out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9412 $out .= ' /Font <<';
9413 foreach ($this->fontkeys as $fontkey) {
9414 $font = $this->getFontBuffer($fontkey);
9415 $out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
9416 }
9417 $out .= ' >>';
9418 $out .= ' /XObject <<';
9419 $out .= $this->_getxobjectdict();
9420 $out .= ' >>';
9421 // layers
9422 if (!empty($this->pdflayers)) {
9423 $out .= ' /Properties <<';
9424 foreach ($this->pdflayers as $layer) {
9425 $out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
9426 }
9427 $out .= ' >>';
9428 }
9429 if (!$this->pdfa_mode) {
9430 // transparency
9431 if (isset($this->extgstates) AND !empty($this->extgstates)) {
9432 $out .= ' /ExtGState <<';
9433 foreach ($this->extgstates as $k => $extgstate) {
9434 if (isset($extgstate['name'])) {
9435 $out .= ' /'.$extgstate['name'];
9436 } else {
9437 $out .= ' /GS'.$k;
9438 }
9439 $out .= ' '.$extgstate['n'].' 0 R';
9440 }
9441 $out .= ' >>';
9442 }
9443 if (isset($this->gradients) AND !empty($this->gradients)) {
9444 $gp = '';
9445 $gs = '';
9446 foreach ($this->gradients as $id => $grad) {
9447 // gradient patterns
9448 $gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
9449 // gradient shadings
9450 $gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
9451 }
9452 $out .= ' /Pattern <<'.$gp.' >>';
9453 $out .= ' /Shading <<'.$gs.' >>';
9454 }
9455 }
9456 // spot colors
9457 if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
9458 $out .= ' /ColorSpace <<';
9459 foreach ($this->spot_colors as $color) {
9460 $out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
9461 }
9462 $out .= ' >>';
9463 }
9464 $out .= ' >>';
9465 $out .= "\n".'endobj';
9466 $this->_out($out);
9467 }
9468
9469 /**
9470 * Output Resources.
9471 * @protected
9472 */
9473 protected function _putresources() {
9474 $this->_putextgstates();
9475 $this->_putocg();
9476 $this->_putfonts();
9477 $this->_putimages();
9478 $this->_putspotcolors();
9479 $this->_putshaders();
9480 $this->_putxobjects();
9481 $this->_putresourcedict();
9482 $this->_putdests();
9483 $this->_putEmbeddedFiles();
9484 $this->_putannotsobjs();
9485 $this->_putjavascript();
9486 $this->_putbookmarks();
9487 $this->_putencryption();
9488 }
9489
9490 /**
9491 * Adds some Metadata information (Document Information Dictionary)
9492 * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
9493 * @return int object id
9494 * @protected
9495 */
9496 protected function _putinfo() {
9497 $oid = $this->_newobj();
9498 $out = '<<';
9499 // store current isunicode value
9500 $prev_isunicode = $this->isunicode;
9501 if ($this->docinfounicode) {
9502 $this->isunicode = true;
9503 }
9504 if (!TCPDF_STATIC::empty_string($this->title)) {
9505 // The document's title.
9506 $out .= ' /Title '.$this->_textstring($this->title, $oid);
9507 }
9508 if (!TCPDF_STATIC::empty_string($this->author)) {
9509 // The name of the person who created the document.
9510 $out .= ' /Author '.$this->_textstring($this->author, $oid);
9511 }
9512 if (!TCPDF_STATIC::empty_string($this->subject)) {
9513 // The subject of the document.
9514 $out .= ' /Subject '.$this->_textstring($this->subject, $oid);
9515 }
9516 if (!TCPDF_STATIC::empty_string($this->keywords)) {
9517 // Keywords associated with the document.
9518 $out .= ' /Keywords '.$this->_textstring($this->keywords, $oid);
9519 }
9520 if (!TCPDF_STATIC::empty_string($this->creator)) {
9521 // 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.
9522 $out .= ' /Creator '.$this->_textstring($this->creator, $oid);
9523 }
9524 // restore previous isunicode value
9525 $this->isunicode = $prev_isunicode;
9526 // default producer
9527 $out .= ' /Producer '.$this->_textstring(TCPDF_STATIC::getTCPDFProducer(), $oid);
9528 // The date and time the document was created, in human-readable form
9529 $out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
9530 // The date and time the document was most recently modified, in human-readable form
9531 $out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
9532 // A name object indicating whether the document has been modified to include trapping information
9533 $out .= ' /Trapped /False';
9534 $out .= ' >>';
9535 $out .= "\n".'endobj';
9536 $this->_out($out);
9537 return $oid;
9538 }
9539
9540 /**
9541 * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
9542 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9543 * @param $xmp (string) Custom XMP data.
9544 * @since 5.9.128 (2011-10-06)
9545 * @public
9546 */
9547 public function setExtraXMP($xmp) {
9548 $this->custom_xmp = $xmp;
9549 }
9550
9551 /**
9552 * Set additional XMP data to be added on the default XMP data just before the end of "rdf:RDF" tag.
9553 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9554 * @param $xmp (string) Custom XMP RDF data.
9555 * @since 6.3.0 (2019-09-19)
9556 * @public
9557 */
9558 public function setExtraXMPRDF($xmp) {
9559 $this->custom_xmp_rdf = $xmp;
9560 }
9561
9562 /**
9563 * Put XMP data object and return ID.
9564 * @return (int) The object ID.
9565 * @since 5.9.121 (2011-09-28)
9566 * @protected
9567 */
9568 protected function _putXMP() {
9569 $oid = $this->_newobj();
9570 // store current isunicode value
9571 $prev_isunicode = $this->isunicode;
9572 $this->isunicode = true;
9573 $prev_encrypted = $this->encrypted;
9574 $this->encrypted = false;
9575 // set XMP data
9576 $xmp = '<?xpacket begin="'.TCPDF_FONTS::unichr(0xfeff, $this->isunicode).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
9577 $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";
9578 $xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
9579 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
9580 $xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
9581 $xmp .= "\t\t\t".'<dc:title>'."\n";
9582 $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9583 $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->title).'</rdf:li>'."\n";
9584 $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9585 $xmp .= "\t\t\t".'</dc:title>'."\n";
9586 $xmp .= "\t\t\t".'<dc:creator>'."\n";
9587 $xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
9588 $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->author).'</rdf:li>'."\n";
9589 $xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
9590 $xmp .= "\t\t\t".'</dc:creator>'."\n";
9591 $xmp .= "\t\t\t".'<dc:description>'."\n";
9592 $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9593 $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->subject).'</rdf:li>'."\n";
9594 $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9595 $xmp .= "\t\t\t".'</dc:description>'."\n";
9596 $xmp .= "\t\t\t".'<dc:subject>'."\n";
9597 $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9598 $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->keywords).'</rdf:li>'."\n";
9599 $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9600 $xmp .= "\t\t\t".'</dc:subject>'."\n";
9601 $xmp .= "\t\t".'</rdf:Description>'."\n";
9602 // convert doc creation date format
9603 $dcdate = TCPDF_STATIC::getFormattedDate($this->doc_creation_timestamp);
9604 $doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
9605 $doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
9606 $doccreationdate .= substr($dcdate, 14, 3).':'.substr($dcdate, 18, 2);
9607 $doccreationdate = TCPDF_STATIC::_escapeXML($doccreationdate);
9608 // convert doc modification date format
9609 $dmdate = TCPDF_STATIC::getFormattedDate($this->doc_modification_timestamp);
9610 $docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
9611 $docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
9612 $docmoddate .= substr($dmdate, 14, 3).':'.substr($dmdate, 18, 2);
9613 $docmoddate = TCPDF_STATIC::_escapeXML($docmoddate);
9614 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
9615 $xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
9616 $xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
9617 $xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
9618 $xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
9619 $xmp .= "\t\t".'</rdf:Description>'."\n";
9620 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
9621 $xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC::_escapeXML($this->keywords).'</pdf:Keywords>'."\n";
9622 $xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC::_escapeXML(TCPDF_STATIC::getTCPDFProducer()).'</pdf:Producer>'."\n";
9623 $xmp .= "\t\t".'</rdf:Description>'."\n";
9624 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
9625 $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);
9626 $xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
9627 $xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
9628 $xmp .= "\t\t".'</rdf:Description>'."\n";
9629 if ($this->pdfa_mode) {
9630 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
9631 $xmp .= "\t\t\t".'<pdfaid:part>'.$this->pdfa_version.'</pdfaid:part>'."\n";
9632 $xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
9633 $xmp .= "\t\t".'</rdf:Description>'."\n";
9634 }
9635 // XMP extension schemas
9636 $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";
9637 $xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
9638 $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9639 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9640 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
9641 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
9642 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
9643 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9644 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9645 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9646 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9647 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Adobe PDF Schema</pdfaProperty:description>'."\n";
9648 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9649 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9650 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9651 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9652 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9653 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9654 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9655 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
9656 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
9657 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
9658 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9659 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9660 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9661 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9662 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
9663 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9664 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9665 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9666 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9667 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9668 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9669 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9670 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
9671 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
9672 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
9673 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9674 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9675 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9676 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9677 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
9678 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
9679 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
9680 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9681 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9682 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9683 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
9684 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
9685 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9686 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9687 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9688 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9689 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
9690 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
9691 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9692 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9693 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9694 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9695 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9696 $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9697 $xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
9698 $xmp .= "\t\t".'</rdf:Description>'."\n";
9699 $xmp .= $this->custom_xmp_rdf;
9700 $xmp .= "\t".'</rdf:RDF>'."\n";
9701 $xmp .= $this->custom_xmp;
9702 $xmp .= '</x:xmpmeta>'."\n";
9703 $xmp .= '<?xpacket end="w"?>';
9704 $out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
9705 // restore previous isunicode value
9706 $this->isunicode = $prev_isunicode;
9707 $this->encrypted = $prev_encrypted;
9708 $this->_out($out);
9709 return $oid;
9710 }
9711
9712 /**
9713 * Output Catalog.
9714 * @return int object id
9715 * @protected
9716 */
9717 protected function _putcatalog() {
9718 // put XMP
9719 $xmpobj = $this->_putXMP();
9720 // if required, add standard sRGB ICC colour profile
9721 if ($this->pdfa_mode OR $this->force_srgb) {
9722 $iccobj = $this->_newobj();
9723 $icc = file_get_contents(dirname(__FILE__).'/include/sRGB.icc');
9724 $filter = '';
9725 if ($this->compress) {
9726 $filter = ' /Filter /FlateDecode';
9727 $icc = gzcompress($icc);
9728 }
9729 $icc = $this->_getrawstream($icc);
9730 $this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9731 }
9732 // start catalog
9733 $oid = $this->_newobj();
9734 $out = '<< /Type /Catalog';
9735 $out .= ' /Version /'.$this->PDFVersion;
9736 //$out .= ' /Extensions <<>>';
9737 $out .= ' /Pages 1 0 R';
9738 //$out .= ' /PageLabels ' //...;
9739 $out .= ' /Names <<';
9740 if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
9741 $out .= ' /JavaScript '.$this->n_js;
9742 }
9743 if (!empty($this->efnames)) {
9744 $out .= ' /EmbeddedFiles <</Names [';
9745 foreach ($this->efnames AS $fn => $fref) {
9746 $out .= ' '.$this->_datastring($fn).' '.$fref;
9747 }
9748 $out .= ' ]>>';
9749 }
9750 $out .= ' >>';
9751 if (!empty($this->dests)) {
9752 $out .= ' /Dests '.($this->n_dests).' 0 R';
9753 }
9754 $out .= $this->_putviewerpreferences();
9755 if (isset($this->LayoutMode) AND (!TCPDF_STATIC::empty_string($this->LayoutMode))) {
9756 $out .= ' /PageLayout /'.$this->LayoutMode;
9757 }
9758 if (isset($this->PageMode) AND (!TCPDF_STATIC::empty_string($this->PageMode))) {
9759 $out .= ' /PageMode /'.$this->PageMode;
9760 }
9761 if (count($this->outlines) > 0) {
9762 $out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
9763 $out .= ' /PageMode /UseOutlines';
9764 }
9765 //$out .= ' /Threads []';
9766 if ($this->ZoomMode == 'fullpage') {
9767 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
9768 } elseif ($this->ZoomMode == 'fullwidth') {
9769 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
9770 } elseif ($this->ZoomMode == 'real') {
9771 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
9772 } elseif (!is_string($this->ZoomMode)) {
9773 $out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
9774 }
9775 //$out .= ' /AA <<>>';
9776 //$out .= ' /URI <<>>';
9777 $out .= ' /Metadata '.$xmpobj.' 0 R';
9778 //$out .= ' /StructTreeRoot <<>>';
9779 //$out .= ' /MarkInfo <<>>';
9780 if (isset($this->l['a_meta_language'])) {
9781 $out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
9782 }
9783 //$out .= ' /SpiderInfo <<>>';
9784 // set OutputIntent to sRGB IEC61966-2.1 if required
9785 if ($this->pdfa_mode OR $this->force_srgb) {
9786 $out .= ' /OutputIntents [<<';
9787 $out .= ' /Type /OutputIntent';
9788 $out .= ' /S /GTS_PDFA1';
9789 $out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9790 $out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9791 $out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
9792 $out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9793 $out .= ' /DestOutputProfile '.$iccobj.' 0 R';
9794 $out .= ' >>]';
9795 }
9796 //$out .= ' /PieceInfo <<>>';
9797 if (!empty($this->pdflayers)) {
9798 $lyrobjs = '';
9799 $lyrobjs_off = '';
9800 $lyrobjs_lock = '';
9801 foreach ($this->pdflayers as $layer) {
9802 $layer_obj_ref = ' '.$layer['objid'].' 0 R';
9803 $lyrobjs .= $layer_obj_ref;
9804 if ($layer['view'] === false) {
9805 $lyrobjs_off .= $layer_obj_ref;
9806 }
9807 if ($layer['lock']) {
9808 $lyrobjs_lock .= $layer_obj_ref;
9809 }
9810 }
9811 $out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
9812 $out .= ' /D <<';
9813 $out .= ' /Name '.$this->_textstring('Layers', $oid);
9814 $out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
9815 $out .= ' /BaseState /ON';
9816 $out .= ' /OFF ['.$lyrobjs_off.']';
9817 $out .= ' /Locked ['.$lyrobjs_lock.']';
9818 $out .= ' /Intent /View';
9819 $out .= ' /AS [';
9820 $out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
9821 $out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
9822 $out .= ' ]';
9823 $out .= ' /Order ['.$lyrobjs.']';
9824 $out .= ' /ListMode /AllPages';
9825 //$out .= ' /RBGroups ['..']';
9826 //$out .= ' /Locked ['..']';
9827 $out .= ' >>';
9828 $out .= ' >>';
9829 }
9830 // AcroForm
9831 if (!empty($this->form_obj_id)
9832 OR ($this->sign AND isset($this->signature_data['cert_type']))
9833 OR !empty($this->empty_signature_appearance)) {
9834 $out .= ' /AcroForm <<';
9835 $objrefs = '';
9836 if ($this->sign AND isset($this->signature_data['cert_type'])) {
9837 // set reference for signature object
9838 $objrefs .= $this->sig_obj_id.' 0 R';
9839 }
9840 if (!empty($this->empty_signature_appearance)) {
9841 foreach ($this->empty_signature_appearance as $esa) {
9842 // set reference for empty signature objects
9843 $objrefs .= ' '.$esa['objid'].' 0 R';
9844 }
9845 }
9846 if (!empty($this->form_obj_id)) {
9847 foreach($this->form_obj_id as $objid) {
9848 $objrefs .= ' '.$objid.' 0 R';
9849 }
9850 }
9851 $out .= ' /Fields ['.$objrefs.']';
9852 // It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
9853 if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
9854 $out .= ' /NeedAppearances false';
9855 }
9856 if ($this->sign AND isset($this->signature_data['cert_type'])) {
9857 if ($this->signature_data['cert_type'] > 0) {
9858 $out .= ' /SigFlags 3';
9859 } else {
9860 $out .= ' /SigFlags 1';
9861 }
9862 }
9863 //$out .= ' /CO ';
9864 if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
9865 $out .= ' /DR <<';
9866 $out .= ' /Font <<';
9867 foreach ($this->annotation_fonts as $fontkey => $fontid) {
9868 $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9869 }
9870 $out .= ' >> >>';
9871 }
9872 $font = $this->getFontBuffer('helvetica');
9873 $out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
9874 $out .= ' /Q '.(($this->rtl)?'2':'0');
9875 //$out .= ' /XFA ';
9876 $out .= ' >>';
9877 // signatures
9878 if ($this->sign AND isset($this->signature_data['cert_type'])
9879 AND (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A'))) {
9880 if ($this->signature_data['cert_type'] > 0) {
9881 $out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
9882 } else {
9883 $out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
9884 }
9885 }
9886 }
9887 //$out .= ' /Legal <<>>';
9888 //$out .= ' /Requirements []';
9889 //$out .= ' /Collection <<>>';
9890 //$out .= ' /NeedsRendering true';
9891 $out .= ' >>';
9892 $out .= "\n".'endobj';
9893 $this->_out($out);
9894 return $oid;
9895 }
9896
9897 /**
9898 * Output viewer preferences.
9899 * @return string for viewer preferences
9900 * @author Nicola asuni
9901 * @since 3.1.000 (2008-06-09)
9902 * @protected
9903 */
9904 protected function _putviewerpreferences() {
9905 $vp = $this->viewer_preferences;
9906 $out = ' /ViewerPreferences <<';
9907 if ($this->rtl) {
9908 $out .= ' /Direction /R2L';
9909 } else {
9910 $out .= ' /Direction /L2R';
9911 }
9912 if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
9913 $out .= ' /HideToolbar true';
9914 }
9915 if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
9916 $out .= ' /HideMenubar true';
9917 }
9918 if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
9919 $out .= ' /HideWindowUI true';
9920 }
9921 if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
9922 $out .= ' /FitWindow true';
9923 }
9924 if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
9925 $out .= ' /CenterWindow true';
9926 }
9927 if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
9928 $out .= ' /DisplayDocTitle true';
9929 }
9930 if (isset($vp['NonFullScreenPageMode'])) {
9931 $out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
9932 }
9933 if (isset($vp['ViewArea'])) {
9934 $out .= ' /ViewArea /'.$vp['ViewArea'];
9935 }
9936 if (isset($vp['ViewClip'])) {
9937 $out .= ' /ViewClip /'.$vp['ViewClip'];
9938 }
9939 if (isset($vp['PrintArea'])) {
9940 $out .= ' /PrintArea /'.$vp['PrintArea'];
9941 }
9942 if (isset($vp['PrintClip'])) {
9943 $out .= ' /PrintClip /'.$vp['PrintClip'];
9944 }
9945 if (isset($vp['PrintScaling'])) {
9946 $out .= ' /PrintScaling /'.$vp['PrintScaling'];
9947 }
9948 if (isset($vp['Duplex']) AND (!TCPDF_STATIC::empty_string($vp['Duplex']))) {
9949 $out .= ' /Duplex /'.$vp['Duplex'];
9950 }
9951 if (isset($vp['PickTrayByPDFSize'])) {
9952 if ($vp['PickTrayByPDFSize']) {
9953 $out .= ' /PickTrayByPDFSize true';
9954 } else {
9955 $out .= ' /PickTrayByPDFSize false';
9956 }
9957 }
9958 if (isset($vp['PrintPageRange'])) {
9959 $PrintPageRangeNum = '';
9960 foreach ($vp['PrintPageRange'] as $k => $v) {
9961 $PrintPageRangeNum .= ' '.($v - 1).'';
9962 }
9963 $out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
9964 }
9965 if (isset($vp['NumCopies'])) {
9966 $out .= ' /NumCopies '.intval($vp['NumCopies']);
9967 }
9968 $out .= ' >>';
9969 return $out;
9970 }
9971
9972 /**
9973 * Output PDF File Header (7.5.2).
9974 * @protected
9975 */
9976 protected function _putheader() {
9977 $this->_out('%PDF-'.$this->PDFVersion);
9978 $this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
9979 }
9980
9981 /**
9982 * Output end of document (EOF).
9983 * @protected
9984 */
9985 protected function _enddoc() {
9986 if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
9987 // save subset chars of the previous font
9988 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
9989 }
9990 $this->state = 1;
9991 $this->_putheader();
9992 $this->_putpages();
9993 $this->_putresources();
9994 // empty signature fields
9995 if (!empty($this->empty_signature_appearance)) {
9996 foreach ($this->empty_signature_appearance as $key => $esa) {
9997 // widget annotation for empty signature
9998 $out = $this->_getobj($esa['objid'])."\n";
9999 $out .= '<< /Type /Annot';
10000 $out .= ' /Subtype /Widget';
10001 $out .= ' /Rect ['.$esa['rect'].']';
10002 $out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
10003 $out .= ' /F 4';
10004 $out .= ' /FT /Sig';
10005 $signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
10006 $out .= ' /T '.$this->_textstring($signame, $esa['objid']);
10007 $out .= ' /Ff 0';
10008 $out .= ' >>';
10009 $out .= "\n".'endobj';
10010 $this->_out($out);
10011 }
10012 }
10013 // Signature
10014 if ($this->sign AND isset($this->signature_data['cert_type'])) {
10015 // widget annotation for signature
10016 $out = $this->_getobj($this->sig_obj_id)."\n";
10017 $out .= '<< /Type /Annot';
10018 $out .= ' /Subtype /Widget';
10019 $out .= ' /Rect ['.$this->signature_appearance['rect'].']';
10020 $out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
10021 $out .= ' /F 4';
10022 $out .= ' /FT /Sig';
10023 $out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
10024 $out .= ' /Ff 0';
10025 $out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
10026 $out .= ' >>';
10027 $out .= "\n".'endobj';
10028 $this->_out($out);
10029 // signature
10030 $this->_putsignature();
10031 }
10032 // Info
10033 $objid_info = $this->_putinfo();
10034 // Catalog
10035 $objid_catalog = $this->_putcatalog();
10036 // Cross-ref
10037 $o = $this->bufferlen;
10038 // XREF section
10039 $this->_out('xref');
10040 $this->_out('0 '.($this->n + 1));
10041 $this->_out('0000000000 65535 f ');
10042 $freegen = ($this->n + 2);
10043 for ($i=1; $i <= $this->n; ++$i) {
10044 if (!isset($this->offsets[$i]) AND ($i > 1)) {
10045 $this->_out(sprintf('0000000000 %05d f ', $freegen));
10046 ++$freegen;
10047 } else {
10048 $this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
10049 }
10050 }
10051 // TRAILER
10052 $out = 'trailer'."\n";
10053 $out .= '<<';
10054 $out .= ' /Size '.($this->n + 1);
10055 $out .= ' /Root '.$objid_catalog.' 0 R';
10056 $out .= ' /Info '.$objid_info.' 0 R';
10057 if ($this->encrypted) {
10058 $out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
10059 }
10060 $out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
10061 $out .= ' >>';
10062 $this->_out($out);
10063 $this->_out('startxref');
10064 $this->_out($o);
10065 $this->_out('%%EOF');
10066 $this->state = 3; // end-of-doc
10067 }
10068
10069 /**
10070 * Initialize a new page.
10071 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
10072 * @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().
10073 * @protected
10074 * @see getPageSizeFromFormat(), setPageFormat()
10075 */
10076 protected function _beginpage($orientation='', $format='') {
10077 ++$this->page;
10078 $this->pageobjects[$this->page] = array();
10079 $this->setPageBuffer($this->page, '');
10080 // initialize array for graphics tranformation positions inside a page buffer
10081 $this->transfmrk[$this->page] = array();
10082 $this->state = 2;
10083 if (TCPDF_STATIC::empty_string($orientation)) {
10084 if (isset($this->CurOrientation)) {
10085 $orientation = $this->CurOrientation;
10086 } elseif ($this->fwPt > $this->fhPt) {
10087 // landscape
10088 $orientation = 'L';
10089 } else {
10090 // portrait
10091 $orientation = 'P';
10092 }
10093 }
10094 if (TCPDF_STATIC::empty_string($format)) {
10095 $this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10096 $this->setPageOrientation($orientation);
10097 } else {
10098 $this->setPageFormat($format, $orientation);
10099 }
10100 if ($this->rtl) {
10101 $this->x = $this->w - $this->rMargin;
10102 } else {
10103 $this->x = $this->lMargin;
10104 }
10105 $this->y = $this->tMargin;
10106 if (isset($this->newpagegroup[$this->page])) {
10107 // start a new group
10108 $this->currpagegroup = $this->newpagegroup[$this->page];
10109 $this->pagegroups[$this->currpagegroup] = 1;
10110 } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
10111 ++$this->pagegroups[$this->currpagegroup];
10112 }
10113 }
10114
10115 /**
10116 * Mark end of page.
10117 * @protected
10118 */
10119 protected function _endpage() {
10120 $this->setVisibility('all');
10121 $this->state = 1;
10122 }
10123
10124 /**
10125 * Begin a new object and return the object number.
10126 * @return int object number
10127 * @protected
10128 */
10129 protected function _newobj() {
10130 $this->_out($this->_getobj());
10131 return $this->n;
10132 }
10133
10134 /**
10135 * Return the starting object string for the selected object ID.
10136 * @param $objid (int) Object ID (leave empty to get a new ID).
10137 * @return string the starting object string
10138 * @protected
10139 * @since 5.8.009 (2010-08-20)
10140 */
10141 protected function _getobj($objid='') {
10142 if ($objid === '') {
10143 ++$this->n;
10144 $objid = $this->n;
10145 }
10146 $this->offsets[$objid] = $this->bufferlen;
10147 $this->pageobjects[$this->page][] = $objid;
10148 return $objid.' 0 obj';
10149 }
10150
10151 /**
10152 * Underline text.
10153 * @param $x (int) X coordinate
10154 * @param $y (int) Y coordinate
10155 * @param $txt (string) text to underline
10156 * @protected
10157 */
10158 protected function _dounderline($x, $y, $txt) {
10159 $w = $this->GetStringWidth($txt);
10160 return $this->_dounderlinew($x, $y, $w);
10161 }
10162
10163 /**
10164 * Underline for rectangular text area.
10165 * @param $x (int) X coordinate
10166 * @param $y (int) Y coordinate
10167 * @param $w (int) width to underline
10168 * @protected
10169 * @since 4.8.008 (2009-09-29)
10170 */
10171 protected function _dounderlinew($x, $y, $w) {
10172 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10173 return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
10174 }
10175
10176 /**
10177 * Line through text.
10178 * @param $x (int) X coordinate
10179 * @param $y (int) Y coordinate
10180 * @param $txt (string) text to linethrough
10181 * @protected
10182 */
10183 protected function _dolinethrough($x, $y, $txt) {
10184 $w = $this->GetStringWidth($txt);
10185 return $this->_dolinethroughw($x, $y, $w);
10186 }
10187
10188 /**
10189 * Line through for rectangular text area.
10190 * @param $x (int) X coordinate
10191 * @param $y (int) Y coordinate
10192 * @param $w (int) line length (width)
10193 * @protected
10194 * @since 4.9.008 (2009-09-29)
10195 */
10196 protected function _dolinethroughw($x, $y, $w) {
10197 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10198 return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
10199 }
10200
10201 /**
10202 * Overline text.
10203 * @param $x (int) X coordinate
10204 * @param $y (int) Y coordinate
10205 * @param $txt (string) text to overline
10206 * @protected
10207 * @since 4.9.015 (2010-04-19)
10208 */
10209 protected function _dooverline($x, $y, $txt) {
10210 $w = $this->GetStringWidth($txt);
10211 return $this->_dooverlinew($x, $y, $w);
10212 }
10213
10214 /**
10215 * Overline for rectangular text area.
10216 * @param $x (int) X coordinate
10217 * @param $y (int) Y coordinate
10218 * @param $w (int) width to overline
10219 * @protected
10220 * @since 4.9.015 (2010-04-19)
10221 */
10222 protected function _dooverlinew($x, $y, $w) {
10223 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10224 return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
10225
10226 }
10227
10228 /**
10229 * Format a data string for meta information
10230 * @param $s (string) data string to escape.
10231 * @param $n (int) object ID
10232 * @return string escaped string.
10233 * @protected
10234 */
10235 protected function _datastring($s, $n=0) {
10236 if ($n == 0) {
10237 $n = $this->n;
10238 }
10239 $s = $this->_encrypt_data($n, $s);
10240 return '('. TCPDF_STATIC::_escape($s).')';
10241 }
10242
10243 /**
10244 * Set the document creation timestamp
10245 * @param $time (mixed) Document creation timestamp in seconds or date-time string.
10246 * @public
10247 * @since 5.9.152 (2012-03-23)
10248 */
10249 public function setDocCreationTimestamp($time) {
10250 if (is_string($time)) {
10251 $time = TCPDF_STATIC::getTimestamp($time);
10252 }
10253 $this->doc_creation_timestamp = intval($time);
10254 }
10255
10256 /**
10257 * Set the document modification timestamp
10258 * @param $time (mixed) Document modification timestamp in seconds or date-time string.
10259 * @public
10260 * @since 5.9.152 (2012-03-23)
10261 */
10262 public function setDocModificationTimestamp($time) {
10263 if (is_string($time)) {
10264 $time = TCPDF_STATIC::getTimestamp($time);
10265 }
10266 $this->doc_modification_timestamp = intval($time);
10267 }
10268
10269 /**
10270 * Returns document creation timestamp in seconds.
10271 * @return (int) Creation timestamp in seconds.
10272 * @public
10273 * @since 5.9.152 (2012-03-23)
10274 */
10275 public function getDocCreationTimestamp() {
10276 return $this->doc_creation_timestamp;
10277 }
10278
10279 /**
10280 * Returns document modification timestamp in seconds.
10281 * @return (int) Modfication timestamp in seconds.
10282 * @public
10283 * @since 5.9.152 (2012-03-23)
10284 */
10285 public function getDocModificationTimestamp() {
10286 return $this->doc_modification_timestamp;
10287 }
10288
10289 /**
10290 * Returns a formatted date for meta information
10291 * @param $n (int) Object ID.
10292 * @param $timestamp (int) Timestamp to convert.
10293 * @return string escaped date string.
10294 * @protected
10295 * @since 4.6.028 (2009-08-25)
10296 */
10297 protected function _datestring($n=0, $timestamp=0) {
10298 if ((empty($timestamp)) OR ($timestamp < 0)) {
10299 $timestamp = $this->doc_creation_timestamp;
10300 }
10301 return $this->_datastring('D:'.TCPDF_STATIC::getFormattedDate($timestamp), $n);
10302 }
10303
10304 /**
10305 * Format a text string for meta information
10306 * @param $s (string) string to escape.
10307 * @param $n (int) object ID
10308 * @return string escaped string.
10309 * @protected
10310 */
10311 protected function _textstring($s, $n=0) {
10312 if ($this->isunicode) {
10313 //Convert string to UTF-16BE
10314 $s = TCPDF_FONTS::UTF8ToUTF16BE($s, true, $this->isunicode, $this->CurrentFont);
10315 }
10316 return $this->_datastring($s, $n);
10317 }
10318
10319 /**
10320 * get raw output stream.
10321 * @param $s (string) string to output.
10322 * @param $n (int) object reference for encryption mode
10323 * @protected
10324 * @author Nicola Asuni
10325 * @since 5.5.000 (2010-06-22)
10326 */
10327 protected function _getrawstream($s, $n=0) {
10328 if ($n <= 0) {
10329 // default to current object
10330 $n = $this->n;
10331 }
10332 return $this->_encrypt_data($n, $s);
10333 }
10334
10335 /**
10336 * Output a string to the document.
10337 * @param $s (string) string to output.
10338 * @protected
10339 */
10340 protected function _out($s) {
10341 if ($this->state == 2) {
10342 if ($this->inxobj) {
10343 // we are inside an XObject template
10344 $this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
10345 } elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
10346 // puts data before page footer
10347 $pagebuff = $this->getPageBuffer($this->page);
10348 $page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
10349 $footer = substr($pagebuff, -$this->footerlen[$this->page]);
10350 $this->setPageBuffer($this->page, $page.$s."\n".$footer);
10351 // update footer position
10352 $this->footerpos[$this->page] += strlen($s."\n");
10353 } else {
10354 // set page data
10355 $this->setPageBuffer($this->page, $s."\n", true);
10356 }
10357 } elseif ($this->state > 0) {
10358 // set general data
10359 $this->setBuffer($s."\n");
10360 }
10361 }
10362
10363 /**
10364 * Set header font.
10365 * @param $font (array) Array describing the basic font parameters: (family, style, size).
10366 * @public
10367 * @since 1.1
10368 */
10369 public function setHeaderFont($font) {
10370 $this->header_font = $font;
10371 }
10372
10373 /**
10374 * Get header font.
10375 * @return array() Array describing the basic font parameters: (family, style, size).
10376 * @public
10377 * @since 4.0.012 (2008-07-24)
10378 */
10379 public function getHeaderFont() {
10380 return $this->header_font;
10381 }
10382
10383 /**
10384 * Set footer font.
10385 * @param $font (array) Array describing the basic font parameters: (family, style, size).
10386 * @public
10387 * @since 1.1
10388 */
10389 public function setFooterFont($font) {
10390 $this->footer_font = $font;
10391 }
10392
10393 /**
10394 * Get Footer font.
10395 * @return array() Array describing the basic font parameters: (family, style, size).
10396 * @public
10397 * @since 4.0.012 (2008-07-24)
10398 */
10399 public function getFooterFont() {
10400 return $this->footer_font;
10401 }
10402
10403 /**
10404 * Set language array.
10405 * @param $language (array)
10406 * @public
10407 * @since 1.1
10408 */
10409 public function setLanguageArray($language) {
10410 $this->l = $language;
10411 if (isset($this->l['a_meta_dir'])) {
10412 $this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
10413 } else {
10414 $this->rtl = false;
10415 }
10416 }
10417
10418 /**
10419 * Returns the PDF data.
10420 * @public
10421 */
10422 public function getPDFData() {
10423 if ($this->state < 3) {
10424 $this->Close();
10425 }
10426 return $this->buffer;
10427 }
10428
10429 /**
10430 * Output anchor link.
10431 * @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;)
10432 * @param $name (string) link name
10433 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
10434 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
10435 * @param $color (array) array of RGB text color
10436 * @param $style (string) font style (U, D, B, I)
10437 * @param $firstblock (boolean) if true the string is the starting of a line.
10438 * @return the number of cells used or the remaining text if $firstline = true;
10439 * @public
10440 */
10441 public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
10442 if (isset($url[1]) AND ($url[0] == '#') AND is_numeric($url[1])) {
10443 // convert url to internal link
10444 $lnkdata = explode(',', $url);
10445 if (isset($lnkdata[0]) ) {
10446 $page = substr($lnkdata[0], 1);
10447 if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
10448 $lnky = floatval($lnkdata[1]);
10449 } else {
10450 $lnky = 0;
10451 }
10452 $url = $this->AddLink();
10453 $this->SetLink($url, $lnky, $page);
10454 }
10455 }
10456 // store current settings
10457 $prevcolor = $this->fgcolor;
10458 $prevstyle = $this->FontStyle;
10459 if (empty($color)) {
10460 $this->SetTextColorArray($this->htmlLinkColorArray);
10461 } else {
10462 $this->SetTextColorArray($color);
10463 }
10464 if ($style == -1) {
10465 $this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
10466 } else {
10467 $this->SetFont('', $this->FontStyle.$style);
10468 }
10469 $ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
10470 // restore settings
10471 $this->SetFont('', $prevstyle);
10472 $this->SetTextColorArray($prevcolor);
10473 return $ret;
10474 }
10475
10476 /**
10477 * Converts pixels to User's Units.
10478 * @param $px (int) pixels
10479 * @return float value in user's unit
10480 * @public
10481 * @see setImageScale(), getImageScale()
10482 */
10483 public function pixelsToUnits($px) {
10484 return ($px / ($this->imgscale * $this->k));
10485 }
10486
10487 /**
10488 * Reverse function for htmlentities.
10489 * Convert entities in UTF-8.
10490 * @param $text_to_convert (string) Text to convert.
10491 * @return string converted text string
10492 * @public
10493 */
10494 public function unhtmlentities($text_to_convert) {
10495 return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
10496 }
10497
10498 // ENCRYPTION METHODS ----------------------------------
10499
10500 /**
10501 * Compute encryption key depending on object number where the encrypted data is stored.
10502 * This is used for all strings and streams without crypt filter specifier.
10503 * @param $n (int) object number
10504 * @return int object key
10505 * @protected
10506 * @author Nicola Asuni
10507 * @since 2.0.000 (2008-01-02)
10508 */
10509 protected function _objectkey($n) {
10510 $objkey = $this->encryptdata['key'].pack('VXxx', $n);
10511 if ($this->encryptdata['mode'] == 2) { // AES-128
10512 // AES padding
10513 $objkey .= "\x73\x41\x6C\x54"; // sAlT
10514 }
10515 $objkey = substr(TCPDF_STATIC::_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
10516 $objkey = substr($objkey, 0, 16);
10517 return $objkey;
10518 }
10519
10520 /**
10521 * Encrypt the input string.
10522 * @param $n (int) object number
10523 * @param $s (string) data string to encrypt
10524 * @return encrypted string
10525 * @protected
10526 * @author Nicola Asuni
10527 * @since 5.0.005 (2010-05-11)
10528 */
10529 protected function _encrypt_data($n, $s) {
10530 if (!$this->encrypted) {
10531 return $s;
10532 }
10533 switch ($this->encryptdata['mode']) {
10534 case 0: // RC4-40
10535 case 1: { // RC4-128
10536 $s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
10537 break;
10538 }
10539 case 2: { // AES-128
10540 $s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
10541 break;
10542 }
10543 case 3: { // AES-256
10544 $s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
10545 break;
10546 }
10547 }
10548 return $s;
10549 }
10550
10551 /**
10552 * Put encryption on PDF document.
10553 * @protected
10554 * @author Nicola Asuni
10555 * @since 2.0.000 (2008-01-02)
10556 */
10557 protected function _putencryption() {
10558 if (!$this->encrypted) {
10559 return;
10560 }
10561 $this->encryptdata['objid'] = $this->_newobj();
10562 $out = '<<';
10563 if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
10564 $this->encryptdata['Filter'] = 'Standard';
10565 }
10566 $out .= ' /Filter /'.$this->encryptdata['Filter'];
10567 if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
10568 $out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
10569 }
10570 if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
10571 $this->encryptdata['V'] = 1;
10572 }
10573 // V is a code specifying the algorithm to be used in encrypting and decrypting the document
10574 $out .= ' /V '.$this->encryptdata['V'];
10575 if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
10576 // The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
10577 $out .= ' /Length '.$this->encryptdata['Length'];
10578 } else {
10579 $out .= ' /Length 40';
10580 }
10581 if ($this->encryptdata['V'] >= 4) {
10582 if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
10583 $this->encryptdata['StmF'] = 'Identity';
10584 }
10585 if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
10586 // The name of the crypt filter that shall be used when decrypting all strings in the document.
10587 $this->encryptdata['StrF'] = 'Identity';
10588 }
10589 // A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
10590 if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
10591 $out .= ' /CF <<';
10592 $out .= ' /'.$this->encryptdata['StmF'].' <<';
10593 $out .= ' /Type /CryptFilter';
10594 if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
10595 // The method used
10596 $out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
10597 if ($this->encryptdata['pubkey']) {
10598 $out .= ' /Recipients [';
10599 foreach ($this->encryptdata['Recipients'] as $rec) {
10600 $out .= ' <'.$rec.'>';
10601 }
10602 $out .= ' ]';
10603 if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
10604 $out .= ' /EncryptMetadata false';
10605 } else {
10606 $out .= ' /EncryptMetadata true';
10607 }
10608 }
10609 } else {
10610 $out .= ' /CFM /None';
10611 }
10612 if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
10613 // The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
10614 $out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
10615 } else {
10616 $out .= ' /AuthEvent /DocOpen';
10617 }
10618 if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
10619 // The bit length of the encryption key.
10620 $out .= ' /Length '.$this->encryptdata['CF']['Length'];
10621 }
10622 $out .= ' >> >>';
10623 }
10624 // The name of the crypt filter that shall be used by default when decrypting streams.
10625 $out .= ' /StmF /'.$this->encryptdata['StmF'];
10626 // The name of the crypt filter that shall be used when decrypting all strings in the document.
10627 $out .= ' /StrF /'.$this->encryptdata['StrF'];
10628 if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
10629 // The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
10630 $out .= ' /EFF /'.$this->encryptdata[''];
10631 }
10632 }
10633 // Additional encryption dictionary entries for the standard security handler
10634 if ($this->encryptdata['pubkey']) {
10635 if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
10636 $out .= ' /Recipients [';
10637 foreach ($this->encryptdata['Recipients'] as $rec) {
10638 $out .= ' <'.$rec.'>';
10639 }
10640 $out .= ' ]';
10641 }
10642 } else {
10643 $out .= ' /R';
10644 if ($this->encryptdata['V'] == 5) { // AES-256
10645 $out .= ' 5';
10646 $out .= ' /OE ('.TCPDF_STATIC::_escape($this->encryptdata['OE']).')';
10647 $out .= ' /UE ('.TCPDF_STATIC::_escape($this->encryptdata['UE']).')';
10648 $out .= ' /Perms ('.TCPDF_STATIC::_escape($this->encryptdata['perms']).')';
10649 } elseif ($this->encryptdata['V'] == 4) { // AES-128
10650 $out .= ' 4';
10651 } elseif ($this->encryptdata['V'] < 2) { // RC-40
10652 $out .= ' 2';
10653 } else { // RC-128
10654 $out .= ' 3';
10655 }
10656 $out .= ' /O ('.TCPDF_STATIC::_escape($this->encryptdata['O']).')';
10657 $out .= ' /U ('.TCPDF_STATIC::_escape($this->encryptdata['U']).')';
10658 $out .= ' /P '.$this->encryptdata['P'];
10659 if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
10660 $out .= ' /EncryptMetadata false';
10661 } else {
10662 $out .= ' /EncryptMetadata true';
10663 }
10664 }
10665 $out .= ' >>';
10666 $out .= "\n".'endobj';
10667 $this->_out($out);
10668 }
10669
10670 /**
10671 * Compute U value (used for encryption)
10672 * @return string U value
10673 * @protected
10674 * @since 2.0.000 (2008-01-02)
10675 * @author Nicola Asuni
10676 */
10677 protected function _Uvalue() {
10678 if ($this->encryptdata['mode'] == 0) { // RC4-40
10679 return TCPDF_STATIC::_RC4($this->encryptdata['key'], TCPDF_STATIC::$enc_padding, $this->last_enc_key, $this->last_enc_key_c);
10680 } elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
10681 $tmp = TCPDF_STATIC::_md5_16(TCPDF_STATIC::$enc_padding.$this->encryptdata['fileid']);
10682 $enc = TCPDF_STATIC::_RC4($this->encryptdata['key'], $tmp, $this->last_enc_key, $this->last_enc_key_c);
10683 $len = strlen($tmp);
10684 for ($i = 1; $i <= 19; ++$i) {
10685 $ek = '';
10686 for ($j = 0; $j < $len; ++$j) {
10687 $ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
10688 }
10689 $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10690 }
10691 $enc .= str_repeat("\x00", 16);
10692 return substr($enc, 0, 32);
10693 } elseif ($this->encryptdata['mode'] == 3) { // AES-256
10694 $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10695 // User Validation Salt
10696 $this->encryptdata['UVS'] = substr($seed, 0, 8);
10697 // User Key Salt
10698 $this->encryptdata['UKS'] = substr($seed, 8, 16);
10699 return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
10700 }
10701 }
10702
10703 /**
10704 * Compute UE value (used for encryption)
10705 * @return string UE value
10706 * @protected
10707 * @since 5.9.006 (2010-10-19)
10708 * @author Nicola Asuni
10709 */
10710 protected function _UEvalue() {
10711 $hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
10712 return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10713 }
10714
10715 /**
10716 * Compute O value (used for encryption)
10717 * @return string O value
10718 * @protected
10719 * @since 2.0.000 (2008-01-02)
10720 * @author Nicola Asuni
10721 */
10722 protected function _Ovalue() {
10723 if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
10724 $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['owner_password']);
10725 if ($this->encryptdata['mode'] > 0) {
10726 for ($i = 0; $i < 50; ++$i) {
10727 $tmp = TCPDF_STATIC::_md5_16($tmp);
10728 }
10729 }
10730 $owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
10731 $enc = TCPDF_STATIC::_RC4($owner_key, $this->encryptdata['user_password'], $this->last_enc_key, $this->last_enc_key_c);
10732 if ($this->encryptdata['mode'] > 0) {
10733 $len = strlen($owner_key);
10734 for ($i = 1; $i <= 19; ++$i) {
10735 $ek = '';
10736 for ($j = 0; $j < $len; ++$j) {
10737 $ek .= chr(ord($owner_key[$j]) ^ $i);
10738 }
10739 $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10740 }
10741 }
10742 return $enc;
10743 } elseif ($this->encryptdata['mode'] == 3) { // AES-256
10744 $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10745 // Owner Validation Salt
10746 $this->encryptdata['OVS'] = substr($seed, 0, 8);
10747 // Owner Key Salt
10748 $this->encryptdata['OKS'] = substr($seed, 8, 16);
10749 return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
10750 }
10751 }
10752
10753 /**
10754 * Compute OE value (used for encryption)
10755 * @return string OE value
10756 * @protected
10757 * @since 5.9.006 (2010-10-19)
10758 * @author Nicola Asuni
10759 */
10760 protected function _OEvalue() {
10761 $hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
10762 return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10763 }
10764
10765 /**
10766 * Convert password for AES-256 encryption mode
10767 * @param $password (string) password
10768 * @return string password
10769 * @protected
10770 * @since 5.9.006 (2010-10-19)
10771 * @author Nicola Asuni
10772 */
10773 protected function _fixAES256Password($password) {
10774 $psw = ''; // password to be returned
10775 $psw_array = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($password, $this->isunicode, $this->CurrentFont), $password, $this->rtl, $this->isunicode, $this->CurrentFont);
10776 foreach ($psw_array as $c) {
10777 $psw .= TCPDF_FONTS::unichr($c, $this->isunicode);
10778 }
10779 return substr($psw, 0, 127);
10780 }
10781
10782 /**
10783 * Compute encryption key
10784 * @protected
10785 * @since 2.0.000 (2008-01-02)
10786 * @author Nicola Asuni
10787 */
10788 protected function _generateencryptionkey() {
10789 $keybytelen = ($this->encryptdata['Length'] / 8);
10790 if (!$this->encryptdata['pubkey']) { // standard mode
10791 if ($this->encryptdata['mode'] == 3) { // AES-256
10792 // generate 256 bit random key
10793 $this->encryptdata['key'] = substr(hash('sha256', TCPDF_STATIC::getRandomSeed(), true), 0, $keybytelen);
10794 // truncate passwords
10795 $this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
10796 $this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
10797 // Compute U value
10798 $this->encryptdata['U'] = $this->_Uvalue();
10799 // Compute UE value
10800 $this->encryptdata['UE'] = $this->_UEvalue();
10801 // Compute O value
10802 $this->encryptdata['O'] = $this->_Ovalue();
10803 // Compute OE value
10804 $this->encryptdata['OE'] = $this->_OEvalue();
10805 // Compute P value
10806 $this->encryptdata['P'] = $this->encryptdata['protection'];
10807 // Computing the encryption dictionary's Perms (permissions) value
10808 $perms = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
10809 $perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
10810 if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
10811 $perms .= 'F';
10812 } else {
10813 $perms .= 'T';
10814 }
10815 $perms .= 'adb'; // bytes 9-11
10816 $perms .= 'nick'; // bytes 12-15
10817 $this->encryptdata['perms'] = TCPDF_STATIC::_AESnopad($this->encryptdata['key'], $perms);
10818 } else { // RC4-40, RC4-128, AES-128
10819 // Pad passwords
10820 $this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10821 $this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10822 // Compute O value
10823 $this->encryptdata['O'] = $this->_Ovalue();
10824 // get default permissions (reverse byte order)
10825 $permissions = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']);
10826 // Compute encryption key
10827 $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
10828 if ($this->encryptdata['mode'] > 0) {
10829 for ($i = 0; $i < 50; ++$i) {
10830 $tmp = TCPDF_STATIC::_md5_16(substr($tmp, 0, $keybytelen));
10831 }
10832 }
10833 $this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
10834 // Compute U value
10835 $this->encryptdata['U'] = $this->_Uvalue();
10836 // Compute P value
10837 $this->encryptdata['P'] = $this->encryptdata['protection'];
10838 }
10839 } else { // Public-Key mode
10840 // random 20-byte seed
10841 $seed = sha1(TCPDF_STATIC::getRandomSeed(), true);
10842 $recipient_bytes = '';
10843 foreach ($this->encryptdata['pubkeys'] as $pubkey) {
10844 // for each public certificate
10845 if (isset($pubkey['p'])) {
10846 $pkprotection = TCPDF_STATIC::getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
10847 } else {
10848 $pkprotection = $this->encryptdata['protection'];
10849 }
10850 // get default permissions (reverse byte order)
10851 $pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
10852 // envelope data
10853 $envelope = $seed.$pkpermissions;
10854 // write the envelope data to a temporary file
10855 $tempkeyfile = TCPDF_STATIC::getObjFilename('key', $this->file_id);
10856 $f = TCPDF_STATIC::fopenLocal($tempkeyfile, 'wb');
10857 if (!$f) {
10858 $this->Error('Unable to create temporary key file: '.$tempkeyfile);
10859 }
10860 $envelope_length = strlen($envelope);
10861 fwrite($f, $envelope, $envelope_length);
10862 fclose($f);
10863 $tempencfile = TCPDF_STATIC::getObjFilename('enc', $this->file_id);
10864 if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
10865 $this->Error('Unable to encrypt the file: '.$tempkeyfile);
10866 }
10867 // read encryption signature
10868 $signature = file_get_contents($tempencfile, false, null, $envelope_length);
10869 // extract signature
10870 $signature = substr($signature, strpos($signature, 'Content-Disposition'));
10871 $tmparr = explode("\n\n", $signature);
10872 $signature = trim($tmparr[1]);
10873 unset($tmparr);
10874 // decode signature
10875 $signature = base64_decode($signature);
10876 // convert signature to hex
10877 $hexsignature = current(unpack('H*', $signature));
10878 // store signature on recipients array
10879 $this->encryptdata['Recipients'][] = $hexsignature;
10880 // The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
10881 $recipient_bytes .= $signature;
10882 }
10883 // calculate encryption key
10884 if ($this->encryptdata['mode'] == 3) { // AES-256
10885 $this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
10886 } else { // RC4-40, RC4-128, AES-128
10887 $this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
10888 }
10889 }
10890 }
10891
10892 /**
10893 * Set document protection
10894 * Remark: the protection against modification is for people who have the full Acrobat product.
10895 * 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.
10896 * 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.
10897 * @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>
10898 * @param $user_pass (String) user password. Empty by default.
10899 * @param $owner_pass (String) owner password. If not specified, a random value is used.
10900 * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
10901 * @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')))
10902 * @public
10903 * @since 2.0.000 (2008-01-02)
10904 * @author Nicola Asuni
10905 */
10906 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) {
10907 if ($this->pdfa_mode) {
10908 // encryption is not allowed in PDF/A mode
10909 return;
10910 }
10911 $this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
10912 if (($pubkeys !== null) AND (is_array($pubkeys))) {
10913 // public-key mode
10914 $this->encryptdata['pubkeys'] = $pubkeys;
10915 if ($mode == 0) {
10916 // public-Key Security requires at least 128 bit
10917 $mode = 1;
10918 }
10919 if (!function_exists('openssl_pkcs7_encrypt')) {
10920 $this->Error('Public-Key Security requires openssl library.');
10921 }
10922 // Set Public-Key filter (available are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
10923 $this->encryptdata['pubkey'] = true;
10924 $this->encryptdata['Filter'] = 'Adobe.PubSec';
10925 $this->encryptdata['StmF'] = 'DefaultCryptFilter';
10926 $this->encryptdata['StrF'] = 'DefaultCryptFilter';
10927 } else {
10928 // standard mode (password mode)
10929 $this->encryptdata['pubkey'] = false;
10930 $this->encryptdata['Filter'] = 'Standard';
10931 $this->encryptdata['StmF'] = 'StdCF';
10932 $this->encryptdata['StrF'] = 'StdCF';
10933 }
10934 if ($mode > 1) { // AES
10935 if (!extension_loaded('openssl') && !extension_loaded('mcrypt')) {
10936 $this->Error('AES encryption requires openssl or mcrypt extension (http://www.php.net/manual/en/mcrypt.requirements.php).');
10937 }
10938 if (extension_loaded('openssl') && !in_array('aes-256-cbc', openssl_get_cipher_methods())) {
10939 $this->Error('AES encryption requires openssl/aes-256-cbc cypher.');
10940 }
10941 if (($mode == 3) AND !function_exists('hash')) {
10942 // the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
10943 $this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
10944 }
10945 }
10946 if ($owner_pass === null) {
10947 $owner_pass = md5(TCPDF_STATIC::getRandomSeed());
10948 }
10949 $this->encryptdata['user_password'] = $user_pass;
10950 $this->encryptdata['owner_password'] = $owner_pass;
10951 $this->encryptdata['mode'] = $mode;
10952 switch ($mode) {
10953 case 0: { // RC4 40 bit
10954 $this->encryptdata['V'] = 1;
10955 $this->encryptdata['Length'] = 40;
10956 $this->encryptdata['CF']['CFM'] = 'V2';
10957 break;
10958 }
10959 case 1: { // RC4 128 bit
10960 $this->encryptdata['V'] = 2;
10961 $this->encryptdata['Length'] = 128;
10962 $this->encryptdata['CF']['CFM'] = 'V2';
10963 if ($this->encryptdata['pubkey']) {
10964 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
10965 $this->encryptdata['Recipients'] = array();
10966 }
10967 break;
10968 }
10969 case 2: { // AES 128 bit
10970 $this->encryptdata['V'] = 4;
10971 $this->encryptdata['Length'] = 128;
10972 $this->encryptdata['CF']['CFM'] = 'AESV2';
10973 $this->encryptdata['CF']['Length'] = 128;
10974 if ($this->encryptdata['pubkey']) {
10975 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
10976 $this->encryptdata['Recipients'] = array();
10977 }
10978 break;
10979 }
10980 case 3: { // AES 256 bit
10981 $this->encryptdata['V'] = 5;
10982 $this->encryptdata['Length'] = 256;
10983 $this->encryptdata['CF']['CFM'] = 'AESV3';
10984 $this->encryptdata['CF']['Length'] = 256;
10985 if ($this->encryptdata['pubkey']) {
10986 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
10987 $this->encryptdata['Recipients'] = array();
10988 }
10989 break;
10990 }
10991 }
10992 $this->encrypted = true;
10993 $this->encryptdata['fileid'] = TCPDF_STATIC::convertHexStringToString($this->file_id);
10994 $this->_generateencryptionkey();
10995 }
10996
10997 // END OF ENCRYPTION FUNCTIONS -------------------------
10998
10999 // START TRANSFORMATIONS SECTION -----------------------
11000
11001 /**
11002 * Starts a 2D tranformation saving current graphic state.
11003 * This function must be called before scaling, mirroring, translation, rotation and skewing.
11004 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11005 * @public
11006 * @since 2.1.000 (2008-01-07)
11007 * @see StartTransform(), StopTransform()
11008 */
11009 public function StartTransform() {
11010 if ($this->state != 2) {
11011 return;
11012 }
11013 $this->_outSaveGraphicsState();
11014 if ($this->inxobj) {
11015 // we are inside an XObject template
11016 $this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
11017 } else {
11018 $this->transfmrk[$this->page][] = $this->pagelen[$this->page];
11019 }
11020 ++$this->transfmatrix_key;
11021 $this->transfmatrix[$this->transfmatrix_key] = array();
11022 }
11023
11024 /**
11025 * Stops a 2D tranformation restoring previous graphic state.
11026 * This function must be called after scaling, mirroring, translation, rotation and skewing.
11027 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11028 * @public
11029 * @since 2.1.000 (2008-01-07)
11030 * @see StartTransform(), StopTransform()
11031 */
11032 public function StopTransform() {
11033 if ($this->state != 2) {
11034 return;
11035 }
11036 $this->_outRestoreGraphicsState();
11037 if (isset($this->transfmatrix[$this->transfmatrix_key])) {
11038 array_pop($this->transfmatrix[$this->transfmatrix_key]);
11039 --$this->transfmatrix_key;
11040 }
11041 if ($this->inxobj) {
11042 // we are inside an XObject template
11043 array_pop($this->xobjects[$this->xobjid]['transfmrk']);
11044 } else {
11045 array_pop($this->transfmrk[$this->page]);
11046 }
11047 }
11048 /**
11049 * Horizontal Scaling.
11050 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
11051 * @param $x (int) abscissa of the scaling center. Default is current x position
11052 * @param $y (int) ordinate of the scaling center. Default is current y position
11053 * @public
11054 * @since 2.1.000 (2008-01-07)
11055 * @see StartTransform(), StopTransform()
11056 */
11057 public function ScaleX($s_x, $x='', $y='') {
11058 $this->Scale($s_x, 100, $x, $y);
11059 }
11060
11061 /**
11062 * Vertical Scaling.
11063 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
11064 * @param $x (int) abscissa of the scaling center. Default is current x position
11065 * @param $y (int) ordinate of the scaling center. Default is current y position
11066 * @public
11067 * @since 2.1.000 (2008-01-07)
11068 * @see StartTransform(), StopTransform()
11069 */
11070 public function ScaleY($s_y, $x='', $y='') {
11071 $this->Scale(100, $s_y, $x, $y);
11072 }
11073
11074 /**
11075 * Vertical and horizontal proportional Scaling.
11076 * @param $s (float) scaling factor for width and height as percent. 0 is not allowed.
11077 * @param $x (int) abscissa of the scaling center. Default is current x position
11078 * @param $y (int) ordinate of the scaling center. Default is current y position
11079 * @public
11080 * @since 2.1.000 (2008-01-07)
11081 * @see StartTransform(), StopTransform()
11082 */
11083 public function ScaleXY($s, $x='', $y='') {
11084 $this->Scale($s, $s, $x, $y);
11085 }
11086
11087 /**
11088 * Vertical and horizontal non-proportional Scaling.
11089 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
11090 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
11091 * @param $x (int) abscissa of the scaling center. Default is current x position
11092 * @param $y (int) ordinate of the scaling center. Default is current y position
11093 * @public
11094 * @since 2.1.000 (2008-01-07)
11095 * @see StartTransform(), StopTransform()
11096 */
11097 public function Scale($s_x, $s_y, $x='', $y='') {
11098 if ($x === '') {
11099 $x = $this->x;
11100 }
11101 if ($y === '') {
11102 $y = $this->y;
11103 }
11104 if (($s_x == 0) OR ($s_y == 0)) {
11105 $this->Error('Please do not use values equal to zero for scaling');
11106 }
11107 $y = ($this->h - $y) * $this->k;
11108 $x *= $this->k;
11109 //calculate elements of transformation matrix
11110 $s_x /= 100;
11111 $s_y /= 100;
11112 $tm = array();
11113 $tm[0] = $s_x;
11114 $tm[1] = 0;
11115 $tm[2] = 0;
11116 $tm[3] = $s_y;
11117 $tm[4] = $x * (1 - $s_x);
11118 $tm[5] = $y * (1 - $s_y);
11119 //scale the coordinate system
11120 $this->Transform($tm);
11121 }
11122
11123 /**
11124 * Horizontal Mirroring.
11125 * @param $x (int) abscissa of the point. Default is current x position
11126 * @public
11127 * @since 2.1.000 (2008-01-07)
11128 * @see StartTransform(), StopTransform()
11129 */
11130 public function MirrorH($x='') {
11131 $this->Scale(-100, 100, $x);
11132 }
11133
11134 /**
11135 * Verical Mirroring.
11136 * @param $y (int) ordinate of the point. Default is current y position
11137 * @public
11138 * @since 2.1.000 (2008-01-07)
11139 * @see StartTransform(), StopTransform()
11140 */
11141 public function MirrorV($y='') {
11142 $this->Scale(100, -100, '', $y);
11143 }
11144
11145 /**
11146 * Point reflection mirroring.
11147 * @param $x (int) abscissa of the point. Default is current x position
11148 * @param $y (int) ordinate of the point. Default is current y position
11149 * @public
11150 * @since 2.1.000 (2008-01-07)
11151 * @see StartTransform(), StopTransform()
11152 */
11153 public function MirrorP($x='',$y='') {
11154 $this->Scale(-100, -100, $x, $y);
11155 }
11156
11157 /**
11158 * Reflection against a straight line through point (x, y) with the gradient angle (angle).
11159 * @param $angle (float) gradient angle of the straight line. Default is 0 (horizontal line).
11160 * @param $x (int) abscissa of the point. Default is current x position
11161 * @param $y (int) ordinate of the point. Default is current y position
11162 * @public
11163 * @since 2.1.000 (2008-01-07)
11164 * @see StartTransform(), StopTransform()
11165 */
11166 public function MirrorL($angle=0, $x='',$y='') {
11167 $this->Scale(-100, 100, $x, $y);
11168 $this->Rotate(-2*($angle-90), $x, $y);
11169 }
11170
11171 /**
11172 * Translate graphic object horizontally.
11173 * @param $t_x (int) movement to the right (or left for RTL)
11174 * @public
11175 * @since 2.1.000 (2008-01-07)
11176 * @see StartTransform(), StopTransform()
11177 */
11178 public function TranslateX($t_x) {
11179 $this->Translate($t_x, 0);
11180 }
11181
11182 /**
11183 * Translate graphic object vertically.
11184 * @param $t_y (int) movement to the bottom
11185 * @public
11186 * @since 2.1.000 (2008-01-07)
11187 * @see StartTransform(), StopTransform()
11188 */
11189 public function TranslateY($t_y) {
11190 $this->Translate(0, $t_y);
11191 }
11192
11193 /**
11194 * Translate graphic object horizontally and vertically.
11195 * @param $t_x (int) movement to the right
11196 * @param $t_y (int) movement to the bottom
11197 * @public
11198 * @since 2.1.000 (2008-01-07)
11199 * @see StartTransform(), StopTransform()
11200 */
11201 public function Translate($t_x, $t_y) {
11202 //calculate elements of transformation matrix
11203 $tm = array();
11204 $tm[0] = 1;
11205 $tm[1] = 0;
11206 $tm[2] = 0;
11207 $tm[3] = 1;
11208 $tm[4] = $t_x * $this->k;
11209 $tm[5] = -$t_y * $this->k;
11210 //translate the coordinate system
11211 $this->Transform($tm);
11212 }
11213
11214 /**
11215 * Rotate object.
11216 * @param $angle (float) angle in degrees for counter-clockwise rotation
11217 * @param $x (int) abscissa of the rotation center. Default is current x position
11218 * @param $y (int) ordinate of the rotation center. Default is current y position
11219 * @public
11220 * @since 2.1.000 (2008-01-07)
11221 * @see StartTransform(), StopTransform()
11222 */
11223 public function Rotate($angle, $x='', $y='') {
11224 if ($x === '') {
11225 $x = $this->x;
11226 }
11227 if ($y === '') {
11228 $y = $this->y;
11229 }
11230 $y = ($this->h - $y) * $this->k;
11231 $x *= $this->k;
11232 //calculate elements of transformation matrix
11233 $tm = array();
11234 $tm[0] = cos(deg2rad($angle));
11235 $tm[1] = sin(deg2rad($angle));
11236 $tm[2] = -$tm[1];
11237 $tm[3] = $tm[0];
11238 $tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
11239 $tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
11240 //rotate the coordinate system around ($x,$y)
11241 $this->Transform($tm);
11242 }
11243
11244 /**
11245 * Skew horizontally.
11246 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11247 * @param $x (int) abscissa of the skewing center. default is current x position
11248 * @param $y (int) ordinate of the skewing center. default is current y position
11249 * @public
11250 * @since 2.1.000 (2008-01-07)
11251 * @see StartTransform(), StopTransform()
11252 */
11253 public function SkewX($angle_x, $x='', $y='') {
11254 $this->Skew($angle_x, 0, $x, $y);
11255 }
11256
11257 /**
11258 * Skew vertically.
11259 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11260 * @param $x (int) abscissa of the skewing center. default is current x position
11261 * @param $y (int) ordinate of the skewing center. default is current y position
11262 * @public
11263 * @since 2.1.000 (2008-01-07)
11264 * @see StartTransform(), StopTransform()
11265 */
11266 public function SkewY($angle_y, $x='', $y='') {
11267 $this->Skew(0, $angle_y, $x, $y);
11268 }
11269
11270 /**
11271 * Skew.
11272 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11273 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11274 * @param $x (int) abscissa of the skewing center. default is current x position
11275 * @param $y (int) ordinate of the skewing center. default is current y position
11276 * @public
11277 * @since 2.1.000 (2008-01-07)
11278 * @see StartTransform(), StopTransform()
11279 */
11280 public function Skew($angle_x, $angle_y, $x='', $y='') {
11281 if ($x === '') {
11282 $x = $this->x;
11283 }
11284 if ($y === '') {
11285 $y = $this->y;
11286 }
11287 if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
11288 $this->Error('Please use values between -90 and +90 degrees for Skewing.');
11289 }
11290 $x *= $this->k;
11291 $y = ($this->h - $y) * $this->k;
11292 //calculate elements of transformation matrix
11293 $tm = array();
11294 $tm[0] = 1;
11295 $tm[1] = tan(deg2rad($angle_y));
11296 $tm[2] = tan(deg2rad($angle_x));
11297 $tm[3] = 1;
11298 $tm[4] = -$tm[2] * $y;
11299 $tm[5] = -$tm[1] * $x;
11300 //skew the coordinate system
11301 $this->Transform($tm);
11302 }
11303
11304 /**
11305 * Apply graphic transformations.
11306 * @param $tm (array) transformation matrix
11307 * @protected
11308 * @since 2.1.000 (2008-01-07)
11309 * @see StartTransform(), StopTransform()
11310 */
11311 protected function Transform($tm) {
11312 if ($this->state != 2) {
11313 return;
11314 }
11315 $this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
11316 // add tranformation matrix
11317 $this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
11318 // update transformation mark
11319 if ($this->inxobj) {
11320 // we are inside an XObject template
11321 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
11322 $key = key($this->xobjects[$this->xobjid]['transfmrk']);
11323 $this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
11324 }
11325 } elseif (end($this->transfmrk[$this->page]) !== false) {
11326 $key = key($this->transfmrk[$this->page]);
11327 $this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
11328 }
11329 }
11330
11331 // END TRANSFORMATIONS SECTION -------------------------
11332
11333 // START GRAPHIC FUNCTIONS SECTION ---------------------
11334 // The following section is based on the code provided by David Hernandez Sanz
11335
11336 /**
11337 * 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.
11338 * @param $width (float) The width.
11339 * @public
11340 * @since 1.0
11341 * @see Line(), Rect(), Cell(), MultiCell()
11342 */
11343 public function SetLineWidth($width) {
11344 //Set line width
11345 $this->LineWidth = $width;
11346 $this->linestyleWidth = sprintf('%F w', ($width * $this->k));
11347 if ($this->state == 2) {
11348 $this->_out($this->linestyleWidth);
11349 }
11350 }
11351
11352 /**
11353 * Returns the current the line width.
11354 * @return int Line width
11355 * @public
11356 * @since 2.1.000 (2008-01-07)
11357 * @see Line(), SetLineWidth()
11358 */
11359 public function GetLineWidth() {
11360 return $this->LineWidth;
11361 }
11362
11363 /**
11364 * Set line style.
11365 * @param $style (array) Line style. Array with keys among the following:
11366 * <ul>
11367 * <li>width (float): Width of the line in user units.</li>
11368 * <li>cap (string): Type of cap to put on the line. Possible values are:
11369 * butt, round, square. The difference between "square" and "butt" is that
11370 * "square" projects a flat end past the end of the line.</li>
11371 * <li>join (string): Type of join. Possible values are: miter, round,
11372 * bevel.</li>
11373 * <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
11374 * series of length values, which are the lengths of the on and off dashes.
11375 * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
11376 * 1 off, 2 on, 1 off, ...</li>
11377 * <li>phase (integer): Modifier on the dash pattern which is used to shift
11378 * the point at which the pattern starts.</li>
11379 * <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>
11380 * </ul>
11381 * @param $ret (boolean) if true do not send the command.
11382 * @return string the PDF command
11383 * @public
11384 * @since 2.1.000 (2008-01-08)
11385 */
11386 public function SetLineStyle($style, $ret=false) {
11387 $s = ''; // string to be returned
11388 if (!is_array($style)) {
11389 return;
11390 }
11391 if (isset($style['width'])) {
11392 $this->LineWidth = $style['width'];
11393 $this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
11394 $s .= $this->linestyleWidth.' ';
11395 }
11396 if (isset($style['cap'])) {
11397 $ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
11398 if (isset($ca[$style['cap']])) {
11399 $this->linestyleCap = $ca[$style['cap']].' J';
11400 $s .= $this->linestyleCap.' ';
11401 }
11402 }
11403 if (isset($style['join'])) {
11404 $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
11405 if (isset($ja[$style['join']])) {
11406 $this->linestyleJoin = $ja[$style['join']].' j';
11407 $s .= $this->linestyleJoin.' ';
11408 }
11409 }
11410 if (isset($style['dash'])) {
11411 $dash_string = '';
11412 if ($style['dash']) {
11413 if (preg_match('/^.+,/', $style['dash']) > 0) {
11414 $tab = explode(',', $style['dash']);
11415 } else {
11416 $tab = array($style['dash']);
11417 }
11418 $dash_string = '';
11419 foreach ($tab as $i => $v) {
11420 if ($i) {
11421 $dash_string .= ' ';
11422 }
11423 $dash_string .= sprintf('%F', $v);
11424 }
11425 }
11426 if (!isset($style['phase']) OR !$style['dash']) {
11427 $style['phase'] = 0;
11428 }
11429 $this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
11430 $s .= $this->linestyleDash.' ';
11431 }
11432 if (isset($style['color'])) {
11433 $s .= $this->SetDrawColorArray($style['color'], true).' ';
11434 }
11435 if (!$ret AND ($this->state == 2)) {
11436 $this->_out($s);
11437 }
11438 return $s;
11439 }
11440
11441 /**
11442 * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
11443 * @param $x (float) Abscissa of point.
11444 * @param $y (float) Ordinate of point.
11445 * @protected
11446 * @since 2.1.000 (2008-01-08)
11447 */
11448 protected function _outPoint($x, $y) {
11449 if ($this->state == 2) {
11450 $this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
11451 }
11452 }
11453
11454 /**
11455 * Append a straight line segment from the current point to the point (x, y).
11456 * The new current point shall be (x, y).
11457 * @param $x (float) Abscissa of end point.
11458 * @param $y (float) Ordinate of end point.
11459 * @protected
11460 * @since 2.1.000 (2008-01-08)
11461 */
11462 protected function _outLine($x, $y) {
11463 if ($this->state == 2) {
11464 $this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
11465 }
11466 }
11467
11468 /**
11469 * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
11470 * @param $x (float) Abscissa of upper-left corner.
11471 * @param $y (float) Ordinate of upper-left corner.
11472 * @param $w (float) Width.
11473 * @param $h (float) Height.
11474 * @param $op (string) options
11475 * @protected
11476 * @since 2.1.000 (2008-01-08)
11477 */
11478 protected function _outRect($x, $y, $w, $h, $op) {
11479 if ($this->state == 2) {
11480 $this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k), (($this->h - $y) * $this->k), ($w * $this->k), (-$h * $this->k), $op));
11481 }
11482 }
11483
11484 /**
11485 * 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.
11486 * The new current point shall be (x3, y3).
11487 * @param $x1 (float) Abscissa of control point 1.
11488 * @param $y1 (float) Ordinate of control point 1.
11489 * @param $x2 (float) Abscissa of control point 2.
11490 * @param $y2 (float) Ordinate of control point 2.
11491 * @param $x3 (float) Abscissa of end point.
11492 * @param $y3 (float) Ordinate of end point.
11493 * @protected
11494 * @since 2.1.000 (2008-01-08)
11495 */
11496 protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
11497 if ($this->state == 2) {
11498 $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)));
11499 }
11500 }
11501
11502 /**
11503 * 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.
11504 * The new current point shall be (x3, y3).
11505 * @param $x2 (float) Abscissa of control point 2.
11506 * @param $y2 (float) Ordinate of control point 2.
11507 * @param $x3 (float) Abscissa of end point.
11508 * @param $y3 (float) Ordinate of end point.
11509 * @protected
11510 * @since 4.9.019 (2010-04-26)
11511 */
11512 protected function _outCurveV($x2, $y2, $x3, $y3) {
11513 if ($this->state == 2) {
11514 $this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11515 }
11516 }
11517
11518 /**
11519 * 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.
11520 * The new current point shall be (x3, y3).
11521 * @param $x1 (float) Abscissa of control point 1.
11522 * @param $y1 (float) Ordinate of control point 1.
11523 * @param $x3 (float) Abscissa of end point.
11524 * @param $y3 (float) Ordinate of end point.
11525 * @protected
11526 * @since 2.1.000 (2008-01-08)
11527 */
11528 protected function _outCurveY($x1, $y1, $x3, $y3) {
11529 if ($this->state == 2) {
11530 $this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11531 }
11532 }
11533
11534 /**
11535 * Draws a line between two points.
11536 * @param $x1 (float) Abscissa of first point.
11537 * @param $y1 (float) Ordinate of first point.
11538 * @param $x2 (float) Abscissa of second point.
11539 * @param $y2 (float) Ordinate of second point.
11540 * @param $style (array) Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
11541 * @public
11542 * @since 1.0
11543 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
11544 */
11545 public function Line($x1, $y1, $x2, $y2, $style=array()) {
11546 if ($this->state != 2) {
11547 return;
11548 }
11549 if (is_array($style)) {
11550 $this->SetLineStyle($style);
11551 }
11552 $this->_outPoint($x1, $y1);
11553 $this->_outLine($x2, $y2);
11554 $this->_out('S');
11555 }
11556
11557 /**
11558 * Draws a rectangle.
11559 * @param $x (float) Abscissa of upper-left corner.
11560 * @param $y (float) Ordinate of upper-left corner.
11561 * @param $w (float) Width.
11562 * @param $h (float) Height.
11563 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11564 * @param $border_style (array) Border style of rectangle. Array with keys among the following:
11565 * <ul>
11566 * <li>all: Line style of all borders. Array like for SetLineStyle().</li>
11567 * <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
11568 * </ul>
11569 * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
11570 * @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).
11571 * @public
11572 * @since 1.0
11573 * @see SetLineStyle()
11574 */
11575 public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
11576 if ($this->state != 2) {
11577 return;
11578 }
11579 if (empty($style)) {
11580 $style = 'S';
11581 }
11582 if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
11583 // set background color
11584 $this->SetFillColorArray($fill_color);
11585 }
11586 if (!empty($border_style)) {
11587 if (isset($border_style['all']) AND !empty($border_style['all'])) {
11588 //set global style for border
11589 $this->SetLineStyle($border_style['all']);
11590 $border_style = array();
11591 } else {
11592 // remove stroke operator from style
11593 $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*' );
11594 if (isset($opnostroke[$style])) {
11595 $style = $opnostroke[$style];
11596 }
11597 }
11598 }
11599 if (!empty($style)) {
11600 $op = TCPDF_STATIC::getPathPaintOperator($style);
11601 $this->_outRect($x, $y, $w, $h, $op);
11602 }
11603 if (!empty($border_style)) {
11604 $border_style2 = array();
11605 foreach ($border_style as $line => $value) {
11606 $length = strlen($line);
11607 for ($i = 0; $i < $length; ++$i) {
11608 $border_style2[$line[$i]] = $value;
11609 }
11610 }
11611 $border_style = $border_style2;
11612 if (isset($border_style['L']) AND $border_style['L']) {
11613 $this->Line($x, $y, $x, $y + $h, $border_style['L']);
11614 }
11615 if (isset($border_style['T']) AND $border_style['T']) {
11616 $this->Line($x, $y, $x + $w, $y, $border_style['T']);
11617 }
11618 if (isset($border_style['R']) AND $border_style['R']) {
11619 $this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
11620 }
11621 if (isset($border_style['B']) AND $border_style['B']) {
11622 $this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
11623 }
11624 }
11625 }
11626
11627 /**
11628 * Draws a Bezier curve.
11629 * The Bezier curve is a tangent to the line between the control points at
11630 * either end of the curve.
11631 * @param $x0 (float) Abscissa of start point.
11632 * @param $y0 (float) Ordinate of start point.
11633 * @param $x1 (float) Abscissa of control point 1.
11634 * @param $y1 (float) Ordinate of control point 1.
11635 * @param $x2 (float) Abscissa of control point 2.
11636 * @param $y2 (float) Ordinate of control point 2.
11637 * @param $x3 (float) Abscissa of end point.
11638 * @param $y3 (float) Ordinate of end point.
11639 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11640 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11641 * @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).
11642 * @public
11643 * @see SetLineStyle()
11644 * @since 2.1.000 (2008-01-08)
11645 */
11646 public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
11647 if ($this->state != 2) {
11648 return;
11649 }
11650 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11651 $this->SetFillColorArray($fill_color);
11652 }
11653 $op = TCPDF_STATIC::getPathPaintOperator($style);
11654 if ($line_style) {
11655 $this->SetLineStyle($line_style);
11656 }
11657 $this->_outPoint($x0, $y0);
11658 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11659 $this->_out($op);
11660 }
11661
11662 /**
11663 * Draws a poly-Bezier curve.
11664 * Each Bezier curve segment is a tangent to the line between the control points at
11665 * either end of the curve.
11666 * @param $x0 (float) Abscissa of start point.
11667 * @param $y0 (float) Ordinate of start point.
11668 * @param $segments (float) An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
11669 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11670 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11671 * @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).
11672 * @public
11673 * @see SetLineStyle()
11674 * @since 3.0008 (2008-05-12)
11675 */
11676 public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
11677 if ($this->state != 2) {
11678 return;
11679 }
11680 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11681 $this->SetFillColorArray($fill_color);
11682 }
11683 $op = TCPDF_STATIC::getPathPaintOperator($style);
11684 if ($op == 'f') {
11685 $line_style = array();
11686 }
11687 if ($line_style) {
11688 $this->SetLineStyle($line_style);
11689 }
11690 $this->_outPoint($x0, $y0);
11691 foreach ($segments as $segment) {
11692 list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
11693 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11694 }
11695 $this->_out($op);
11696 }
11697
11698 /**
11699 * Draws an ellipse.
11700 * An ellipse is formed from n Bezier curves.
11701 * @param $x0 (float) Abscissa of center point.
11702 * @param $y0 (float) Ordinate of center point.
11703 * @param $rx (float) Horizontal radius.
11704 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11705 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
11706 * @param $astart: (float) Angle start of draw line. Default value: 0.
11707 * @param $afinish: (float) Angle finish of draw line. Default value: 360.
11708 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11709 * @param $line_style (array) Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
11710 * @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).
11711 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11712 * @author Nicola Asuni
11713 * @public
11714 * @since 2.1.000 (2008-01-08)
11715 */
11716 public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11717 if ($this->state != 2) {
11718 return;
11719 }
11720 if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
11721 $ry = $rx;
11722 }
11723 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11724 $this->SetFillColorArray($fill_color);
11725 }
11726 $op = TCPDF_STATIC::getPathPaintOperator($style);
11727 if ($op == 'f') {
11728 $line_style = array();
11729 }
11730 if ($line_style) {
11731 $this->SetLineStyle($line_style);
11732 }
11733 $this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
11734 $this->_out($op);
11735 }
11736
11737 /**
11738 * Append an elliptical arc to the current path.
11739 * An ellipse is formed from n Bezier curves.
11740 * @param $xc (float) Abscissa of center point.
11741 * @param $yc (float) Ordinate of center point.
11742 * @param $rx (float) Horizontal radius.
11743 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11744 * @param $xang: (float) Angle between the X-axis and the major axis of the ellipse. Default value: 0.
11745 * @param $angs: (float) Angle start of draw line. Default value: 0.
11746 * @param $angf: (float) Angle finish of draw line. Default value: 360.
11747 * @param $pie (boolean) if true do not mark the border point (used to draw pie sectors).
11748 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11749 * @param $startpoint (boolean) if true output a starting point.
11750 * @param $ccw (boolean) if true draws in counter-clockwise.
11751 * @param $svg (boolean) if true the angles are in svg mode (already calculated).
11752 * @return array bounding box coordinates (x min, y min, x max, y max)
11753 * @author Nicola Asuni
11754 * @protected
11755 * @since 4.9.019 (2010-04-26)
11756 */
11757 protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
11758 if (($rx <= 0) OR ($ry < 0)) {
11759 return;
11760 }
11761 $k = $this->k;
11762 if ($nc < 2) {
11763 $nc = 2;
11764 }
11765 $xmin = 2147483647;
11766 $ymin = 2147483647;
11767 $xmax = 0;
11768 $ymax = 0;
11769 if ($pie) {
11770 // center of the arc
11771 $this->_outPoint($xc, $yc);
11772 }
11773 $xang = deg2rad((float) $xang);
11774 $angs = deg2rad((float) $angs);
11775 $angf = deg2rad((float) $angf);
11776 if ($svg) {
11777 $as = $angs;
11778 $af = $angf;
11779 } else {
11780 $as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
11781 $af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
11782 }
11783 if ($as < 0) {
11784 $as += (2 * M_PI);
11785 }
11786 if ($af < 0) {
11787 $af += (2 * M_PI);
11788 }
11789 if ($ccw AND ($as > $af)) {
11790 // reverse rotation
11791 $as -= (2 * M_PI);
11792 } elseif (!$ccw AND ($as < $af)) {
11793 // reverse rotation
11794 $af -= (2 * M_PI);
11795 }
11796 $total_angle = ($af - $as);
11797 if ($nc < 2) {
11798 $nc = 2;
11799 }
11800 // total arcs to draw
11801 $nc *= (2 * abs($total_angle) / M_PI);
11802 $nc = round($nc) + 1;
11803 // angle of each arc
11804 $arcang = ($total_angle / $nc);
11805 // center point in PDF coordinates
11806 $x0 = $xc;
11807 $y0 = ($this->h - $yc);
11808 // starting angle
11809 $ang = $as;
11810 $alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
11811 $cos_xang = cos($xang);
11812 $sin_xang = sin($xang);
11813 $cos_ang = cos($ang);
11814 $sin_ang = sin($ang);
11815 // first arc point
11816 $px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11817 $py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11818 // first Bezier control point
11819 $qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11820 $qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11821 if ($pie) {
11822 // line from center to arc starting point
11823 $this->_outLine($px1, $this->h - $py1);
11824 } elseif ($startpoint) {
11825 // arc starting point
11826 $this->_outPoint($px1, $this->h - $py1);
11827 }
11828 // draw arcs
11829 for ($i = 1; $i <= $nc; ++$i) {
11830 // starting angle
11831 $ang = $as + ($i * $arcang);
11832 if ($i == $nc) {
11833 $ang = $af;
11834 }
11835 $cos_ang = cos($ang);
11836 $sin_ang = sin($ang);
11837 // second arc point
11838 $px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11839 $py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11840 // second Bezier control point
11841 $qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11842 $qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11843 // draw arc
11844 $cx1 = ($px1 + $qx1);
11845 $cy1 = ($this->h - ($py1 + $qy1));
11846 $cx2 = ($px2 - $qx2);
11847 $cy2 = ($this->h - ($py2 - $qy2));
11848 $cx3 = $px2;
11849 $cy3 = ($this->h - $py2);
11850 $this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
11851 // get bounding box coordinates
11852 $xmin = min($xmin, $cx1, $cx2, $cx3);
11853 $ymin = min($ymin, $cy1, $cy2, $cy3);
11854 $xmax = max($xmax, $cx1, $cx2, $cx3);
11855 $ymax = max($ymax, $cy1, $cy2, $cy3);
11856 // move to next point
11857 $px1 = $px2;
11858 $py1 = $py2;
11859 $qx1 = $qx2;
11860 $qy1 = $qy2;
11861 }
11862 if ($pie) {
11863 $this->_outLine($xc, $yc);
11864 // get bounding box coordinates
11865 $xmin = min($xmin, $xc);
11866 $ymin = min($ymin, $yc);
11867 $xmax = max($xmax, $xc);
11868 $ymax = max($ymax, $yc);
11869 }
11870 return array($xmin, $ymin, $xmax, $ymax);
11871 }
11872
11873 /**
11874 * Draws a circle.
11875 * A circle is formed from n Bezier curves.
11876 * @param $x0 (float) Abscissa of center point.
11877 * @param $y0 (float) Ordinate of center point.
11878 * @param $r (float) Radius.
11879 * @param $angstr: (float) Angle start of draw line. Default value: 0.
11880 * @param $angend: (float) Angle finish of draw line. Default value: 360.
11881 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11882 * @param $line_style (array) Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
11883 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11884 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of circle.
11885 * @public
11886 * @since 2.1.000 (2008-01-08)
11887 */
11888 public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11889 $this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
11890 }
11891
11892 /**
11893 * Draws a polygonal line
11894 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11895 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11896 * @param $line_style (array) Line style of polygon. Array with keys among the following:
11897 * <ul>
11898 * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11899 * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11900 * </ul>
11901 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11902 * @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).
11903 * @since 4.8.003 (2009-09-15)
11904 * @public
11905 */
11906 public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
11907 $this->Polygon($p, $style, $line_style, $fill_color, false);
11908 }
11909
11910 /**
11911 * Draws a polygon.
11912 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11913 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11914 * @param $line_style (array) Line style of polygon. Array with keys among the following:
11915 * <ul>
11916 * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11917 * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11918 * </ul>
11919 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11920 * @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).
11921 * @param $closed (boolean) if true the polygon is closes, otherwise will remain open
11922 * @public
11923 * @since 2.1.000 (2008-01-08)
11924 */
11925 public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
11926 if ($this->state != 2) {
11927 return;
11928 }
11929 $nc = count($p); // number of coordinates
11930 $np = $nc / 2; // number of points
11931 if ($closed) {
11932 // close polygon by adding the first 2 points at the end (one line)
11933 for ($i = 0; $i < 4; ++$i) {
11934 $p[$nc + $i] = $p[$i];
11935 }
11936 // copy style for the last added line
11937 if (isset($line_style[0])) {
11938 $line_style[$np] = $line_style[0];
11939 }
11940 $nc += 4;
11941 }
11942 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11943 $this->SetFillColorArray($fill_color);
11944 }
11945 $op = TCPDF_STATIC::getPathPaintOperator($style);
11946 if ($op == 'f') {
11947 $line_style = array();
11948 }
11949 $draw = true;
11950 if ($line_style) {
11951 if (isset($line_style['all'])) {
11952 $this->SetLineStyle($line_style['all']);
11953 } else {
11954 $draw = false;
11955 if ($op == 'B') {
11956 // draw fill
11957 $op = 'f';
11958 $this->_outPoint($p[0], $p[1]);
11959 for ($i = 2; $i < $nc; $i = $i + 2) {
11960 $this->_outLine($p[$i], $p[$i + 1]);
11961 }
11962 $this->_out($op);
11963 }
11964 // draw outline
11965 $this->_outPoint($p[0], $p[1]);
11966 for ($i = 2; $i < $nc; $i = $i + 2) {
11967 $line_num = ($i / 2) - 1;
11968 if (isset($line_style[$line_num])) {
11969 if ($line_style[$line_num] != 0) {
11970 if (is_array($line_style[$line_num])) {
11971 $this->_out('S');
11972 $this->SetLineStyle($line_style[$line_num]);
11973 $this->_outPoint($p[$i - 2], $p[$i - 1]);
11974 $this->_outLine($p[$i], $p[$i + 1]);
11975 $this->_out('S');
11976 $this->_outPoint($p[$i], $p[$i + 1]);
11977 } else {
11978 $this->_outLine($p[$i], $p[$i + 1]);
11979 }
11980 }
11981 } else {
11982 $this->_outLine($p[$i], $p[$i + 1]);
11983 }
11984 }
11985 $this->_out($op);
11986 }
11987 }
11988 if ($draw) {
11989 $this->_outPoint($p[0], $p[1]);
11990 for ($i = 2; $i < $nc; $i = $i + 2) {
11991 $this->_outLine($p[$i], $p[$i + 1]);
11992 }
11993 $this->_out($op);
11994 }
11995 }
11996
11997 /**
11998 * Draws a regular polygon.
11999 * @param $x0 (float) Abscissa of center point.
12000 * @param $y0 (float) Ordinate of center point.
12001 * @param $r: (float) Radius of inscribed circle.
12002 * @param $ns (integer) Number of sides.
12003 * @param $angle (float) Angle oriented (anti-clockwise). Default value: 0.
12004 * @param $draw_circle (boolean) Draw inscribed circle or not. Default value: false.
12005 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12006 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
12007 * <ul>
12008 * <li>all: Line style of all sides. Array like for SetLineStyle().</li>
12009 * <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
12010 * </ul>
12011 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12012 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12013 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
12014 * <ul>
12015 * <li>D or empty string: Draw (default).</li>
12016 * <li>F: Fill.</li>
12017 * <li>DF or FD: Draw and fill.</li>
12018 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12019 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12020 * </ul>
12021 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12022 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12023 * @public
12024 * @since 2.1.000 (2008-01-08)
12025 */
12026 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()) {
12027 if (3 > $ns) {
12028 $ns = 3;
12029 }
12030 if ($draw_circle) {
12031 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12032 }
12033 $p = array();
12034 for ($i = 0; $i < $ns; ++$i) {
12035 $a = $angle + ($i * 360 / $ns);
12036 $a_rad = deg2rad((float) $a);
12037 $p[] = $x0 + ($r * sin($a_rad));
12038 $p[] = $y0 + ($r * cos($a_rad));
12039 }
12040 $this->Polygon($p, $style, $line_style, $fill_color);
12041 }
12042
12043 /**
12044 * Draws a star polygon
12045 * @param $x0 (float) Abscissa of center point.
12046 * @param $y0 (float) Ordinate of center point.
12047 * @param $r (float) Radius of inscribed circle.
12048 * @param $nv (integer) Number of vertices.
12049 * @param $ng (integer) Number of gap (if ($ng % $nv = 1) then is a regular polygon).
12050 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
12051 * @param $draw_circle: (boolean) Draw inscribed circle or not. Default value is false.
12052 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12053 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
12054 * <ul>
12055 * <li>all: Line style of all sides. Array like for
12056 * SetLineStyle().</li>
12057 * <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
12058 * </ul>
12059 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12060 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12061 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
12062 * <ul>
12063 * <li>D or empty string: Draw (default).</li>
12064 * <li>F: Fill.</li>
12065 * <li>DF or FD: Draw and fill.</li>
12066 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12067 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12068 * </ul>
12069 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12070 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12071 * @public
12072 * @since 2.1.000 (2008-01-08)
12073 */
12074 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()) {
12075 if ($nv < 2) {
12076 $nv = 2;
12077 }
12078 if ($draw_circle) {
12079 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12080 }
12081 $p2 = array();
12082 $visited = array();
12083 for ($i = 0; $i < $nv; ++$i) {
12084 $a = $angle + ($i * 360 / $nv);
12085 $a_rad = deg2rad((float) $a);
12086 $p2[] = $x0 + ($r * sin($a_rad));
12087 $p2[] = $y0 + ($r * cos($a_rad));
12088 $visited[] = false;
12089 }
12090 $p = array();
12091 $i = 0;
12092 do {
12093 $p[] = $p2[$i * 2];
12094 $p[] = $p2[($i * 2) + 1];
12095 $visited[$i] = true;
12096 $i += $ng;
12097 $i %= $nv;
12098 } while (!$visited[$i]);
12099 $this->Polygon($p, $style, $line_style, $fill_color);
12100 }
12101
12102 /**
12103 * Draws a rounded rectangle.
12104 * @param $x (float) Abscissa of upper-left corner.
12105 * @param $y (float) Ordinate of upper-left corner.
12106 * @param $w (float) Width.
12107 * @param $h (float) Height.
12108 * @param $r (float) the radius of the circle used to round off the corners of the rectangle.
12109 * @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").
12110 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12111 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12112 * @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).
12113 * @public
12114 * @since 2.1.000 (2008-01-08)
12115 */
12116 public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12117 $this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
12118 }
12119
12120 /**
12121 * Draws a rounded rectangle.
12122 * @param $x (float) Abscissa of upper-left corner.
12123 * @param $y (float) Ordinate of upper-left corner.
12124 * @param $w (float) Width.
12125 * @param $h (float) Height.
12126 * @param $rx (float) the x-axis radius of the ellipse used to round off the corners of the rectangle.
12127 * @param $ry (float) the y-axis radius of the ellipse used to round off the corners of the rectangle.
12128 * @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").
12129 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12130 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12131 * @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).
12132 * @public
12133 * @since 4.9.019 (2010-04-22)
12134 */
12135 public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12136 if ($this->state != 2) {
12137 return;
12138 }
12139 if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
12140 // Not rounded
12141 $this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
12142 return;
12143 }
12144 // Rounded
12145 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12146 $this->SetFillColorArray($fill_color);
12147 }
12148 $op = TCPDF_STATIC::getPathPaintOperator($style);
12149 if ($op == 'f') {
12150 $border_style = array();
12151 }
12152 if ($border_style) {
12153 $this->SetLineStyle($border_style);
12154 }
12155 $MyArc = 4 / 3 * (sqrt(2) - 1);
12156 $this->_outPoint($x + $rx, $y);
12157 $xc = $x + $w - $rx;
12158 $yc = $y + $ry;
12159 $this->_outLine($xc, $y);
12160 if ($round_corner[0]) {
12161 $this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
12162 } else {
12163 $this->_outLine($x + $w, $y);
12164 }
12165 $xc = $x + $w - $rx;
12166 $yc = $y + $h - $ry;
12167 $this->_outLine($x + $w, $yc);
12168 if ($round_corner[1]) {
12169 $this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
12170 } else {
12171 $this->_outLine($x + $w, $y + $h);
12172 }
12173 $xc = $x + $rx;
12174 $yc = $y + $h - $ry;
12175 $this->_outLine($xc, $y + $h);
12176 if ($round_corner[2]) {
12177 $this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
12178 } else {
12179 $this->_outLine($x, $y + $h);
12180 }
12181 $xc = $x + $rx;
12182 $yc = $y + $ry;
12183 $this->_outLine($x, $yc);
12184 if ($round_corner[3]) {
12185 $this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
12186 } else {
12187 $this->_outLine($x, $y);
12188 $this->_outLine($x + $rx, $y);
12189 }
12190 $this->_out($op);
12191 }
12192
12193 /**
12194 * Draws a grahic arrow.
12195 * @param $x0 (float) Abscissa of first point.
12196 * @param $y0 (float) Ordinate of first point.
12197 * @param $x1 (float) Abscissa of second point.
12198 * @param $y1 (float) Ordinate of second point.
12199 * @param $head_style (int) (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
12200 * @param $arm_size (float) length of arrowhead arms
12201 * @param $arm_angle (int) angle between an arm and the shaft
12202 * @author Piotr Galecki, Nicola Asuni, Andy Meier
12203 * @since 4.6.018 (2009-07-10)
12204 */
12205 public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
12206 // getting arrow direction angle
12207 // 0 deg angle is when both arms go along X axis. angle grows clockwise.
12208 $dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
12209 if ($dir_angle < 0) {
12210 $dir_angle += (2 * M_PI);
12211 }
12212 $arm_angle = deg2rad($arm_angle);
12213 $sx1 = $x1;
12214 $sy1 = $y1;
12215 if ($head_style > 0) {
12216 // calculate the stopping point for the arrow shaft
12217 $sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
12218 $sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
12219 }
12220 // main arrow line / shaft
12221 $this->Line($x0, $y0, $sx1, $sy1);
12222 // left arrowhead arm tip
12223 $x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
12224 $y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
12225 // right arrowhead arm tip
12226 $x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
12227 $y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
12228 $mode = 'D';
12229 $style = array();
12230 switch ($head_style) {
12231 case 0: {
12232 // draw only arrowhead arms
12233 $mode = 'D';
12234 $style = array(1, 1, 0);
12235 break;
12236 }
12237 case 1: {
12238 // draw closed arrowhead, but no fill
12239 $mode = 'D';
12240 break;
12241 }
12242 case 2: {
12243 // closed and filled arrowhead
12244 $mode = 'DF';
12245 break;
12246 }
12247 case 3: {
12248 // filled arrowhead
12249 $mode = 'F';
12250 break;
12251 }
12252 }
12253 $this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
12254 }
12255
12256 // END GRAPHIC FUNCTIONS SECTION -----------------------
12257
12258 /**
12259 * Add a Named Destination.
12260 * NOTE: destination names are unique, so only last entry will be saved.
12261 * @param $name (string) Destination name.
12262 * @param $y (float) Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
12263 * @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.
12264 * @param $x (float) X position in user units of the destiantion on the selected page (default = -1 = current position;).
12265 * @return (string) Stripped named destination identifier or false in case of error.
12266 * @public
12267 * @author Christian Deligant, Nicola Asuni
12268 * @since 5.9.097 (2011-06-23)
12269 */
12270 public function setDestination($name, $y=-1, $page='', $x=-1) {
12271 // remove unsupported characters
12272 $name = TCPDF_STATIC::encodeNameObject($name);
12273 if (TCPDF_STATIC::empty_string($name)) {
12274 return false;
12275 }
12276 if ($y == -1) {
12277 $y = $this->GetY();
12278 } elseif ($y < 0) {
12279 $y = 0;
12280 } elseif ($y > $this->h) {
12281 $y = $this->h;
12282 }
12283 if ($x == -1) {
12284 $x = $this->GetX();
12285 } elseif ($x < 0) {
12286 $x = 0;
12287 } elseif ($x > $this->w) {
12288 $x = $this->w;
12289 }
12290 $fixed = false;
12291 if (!empty($page) AND (substr($page, 0, 1) == '*')) {
12292 $page = intval(substr($page, 1));
12293 // this page number will not be changed when moving/add/deleting pages
12294 $fixed = true;
12295 }
12296 if (empty($page)) {
12297 $page = $this->PageNo();
12298 if (empty($page)) {
12299 return;
12300 }
12301 }
12302 $this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
12303 return $name;
12304 }
12305
12306 /**
12307 * Return the Named Destination array.
12308 * @return (array) Named Destination array.
12309 * @public
12310 * @author Nicola Asuni
12311 * @since 5.9.097 (2011-06-23)
12312 */
12313 public function getDestination() {
12314 return $this->dests;
12315 }
12316
12317 /**
12318 * Insert Named Destinations.
12319 * @protected
12320 * @author Johannes G\FCntert, Nicola Asuni
12321 * @since 5.9.098 (2011-06-23)
12322 */
12323 protected function _putdests() {
12324 if (empty($this->dests)) {
12325 return;
12326 }
12327 $this->n_dests = $this->_newobj();
12328 $out = ' <<';
12329 foreach($this->dests as $name => $o) {
12330 $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)));
12331 }
12332 $out .= ' >>';
12333 $out .= "\n".'endobj';
12334 $this->_out($out);
12335 }
12336
12337 /**
12338 * Adds a bookmark - alias for Bookmark().
12339 * @param $txt (string) Bookmark description.
12340 * @param $level (int) Bookmark level (minimum value is 0).
12341 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12342 * @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.
12343 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12344 * @param $color (array) RGB color array (values from 0 to 255).
12345 * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12346 * @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).
12347 * @public
12348 */
12349 public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12350 $this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
12351 }
12352
12353 /**
12354 * Adds a bookmark.
12355 * @param $txt (string) Bookmark description.
12356 * @param $level (int) Bookmark level (minimum value is 0).
12357 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12358 * @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.
12359 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12360 * @param $color (array) RGB color array (values from 0 to 255).
12361 * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12362 * @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).
12363 * @public
12364 * @since 2.1.002 (2008-02-12)
12365 */
12366 public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12367 if ($level < 0) {
12368 $level = 0;
12369 }
12370 if (isset($this->outlines[0])) {
12371 $lastoutline = end($this->outlines);
12372 $maxlevel = $lastoutline['l'] + 1;
12373 } else {
12374 $maxlevel = 0;
12375 }
12376 if ($level > $maxlevel) {
12377 $level = $maxlevel;
12378 }
12379 if ($y == -1) {
12380 $y = $this->GetY();
12381 } elseif ($y < 0) {
12382 $y = 0;
12383 } elseif ($y > $this->h) {
12384 $y = $this->h;
12385 }
12386 if ($x == -1) {
12387 $x = $this->GetX();
12388 } elseif ($x < 0) {
12389 $x = 0;
12390 } elseif ($x > $this->w) {
12391 $x = $this->w;
12392 }
12393 $fixed = false;
12394 $pageAsString = (string) $page;
12395 if ($pageAsString && $pageAsString[0] == '*') {
12396 $page = intval(substr($page, 1));
12397 // this page number will not be changed when moving/add/deleting pages
12398 $fixed = true;
12399 }
12400 if (empty($page)) {
12401 $page = $this->PageNo();
12402 if (empty($page)) {
12403 return;
12404 }
12405 }
12406 $this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
12407 }
12408
12409 /**
12410 * Sort bookmarks for page and key.
12411 * @protected
12412 * @since 5.9.119 (2011-09-19)
12413 */
12414 protected function sortBookmarks() {
12415 // get sorting columns
12416 $outline_p = array();
12417 $outline_y = array();
12418 foreach ($this->outlines as $key => $row) {
12419 $outline_p[$key] = $row['p'];
12420 $outline_k[$key] = $key;
12421 }
12422 // sort outlines by page and original position
12423 array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
12424 }
12425
12426 /**
12427 * Create a bookmark PDF string.
12428 * @protected
12429 * @author Olivier Plathey, Nicola Asuni
12430 * @since 2.1.002 (2008-02-12)
12431 */
12432 protected function _putbookmarks() {
12433 $nb = count($this->outlines);
12434 if ($nb == 0) {
12435 return;
12436 }
12437 // sort bookmarks
12438 $this->sortBookmarks();
12439 $lru = array();
12440 $level = 0;
12441 foreach ($this->outlines as $i => $o) {
12442 if ($o['l'] > 0) {
12443 $parent = $lru[($o['l'] - 1)];
12444 //Set parent and last pointers
12445 $this->outlines[$i]['parent'] = $parent;
12446 $this->outlines[$parent]['last'] = $i;
12447 if ($o['l'] > $level) {
12448 //Level increasing: set first pointer
12449 $this->outlines[$parent]['first'] = $i;
12450 }
12451 } else {
12452 $this->outlines[$i]['parent'] = $nb;
12453 }
12454 if (($o['l'] <= $level) AND ($i > 0)) {
12455 //Set prev and next pointers
12456 $prev = $lru[$o['l']];
12457 $this->outlines[$prev]['next'] = $i;
12458 $this->outlines[$i]['prev'] = $prev;
12459 }
12460 $lru[$o['l']] = $i;
12461 $level = $o['l'];
12462 }
12463 //Outline items
12464 $n = $this->n + 1;
12465 $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';
12466 foreach ($this->outlines as $i => $o) {
12467 $oid = $this->_newobj();
12468 // covert HTML title to string
12469 $title = preg_replace($nltags, "\n", $o['t']);
12470 $title = preg_replace("/[\r]+/si", '', $title);
12471 $title = preg_replace("/[\n]+/si", "\n", $title);
12472 $title = strip_tags($title);
12473 $title = $this->stringTrim($title);
12474 $out = '<</Title '.$this->_textstring($title, $oid);
12475 $out .= ' /Parent '.($n + $o['parent']).' 0 R';
12476 if (isset($o['prev'])) {
12477 $out .= ' /Prev '.($n + $o['prev']).' 0 R';
12478 }
12479 if (isset($o['next'])) {
12480 $out .= ' /Next '.($n + $o['next']).' 0 R';
12481 }
12482 if (isset($o['first'])) {
12483 $out .= ' /First '.($n + $o['first']).' 0 R';
12484 }
12485 if (isset($o['last'])) {
12486 $out .= ' /Last '.($n + $o['last']).' 0 R';
12487 }
12488 if (isset($o['u']) AND !empty($o['u'])) {
12489 // link
12490 if (is_string($o['u'])) {
12491 if ($o['u'][0] == '#') {
12492 // internal destination
12493 $out .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($o['u'], 1));
12494 } elseif ($o['u'][0] == '%') {
12495 // embedded PDF file
12496 $filename = basename(substr($o['u'], 1));
12497 $out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
12498 } elseif ($o['u'][0] == '*') {
12499 // embedded generic file
12500 $filename = basename(substr($o['u'], 1));
12501 $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});';
12502 $out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
12503 } else {
12504 // external URI link
12505 $out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
12506 }
12507 } elseif (isset($this->links[$o['u']])) {
12508 // internal link ID
12509 $l = $this->links[$o['u']];
12510 if (isset($this->page_obj_id[($l['p'])])) {
12511 $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)));
12512 }
12513 }
12514 } elseif (isset($this->page_obj_id[($o['p'])])) {
12515 // link to a page
12516 $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)));
12517 }
12518 // set font style
12519 $style = 0;
12520 if (!empty($o['s'])) {
12521 // bold
12522 if (strpos($o['s'], 'B') !== false) {
12523 $style |= 2;
12524 }
12525 // oblique
12526 if (strpos($o['s'], 'I') !== false) {
12527 $style |= 1;
12528 }
12529 }
12530 $out .= sprintf(' /F %d', $style);
12531 // set bookmark color
12532 if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
12533 $color = array_values($o['c']);
12534 $out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
12535 } else {
12536 // black
12537 $out .= ' /C [0.0 0.0 0.0]';
12538 }
12539 $out .= ' /Count 0'; // normally closed item
12540 $out .= ' >>';
12541 $out .= "\n".'endobj';
12542 $this->_out($out);
12543 }
12544 //Outline root
12545 $this->OutlineRoot = $this->_newobj();
12546 $this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
12547 }
12548
12549 // --- JAVASCRIPT ------------------------------------------------------
12550
12551 /**
12552 * Adds a javascript
12553 * @param $script (string) Javascript code
12554 * @public
12555 * @author Johannes G\FCntert, Nicola Asuni
12556 * @since 2.1.002 (2008-02-12)
12557 */
12558 public function IncludeJS($script) {
12559 $this->javascript .= $script;
12560 }
12561
12562 /**
12563 * Adds a javascript object and return object ID
12564 * @param $script (string) Javascript code
12565 * @param $onload (boolean) if true executes this object when opening the document
12566 * @return int internal object ID
12567 * @public
12568 * @author Nicola Asuni
12569 * @since 4.8.000 (2009-09-07)
12570 */
12571 public function addJavascriptObject($script, $onload=false) {
12572 if ($this->pdfa_mode) {
12573 // javascript is not allowed in PDF/A mode
12574 return false;
12575 }
12576 ++$this->n;
12577 $this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
12578 return $this->n;
12579 }
12580
12581 /**
12582 * Create a javascript PDF string.
12583 * @protected
12584 * @author Johannes G\FCntert, Nicola Asuni
12585 * @since 2.1.002 (2008-02-12)
12586 */
12587 protected function _putjavascript() {
12588 if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
12589 return;
12590 }
12591 if (strpos($this->javascript, 'this.addField') > 0) {
12592 if (!$this->ur['enabled']) {
12593 //$this->setUserRights();
12594 }
12595 // the following two lines are used to avoid form fields duplication after saving
12596 // The addField method only works when releasing user rights (UR3)
12597 $jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
12598 $jsb = "getField('tcpdfdocsaved').value='saved';";
12599 $this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
12600 }
12601 // name tree for javascript
12602 $this->n_js = '<< /Names [';
12603 if (!empty($this->javascript)) {
12604 $this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
12605 }
12606 if (!empty($this->js_objects)) {
12607 foreach ($this->js_objects as $key => $val) {
12608 if ($val['onload']) {
12609 $this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
12610 }
12611 }
12612 }
12613 $this->n_js .= ' ] >>';
12614 // default Javascript object
12615 if (!empty($this->javascript)) {
12616 $obj_id = $this->_newobj();
12617 $out = '<< /S /JavaScript';
12618 $out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
12619 $out .= ' >>';
12620 $out .= "\n".'endobj';
12621 $this->_out($out);
12622 }
12623 // additional Javascript objects
12624 if (!empty($this->js_objects)) {
12625 foreach ($this->js_objects as $key => $val) {
12626 $out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
12627 $this->_out($out);
12628 }
12629 }
12630 }
12631
12632 /**
12633 * Adds a javascript form field.
12634 * @param $type (string) field type
12635 * @param $name (string) field name
12636 * @param $x (int) horizontal position
12637 * @param $y (int) vertical position
12638 * @param $w (int) width
12639 * @param $h (int) height
12640 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12641 * @protected
12642 * @author Denis Van Nuffelen, Nicola Asuni
12643 * @since 2.1.002 (2008-02-12)
12644 */
12645 protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
12646 if ($this->rtl) {
12647 $x = $x - $w;
12648 }
12649 // the followind avoid fields duplication after saving the document
12650 $this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
12651 $k = $this->k;
12652 $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";
12653 $this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
12654 foreach($prop as $key => $val) {
12655 if (strcmp(substr($key, -5), 'Color') == 0) {
12656 $val = TCPDF_COLORS::_JScolor($val);
12657 } else {
12658 $val = "'".$val."'";
12659 }
12660 $this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
12661 }
12662 if ($this->rtl) {
12663 $this->x -= $w;
12664 } else {
12665 $this->x += $w;
12666 }
12667 $this->javascript .= '}';
12668 }
12669
12670 // --- FORM FIELDS -----------------------------------------------------
12671
12672
12673
12674 /**
12675 * Set default properties for form fields.
12676 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12677 * @public
12678 * @author Nicola Asuni
12679 * @since 4.8.000 (2009-09-06)
12680 */
12681 public function setFormDefaultProp($prop=array()) {
12682 $this->default_form_prop = $prop;
12683 }
12684
12685 /**
12686 * Return the default properties for form fields.
12687 * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12688 * @public
12689 * @author Nicola Asuni
12690 * @since 4.8.000 (2009-09-06)
12691 */
12692 public function getFormDefaultProp() {
12693 return $this->default_form_prop;
12694 }
12695
12696 /**
12697 * Creates a text field
12698 * @param $name (string) field name
12699 * @param $w (float) Width of the rectangle
12700 * @param $h (float) Height of the rectangle
12701 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12702 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12703 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12704 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12705 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12706 * @public
12707 * @author Nicola Asuni
12708 * @since 4.8.000 (2009-09-07)
12709 */
12710 public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12711 if ($x === '') {
12712 $x = $this->x;
12713 }
12714 if ($y === '') {
12715 $y = $this->y;
12716 }
12717 // check page for no-write regions and adapt page margins if necessary
12718 list($x, $y) = $this->checkPageRegions($h, $x, $y);
12719 if ($js) {
12720 $this->_addfield('text', $name, $x, $y, $w, $h, $prop);
12721 return;
12722 }
12723 // get default style
12724 $prop = array_merge($this->getFormDefaultProp(), $prop);
12725 // get annotation data
12726 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12727 // set default appearance stream
12728 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12729 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12730 $popt['da'] = $fontstyle;
12731 // build appearance stream
12732 $popt['ap'] = array();
12733 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12734 $text = '';
12735 if (isset($prop['value']) AND !empty($prop['value'])) {
12736 $text = $prop['value'];
12737 } elseif (isset($opt['v']) AND !empty($opt['v'])) {
12738 $text = $opt['v'];
12739 }
12740 $tmpid = $this->startTemplate($w, $h, false);
12741 $align = '';
12742 if (isset($popt['q'])) {
12743 switch ($popt['q']) {
12744 case 0: {
12745 $align = 'L';
12746 break;
12747 }
12748 case 1: {
12749 $align = 'C';
12750 break;
12751 }
12752 case 2: {
12753 $align = 'R';
12754 break;
12755 }
12756 default: {
12757 $align = '';
12758 break;
12759 }
12760 }
12761 }
12762 $this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12763 $this->endTemplate();
12764 --$this->n;
12765 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12766 unset($this->xobjects[$tmpid]);
12767 $popt['ap']['n'] .= 'Q EMC';
12768 // merge options
12769 $opt = array_merge($popt, $opt);
12770 // remove some conflicting options
12771 unset($opt['bs']);
12772 // set remaining annotation data
12773 $opt['Subtype'] = 'Widget';
12774 $opt['ft'] = 'Tx';
12775 $opt['t'] = $name;
12776 // Additional annotation's parameters (check _putannotsobj() method):
12777 //$opt['f']
12778 //$opt['as']
12779 //$opt['bs']
12780 //$opt['be']
12781 //$opt['c']
12782 //$opt['border']
12783 //$opt['h']
12784 //$opt['mk'];
12785 //$opt['mk']['r']
12786 //$opt['mk']['bc'];
12787 //$opt['mk']['bg'];
12788 unset($opt['mk']['ca']);
12789 unset($opt['mk']['rc']);
12790 unset($opt['mk']['ac']);
12791 unset($opt['mk']['i']);
12792 unset($opt['mk']['ri']);
12793 unset($opt['mk']['ix']);
12794 unset($opt['mk']['if']);
12795 //$opt['mk']['if']['sw'];
12796 //$opt['mk']['if']['s'];
12797 //$opt['mk']['if']['a'];
12798 //$opt['mk']['if']['fb'];
12799 unset($opt['mk']['tp']);
12800 //$opt['tu']
12801 //$opt['tm']
12802 //$opt['ff']
12803 //$opt['v']
12804 //$opt['dv']
12805 //$opt['a']
12806 //$opt['aa']
12807 //$opt['q']
12808 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12809 if ($this->rtl) {
12810 $this->x -= $w;
12811 } else {
12812 $this->x += $w;
12813 }
12814 }
12815
12816 /**
12817 * Creates a RadioButton field.
12818 * @param $name (string) Field name.
12819 * @param $w (int) Width of the radio button.
12820 * @param $prop (array) Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12821 * @param $opt (array) Annotation parameters. Possible values are described on official PDF32000_2008 reference.
12822 * @param $onvalue (string) Value to be returned if selected.
12823 * @param $checked (boolean) Define the initial state.
12824 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12825 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12826 * @param $js (boolean) If true put the field using JavaScript (requires Acrobat Writer to be rendered).
12827 * @public
12828 * @author Nicola Asuni
12829 * @since 4.8.000 (2009-09-07)
12830 */
12831 public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
12832 if ($x === '') {
12833 $x = $this->x;
12834 }
12835 if ($y === '') {
12836 $y = $this->y;
12837 }
12838 // check page for no-write regions and adapt page margins if necessary
12839 list($x, $y) = $this->checkPageRegions($w, $x, $y);
12840 if ($js) {
12841 $this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
12842 return;
12843 }
12844 if (TCPDF_STATIC::empty_string($onvalue)) {
12845 $onvalue = 'On';
12846 }
12847 if ($checked) {
12848 $defval = $onvalue;
12849 } else {
12850 $defval = 'Off';
12851 }
12852 // set font
12853 $font = 'zapfdingbats';
12854 if ($this->pdfa_mode) {
12855 // all fonts must be embedded
12856 $font = 'pdfa'.$font;
12857 }
12858 $this->AddFont($font);
12859 $tmpfont = $this->getFontBuffer($font);
12860 // set data for parent group
12861 if (!isset($this->radiobutton_groups[$this->page])) {
12862 $this->radiobutton_groups[$this->page] = array();
12863 }
12864 if (!isset($this->radiobutton_groups[$this->page][$name])) {
12865 $this->radiobutton_groups[$this->page][$name] = array();
12866 ++$this->n;
12867 $this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
12868 $this->radio_groups[] = $this->n;
12869 }
12870 $kid = ($this->n + 1);
12871 // save object ID to be added on Kids entry on parent object
12872 $this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
12873 // get default style
12874 $prop = array_merge($this->getFormDefaultProp(), $prop);
12875 $prop['NoToggleToOff'] = 'true';
12876 $prop['Radio'] = 'true';
12877 $prop['borderStyle'] = 'inset';
12878 // get annotation data
12879 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12880 // set additional default options
12881 $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
12882 $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
12883 $popt['da'] = $fontstyle;
12884 // build appearance stream
12885 $popt['ap'] = array();
12886 $popt['ap']['n'] = array();
12887 $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
12888 $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
12889 $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);
12890 $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);
12891 if (!isset($popt['mk'])) {
12892 $popt['mk'] = array();
12893 }
12894 $popt['mk']['ca'] = '(l)';
12895 // merge options
12896 $opt = array_merge($popt, $opt);
12897 // set remaining annotation data
12898 $opt['Subtype'] = 'Widget';
12899 $opt['ft'] = 'Btn';
12900 if ($checked) {
12901 $opt['v'] = array('/'.$onvalue);
12902 $opt['as'] = $onvalue;
12903 } else {
12904 $opt['as'] = 'Off';
12905 }
12906 // store readonly flag
12907 if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
12908 $this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
12909 }
12910 $this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
12911 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
12912 if ($this->rtl) {
12913 $this->x -= $w;
12914 } else {
12915 $this->x += $w;
12916 }
12917 }
12918
12919 /**
12920 * Creates a List-box field
12921 * @param $name (string) field name
12922 * @param $w (int) width
12923 * @param $h (int) height
12924 * @param $values (array) array containing the list of values.
12925 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12926 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12927 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12928 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12929 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12930 * @public
12931 * @author Nicola Asuni
12932 * @since 4.8.000 (2009-09-07)
12933 */
12934 public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12935 if ($x === '') {
12936 $x = $this->x;
12937 }
12938 if ($y === '') {
12939 $y = $this->y;
12940 }
12941 // check page for no-write regions and adapt page margins if necessary
12942 list($x, $y) = $this->checkPageRegions($h, $x, $y);
12943 if ($js) {
12944 $this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
12945 $s = '';
12946 foreach ($values as $value) {
12947 if (is_array($value)) {
12948 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
12949 } else {
12950 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
12951 }
12952 }
12953 $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
12954 return;
12955 }
12956 // get default style
12957 $prop = array_merge($this->getFormDefaultProp(), $prop);
12958 // get annotation data
12959 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12960 // set additional default values
12961 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12962 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12963 $popt['da'] = $fontstyle;
12964 // build appearance stream
12965 $popt['ap'] = array();
12966 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12967 $text = '';
12968 foreach($values as $item) {
12969 if (is_array($item)) {
12970 $text .= $item[1]."\n";
12971 } else {
12972 $text .= $item."\n";
12973 }
12974 }
12975 $tmpid = $this->startTemplate($w, $h, false);
12976 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12977 $this->endTemplate();
12978 --$this->n;
12979 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12980 unset($this->xobjects[$tmpid]);
12981 $popt['ap']['n'] .= 'Q EMC';
12982 // merge options
12983 $opt = array_merge($popt, $opt);
12984 // set remaining annotation data
12985 $opt['Subtype'] = 'Widget';
12986 $opt['ft'] = 'Ch';
12987 $opt['t'] = $name;
12988 $opt['opt'] = $values;
12989 unset($opt['mk']['ca']);
12990 unset($opt['mk']['rc']);
12991 unset($opt['mk']['ac']);
12992 unset($opt['mk']['i']);
12993 unset($opt['mk']['ri']);
12994 unset($opt['mk']['ix']);
12995 unset($opt['mk']['if']);
12996 unset($opt['mk']['tp']);
12997 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12998 if ($this->rtl) {
12999 $this->x -= $w;
13000 } else {
13001 $this->x += $w;
13002 }
13003 }
13004
13005 /**
13006 * Creates a Combo-box field
13007 * @param $name (string) field name
13008 * @param $w (int) width
13009 * @param $h (int) height
13010 * @param $values (array) array containing the list of values.
13011 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13012 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13013 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13014 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13015 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13016 * @public
13017 * @author Nicola Asuni
13018 * @since 4.8.000 (2009-09-07)
13019 */
13020 public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
13021 if ($x === '') {
13022 $x = $this->x;
13023 }
13024 if ($y === '') {
13025 $y = $this->y;
13026 }
13027 // check page for no-write regions and adapt page margins if necessary
13028 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13029 if ($js) {
13030 $this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
13031 $s = '';
13032 foreach ($values as $value) {
13033 if (is_array($value)) {
13034 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13035 } else {
13036 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13037 }
13038 }
13039 $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13040 return;
13041 }
13042 // get default style
13043 $prop = array_merge($this->getFormDefaultProp(), $prop);
13044 $prop['Combo'] = true;
13045 // get annotation data
13046 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13047 // set additional default options
13048 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13049 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13050 $popt['da'] = $fontstyle;
13051 // build appearance stream
13052 $popt['ap'] = array();
13053 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13054 $text = '';
13055 foreach($values as $item) {
13056 if (is_array($item)) {
13057 $text .= $item[1]."\n";
13058 } else {
13059 $text .= $item."\n";
13060 }
13061 }
13062 $tmpid = $this->startTemplate($w, $h, false);
13063 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13064 $this->endTemplate();
13065 --$this->n;
13066 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13067 unset($this->xobjects[$tmpid]);
13068 $popt['ap']['n'] .= 'Q EMC';
13069 // merge options
13070 $opt = array_merge($popt, $opt);
13071 // set remaining annotation data
13072 $opt['Subtype'] = 'Widget';
13073 $opt['ft'] = 'Ch';
13074 $opt['t'] = $name;
13075 $opt['opt'] = $values;
13076 unset($opt['mk']['ca']);
13077 unset($opt['mk']['rc']);
13078 unset($opt['mk']['ac']);
13079 unset($opt['mk']['i']);
13080 unset($opt['mk']['ri']);
13081 unset($opt['mk']['ix']);
13082 unset($opt['mk']['if']);
13083 unset($opt['mk']['tp']);
13084 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13085 if ($this->rtl) {
13086 $this->x -= $w;
13087 } else {
13088 $this->x += $w;
13089 }
13090 }
13091
13092 /**
13093 * Creates a CheckBox field
13094 * @param $name (string) field name
13095 * @param $w (int) width
13096 * @param $checked (boolean) define the initial state.
13097 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13098 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13099 * @param $onvalue (string) value to be returned if selected.
13100 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13101 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13102 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13103 * @public
13104 * @author Nicola Asuni
13105 * @since 4.8.000 (2009-09-07)
13106 */
13107 public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
13108 if ($x === '') {
13109 $x = $this->x;
13110 }
13111 if ($y === '') {
13112 $y = $this->y;
13113 }
13114 // check page for no-write regions and adapt page margins if necessary
13115 list($x, $y) = $this->checkPageRegions($w, $x, $y);
13116 if ($js) {
13117 $this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
13118 return;
13119 }
13120 if (!isset($prop['value'])) {
13121 $prop['value'] = array('Yes');
13122 }
13123 // get default style
13124 $prop = array_merge($this->getFormDefaultProp(), $prop);
13125 $prop['borderStyle'] = 'inset';
13126 // get annotation data
13127 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13128 // set additional default options
13129 $font = 'zapfdingbats';
13130 if ($this->pdfa_mode) {
13131 // all fonts must be embedded
13132 $font = 'pdfa'.$font;
13133 }
13134 $this->AddFont($font);
13135 $tmpfont = $this->getFontBuffer($font);
13136 $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
13137 $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
13138 $popt['da'] = $fontstyle;
13139 // build appearance stream
13140 $popt['ap'] = array();
13141 $popt['ap']['n'] = array();
13142 $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
13143 $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
13144 $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);
13145 $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);
13146 // merge options
13147 $opt = array_merge($popt, $opt);
13148 // set remaining annotation data
13149 $opt['Subtype'] = 'Widget';
13150 $opt['ft'] = 'Btn';
13151 $opt['t'] = $name;
13152 if (TCPDF_STATIC::empty_string($onvalue)) {
13153 $onvalue = 'Yes';
13154 }
13155 $opt['opt'] = array($onvalue);
13156 if ($checked) {
13157 $opt['v'] = array('/Yes');
13158 $opt['as'] = 'Yes';
13159 } else {
13160 $opt['v'] = array('/Off');
13161 $opt['as'] = 'Off';
13162 }
13163 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13164 if ($this->rtl) {
13165 $this->x -= $w;
13166 } else {
13167 $this->x += $w;
13168 }
13169 }
13170
13171 /**
13172 * Creates a button field
13173 * @param $name (string) field name
13174 * @param $w (int) width
13175 * @param $h (int) height
13176 * @param $caption (string) caption.
13177 * @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.
13178 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13179 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13180 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13181 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13182 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13183 * @public
13184 * @author Nicola Asuni
13185 * @since 4.8.000 (2009-09-07)
13186 */
13187 public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
13188 if ($x === '') {
13189 $x = $this->x;
13190 }
13191 if ($y === '') {
13192 $y = $this->y;
13193 }
13194 // check page for no-write regions and adapt page margins if necessary
13195 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13196 if ($js) {
13197 $this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
13198 $this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
13199 $this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
13200 $this->javascript .= 'f'.$name.".highlight='push';\n";
13201 $this->javascript .= 'f'.$name.".print=false;\n";
13202 return;
13203 }
13204 // get default style
13205 $prop = array_merge($this->getFormDefaultProp(), $prop);
13206 $prop['Pushbutton'] = 'true';
13207 $prop['highlight'] = 'push';
13208 $prop['display'] = 'display.noPrint';
13209 // get annotation data
13210 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13211 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13212 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13213 $popt['da'] = $fontstyle;
13214 // build appearance stream
13215 $popt['ap'] = array();
13216 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13217 $tmpid = $this->startTemplate($w, $h, false);
13218 $bw = (2 / $this->k); // border width
13219 $border = array(
13220 'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13221 'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
13222 'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13223 'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
13224 $this->SetFillColor(204);
13225 $this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
13226 $this->endTemplate();
13227 --$this->n;
13228 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13229 unset($this->xobjects[$tmpid]);
13230 $popt['ap']['n'] .= 'Q EMC';
13231 // set additional default options
13232 if (!isset($popt['mk'])) {
13233 $popt['mk'] = array();
13234 }
13235 $ann_obj_id = ($this->n + 1);
13236 if (!empty($action) AND !is_array($action)) {
13237 $ann_obj_id = ($this->n + 2);
13238 }
13239 $popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
13240 $popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
13241 $popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
13242 // merge options
13243 $opt = array_merge($popt, $opt);
13244 // set remaining annotation data
13245 $opt['Subtype'] = 'Widget';
13246 $opt['ft'] = 'Btn';
13247 $opt['t'] = $caption;
13248 $opt['v'] = $name;
13249 if (!empty($action)) {
13250 if (is_array($action)) {
13251 // form action options as on section 12.7.5 of PDF32000_2008.
13252 $opt['aa'] = '/D <<';
13253 $bmode = array('SubmitForm', 'ResetForm', 'ImportData');
13254 foreach ($action AS $key => $val) {
13255 if (($key == 'S') AND in_array($val, $bmode)) {
13256 $opt['aa'] .= ' /S /'.$val;
13257 } elseif (($key == 'F') AND (!empty($val))) {
13258 $opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
13259 } elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
13260 $opt['aa'] .= ' /Fields [';
13261 foreach ($val AS $field) {
13262 $opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
13263 }
13264 $opt['aa'] .= ']';
13265 } elseif (($key == 'Flags')) {
13266 $ff = 0;
13267 if (is_array($val)) {
13268 foreach ($val AS $flag) {
13269 switch ($flag) {
13270 case 'Include/Exclude': {
13271 $ff += 1 << 0;
13272 break;
13273 }
13274 case 'IncludeNoValueFields': {
13275 $ff += 1 << 1;
13276 break;
13277 }
13278 case 'ExportFormat': {
13279 $ff += 1 << 2;
13280 break;
13281 }
13282 case 'GetMethod': {
13283 $ff += 1 << 3;
13284 break;
13285 }
13286 case 'SubmitCoordinates': {
13287 $ff += 1 << 4;
13288 break;
13289 }
13290 case 'XFDF': {
13291 $ff += 1 << 5;
13292 break;
13293 }
13294 case 'IncludeAppendSaves': {
13295 $ff += 1 << 6;
13296 break;
13297 }
13298 case 'IncludeAnnotations': {
13299 $ff += 1 << 7;
13300 break;
13301 }
13302 case 'SubmitPDF': {
13303 $ff += 1 << 8;
13304 break;
13305 }
13306 case 'CanonicalFormat': {
13307 $ff += 1 << 9;
13308 break;
13309 }
13310 case 'ExclNonUserAnnots': {
13311 $ff += 1 << 10;
13312 break;
13313 }
13314 case 'ExclFKey': {
13315 $ff += 1 << 11;
13316 break;
13317 }
13318 case 'EmbedForm': {
13319 $ff += 1 << 13;
13320 break;
13321 }
13322 }
13323 }
13324 } else {
13325 $ff = intval($val);
13326 }
13327 $opt['aa'] .= ' /Flags '.$ff;
13328 }
13329 }
13330 $opt['aa'] .= ' >>';
13331 } else {
13332 // Javascript action or raw action command
13333 $js_obj_id = $this->addJavascriptObject($action);
13334 $opt['aa'] = '/D '.$js_obj_id.' 0 R';
13335 }
13336 }
13337 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13338 if ($this->rtl) {
13339 $this->x -= $w;
13340 } else {
13341 $this->x += $w;
13342 }
13343 }
13344
13345 // --- END FORMS FIELDS ------------------------------------------------
13346
13347 /**
13348 * Add certification signature (DocMDP or UR3)
13349 * You can set only one signature type
13350 * @protected
13351 * @author Nicola Asuni
13352 * @since 4.6.008 (2009-05-07)
13353 */
13354 protected function _putsignature() {
13355 if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
13356 return;
13357 }
13358 $sigobjid = ($this->sig_obj_id + 1);
13359 $out = $this->_getobj($sigobjid)."\n";
13360 $out .= '<< /Type /Sig';
13361 $out .= ' /Filter /Adobe.PPKLite';
13362 $out .= ' /SubFilter /adbe.pkcs7.detached';
13363 $out .= ' '.TCPDF_STATIC::$byterange_string;
13364 $out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
13365 if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
13366 $out .= ' /Reference ['; // array of signature reference dictionaries
13367 $out .= ' << /Type /SigRef';
13368 if ($this->signature_data['cert_type'] > 0) {
13369 $out .= ' /TransformMethod /DocMDP';
13370 $out .= ' /TransformParams <<';
13371 $out .= ' /Type /TransformParams';
13372 $out .= ' /P '.$this->signature_data['cert_type'];
13373 $out .= ' /V /1.2';
13374 } else {
13375 $out .= ' /TransformMethod /UR3';
13376 $out .= ' /TransformParams <<';
13377 $out .= ' /Type /TransformParams';
13378 $out .= ' /V /2.2';
13379 if (!TCPDF_STATIC::empty_string($this->ur['document'])) {
13380 $out .= ' /Document['.$this->ur['document'].']';
13381 }
13382 if (!TCPDF_STATIC::empty_string($this->ur['form'])) {
13383 $out .= ' /Form['.$this->ur['form'].']';
13384 }
13385 if (!TCPDF_STATIC::empty_string($this->ur['signature'])) {
13386 $out .= ' /Signature['.$this->ur['signature'].']';
13387 }
13388 if (!TCPDF_STATIC::empty_string($this->ur['annots'])) {
13389 $out .= ' /Annots['.$this->ur['annots'].']';
13390 }
13391 if (!TCPDF_STATIC::empty_string($this->ur['ef'])) {
13392 $out .= ' /EF['.$this->ur['ef'].']';
13393 }
13394 if (!TCPDF_STATIC::empty_string($this->ur['formex'])) {
13395 $out .= ' /FormEX['.$this->ur['formex'].']';
13396 }
13397 }
13398 $out .= ' >>'; // close TransformParams
13399 // optional digest data (values must be calculated and replaced later)
13400 //$out .= ' /Data ********** 0 R';
13401 //$out .= ' /DigestMethod/MD5';
13402 //$out .= ' /DigestLocation[********** 34]';
13403 //$out .= ' /DigestValue<********************************>';
13404 $out .= ' >>';
13405 $out .= ' ]'; // end of reference
13406 }
13407 if (isset($this->signature_data['info']['Name']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Name'])) {
13408 $out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
13409 }
13410 if (isset($this->signature_data['info']['Location']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Location'])) {
13411 $out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
13412 }
13413 if (isset($this->signature_data['info']['Reason']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Reason'])) {
13414 $out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
13415 }
13416 if (isset($this->signature_data['info']['ContactInfo']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['ContactInfo'])) {
13417 $out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
13418 }
13419 $out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
13420 $out .= ' >>';
13421 $out .= "\n".'endobj';
13422 $this->_out($out);
13423 }
13424
13425 /**
13426 * Set User's Rights for PDF Reader
13427 * WARNING: This is experimental and currently do not work.
13428 * Check the PDF Reference 8.7.1 Transform Methods,
13429 * Table 8.105 Entries in the UR transform parameters dictionary
13430 * @param $enable (boolean) if true enable user's rights on PDF reader
13431 * @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.
13432 * @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.
13433 * @param $form (string) Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
13434 * @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.
13435 * @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
13436 Names specifying additional embedded-files-related usage rights for the document.
13437 * @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.
13438 * @public
13439 * @author Nicola Asuni
13440 * @since 2.9.000 (2008-03-26)
13441 */
13442 public function setUserRights(
13443 $enable=true,
13444 $document='/FullSave',
13445 $annots='/Create/Delete/Modify/Copy/Import/Export',
13446 $form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
13447 $signature='/Modify',
13448 $ef='/Create/Delete/Modify/Import',
13449 $formex='') {
13450 $this->ur['enabled'] = $enable;
13451 $this->ur['document'] = $document;
13452 $this->ur['annots'] = $annots;
13453 $this->ur['form'] = $form;
13454 $this->ur['signature'] = $signature;
13455 $this->ur['ef'] = $ef;
13456 $this->ur['formex'] = $formex;
13457 if (!$this->sign) {
13458 $this->setSignature('', '', '', '', 0, array());
13459 }
13460 }
13461
13462 /**
13463 * Enable document signature (requires the OpenSSL Library).
13464 * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
13465 * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13466 * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13467 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
13468 * @param $signing_cert (mixed) signing certificate (string or filename prefixed with 'file://')
13469 * @param $private_key (mixed) private key (string or filename prefixed with 'file://')
13470 * @param $private_key_password (string) password
13471 * @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.
13472 * @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.
13473 * @param $info (array) array of option information: Name, Location, Reason, ContactInfo.
13474 * @param $approval (string) Enable approval signature eg. for PDF incremental update
13475 * @public
13476 * @author Nicola Asuni
13477 * @since 4.6.005 (2009-04-24)
13478 */
13479 public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array(), $approval='') {
13480 // to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13481 // to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13482 // to convert pfx certificate to pem: openssl
13483 // OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
13484 $this->sign = true;
13485 ++$this->n;
13486 $this->sig_obj_id = $this->n; // signature widget
13487 ++$this->n; // signature object ($this->sig_obj_id + 1)
13488 $this->signature_data = array();
13489 if (strlen($signing_cert) == 0) {
13490 $this->Error('Please provide a certificate file and password!');
13491 }
13492 if (strlen($private_key) == 0) {
13493 $private_key = $signing_cert;
13494 }
13495 $this->signature_data['signcert'] = $signing_cert;
13496 $this->signature_data['privkey'] = $private_key;
13497 $this->signature_data['password'] = $private_key_password;
13498 $this->signature_data['extracerts'] = $extracerts;
13499 $this->signature_data['cert_type'] = $cert_type;
13500 $this->signature_data['info'] = $info;
13501 $this->signature_data['approval'] = $approval;
13502 }
13503
13504 /**
13505 * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
13506 * @param $x (float) Abscissa of the upper-left corner.
13507 * @param $y (float) Ordinate of the upper-left corner.
13508 * @param $w (float) Width of the signature area.
13509 * @param $h (float) Height of the signature area.
13510 * @param $page (int) option page number (if < 0 the current page is used).
13511 * @param $name (string) Name of the signature.
13512 * @public
13513 * @author Nicola Asuni
13514 * @since 5.3.011 (2010-06-17)
13515 */
13516 public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13517 $this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13518 }
13519
13520 /**
13521 * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
13522 * @param $x (float) Abscissa of the upper-left corner.
13523 * @param $y (float) Ordinate of the upper-left corner.
13524 * @param $w (float) Width of the signature area.
13525 * @param $h (float) Height of the signature area.
13526 * @param $page (int) option page number (if < 0 the current page is used).
13527 * @param $name (string) Name of the signature.
13528 * @public
13529 * @author Nicola Asuni
13530 * @since 5.9.101 (2011-07-06)
13531 */
13532 public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13533 ++$this->n;
13534 $this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13535 }
13536
13537 /**
13538 * Get the array that defines the signature appearance (page and rectangle coordinates).
13539 * @param $x (float) Abscissa of the upper-left corner.
13540 * @param $y (float) Ordinate of the upper-left corner.
13541 * @param $w (float) Width of the signature area.
13542 * @param $h (float) Height of the signature area.
13543 * @param $page (int) option page number (if < 0 the current page is used).
13544 * @param $name (string) Name of the signature.
13545 * @return (array) Array defining page and rectangle coordinates of signature appearance.
13546 * @protected
13547 * @author Nicola Asuni
13548 * @since 5.9.101 (2011-07-06)
13549 */
13550 protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13551 $sigapp = array();
13552 if (($page < 1) OR ($page > $this->numpages)) {
13553 $sigapp['page'] = $this->page;
13554 } else {
13555 $sigapp['page'] = intval($page);
13556 }
13557 if (empty($name)) {
13558 $sigapp['name'] = 'Signature';
13559 } else {
13560 $sigapp['name'] = $name;
13561 }
13562 $a = $x * $this->k;
13563 $b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
13564 $c = $w * $this->k;
13565 $d = $h * $this->k;
13566 $sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
13567 return $sigapp;
13568 }
13569
13570 /**
13571 * Enable document timestamping (requires the OpenSSL Library).
13572 * The trusted timestamping improve document security that means that no one should be able to change the document once it has been recorded.
13573 * Use with digital signature only!
13574 * @param $tsa_host (string) Time Stamping Authority (TSA) server (prefixed with 'https://')
13575 * @param $tsa_username (string) Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional)
13576 * @param $tsa_password (string) Specifies the password for TSA authorization (optional)
13577 * @param $tsa_cert (string) Specifies the location of TSA certificate for authorization (optional for cURL)
13578 * @public
13579 * @author Richard Stockinger
13580 * @since 6.0.090 (2014-06-16)
13581 */
13582 public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') {
13583 $this->tsa_data = array();
13584 if (!function_exists('curl_init')) {
13585 $this->Error('Please enable cURL PHP extension!');
13586 }
13587 if (strlen($tsa_host) == 0) {
13588 $this->Error('Please specify the host of Time Stamping Authority (TSA)!');
13589 }
13590 $this->tsa_data['tsa_host'] = $tsa_host;
13591 if (is_file($tsa_username)) {
13592 $this->tsa_data['tsa_auth'] = $tsa_username;
13593 } else {
13594 $this->tsa_data['tsa_username'] = $tsa_username;
13595 }
13596 $this->tsa_data['tsa_password'] = $tsa_password;
13597 $this->tsa_data['tsa_cert'] = $tsa_cert;
13598 $this->tsa_timestamp = true;
13599 }
13600
13601 /**
13602 * NOT YET IMPLEMENTED
13603 * Request TSA for a timestamp
13604 * @param $signature (string) Digital signature as binary string
13605 * @return (string) Timestamped digital signature
13606 * @protected
13607 * @author Richard Stockinger
13608 * @since 6.0.090 (2014-06-16)
13609 */
13610 protected function applyTSA($signature) {
13611 if (!$this->tsa_timestamp) {
13612 return $signature;
13613 }
13614 //@TODO: implement this feature
13615 return $signature;
13616 }
13617
13618 /**
13619 * Create a new page group.
13620 * NOTE: call this function before calling AddPage()
13621 * @param $page (int) starting group page (leave empty for next page).
13622 * @public
13623 * @since 3.0.000 (2008-03-27)
13624 */
13625 public function startPageGroup($page='') {
13626 if (empty($page)) {
13627 $page = $this->page + 1;
13628 }
13629 $this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
13630 }
13631
13632 /**
13633 * Set the starting page number.
13634 * @param $num (int) Starting page number.
13635 * @since 5.9.093 (2011-06-16)
13636 * @public
13637 */
13638 public function setStartingPageNumber($num=1) {
13639 $this->starting_page_number = max(0, intval($num));
13640 }
13641
13642 /**
13643 * Returns the string alias used right align page numbers.
13644 * If the current font is unicode type, the returned string wil contain an additional open curly brace.
13645 * @return string
13646 * @since 5.9.099 (2011-06-27)
13647 * @public
13648 */
13649 public function getAliasRightShift() {
13650 // calculate aproximatively the ratio between widths of aliases and replacements.
13651 $ref = '{'.TCPDF_STATIC::$alias_right_shift.'}{'.TCPDF_STATIC::$alias_tot_pages.'}{'.TCPDF_STATIC::$alias_num_page.'}';
13652 $rep = str_repeat(' ', $this->GetNumChars($ref));
13653 $wrep = $this->GetStringWidth($rep);
13654 if ($wrep > 0) {
13655 $wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
13656 } else {
13657 $wdiff = 1;
13658 }
13659 $sdiff = sprintf('%F', $wdiff);
13660 $alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
13661 if ($this->isUnicodeFont()) {
13662 $alias = '{'.$alias;
13663 }
13664 return $alias;
13665 }
13666
13667 /**
13668 * Returns the string alias used for the total number of pages.
13669 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13670 * This alias will be replaced by the total number of pages in the document.
13671 * @return string
13672 * @since 4.0.018 (2008-08-08)
13673 * @public
13674 */
13675 public function getAliasNbPages() {
13676 if ($this->isUnicodeFont()) {
13677 return '{'.TCPDF_STATIC::$alias_tot_pages.'}';
13678 }
13679 return TCPDF_STATIC::$alias_tot_pages;
13680 }
13681
13682 /**
13683 * Returns the string alias used for the page number.
13684 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13685 * This alias will be replaced by the page number.
13686 * @return string
13687 * @since 4.5.000 (2009-01-02)
13688 * @public
13689 */
13690 public function getAliasNumPage() {
13691 if ($this->isUnicodeFont()) {
13692 return '{'.TCPDF_STATIC::$alias_num_page.'}';
13693 }
13694 return TCPDF_STATIC::$alias_num_page;
13695 }
13696
13697 /**
13698 * Return the alias for the total number of pages in the current page group.
13699 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13700 * This alias will be replaced by the total number of pages in this group.
13701 * @return alias of the current page group
13702 * @public
13703 * @since 3.0.000 (2008-03-27)
13704 */
13705 public function getPageGroupAlias() {
13706 if ($this->isUnicodeFont()) {
13707 return '{'.TCPDF_STATIC::$alias_group_tot_pages.'}';
13708 }
13709 return TCPDF_STATIC::$alias_group_tot_pages;
13710 }
13711
13712 /**
13713 * Return the alias for the page number on the current page group.
13714 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13715 * This alias will be replaced by the page number (relative to the belonging group).
13716 * @return alias of the current page group
13717 * @public
13718 * @since 4.5.000 (2009-01-02)
13719 */
13720 public function getPageNumGroupAlias() {
13721 if ($this->isUnicodeFont()) {
13722 return '{'.TCPDF_STATIC::$alias_group_num_page.'}';
13723 }
13724 return TCPDF_STATIC::$alias_group_num_page;
13725 }
13726
13727 /**
13728 * Return the current page in the group.
13729 * @return current page in the group
13730 * @public
13731 * @since 3.0.000 (2008-03-27)
13732 */
13733 public function getGroupPageNo() {
13734 return $this->pagegroups[$this->currpagegroup];
13735 }
13736
13737 /**
13738 * Returns the current group page number formatted as a string.
13739 * @public
13740 * @since 4.3.003 (2008-11-18)
13741 * @see PaneNo(), formatPageNumber()
13742 */
13743 public function getGroupPageNoFormatted() {
13744 return TCPDF_STATIC::formatPageNumber($this->getGroupPageNo());
13745 }
13746
13747 /**
13748 * Returns the current page number formatted as a string.
13749 * @public
13750 * @since 4.2.005 (2008-11-06)
13751 * @see PaneNo(), formatPageNumber()
13752 */
13753 public function PageNoFormatted() {
13754 return TCPDF_STATIC::formatPageNumber($this->PageNo());
13755 }
13756
13757 /**
13758 * Put pdf layers.
13759 * @protected
13760 * @since 3.0.000 (2008-03-27)
13761 */
13762 protected function _putocg() {
13763 if (empty($this->pdflayers)) {
13764 return;
13765 }
13766 foreach ($this->pdflayers as $key => $layer) {
13767 $this->pdflayers[$key]['objid'] = $this->_newobj();
13768 $out = '<< /Type /OCG';
13769 $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
13770 $out .= ' /Usage <<';
13771 if (isset($layer['print']) AND ($layer['print'] !== NULL)) {
13772 $out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
13773 }
13774 $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
13775 $out .= ' >> >>';
13776 $out .= "\n".'endobj';
13777 $this->_out($out);
13778 }
13779 }
13780
13781 /**
13782 * Start a new pdf layer.
13783 * @param $name (string) Layer name (only a-z letters and numbers). Leave empty for automatic name.
13784 * @param $print (boolean|null) Set to TRUE to print this layer, FALSE to not print and NULL to not set this option
13785 * @param $view (boolean) Set to true to view this layer.
13786 * @param $lock (boolean) If true lock the layer
13787 * @public
13788 * @since 5.9.102 (2011-07-13)
13789 */
13790 public function startLayer($name='', $print=true, $view=true, $lock=true) {
13791 if ($this->state != 2) {
13792 return;
13793 }
13794 $layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
13795 if (empty($name)) {
13796 $name = $layer;
13797 } else {
13798 $name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
13799 }
13800 $this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view, 'lock' => $lock);
13801 $this->openMarkedContent = true;
13802 $this->_out('/OC /'.$layer.' BDC');
13803 }
13804
13805 /**
13806 * End the current PDF layer.
13807 * @public
13808 * @since 5.9.102 (2011-07-13)
13809 */
13810 public function endLayer() {
13811 if ($this->state != 2) {
13812 return;
13813 }
13814 if ($this->openMarkedContent) {
13815 // close existing open marked-content layer
13816 $this->_out('EMC');
13817 $this->openMarkedContent = false;
13818 }
13819 }
13820
13821 /**
13822 * Set the visibility of the successive elements.
13823 * This can be useful, for instance, to put a background
13824 * image or color that will show on screen but won't print.
13825 * @param $v (string) visibility mode. Legal values are: all, print, screen or view.
13826 * @public
13827 * @since 3.0.000 (2008-03-27)
13828 */
13829 public function setVisibility($v) {
13830 if ($this->state != 2) {
13831 return;
13832 }
13833 $this->endLayer();
13834 switch($v) {
13835 case 'print': {
13836 $this->startLayer('Print', true, false);
13837 break;
13838 }
13839 case 'view':
13840 case 'screen': {
13841 $this->startLayer('View', false, true);
13842 break;
13843 }
13844 case 'all': {
13845 $this->_out('');
13846 break;
13847 }
13848 default: {
13849 $this->Error('Incorrect visibility: '.$v);
13850 break;
13851 }
13852 }
13853 }
13854
13855 /**
13856 * Add transparency parameters to the current extgstate
13857 * @param $parms (array) parameters
13858 * @return the number of extgstates
13859 * @protected
13860 * @since 3.0.000 (2008-03-27)
13861 */
13862 protected function addExtGState($parms) {
13863 if ($this->pdfa_mode) {
13864 // transparencies are not allowed in PDF/A mode
13865 return;
13866 }
13867 // check if this ExtGState already exist
13868 foreach ($this->extgstates as $i => $ext) {
13869 if ($ext['parms'] == $parms) {
13870 if ($this->inxobj) {
13871 // we are inside an XObject template
13872 $this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
13873 }
13874 // return reference to existing ExtGState
13875 return $i;
13876 }
13877 }
13878 $n = (count($this->extgstates) + 1);
13879 $this->extgstates[$n] = array('parms' => $parms);
13880 if ($this->inxobj) {
13881 // we are inside an XObject template
13882 $this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
13883 }
13884 return $n;
13885 }
13886
13887 /**
13888 * Add an extgstate
13889 * @param $gs (array) extgstate
13890 * @protected
13891 * @since 3.0.000 (2008-03-27)
13892 */
13893 protected function setExtGState($gs) {
13894 if ($this->pdfa_mode OR ($this->state != 2)) {
13895 // transparency is not allowed in PDF/A mode
13896 return;
13897 }
13898 $this->_out(sprintf('/GS%d gs', $gs));
13899 }
13900
13901 /**
13902 * Put extgstates for object transparency
13903 * @protected
13904 * @since 3.0.000 (2008-03-27)
13905 */
13906 protected function _putextgstates() {
13907 foreach ($this->extgstates as $i => $ext) {
13908 $this->extgstates[$i]['n'] = $this->_newobj();
13909 $out = '<< /Type /ExtGState';
13910 foreach ($ext['parms'] as $k => $v) {
13911 if (is_float($v)) {
13912 $v = sprintf('%F', $v);
13913 } elseif ($v === true) {
13914 $v = 'true';
13915 } elseif ($v === false) {
13916 $v = 'false';
13917 }
13918 $out .= ' /'.$k.' '.$v;
13919 }
13920 $out .= ' >>';
13921 $out .= "\n".'endobj';
13922 $this->_out($out);
13923 }
13924 }
13925
13926 /**
13927 * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
13928 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13929 * @param $stroking (boolean) If true apply overprint for stroking operations.
13930 * @param $nonstroking (boolean) If true apply overprint for painting operations other than stroking.
13931 * @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).
13932 * @public
13933 * @since 5.9.152 (2012-03-23)
13934 */
13935 public function setOverprint($stroking=true, $nonstroking='', $mode=0) {
13936 if ($this->state != 2) {
13937 return;
13938 }
13939 $stroking = $stroking ? true : false;
13940 if (TCPDF_STATIC::empty_string($nonstroking)) {
13941 // default value if not set
13942 $nonstroking = $stroking;
13943 } else {
13944 $nonstroking = $nonstroking ? true : false;
13945 }
13946 if (($mode != 0) AND ($mode != 1)) {
13947 $mode = 0;
13948 }
13949 $this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
13950 $gs = $this->addExtGState($this->overprint);
13951 $this->setExtGState($gs);
13952 }
13953
13954 /**
13955 * Get the overprint mode array (OP, op, OPM).
13956 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13957 * @return array.
13958 * @public
13959 * @since 5.9.152 (2012-03-23)
13960 */
13961 public function getOverprint() {
13962 return $this->overprint;
13963 }
13964
13965 /**
13966 * Set alpha for stroking (CA) and non-stroking (ca) operations.
13967 * @param $stroking (float) Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
13968 * @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
13969 * @param $nonstroking (float) Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
13970 * @param $ais (boolean)
13971 * @public
13972 * @since 3.0.000 (2008-03-27)
13973 */
13974 public function setAlpha($stroking=1, $bm='Normal', $nonstroking='', $ais=false) {
13975 if ($this->pdfa_mode) {
13976 // transparency is not allowed in PDF/A mode
13977 return;
13978 }
13979 $stroking = floatval($stroking);
13980 if (TCPDF_STATIC::empty_string($nonstroking)) {
13981 // default value if not set
13982 $nonstroking = $stroking;
13983 } else {
13984 $nonstroking = floatval($nonstroking);
13985 }
13986 if ($bm[0] == '/') {
13987 // remove trailing slash
13988 $bm = substr($bm, 1);
13989 }
13990 if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
13991 $bm = 'Normal';
13992 }
13993 $ais = $ais ? true : false;
13994 $this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
13995 $gs = $this->addExtGState($this->alpha);
13996 $this->setExtGState($gs);
13997 }
13998
13999 /**
14000 * Get the alpha mode array (CA, ca, BM, AIS).
14001 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14002 * @return array.
14003 * @public
14004 * @since 5.9.152 (2012-03-23)
14005 */
14006 public function getAlpha() {
14007 return $this->alpha;
14008 }
14009
14010 /**
14011 * Set the default JPEG compression quality (1-100)
14012 * @param $quality (int) JPEG quality, integer between 1 and 100
14013 * @public
14014 * @since 3.0.000 (2008-03-27)
14015 */
14016 public function setJPEGQuality($quality) {
14017 if (($quality < 1) OR ($quality > 100)) {
14018 $quality = 75;
14019 }
14020 $this->jpeg_quality = intval($quality);
14021 }
14022
14023 /**
14024 * Set the default number of columns in a row for HTML tables.
14025 * @param $cols (int) number of columns
14026 * @public
14027 * @since 3.0.014 (2008-06-04)
14028 */
14029 public function setDefaultTableColumns($cols=4) {
14030 $this->default_table_columns = intval($cols);
14031 }
14032
14033 /**
14034 * Set the height of the cell (line height) respect the font height.
14035 * @param $h (int) cell proportion respect font height (typical value = 1.25).
14036 * @public
14037 * @since 3.0.014 (2008-06-04)
14038 */
14039 public function setCellHeightRatio($h) {
14040 $this->cell_height_ratio = $h;
14041 }
14042
14043 /**
14044 * return the height of cell repect font height.
14045 * @public
14046 * @since 4.0.012 (2008-07-24)
14047 */
14048 public function getCellHeightRatio() {
14049 return $this->cell_height_ratio;
14050 }
14051
14052 /**
14053 * Set the PDF version (check PDF reference for valid values).
14054 * @param $version (string) PDF document version.
14055 * @public
14056 * @since 3.1.000 (2008-06-09)
14057 */
14058 public function setPDFVersion($version='1.7') {
14059 if ($this->pdfa_mode && $this->pdfa_version == 1 ) {
14060 // PDF/A mode
14061 $this->PDFVersion = '1.4';
14062 } else {
14063 $this->PDFVersion = $version;
14064 }
14065 }
14066
14067 /**
14068 * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
14069 * (see Section 8.1 of PDF reference, "Viewer Preferences").
14070 * <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>
14071 * @param $preferences (array) array of options.
14072 * @author Nicola Asuni
14073 * @public
14074 * @since 3.1.000 (2008-06-09)
14075 */
14076 public function setViewerPreferences($preferences) {
14077 $this->viewer_preferences = $preferences;
14078 }
14079
14080 /**
14081 * Paints color transition registration bars
14082 * @param $x (float) abscissa of the top left corner of the rectangle.
14083 * @param $y (float) ordinate of the top left corner of the rectangle.
14084 * @param $w (float) width of the rectangle.
14085 * @param $h (float) height of the rectangle.
14086 * @param $transition (boolean) if true prints tcolor transitions to white.
14087 * @param $vertical (boolean) if true prints bar vertically.
14088 * @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.
14089 * @author Nicola Asuni
14090 * @since 4.9.000 (2010-03-26)
14091 * @public
14092 */
14093 public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
14094 if (strpos($colors, 'ALLSPOT') !== false) {
14095 // expand spot colors
14096 $spot_colors = '';
14097 foreach ($this->spot_colors as $spot_color_name => $v) {
14098 $spot_colors .= ','.$spot_color_name;
14099 }
14100 if (!empty($spot_colors)) {
14101 $spot_colors = substr($spot_colors, 1);
14102 $colors = str_replace('ALLSPOT', $spot_colors, $colors);
14103 } else {
14104 $colors = str_replace('ALLSPOT', 'NONE', $colors);
14105 }
14106 }
14107 $bars = explode(',', $colors);
14108 $numbars = count($bars); // number of bars to print
14109 if ($numbars <= 0) {
14110 return;
14111 }
14112 // set bar measures
14113 if ($vertical) {
14114 $coords = array(0, 0, 0, 1);
14115 $wb = $w / $numbars; // bar width
14116 $hb = $h; // bar height
14117 $xd = $wb; // delta x
14118 $yd = 0; // delta y
14119 } else {
14120 $coords = array(1, 0, 0, 0);
14121 $wb = $w; // bar width
14122 $hb = $h / $numbars; // bar height
14123 $xd = 0; // delta x
14124 $yd = $hb; // delta y
14125 }
14126 $xb = $x;
14127 $yb = $y;
14128 foreach ($bars as $col) {
14129 switch ($col) {
14130 // set transition colors
14131 case 'A': { // BLACK (GRAYSCALE)
14132 $col_a = array(255);
14133 $col_b = array(0);
14134 break;
14135 }
14136 case 'W': { // WHITE (GRAYSCALE)
14137 $col_a = array(0);
14138 $col_b = array(255);
14139 break;
14140 }
14141 case 'R': { // RED (RGB)
14142 $col_a = array(255,255,255);
14143 $col_b = array(255,0,0);
14144 break;
14145 }
14146 case 'G': { // GREEN (RGB)
14147 $col_a = array(255,255,255);
14148 $col_b = array(0,255,0);
14149 break;
14150 }
14151 case 'B': { // BLUE (RGB)
14152 $col_a = array(255,255,255);
14153 $col_b = array(0,0,255);
14154 break;
14155 }
14156 case 'C': { // CYAN (CMYK)
14157 $col_a = array(0,0,0,0);
14158 $col_b = array(100,0,0,0);
14159 break;
14160 }
14161 case 'M': { // MAGENTA (CMYK)
14162 $col_a = array(0,0,0,0);
14163 $col_b = array(0,100,0,0);
14164 break;
14165 }
14166 case 'Y': { // YELLOW (CMYK)
14167 $col_a = array(0,0,0,0);
14168 $col_b = array(0,0,100,0);
14169 break;
14170 }
14171 case 'K': { // KEY - BLACK (CMYK)
14172 $col_a = array(0,0,0,0);
14173 $col_b = array(0,0,0,100);
14174 break;
14175 }
14176 case 'RGB': { // BLACK REGISTRATION (RGB)
14177 $col_a = array(255,255,255);
14178 $col_b = array(0,0,0);
14179 break;
14180 }
14181 case 'CMYK': { // BLACK REGISTRATION (CMYK)
14182 $col_a = array(0,0,0,0);
14183 $col_b = array(100,100,100,100);
14184 break;
14185 }
14186 case 'ALL': { // SPOT COLOR REGISTRATION
14187 $col_a = array(0,0,0,0,'None');
14188 $col_b = array(100,100,100,100,'All');
14189 break;
14190 }
14191 case 'NONE': { // SKIP THIS COLOR
14192 $col_a = array(0,0,0,0,'None');
14193 $col_b = array(0,0,0,0,'None');
14194 break;
14195 }
14196 default: { // SPECIFIC SPOT COLOR NAME
14197 $col_a = array(0,0,0,0,'None');
14198 $col_b = TCPDF_COLORS::getSpotColor($col, $this->spot_colors);
14199 if ($col_b === false) {
14200 // in case of error defaults to the registration color
14201 $col_b = array(100,100,100,100,'All');
14202 }
14203 break;
14204 }
14205 }
14206 if ($col != 'NONE') {
14207 if ($transition) {
14208 // color gradient
14209 $this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
14210 } else {
14211 $this->SetFillColorArray($col_b);
14212 // colored rectangle
14213 $this->Rect($xb, $yb, $wb, $hb, 'F', array());
14214 }
14215 $xb += $xd;
14216 $yb += $yd;
14217 }
14218 }
14219 }
14220
14221 /**
14222 * Paints crop marks.
14223 * @param $x (float) abscissa of the crop mark center.
14224 * @param $y (float) ordinate of the crop mark center.
14225 * @param $w (float) width of the crop mark.
14226 * @param $h (float) height of the crop mark.
14227 * @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.
14228 * @param $color (array) crop mark color (default spot registration color).
14229 * @author Nicola Asuni
14230 * @since 4.9.000 (2010-03-26)
14231 * @public
14232 */
14233 public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
14234 $this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
14235 $type = strtoupper($type);
14236 $type = preg_replace('/[^A-Z\-\,]*/', '', $type);
14237 // split type in single components
14238 $type = str_replace('-', ',', $type);
14239 $type = str_replace('TL', 'T,L', $type);
14240 $type = str_replace('TR', 'T,R', $type);
14241 $type = str_replace('BL', 'F,L', $type);
14242 $type = str_replace('BR', 'F,R', $type);
14243 $type = str_replace('A', 'T,L', $type);
14244 $type = str_replace('B', 'T,R', $type);
14245 $type = str_replace('T,RO', 'BO', $type);
14246 $type = str_replace('C', 'F,L', $type);
14247 $type = str_replace('D', 'F,R', $type);
14248 $crops = explode(',', strtoupper($type));
14249 // remove duplicates
14250 $crops = array_unique($crops);
14251 $dw = ($w / 4); // horizontal space to leave before the intersection point
14252 $dh = ($h / 4); // vertical space to leave before the intersection point
14253 foreach ($crops as $crop) {
14254 switch ($crop) {
14255 case 'T':
14256 case 'TOP': {
14257 $x1 = $x;
14258 $y1 = ($y - $h);
14259 $x2 = $x;
14260 $y2 = ($y - $dh);
14261 break;
14262 }
14263 case 'F':
14264 case 'BOTTOM': {
14265 $x1 = $x;
14266 $y1 = ($y + $dh);
14267 $x2 = $x;
14268 $y2 = ($y + $h);
14269 break;
14270 }
14271 case 'L':
14272 case 'LEFT': {
14273 $x1 = ($x - $w);
14274 $y1 = $y;
14275 $x2 = ($x - $dw);
14276 $y2 = $y;
14277 break;
14278 }
14279 case 'R':
14280 case 'RIGHT': {
14281 $x1 = ($x + $dw);
14282 $y1 = $y;
14283 $x2 = ($x + $w);
14284 $y2 = $y;
14285 break;
14286 }
14287 }
14288 $this->Line($x1, $y1, $x2, $y2);
14289 }
14290 }
14291
14292 /**
14293 * Paints a registration mark
14294 * @param $x (float) abscissa of the registration mark center.
14295 * @param $y (float) ordinate of the registration mark center.
14296 * @param $r (float) radius of the crop mark.
14297 * @param $double (boolean) if true print two concentric crop marks.
14298 * @param $cola (array) crop mark color (default spot registration color 'All').
14299 * @param $colb (array) second crop mark color (default spot registration color 'None').
14300 * @author Nicola Asuni
14301 * @since 4.9.000 (2010-03-26)
14302 * @public
14303 */
14304 public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
14305 $line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
14306 $this->SetFillColorArray($cola);
14307 $this->PieSector($x, $y, $r, 90, 180, 'F');
14308 $this->PieSector($x, $y, $r, 270, 360, 'F');
14309 $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14310 if ($double) {
14311 $ri = $r * 0.5;
14312 $this->SetFillColorArray($colb);
14313 $this->PieSector($x, $y, $ri, 90, 180, 'F');
14314 $this->PieSector($x, $y, $ri, 270, 360, 'F');
14315 $this->SetFillColorArray($cola);
14316 $this->PieSector($x, $y, $ri, 0, 90, 'F');
14317 $this->PieSector($x, $y, $ri, 180, 270, 'F');
14318 $this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
14319 }
14320 }
14321
14322 /**
14323 * Paints a CMYK registration mark
14324 * @param $x (float) abscissa of the registration mark center.
14325 * @param $y (float) ordinate of the registration mark center.
14326 * @param $r (float) radius of the crop mark.
14327 * @author Nicola Asuni
14328 * @since 6.0.038 (2013-09-30)
14329 * @public
14330 */
14331 public function registrationMarkCMYK($x, $y, $r) {
14332 // line width
14333 $lw = max((0.5 / $this->k),($r / 8));
14334 // internal radius
14335 $ri = ($r * 0.6);
14336 // external radius
14337 $re = ($r * 1.3);
14338 // Cyan
14339 $this->SetFillColorArray(array(100,0,0,0));
14340 $this->PieSector($x, $y, $ri, 270, 360, 'F');
14341 // Magenta
14342 $this->SetFillColorArray(array(0,100,0,0));
14343 $this->PieSector($x, $y, $ri, 0, 90, 'F');
14344 // Yellow
14345 $this->SetFillColorArray(array(0,0,100,0));
14346 $this->PieSector($x, $y, $ri, 90, 180, 'F');
14347 // Key - black
14348 $this->SetFillColorArray(array(0,0,0,100));
14349 $this->PieSector($x, $y, $ri, 180, 270, 'F');
14350 // registration color
14351 $line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
14352 $this->SetFillColorArray(array(100,100,100,100,'All'));
14353 // external circle
14354 $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14355 // cross lines
14356 $this->Line($x, ($y - $re), $x, ($y - $ri));
14357 $this->Line($x, ($y + $ri), $x, ($y + $re));
14358 $this->Line(($x - $re), $y, ($x - $ri), $y);
14359 $this->Line(($x + $ri), $y, ($x + $re), $y);
14360 }
14361
14362 /**
14363 * Paints a linear colour gradient.
14364 * @param $x (float) abscissa of the top left corner of the rectangle.
14365 * @param $y (float) ordinate of the top left corner of the rectangle.
14366 * @param $w (float) width of the rectangle.
14367 * @param $h (float) height of the rectangle.
14368 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14369 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14370 * @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).
14371 * @author Andreas W\FCrmser, Nicola Asuni
14372 * @since 3.1.000 (2008-06-09)
14373 * @public
14374 */
14375 public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
14376 $this->Clip($x, $y, $w, $h);
14377 $this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14378 }
14379
14380 /**
14381 * Paints a radial colour gradient.
14382 * @param $x (float) abscissa of the top left corner of the rectangle.
14383 * @param $y (float) ordinate of the top left corner of the rectangle.
14384 * @param $w (float) width of the rectangle.
14385 * @param $h (float) height of the rectangle.
14386 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14387 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14388 * @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.
14389 * @author Andreas W\FCrmser, Nicola Asuni
14390 * @since 3.1.000 (2008-06-09)
14391 * @public
14392 */
14393 public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
14394 $this->Clip($x, $y, $w, $h);
14395 $this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14396 }
14397
14398 /**
14399 * Paints a coons patch mesh.
14400 * @param $x (float) abscissa of the top left corner of the rectangle.
14401 * @param $y (float) ordinate of the top left corner of the rectangle.
14402 * @param $w (float) width of the rectangle.
14403 * @param $h (float) height of the rectangle.
14404 * @param $col1 (array) first color (lower left corner) (RGB components).
14405 * @param $col2 (array) second color (lower right corner) (RGB components).
14406 * @param $col3 (array) third color (upper right corner) (RGB components).
14407 * @param $col4 (array) fourth color (upper left corner) (RGB components).
14408 * @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>
14409 * @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
14410 * @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
14411 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14412 * @author Andreas W\FCrmser, Nicola Asuni
14413 * @since 3.1.000 (2008-06-09)
14414 * @public
14415 */
14416 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) {
14417 if ($this->pdfa_mode OR ($this->state != 2)) {
14418 return;
14419 }
14420 $this->Clip($x, $y, $w, $h);
14421 $n = count($this->gradients) + 1;
14422 $this->gradients[$n] = array();
14423 $this->gradients[$n]['type'] = 6; //coons patch mesh
14424 $this->gradients[$n]['coords'] = array();
14425 $this->gradients[$n]['antialias'] = $antialias;
14426 $this->gradients[$n]['colors'] = array();
14427 $this->gradients[$n]['transparency'] = false;
14428 //check the coords array if it is the simple array or the multi patch array
14429 if (!isset($coords[0]['f'])) {
14430 //simple array -> convert to multi patch array
14431 if (!isset($col1[1])) {
14432 $col1[1] = $col1[2] = $col1[0];
14433 }
14434 if (!isset($col2[1])) {
14435 $col2[1] = $col2[2] = $col2[0];
14436 }
14437 if (!isset($col3[1])) {
14438 $col3[1] = $col3[2] = $col3[0];
14439 }
14440 if (!isset($col4[1])) {
14441 $col4[1] = $col4[2] = $col4[0];
14442 }
14443 $patch_array[0]['f'] = 0;
14444 $patch_array[0]['points'] = $coords;
14445 $patch_array[0]['colors'][0]['r'] = $col1[0];
14446 $patch_array[0]['colors'][0]['g'] = $col1[1];
14447 $patch_array[0]['colors'][0]['b'] = $col1[2];
14448 $patch_array[0]['colors'][1]['r'] = $col2[0];
14449 $patch_array[0]['colors'][1]['g'] = $col2[1];
14450 $patch_array[0]['colors'][1]['b'] = $col2[2];
14451 $patch_array[0]['colors'][2]['r'] = $col3[0];
14452 $patch_array[0]['colors'][2]['g'] = $col3[1];
14453 $patch_array[0]['colors'][2]['b'] = $col3[2];
14454 $patch_array[0]['colors'][3]['r'] = $col4[0];
14455 $patch_array[0]['colors'][3]['g'] = $col4[1];
14456 $patch_array[0]['colors'][3]['b'] = $col4[2];
14457 } else {
14458 //multi patch array
14459 $patch_array = $coords;
14460 }
14461 $bpcd = 65535; //16 bits per coordinate
14462 //build the data stream
14463 $this->gradients[$n]['stream'] = '';
14464 $count_patch = count($patch_array);
14465 for ($i=0; $i < $count_patch; ++$i) {
14466 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
14467 $count_points = count($patch_array[$i]['points']);
14468 for ($j=0; $j < $count_points; ++$j) {
14469 //each point as 16 bit
14470 $patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
14471 if ($patch_array[$i]['points'][$j] < 0) {
14472 $patch_array[$i]['points'][$j] = 0;
14473 }
14474 if ($patch_array[$i]['points'][$j] > $bpcd) {
14475 $patch_array[$i]['points'][$j] = $bpcd;
14476 }
14477 $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
14478 $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
14479 }
14480 $count_cols = count($patch_array[$i]['colors']);
14481 for ($j=0; $j < $count_cols; ++$j) {
14482 //each color component as 8 bit
14483 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
14484 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
14485 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
14486 }
14487 }
14488 //paint the gradient
14489 $this->_out('/Sh'.$n.' sh');
14490 //restore previous Graphic State
14491 $this->_outRestoreGraphicsState();
14492 if ($this->inxobj) {
14493 // we are inside an XObject template
14494 $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14495 }
14496 }
14497
14498 /**
14499 * Set a rectangular clipping area.
14500 * @param $x (float) abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
14501 * @param $y (float) ordinate of the top left corner of the rectangle.
14502 * @param $w (float) width of the rectangle.
14503 * @param $h (float) height of the rectangle.
14504 * @author Andreas W\FCrmser, Nicola Asuni
14505 * @since 3.1.000 (2008-06-09)
14506 * @protected
14507 */
14508 protected function Clip($x, $y, $w, $h) {
14509 if ($this->state != 2) {
14510 return;
14511 }
14512 if ($this->rtl) {
14513 $x = $this->w - $x - $w;
14514 }
14515 //save current Graphic State
14516 $s = 'q';
14517 //set clipping area
14518 $s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
14519 //set up transformation matrix for gradient
14520 $s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
14521 $this->_out($s);
14522 }
14523
14524 /**
14525 * Output gradient.
14526 * @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)
14527 * @param $coords (array) array of coordinates.
14528 * @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).
14529 * @param $background (array) An array of colour components appropriate to the colour space, specifying a single background colour value.
14530 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14531 * @author Nicola Asuni
14532 * @since 3.1.000 (2008-06-09)
14533 * @public
14534 */
14535 public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
14536 if ($this->pdfa_mode OR ($this->state != 2)) {
14537 return;
14538 }
14539 $n = count($this->gradients) + 1;
14540 $this->gradients[$n] = array();
14541 $this->gradients[$n]['type'] = $type;
14542 $this->gradients[$n]['coords'] = $coords;
14543 $this->gradients[$n]['antialias'] = $antialias;
14544 $this->gradients[$n]['colors'] = array();
14545 $this->gradients[$n]['transparency'] = false;
14546 // color space
14547 $numcolspace = count($stops[0]['color']);
14548 $bcolor = array_values($background);
14549 switch($numcolspace) {
14550 case 5: // SPOT
14551 case 4: { // CMYK
14552 $this->gradients[$n]['colspace'] = 'DeviceCMYK';
14553 if (!empty($background)) {
14554 $this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
14555 }
14556 break;
14557 }
14558 case 3: { // RGB
14559 $this->gradients[$n]['colspace'] = 'DeviceRGB';
14560 if (!empty($background)) {
14561 $this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
14562 }
14563 break;
14564 }
14565 case 1: { // GRAY SCALE
14566 $this->gradients[$n]['colspace'] = 'DeviceGray';
14567 if (!empty($background)) {
14568 $this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
14569 }
14570 break;
14571 }
14572 }
14573 $num_stops = count($stops);
14574 $last_stop_id = $num_stops - 1;
14575 foreach ($stops as $key => $stop) {
14576 $this->gradients[$n]['colors'][$key] = array();
14577 // offset represents a location along the gradient vector
14578 if (isset($stop['offset'])) {
14579 $this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
14580 } else {
14581 if ($key == 0) {
14582 $this->gradients[$n]['colors'][$key]['offset'] = 0;
14583 } elseif ($key == $last_stop_id) {
14584 $this->gradients[$n]['colors'][$key]['offset'] = 1;
14585 } else {
14586 $offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
14587 $this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
14588 }
14589 }
14590 if (isset($stop['opacity'])) {
14591 $this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
14592 if ((!$this->pdfa_mode) AND ($stop['opacity'] < 1)) {
14593 $this->gradients[$n]['transparency'] = true;
14594 }
14595 } else {
14596 $this->gradients[$n]['colors'][$key]['opacity'] = 1;
14597 }
14598 // exponent for the exponential interpolation function
14599 if (isset($stop['exponent'])) {
14600 $this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
14601 } else {
14602 $this->gradients[$n]['colors'][$key]['exponent'] = 1;
14603 }
14604 // set colors
14605 $color = array_values($stop['color']);
14606 switch($numcolspace) {
14607 case 5: // SPOT
14608 case 4: { // CMYK
14609 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
14610 break;
14611 }
14612 case 3: { // RGB
14613 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
14614 break;
14615 }
14616 case 1: { // GRAY SCALE
14617 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
14618 break;
14619 }
14620 }
14621 }
14622 if ($this->gradients[$n]['transparency']) {
14623 // paint luminosity gradient
14624 $this->_out('/TGS'.$n.' gs');
14625 }
14626 //paint the gradient
14627 $this->_out('/Sh'.$n.' sh');
14628 //restore previous Graphic State
14629 $this->_outRestoreGraphicsState();
14630 if ($this->inxobj) {
14631 // we are inside an XObject template
14632 $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14633 }
14634 }
14635
14636 /**
14637 * Output gradient shaders.
14638 * @author Nicola Asuni
14639 * @since 3.1.000 (2008-06-09)
14640 * @protected
14641 */
14642 function _putshaders() {
14643 if ($this->pdfa_mode) {
14644 return;
14645 }
14646 $idt = count($this->gradients); //index for transparency gradients
14647 foreach ($this->gradients as $id => $grad) {
14648 if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
14649 $fc = $this->_newobj();
14650 $out = '<<';
14651 $out .= ' /FunctionType 3';
14652 $out .= ' /Domain [0 1]';
14653 $functions = '';
14654 $bounds = '';
14655 $encode = '';
14656 $i = 1;
14657 $num_cols = count($grad['colors']);
14658 $lastcols = $num_cols - 1;
14659 for ($i = 1; $i < $num_cols; ++$i) {
14660 $functions .= ($fc + $i).' 0 R ';
14661 if ($i < $lastcols) {
14662 $bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
14663 }
14664 $encode .= '0 1 ';
14665 }
14666 $out .= ' /Functions ['.trim($functions).']';
14667 $out .= ' /Bounds ['.trim($bounds).']';
14668 $out .= ' /Encode ['.trim($encode).']';
14669 $out .= ' >>';
14670 $out .= "\n".'endobj';
14671 $this->_out($out);
14672 for ($i = 1; $i < $num_cols; ++$i) {
14673 $this->_newobj();
14674 $out = '<<';
14675 $out .= ' /FunctionType 2';
14676 $out .= ' /Domain [0 1]';
14677 $out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
14678 $out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
14679 $out .= ' /N '.$grad['colors'][$i]['exponent'];
14680 $out .= ' >>';
14681 $out .= "\n".'endobj';
14682 $this->_out($out);
14683 }
14684 // set transparency functions
14685 if ($grad['transparency']) {
14686 $ft = $this->_newobj();
14687 $out = '<<';
14688 $out .= ' /FunctionType 3';
14689 $out .= ' /Domain [0 1]';
14690 $functions = '';
14691 $i = 1;
14692 $num_cols = count($grad['colors']);
14693 for ($i = 1; $i < $num_cols; ++$i) {
14694 $functions .= ($ft + $i).' 0 R ';
14695 }
14696 $out .= ' /Functions ['.trim($functions).']';
14697 $out .= ' /Bounds ['.trim($bounds).']';
14698 $out .= ' /Encode ['.trim($encode).']';
14699 $out .= ' >>';
14700 $out .= "\n".'endobj';
14701 $this->_out($out);
14702 for ($i = 1; $i < $num_cols; ++$i) {
14703 $this->_newobj();
14704 $out = '<<';
14705 $out .= ' /FunctionType 2';
14706 $out .= ' /Domain [0 1]';
14707 $out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
14708 $out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
14709 $out .= ' /N '.$grad['colors'][$i]['exponent'];
14710 $out .= ' >>';
14711 $out .= "\n".'endobj';
14712 $this->_out($out);
14713 }
14714 }
14715 }
14716 // set shading object
14717 $this->_newobj();
14718 $out = '<< /ShadingType '.$grad['type'];
14719 if (isset($grad['colspace'])) {
14720 $out .= ' /ColorSpace /'.$grad['colspace'];
14721 } else {
14722 $out .= ' /ColorSpace /DeviceRGB';
14723 }
14724 if (isset($grad['background']) AND !empty($grad['background'])) {
14725 $out .= ' /Background ['.$grad['background'].']';
14726 }
14727 if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
14728 $out .= ' /AntiAlias true';
14729 }
14730 if ($grad['type'] == 2) {
14731 $out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
14732 $out .= ' /Domain [0 1]';
14733 $out .= ' /Function '.$fc.' 0 R';
14734 $out .= ' /Extend [true true]';
14735 $out .= ' >>';
14736 } elseif ($grad['type'] == 3) {
14737 //x0, y0, r0, x1, y1, r1
14738 //at this this time radius of inner circle is 0
14739 $out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
14740 $out .= ' /Domain [0 1]';
14741 $out .= ' /Function '.$fc.' 0 R';
14742 $out .= ' /Extend [true true]';
14743 $out .= ' >>';
14744 } elseif ($grad['type'] == 6) {
14745 $out .= ' /BitsPerCoordinate 16';
14746 $out .= ' /BitsPerComponent 8';
14747 $out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
14748 $out .= ' /BitsPerFlag 8';
14749 $stream = $this->_getrawstream($grad['stream']);
14750 $out .= ' /Length '.strlen($stream);
14751 $out .= ' >>';
14752 $out .= ' stream'."\n".$stream."\n".'endstream';
14753 }
14754 $out .= "\n".'endobj';
14755 $this->_out($out);
14756 if ($grad['transparency']) {
14757 $shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
14758 $shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
14759 }
14760 $this->gradients[$id]['id'] = $this->n;
14761 // set pattern object
14762 $this->_newobj();
14763 $out = '<< /Type /Pattern /PatternType 2';
14764 $out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
14765 $out .= ' >>';
14766 $out .= "\n".'endobj';
14767 $this->_out($out);
14768 $this->gradients[$id]['pattern'] = $this->n;
14769 // set shading and pattern for transparency mask
14770 if ($grad['transparency']) {
14771 // luminosity pattern
14772 $idgs = $id + $idt;
14773 $this->_newobj();
14774 $this->_out($shading_transparency);
14775 $this->gradients[$idgs]['id'] = $this->n;
14776 $this->_newobj();
14777 $out = '<< /Type /Pattern /PatternType 2';
14778 $out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
14779 $out .= ' >>';
14780 $out .= "\n".'endobj';
14781 $this->_out($out);
14782 $this->gradients[$idgs]['pattern'] = $this->n;
14783 // luminosity XObject
14784 $oid = $this->_newobj();
14785 $this->xobjects['LX'.$oid] = array('n' => $oid);
14786 $filter = '';
14787 $stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
14788 if ($this->compress) {
14789 $filter = ' /Filter /FlateDecode';
14790 $stream = gzcompress($stream);
14791 }
14792 $stream = $this->_getrawstream($stream);
14793 $out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
14794 $out .= ' /Length '.strlen($stream);
14795 $rect = sprintf('%F %F', $this->wPt, $this->hPt);
14796 $out .= ' /BBox [0 0 '.$rect.']';
14797 $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
14798 $out .= ' /Resources <<';
14799 $out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
14800 $out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
14801 $out .= ' >>';
14802 $out .= ' >> ';
14803 $out .= ' stream'."\n".$stream."\n".'endstream';
14804 $out .= "\n".'endobj';
14805 $this->_out($out);
14806 // SMask
14807 $this->_newobj();
14808 $out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
14809 $this->_out($out);
14810 // ExtGState
14811 $this->_newobj();
14812 $out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
14813 $this->_out($out);
14814 $this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
14815 }
14816 }
14817 }
14818
14819 /**
14820 * Draw the sector of a circle.
14821 * It can be used for instance to render pie charts.
14822 * @param $xc (float) abscissa of the center.
14823 * @param $yc (float) ordinate of the center.
14824 * @param $r (float) radius.
14825 * @param $a (float) start angle (in degrees).
14826 * @param $b (float) end angle (in degrees).
14827 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14828 * @param $cw: (float) indicates whether to go clockwise (default: true).
14829 * @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.
14830 * @author Maxime Delorme, Nicola Asuni
14831 * @since 3.1.000 (2008-06-09)
14832 * @public
14833 */
14834 public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
14835 $this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
14836 }
14837
14838 /**
14839 * Draw the sector of an ellipse.
14840 * It can be used for instance to render pie charts.
14841 * @param $xc (float) abscissa of the center.
14842 * @param $yc (float) ordinate of the center.
14843 * @param $rx (float) the x-axis radius.
14844 * @param $ry (float) the y-axis radius.
14845 * @param $a (float) start angle (in degrees).
14846 * @param $b (float) end angle (in degrees).
14847 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14848 * @param $cw: (float) indicates whether to go clockwise.
14849 * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
14850 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of arc.
14851 * @author Maxime Delorme, Nicola Asuni
14852 * @since 3.1.000 (2008-06-09)
14853 * @public
14854 */
14855 public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
14856 if ($this->state != 2) {
14857 return;
14858 }
14859 if ($this->rtl) {
14860 $xc = ($this->w - $xc);
14861 }
14862 $op = TCPDF_STATIC::getPathPaintOperator($style);
14863 if ($op == 'f') {
14864 $line_style = array();
14865 }
14866 if ($cw) {
14867 $d = $b;
14868 $b = (360 - $a + $o);
14869 $a = (360 - $d + $o);
14870 } else {
14871 $b += $o;
14872 $a += $o;
14873 }
14874 $this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
14875 $this->_out($op);
14876 }
14877
14878 /**
14879 * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
14880 * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
14881 * Only vector drawing is supported, not text or bitmap.
14882 * 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).
14883 * @param $file (string) Name of the file containing the image or a '@' character followed by the EPS/AI data string.
14884 * @param $x (float) Abscissa of the upper-left corner.
14885 * @param $y (float) Ordinate of the upper-left corner.
14886 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
14887 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
14888 * @param $link (mixed) URL or identifier returned by AddLink().
14889 * @param $useBoundingBox (boolean) specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
14890 * @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>
14891 * @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>
14892 * @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)))
14893 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
14894 * @param $fixoutvals (boolean) if true remove values outside the bounding box.
14895 * @author Valentin Schmidt, Nicola Asuni
14896 * @since 3.1.000 (2008-06-09)
14897 * @public
14898 */
14899 public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
14900 if ($this->state != 2) {
14901 return;
14902 }
14903 if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
14904 // convert EPS to raster image using GD or ImageMagick libraries
14905 return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14906 }
14907 if ($x === '') {
14908 $x = $this->x;
14909 }
14910 if ($y === '') {
14911 $y = $this->y;
14912 }
14913 // check page for no-write regions and adapt page margins if necessary
14914 list($x, $y) = $this->checkPageRegions($h, $x, $y);
14915 $k = $this->k;
14916 if ($file[0] === '@') { // image from string
14917 $data = substr($file, 1);
14918 } else { // EPS/AI file
14919 $data = TCPDF_STATIC::fileGetContents($file);
14920 }
14921 if ($data === FALSE) {
14922 $this->Error('EPS file not found: '.$file);
14923 }
14924 $regs = array();
14925 // EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
14926 preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
14927 if (count($regs) > 1) {
14928 $version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
14929 if (strpos($version_str, 'Adobe Illustrator') !== false) {
14930 $versexp = explode(' ', $version_str);
14931 $version = (float)array_pop($versexp);
14932 if ($version >= 9) {
14933 $this->Error('This version of Adobe Illustrator file is not supported: '.$file);
14934 }
14935 }
14936 }
14937 // strip binary bytes in front of PS-header
14938 $start = strpos($data, '%!PS-Adobe');
14939 if ($start > 0) {
14940 $data = substr($data, $start);
14941 }
14942 // find BoundingBox params
14943 preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
14944 if (count($regs) > 1) {
14945 list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
14946 } else {
14947 $this->Error('No BoundingBox found in EPS/AI file: '.$file);
14948 }
14949 $start = strpos($data, '%%EndSetup');
14950 if ($start === false) {
14951 $start = strpos($data, '%%EndProlog');
14952 }
14953 if ($start === false) {
14954 $start = strpos($data, '%%BoundingBox');
14955 }
14956 $data = substr($data, $start);
14957 $end = strpos($data, '%%PageTrailer');
14958 if ($end===false) {
14959 $end = strpos($data, 'showpage');
14960 }
14961 if ($end) {
14962 $data = substr($data, 0, $end);
14963 }
14964 // calculate image width and height on document
14965 if (($w <= 0) AND ($h <= 0)) {
14966 $w = ($x2 - $x1) / $k;
14967 $h = ($y2 - $y1) / $k;
14968 } elseif ($w <= 0) {
14969 $w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
14970 } elseif ($h <= 0) {
14971 $h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
14972 }
14973 // fit the image on available space
14974 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
14975 if ($this->rasterize_vector_images) {
14976 // convert EPS to raster image using GD or ImageMagick libraries
14977 return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14978 }
14979 // set scaling factors
14980 $scale_x = $w / (($x2 - $x1) / $k);
14981 $scale_y = $h / (($y2 - $y1) / $k);
14982 // set alignment
14983 $this->img_rb_y = $y + $h;
14984 // set alignment
14985 if ($this->rtl) {
14986 if ($palign == 'L') {
14987 $ximg = $this->lMargin;
14988 } elseif ($palign == 'C') {
14989 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
14990 } elseif ($palign == 'R') {
14991 $ximg = $this->w - $this->rMargin - $w;
14992 } else {
14993 $ximg = $x - $w;
14994 }
14995 $this->img_rb_x = $ximg;
14996 } else {
14997 if ($palign == 'L') {
14998 $ximg = $this->lMargin;
14999 } elseif ($palign == 'C') {
15000 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15001 } elseif ($palign == 'R') {
15002 $ximg = $this->w - $this->rMargin - $w;
15003 } else {
15004 $ximg = $x;
15005 }
15006 $this->img_rb_x = $ximg + $w;
15007 }
15008 if ($useBoundingBox) {
15009 $dx = $ximg * $k - $x1;
15010 $dy = $y * $k - $y1;
15011 } else {
15012 $dx = $ximg * $k;
15013 $dy = $y * $k;
15014 }
15015 // save the current graphic state
15016 $this->_out('q'.$this->epsmarker);
15017 // translate
15018 $this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
15019 // scale
15020 if (isset($scale_x)) {
15021 $this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
15022 }
15023 // handle pc/unix/mac line endings
15024 $lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
15025 $u=0;
15026 $cnt = count($lines);
15027 for ($i=0; $i < $cnt; ++$i) {
15028 $line = $lines[$i];
15029 if (($line == '') OR ($line[0] == '%')) {
15030 continue;
15031 }
15032 $len = strlen($line);
15033 // check for spot color names
15034 $color_name = '';
15035 if (strcasecmp('x', substr(trim($line), -1)) == 0) {
15036 if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
15037 // extract spot color name
15038 $color_name = $matches[0];
15039 // remove color name from string
15040 $line = str_replace(' '.$color_name, '', $line);
15041 // remove pharentesis from color name
15042 $color_name = substr($color_name, 1, -1);
15043 }
15044 }
15045 $chunks = explode(' ', $line);
15046 $cmd = trim(array_pop($chunks));
15047 // RGB
15048 if (($cmd == 'Xa') OR ($cmd == 'XA')) {
15049 $b = array_pop($chunks);
15050 $g = array_pop($chunks);
15051 $r = array_pop($chunks);
15052 $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!
15053 continue;
15054 }
15055 $skip = false;
15056 if ($fixoutvals) {
15057 // check for values outside the bounding box
15058 switch ($cmd) {
15059 case 'm':
15060 case 'l':
15061 case 'L': {
15062 // skip values outside bounding box
15063 foreach ($chunks as $key => $val) {
15064 if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
15065 $skip = true;
15066 } elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
15067 $skip = true;
15068 }
15069 }
15070 }
15071 }
15072 }
15073 switch ($cmd) {
15074 case 'm':
15075 case 'l':
15076 case 'v':
15077 case 'y':
15078 case 'c':
15079 case 'k':
15080 case 'K':
15081 case 'g':
15082 case 'G':
15083 case 's':
15084 case 'S':
15085 case 'J':
15086 case 'j':
15087 case 'w':
15088 case 'M':
15089 case 'd':
15090 case 'n': {
15091 if ($skip) {
15092 break;
15093 }
15094 $this->_out($line);
15095 break;
15096 }
15097 case 'x': {// custom fill color
15098 if (empty($color_name)) {
15099 // CMYK color
15100 list($col_c, $col_m, $col_y, $col_k) = $chunks;
15101 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
15102 } else {
15103 // Spot Color (CMYK + tint)
15104 list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15105 $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15106 $color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15107 $this->_out($color_cmd);
15108 }
15109 break;
15110 }
15111 case 'X': { // custom stroke color
15112 if (empty($color_name)) {
15113 // CMYK color
15114 list($col_c, $col_m, $col_y, $col_k) = $chunks;
15115 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
15116 } else {
15117 // Spot Color (CMYK + tint)
15118 list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15119 $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15120 $color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15121 $this->_out($color_cmd);
15122 }
15123 break;
15124 }
15125 case 'Y':
15126 case 'N':
15127 case 'V':
15128 case 'L':
15129 case 'C': {
15130 if ($skip) {
15131 break;
15132 }
15133 $line[($len - 1)] = strtolower($cmd);
15134 $this->_out($line);
15135 break;
15136 }
15137 case 'b':
15138 case 'B': {
15139 $this->_out($cmd . '*');
15140 break;
15141 }
15142 case 'f':
15143 case 'F': {
15144 if ($u > 0) {
15145 $isU = false;
15146 $max = min(($i + 5), $cnt);
15147 for ($j = ($i + 1); $j < $max; ++$j) {
15148 $isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
15149 }
15150 if ($isU) {
15151 $this->_out('f*');
15152 }
15153 } else {
15154 $this->_out('f*');
15155 }
15156 break;
15157 }
15158 case '*u': {
15159 ++$u;
15160 break;
15161 }
15162 case '*U': {
15163 --$u;
15164 break;
15165 }
15166 }
15167 }
15168 // restore previous graphic state
15169 $this->_out($this->epsmarker.'Q');
15170 if (!empty($border)) {
15171 $bx = $this->x;
15172 $by = $this->y;
15173 $this->x = $ximg;
15174 if ($this->rtl) {
15175 $this->x += $w;
15176 }
15177 $this->y = $y;
15178 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
15179 $this->x = $bx;
15180 $this->y = $by;
15181 }
15182 if ($link) {
15183 $this->Link($ximg, $y, $w, $h, $link, 0);
15184 }
15185 // set pointer to align the next text/objects
15186 switch($align) {
15187 case 'T':{
15188 $this->y = $y;
15189 $this->x = $this->img_rb_x;
15190 break;
15191 }
15192 case 'M':{
15193 $this->y = $y + round($h/2);
15194 $this->x = $this->img_rb_x;
15195 break;
15196 }
15197 case 'B':{
15198 $this->y = $this->img_rb_y;
15199 $this->x = $this->img_rb_x;
15200 break;
15201 }
15202 case 'N':{
15203 $this->SetY($this->img_rb_y);
15204 break;
15205 }
15206 default:{
15207 break;
15208 }
15209 }
15210 $this->endlinex = $this->img_rb_x;
15211 }
15212
15213 /**
15214 * Set document barcode.
15215 * @param $bc (string) barcode
15216 * @public
15217 */
15218 public function setBarcode($bc='') {
15219 $this->barcode = $bc;
15220 }
15221
15222 /**
15223 * Get current barcode.
15224 * @return string
15225 * @public
15226 * @since 4.0.012 (2008-07-24)
15227 */
15228 public function getBarcode() {
15229 return $this->barcode;
15230 }
15231
15232 /**
15233 * Print a Linear Barcode.
15234 * @param $code (string) code to print
15235 * @param $type (string) type of barcode (see tcpdf_barcodes_1d.php for supported formats).
15236 * @param $x (int) x position in user units (empty string = current x position)
15237 * @param $y (int) y position in user units (empty string = current y position)
15238 * @param $w (int) width in user units (empty string = remaining page width)
15239 * @param $h (int) height in user units (empty string = remaining page height)
15240 * @param $xres (float) width of the smallest bar in user units (empty string = default value = 0.4mm)
15241 * @param $style (array) array of options:<ul>
15242 * <li>boolean $style['border'] if true prints a border</li>
15243 * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
15244 * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
15245 * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
15246 * <li>array $style['fgcolor'] color array for bars and text</li>
15247 * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
15248 * <li>boolean $style['text'] if true prints text below the barcode</li>
15249 * <li>string $style['label'] override default label</li>
15250 * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
15251 * <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>
15252 * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
15253 * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
15254 * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
15255 * <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>
15256 * <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>
15257 * @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>
15258 * @author Nicola Asuni
15259 * @since 3.1.000 (2008-06-09)
15260 * @public
15261 */
15262 public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style=array(), $align='') {
15263 if (TCPDF_STATIC::empty_string(trim($code))) {
15264 return;
15265 }
15266 require_once(dirname(__FILE__).'/tcpdf_barcodes_1d.php');
15267 // save current graphic settings
15268 $gvars = $this->getGraphicVars();
15269 // create new barcode object
15270 $barcodeobj = new TCPDFBarcode($code, $type);
15271 $arrcode = $barcodeobj->getBarcodeArray();
15272 if (($arrcode === false) OR empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
15273 $this->Error('Error in 1D barcode string');
15274 }
15275 if ($arrcode['maxh'] <= 0) {
15276 $arrcode['maxh'] = 1;
15277 }
15278 // set default values
15279 if (!isset($style['position'])) {
15280 $style['position'] = '';
15281 } elseif ($style['position'] == 'S') {
15282 // keep this for backward compatibility
15283 $style['position'] = '';
15284 $style['stretch'] = true;
15285 }
15286 if (!isset($style['fitwidth'])) {
15287 if (!isset($style['stretch'])) {
15288 $style['fitwidth'] = true;
15289 } else {
15290 $style['fitwidth'] = false;
15291 }
15292 }
15293 if ($style['fitwidth']) {
15294 // disable stretch
15295 $style['stretch'] = false;
15296 }
15297 if (!isset($style['stretch'])) {
15298 if (($w === '') OR ($w <= 0)) {
15299 $style['stretch'] = false;
15300 } else {
15301 $style['stretch'] = true;
15302 }
15303 }
15304 if (!isset($style['fgcolor'])) {
15305 $style['fgcolor'] = array(0,0,0); // default black
15306 }
15307 if (!isset($style['bgcolor'])) {
15308 $style['bgcolor'] = false; // default transparent
15309 }
15310 if (!isset($style['border'])) {
15311 $style['border'] = false;
15312 }
15313 $fontsize = 0;
15314 if (!isset($style['text'])) {
15315 $style['text'] = false;
15316 }
15317 if ($style['text'] AND isset($style['font'])) {
15318 if (isset($style['fontsize'])) {
15319 $fontsize = $style['fontsize'];
15320 }
15321 $this->SetFont($style['font'], '', $fontsize);
15322 }
15323 if (!isset($style['stretchtext'])) {
15324 $style['stretchtext'] = 4;
15325 }
15326 if ($x === '') {
15327 $x = $this->x;
15328 }
15329 if ($y === '') {
15330 $y = $this->y;
15331 }
15332 // check page for no-write regions and adapt page margins if necessary
15333 list($x, $y) = $this->checkPageRegions($h, $x, $y);
15334 if (($w === '') OR ($w <= 0)) {
15335 if ($this->rtl) {
15336 $w = $x - $this->lMargin;
15337 } else {
15338 $w = $this->w - $this->rMargin - $x;
15339 }
15340 }
15341 // padding
15342 if (!isset($style['padding'])) {
15343 $padding = 0;
15344 } elseif ($style['padding'] === 'auto') {
15345 $padding = 10 * ($w / ($arrcode['maxw'] + 20));
15346 } else {
15347 $padding = floatval($style['padding']);
15348 }
15349 // horizontal padding
15350 if (!isset($style['hpadding'])) {
15351 $hpadding = $padding;
15352 } elseif ($style['hpadding'] === 'auto') {
15353 $hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
15354 } else {
15355 $hpadding = floatval($style['hpadding']);
15356 }
15357 // vertical padding
15358 if (!isset($style['vpadding'])) {
15359 $vpadding = $padding;
15360 } elseif ($style['vpadding'] === 'auto') {
15361 $vpadding = ($hpadding / 2);
15362 } else {
15363 $vpadding = floatval($style['vpadding']);
15364 }
15365 // calculate xres (single bar width)
15366 $max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
15367 if ($style['stretch']) {
15368 $xres = $max_xres;
15369 } else {
15370 if (TCPDF_STATIC::empty_string($xres)) {
15371 $xres = (0.141 * $this->k); // default bar width = 0.4 mm
15372 }
15373 if ($xres > $max_xres) {
15374 // correct xres to fit on $w
15375 $xres = $max_xres;
15376 }
15377 if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
15378 OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
15379 $hpadding = 10 * $xres;
15380 if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
15381 $vpadding = ($hpadding / 2);
15382 }
15383 }
15384 }
15385 if ($style['fitwidth']) {
15386 $wold = $w;
15387 $w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
15388 if (isset($style['cellfitalign'])) {
15389 switch ($style['cellfitalign']) {
15390 case 'L': {
15391 if ($this->rtl) {
15392 $x -= ($wold - $w);
15393 }
15394 break;
15395 }
15396 case 'R': {
15397 if (!$this->rtl) {
15398 $x += ($wold - $w);
15399 }
15400 break;
15401 }
15402 case 'C': {
15403 if ($this->rtl) {
15404 $x -= (($wold - $w) / 2);
15405 } else {
15406 $x += (($wold - $w) / 2);
15407 }
15408 break;
15409 }
15410 default : {
15411 break;
15412 }
15413 }
15414 }
15415 }
15416 $text_height = $this->getCellHeight($fontsize / $this->k);
15417 // height
15418 if (($h === '') OR ($h <= 0)) {
15419 // set default height
15420 $h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
15421 }
15422 $barh = $h - $text_height - (2 * $vpadding);
15423 if ($barh <=0) {
15424 // try to reduce font or padding to fit barcode on available height
15425 if ($text_height > $h) {
15426 $fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
15427 $text_height = $this->getCellHeight($fontsize / $this->k);
15428 $this->SetFont($style['font'], '', $fontsize);
15429 }
15430 if ($vpadding > 0) {
15431 $vpadding = (($h - $text_height) / 4);
15432 }
15433 $barh = $h - $text_height - (2 * $vpadding);
15434 }
15435 // fit the barcode on available space
15436 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15437 // set alignment
15438 $this->img_rb_y = $y + $h;
15439 // set alignment
15440 if ($this->rtl) {
15441 if ($style['position'] == 'L') {
15442 $xpos = $this->lMargin;
15443 } elseif ($style['position'] == 'C') {
15444 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15445 } elseif ($style['position'] == 'R') {
15446 $xpos = $this->w - $this->rMargin - $w;
15447 } else {
15448 $xpos = $x - $w;
15449 }
15450 $this->img_rb_x = $xpos;
15451 } else {
15452 if ($style['position'] == 'L') {
15453 $xpos = $this->lMargin;
15454 } elseif ($style['position'] == 'C') {
15455 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15456 } elseif ($style['position'] == 'R') {
15457 $xpos = $this->w - $this->rMargin - $w;
15458 } else {
15459 $xpos = $x;
15460 }
15461 $this->img_rb_x = $xpos + $w;
15462 }
15463 $xpos_rect = $xpos;
15464 if (!isset($style['align'])) {
15465 $style['align'] = 'C';
15466 }
15467 switch ($style['align']) {
15468 case 'L': {
15469 $xpos = $xpos_rect + $hpadding;
15470 break;
15471 }
15472 case 'R': {
15473 $xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
15474 break;
15475 }
15476 case 'C':
15477 default : {
15478 $xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
15479 break;
15480 }
15481 }
15482 $xpos_text = $xpos;
15483 // barcode is always printed in LTR direction
15484 $tempRTL = $this->rtl;
15485 $this->rtl = false;
15486 // print background color
15487 if ($style['bgcolor']) {
15488 $this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15489 } elseif ($style['border']) {
15490 $this->Rect($xpos_rect, $y, $w, $h, 'D');
15491 }
15492 // set foreground color
15493 $this->SetDrawColorArray($style['fgcolor']);
15494 $this->SetTextColorArray($style['fgcolor']);
15495 // print bars
15496 foreach ($arrcode['bcode'] as $k => $v) {
15497 $bw = ($v['w'] * $xres);
15498 if ($v['t']) {
15499 // draw a vertical bar
15500 $ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
15501 $this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
15502 }
15503 $xpos += $bw;
15504 }
15505 // print text
15506 if ($style['text']) {
15507 if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
15508 $label = $style['label'];
15509 } else {
15510 $label = $code;
15511 }
15512 $txtwidth = ($arrcode['maxw'] * $xres);
15513 if ($this->GetStringWidth($label) > $txtwidth) {
15514 $style['stretchtext'] = 2;
15515 }
15516 // print text
15517 $this->x = $xpos_text;
15518 $this->y = $y + $vpadding + $barh;
15519 $cellpadding = $this->cell_padding;
15520 $this->SetCellPadding(0);
15521 $this->Cell($txtwidth, '', $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
15522 $this->cell_padding = $cellpadding;
15523 }
15524 // restore original direction
15525 $this->rtl = $tempRTL;
15526 // restore previous settings
15527 $this->setGraphicVars($gvars);
15528 // set pointer to align the next text/objects
15529 switch($align) {
15530 case 'T':{
15531 $this->y = $y;
15532 $this->x = $this->img_rb_x;
15533 break;
15534 }
15535 case 'M':{
15536 $this->y = $y + round($h / 2);
15537 $this->x = $this->img_rb_x;
15538 break;
15539 }
15540 case 'B':{
15541 $this->y = $this->img_rb_y;
15542 $this->x = $this->img_rb_x;
15543 break;
15544 }
15545 case 'N':{
15546 $this->SetY($this->img_rb_y);
15547 break;
15548 }
15549 default:{
15550 break;
15551 }
15552 }
15553 $this->endlinex = $this->img_rb_x;
15554 }
15555
15556 /**
15557 * Print 2D Barcode.
15558 * @param $code (string) code to print
15559 * @param $type (string) type of barcode (see tcpdf_barcodes_2d.php for supported formats).
15560 * @param $x (int) x position in user units
15561 * @param $y (int) y position in user units
15562 * @param $w (int) width in user units
15563 * @param $h (int) height in user units
15564 * @param $style (array) array of options:<ul>
15565 * <li>boolean $style['border'] if true prints a border around the barcode</li>
15566 * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
15567 * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
15568 * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
15569 * <li>int $style['module_width'] width of a single module in points</li>
15570 * <li>int $style['module_height'] height of a single module in points</li>
15571 * <li>array $style['fgcolor'] color array for bars and text</li>
15572 * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
15573 * <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li>
15574 * @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>
15575 * @param $distort (boolean) if true distort the barcode to fit width and height, otherwise preserve aspect ratio
15576 * @author Nicola Asuni
15577 * @since 4.5.037 (2009-04-07)
15578 * @public
15579 */
15580 public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style=array(), $align='', $distort=false) {
15581 if (TCPDF_STATIC::empty_string(trim($code))) {
15582 return;
15583 }
15584 require_once(dirname(__FILE__).'/tcpdf_barcodes_2d.php');
15585 // save current graphic settings
15586 $gvars = $this->getGraphicVars();
15587 // create new barcode object
15588 $barcodeobj = new TCPDF2DBarcode($code, $type);
15589 $arrcode = $barcodeobj->getBarcodeArray();
15590 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)) {
15591 $this->Error('Error in 2D barcode string');
15592 }
15593 // set default values
15594 if (!isset($style['position'])) {
15595 $style['position'] = '';
15596 }
15597 if (!isset($style['fgcolor'])) {
15598 $style['fgcolor'] = array(0,0,0); // default black
15599 }
15600 if (!isset($style['bgcolor'])) {
15601 $style['bgcolor'] = false; // default transparent
15602 }
15603 if (!isset($style['border'])) {
15604 $style['border'] = false;
15605 }
15606 // padding
15607 if (!isset($style['padding'])) {
15608 $style['padding'] = 0;
15609 } elseif ($style['padding'] === 'auto') {
15610 $style['padding'] = 4;
15611 }
15612 if (!isset($style['hpadding'])) {
15613 $style['hpadding'] = $style['padding'];
15614 } elseif ($style['hpadding'] === 'auto') {
15615 $style['hpadding'] = 4;
15616 }
15617 if (!isset($style['vpadding'])) {
15618 $style['vpadding'] = $style['padding'];
15619 } elseif ($style['vpadding'] === 'auto') {
15620 $style['vpadding'] = 4;
15621 }
15622 $hpad = (2 * $style['hpadding']);
15623 $vpad = (2 * $style['vpadding']);
15624 // cell (module) dimension
15625 if (!isset($style['module_width'])) {
15626 $style['module_width'] = 1; // width of a single module in points
15627 }
15628 if (!isset($style['module_height'])) {
15629 $style['module_height'] = 1; // height of a single module in points
15630 }
15631 if ($x === '') {
15632 $x = $this->x;
15633 }
15634 if ($y === '') {
15635 $y = $this->y;
15636 }
15637 // check page for no-write regions and adapt page margins if necessary
15638 list($x, $y) = $this->checkPageRegions($h, $x, $y);
15639 // number of barcode columns and rows
15640 $rows = $arrcode['num_rows'];
15641 $cols = $arrcode['num_cols'];
15642 if (($rows <= 0) || ($cols <= 0)){
15643 $this->Error('Error in 2D barcode string');
15644 }
15645 // module width and height
15646 $mw = $style['module_width'];
15647 $mh = $style['module_height'];
15648 if (($mw <= 0) OR ($mh <= 0)) {
15649 $this->Error('Error in 2D barcode string');
15650 }
15651 // get max dimensions
15652 if ($this->rtl) {
15653 $maxw = $x - $this->lMargin;
15654 } else {
15655 $maxw = $this->w - $this->rMargin - $x;
15656 }
15657 $maxh = ($this->h - $this->tMargin - $this->bMargin);
15658 $ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
15659 $ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
15660 if (!$distort) {
15661 if (($maxw * $ratioHW) > $maxh) {
15662 $maxw = $maxh * $ratioWH;
15663 }
15664 if (($maxh * $ratioWH) > $maxw) {
15665 $maxh = $maxw * $ratioHW;
15666 }
15667 }
15668 // set maximum dimensions
15669 if ($w > $maxw) {
15670 $w = $maxw;
15671 }
15672 if ($h > $maxh) {
15673 $h = $maxh;
15674 }
15675 // set dimensions
15676 if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
15677 $w = ($cols + $hpad) * ($mw / $this->k);
15678 $h = ($rows + $vpad) * ($mh / $this->k);
15679 } elseif (($w === '') OR ($w <= 0)) {
15680 $w = $h * $ratioWH;
15681 } elseif (($h === '') OR ($h <= 0)) {
15682 $h = $w * $ratioHW;
15683 }
15684 // barcode size (excluding padding)
15685 $bw = ($w * $cols) / ($cols + $hpad);
15686 $bh = ($h * $rows) / ($rows + $vpad);
15687 // dimension of single barcode cell unit
15688 $cw = $bw / $cols;
15689 $ch = $bh / $rows;
15690 if (!$distort) {
15691 if (($cw / $ch) > ($mw / $mh)) {
15692 // correct horizontal distortion
15693 $cw = $ch * $mw / $mh;
15694 $bw = $cw * $cols;
15695 $style['hpadding'] = ($w - $bw) / (2 * $cw);
15696 } else {
15697 // correct vertical distortion
15698 $ch = $cw * $mh / $mw;
15699 $bh = $ch * $rows;
15700 $style['vpadding'] = ($h - $bh) / (2 * $ch);
15701 }
15702 }
15703 // fit the barcode on available space
15704 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15705 // set alignment
15706 $this->img_rb_y = $y + $h;
15707 // set alignment
15708 if ($this->rtl) {
15709 if ($style['position'] == 'L') {
15710 $xpos = $this->lMargin;
15711 } elseif ($style['position'] == 'C') {
15712 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15713 } elseif ($style['position'] == 'R') {
15714 $xpos = $this->w - $this->rMargin - $w;
15715 } else {
15716 $xpos = $x - $w;
15717 }
15718 $this->img_rb_x = $xpos;
15719 } else {
15720 if ($style['position'] == 'L') {
15721 $xpos = $this->lMargin;
15722 } elseif ($style['position'] == 'C') {
15723 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15724 } elseif ($style['position'] == 'R') {
15725 $xpos = $this->w - $this->rMargin - $w;
15726 } else {
15727 $xpos = $x;
15728 }
15729 $this->img_rb_x = $xpos + $w;
15730 }
15731 $xstart = $xpos + ($style['hpadding'] * $cw);
15732 $ystart = $y + ($style['vpadding'] * $ch);
15733 // barcode is always printed in LTR direction
15734 $tempRTL = $this->rtl;
15735 $this->rtl = false;
15736 // print background color
15737 if ($style['bgcolor']) {
15738 $this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15739 } elseif ($style['border']) {
15740 $this->Rect($xpos, $y, $w, $h, 'D');
15741 }
15742 // set foreground color
15743 $this->SetDrawColorArray($style['fgcolor']);
15744 // print barcode cells
15745 // for each row
15746 for ($r = 0; $r < $rows; ++$r) {
15747 $xr = $xstart;
15748 // for each column
15749 for ($c = 0; $c < $cols; ++$c) {
15750 if ($arrcode['bcode'][$r][$c] == 1) {
15751 // draw a single barcode cell
15752 $this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
15753 }
15754 $xr += $cw;
15755 }
15756 $ystart += $ch;
15757 }
15758 // restore original direction
15759 $this->rtl = $tempRTL;
15760 // restore previous settings
15761 $this->setGraphicVars($gvars);
15762 // set pointer to align the next text/objects
15763 switch($align) {
15764 case 'T':{
15765 $this->y = $y;
15766 $this->x = $this->img_rb_x;
15767 break;
15768 }
15769 case 'M':{
15770 $this->y = $y + round($h/2);
15771 $this->x = $this->img_rb_x;
15772 break;
15773 }
15774 case 'B':{
15775 $this->y = $this->img_rb_y;
15776 $this->x = $this->img_rb_x;
15777 break;
15778 }
15779 case 'N':{
15780 $this->SetY($this->img_rb_y);
15781 break;
15782 }
15783 default:{
15784 break;
15785 }
15786 }
15787 $this->endlinex = $this->img_rb_x;
15788 }
15789
15790 /**
15791 * Returns an array containing current margins:
15792 * <ul>
15793 <li>$ret['left'] = left margin</li>
15794 <li>$ret['right'] = right margin</li>
15795 <li>$ret['top'] = top margin</li>
15796 <li>$ret['bottom'] = bottom margin</li>
15797 <li>$ret['header'] = header margin</li>
15798 <li>$ret['footer'] = footer margin</li>
15799 <li>$ret['cell'] = cell padding array</li>
15800 <li>$ret['padding_left'] = cell left padding</li>
15801 <li>$ret['padding_top'] = cell top padding</li>
15802 <li>$ret['padding_right'] = cell right padding</li>
15803 <li>$ret['padding_bottom'] = cell bottom padding</li>
15804 * </ul>
15805 * @return array containing all margins measures
15806 * @public
15807 * @since 3.2.000 (2008-06-23)
15808 */
15809 public function getMargins() {
15810 $ret = array(
15811 'left' => $this->lMargin,
15812 'right' => $this->rMargin,
15813 'top' => $this->tMargin,
15814 'bottom' => $this->bMargin,
15815 'header' => $this->header_margin,
15816 'footer' => $this->footer_margin,
15817 'cell' => $this->cell_padding,
15818 'padding_left' => $this->cell_padding['L'],
15819 'padding_top' => $this->cell_padding['T'],
15820 'padding_right' => $this->cell_padding['R'],
15821 'padding_bottom' => $this->cell_padding['B']
15822 );
15823 return $ret;
15824 }
15825
15826 /**
15827 * Returns an array containing original margins:
15828 * <ul>
15829 <li>$ret['left'] = left margin</li>
15830 <li>$ret['right'] = right margin</li>
15831 * </ul>
15832 * @return array containing all margins measures
15833 * @public
15834 * @since 4.0.012 (2008-07-24)
15835 */
15836 public function getOriginalMargins() {
15837 $ret = array(
15838 'left' => $this->original_lMargin,
15839 'right' => $this->original_rMargin
15840 );
15841 return $ret;
15842 }
15843
15844 /**
15845 * Returns the current font size.
15846 * @return current font size
15847 * @public
15848 * @since 3.2.000 (2008-06-23)
15849 */
15850 public function getFontSize() {
15851 return $this->FontSize;
15852 }
15853
15854 /**
15855 * Returns the current font size in points unit.
15856 * @return current font size in points unit
15857 * @public
15858 * @since 3.2.000 (2008-06-23)
15859 */
15860 public function getFontSizePt() {
15861 return $this->FontSizePt;
15862 }
15863
15864 /**
15865 * Returns the current font family name.
15866 * @return string current font family name
15867 * @public
15868 * @since 4.3.008 (2008-12-05)
15869 */
15870 public function getFontFamily() {
15871 return $this->FontFamily;
15872 }
15873
15874 /**
15875 * Returns the current font style.
15876 * @return string current font style
15877 * @public
15878 * @since 4.3.008 (2008-12-05)
15879 */
15880 public function getFontStyle() {
15881 return $this->FontStyle;
15882 }
15883
15884 /**
15885 * Cleanup HTML code (requires HTML Tidy library).
15886 * @param $html (string) htmlcode to fix
15887 * @param $default_css (string) CSS commands to add
15888 * @param $tagvs (array) parameters for setHtmlVSpace method
15889 * @param $tidy_options (array) options for tidy_parse_string function
15890 * @return string XHTML code cleaned up
15891 * @author Nicola Asuni
15892 * @public
15893 * @since 5.9.017 (2010-11-16)
15894 * @see setHtmlVSpace()
15895 */
15896 public function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='') {
15897 return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces);
15898 }
15899
15900 /**
15901 * Returns the border width from CSS property
15902 * @param $width (string) border width
15903 * @return int with in user units
15904 * @protected
15905 * @since 5.7.000 (2010-08-02)
15906 */
15907 protected function getCSSBorderWidth($width) {
15908 if ($width == 'thin') {
15909 $width = (2 / $this->k);
15910 } elseif ($width == 'medium') {
15911 $width = (4 / $this->k);
15912 } elseif ($width == 'thick') {
15913 $width = (6 / $this->k);
15914 } else {
15915 $width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
15916 }
15917 return $width;
15918 }
15919
15920 /**
15921 * Returns the border dash style from CSS property
15922 * @param $style (string) border style to convert
15923 * @return int sash style (return -1 in case of none or hidden border)
15924 * @protected
15925 * @since 5.7.000 (2010-08-02)
15926 */
15927 protected function getCSSBorderDashStyle($style) {
15928 switch (strtolower($style)) {
15929 case 'none':
15930 case 'hidden': {
15931 $dash = -1;
15932 break;
15933 }
15934 case 'dotted': {
15935 $dash = 1;
15936 break;
15937 }
15938 case 'dashed': {
15939 $dash = 3;
15940 break;
15941 }
15942 case 'double':
15943 case 'groove':
15944 case 'ridge':
15945 case 'inset':
15946 case 'outset':
15947 case 'solid':
15948 default: {
15949 $dash = 0;
15950 break;
15951 }
15952 }
15953 return $dash;
15954 }
15955
15956 /**
15957 * Returns the border style array from CSS border properties
15958 * @param $cssborder (string) border properties
15959 * @return array containing border properties
15960 * @protected
15961 * @since 5.7.000 (2010-08-02)
15962 */
15963 protected function getCSSBorderStyle($cssborder) {
15964 $bprop = preg_split('/[\s]+/', trim($cssborder));
15965 $border = array(); // value to be returned
15966 switch (count($bprop)) {
15967 case 3: {
15968 $width = $bprop[0];
15969 $style = $bprop[1];
15970 $color = $bprop[2];
15971 break;
15972 }
15973 case 2: {
15974 $width = 'medium';
15975 $style = $bprop[0];
15976 $color = $bprop[1];
15977 break;
15978 }
15979 case 1: {
15980 $width = 'medium';
15981 $style = $bprop[0];
15982 $color = 'black';
15983 break;
15984 }
15985 default: {
15986 $width = 'medium';
15987 $style = 'solid';
15988 $color = 'black';
15989 break;
15990 }
15991 }
15992 if ($style == 'none') {
15993 return array();
15994 }
15995 $border['cap'] = 'square';
15996 $border['join'] = 'miter';
15997 $border['dash'] = $this->getCSSBorderDashStyle($style);
15998 if ($border['dash'] < 0) {
15999 return array();
16000 }
16001 $border['width'] = $this->getCSSBorderWidth($width);
16002 $border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
16003 return $border;
16004 }
16005
16006 /**
16007 * Get the internal Cell padding from CSS attribute.
16008 * @param $csspadding (string) padding properties
16009 * @param $width (float) width of the containing element
16010 * @return array of cell paddings
16011 * @public
16012 * @since 5.9.000 (2010-10-04)
16013 */
16014 public function getCSSPadding($csspadding, $width=0) {
16015 $padding = preg_split('/[\s]+/', trim($csspadding));
16016 $cell_padding = array(); // value to be returned
16017 switch (count($padding)) {
16018 case 4: {
16019 $cell_padding['T'] = $padding[0];
16020 $cell_padding['R'] = $padding[1];
16021 $cell_padding['B'] = $padding[2];
16022 $cell_padding['L'] = $padding[3];
16023 break;
16024 }
16025 case 3: {
16026 $cell_padding['T'] = $padding[0];
16027 $cell_padding['R'] = $padding[1];
16028 $cell_padding['B'] = $padding[2];
16029 $cell_padding['L'] = $padding[1];
16030 break;
16031 }
16032 case 2: {
16033 $cell_padding['T'] = $padding[0];
16034 $cell_padding['R'] = $padding[1];
16035 $cell_padding['B'] = $padding[0];
16036 $cell_padding['L'] = $padding[1];
16037 break;
16038 }
16039 case 1: {
16040 $cell_padding['T'] = $padding[0];
16041 $cell_padding['R'] = $padding[0];
16042 $cell_padding['B'] = $padding[0];
16043 $cell_padding['L'] = $padding[0];
16044 break;
16045 }
16046 default: {
16047 return $this->cell_padding;
16048 }
16049 }
16050 if ($width == 0) {
16051 $width = $this->w - $this->lMargin - $this->rMargin;
16052 }
16053 $cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
16054 $cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
16055 $cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
16056 $cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
16057 return $cell_padding;
16058 }
16059
16060 /**
16061 * Get the internal Cell margin from CSS attribute.
16062 * @param $cssmargin (string) margin properties
16063 * @param $width (float) width of the containing element
16064 * @return array of cell margins
16065 * @public
16066 * @since 5.9.000 (2010-10-04)
16067 */
16068 public function getCSSMargin($cssmargin, $width=0) {
16069 $margin = preg_split('/[\s]+/', trim($cssmargin));
16070 $cell_margin = array(); // value to be returned
16071 switch (count($margin)) {
16072 case 4: {
16073 $cell_margin['T'] = $margin[0];
16074 $cell_margin['R'] = $margin[1];
16075 $cell_margin['B'] = $margin[2];
16076 $cell_margin['L'] = $margin[3];
16077 break;
16078 }
16079 case 3: {
16080 $cell_margin['T'] = $margin[0];
16081 $cell_margin['R'] = $margin[1];
16082 $cell_margin['B'] = $margin[2];
16083 $cell_margin['L'] = $margin[1];
16084 break;
16085 }
16086 case 2: {
16087 $cell_margin['T'] = $margin[0];
16088 $cell_margin['R'] = $margin[1];
16089 $cell_margin['B'] = $margin[0];
16090 $cell_margin['L'] = $margin[1];
16091 break;
16092 }
16093 case 1: {
16094 $cell_margin['T'] = $margin[0];
16095 $cell_margin['R'] = $margin[0];
16096 $cell_margin['B'] = $margin[0];
16097 $cell_margin['L'] = $margin[0];
16098 break;
16099 }
16100 default: {
16101 return $this->cell_margin;
16102 }
16103 }
16104 if ($width == 0) {
16105 $width = $this->w - $this->lMargin - $this->rMargin;
16106 }
16107 $cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
16108 $cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
16109 $cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
16110 $cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
16111 return $cell_margin;
16112 }
16113
16114 /**
16115 * Get the border-spacing from CSS attribute.
16116 * @param $cssbspace (string) border-spacing CSS properties
16117 * @param $width (float) width of the containing element
16118 * @return array of border spacings
16119 * @public
16120 * @since 5.9.010 (2010-10-27)
16121 */
16122 public function getCSSBorderMargin($cssbspace, $width=0) {
16123 $space = preg_split('/[\s]+/', trim($cssbspace));
16124 $border_spacing = array(); // value to be returned
16125 switch (count($space)) {
16126 case 2: {
16127 $border_spacing['H'] = $space[0];
16128 $border_spacing['V'] = $space[1];
16129 break;
16130 }
16131 case 1: {
16132 $border_spacing['H'] = $space[0];
16133 $border_spacing['V'] = $space[0];
16134 break;
16135 }
16136 default: {
16137 return array('H' => 0, 'V' => 0);
16138 }
16139 }
16140 if ($width == 0) {
16141 $width = $this->w - $this->lMargin - $this->rMargin;
16142 }
16143 $border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
16144 $border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
16145 return $border_spacing;
16146 }
16147
16148 /**
16149 * Returns the letter-spacing value from CSS value
16150 * @param $spacing (string) letter-spacing value
16151 * @param $parent (float) font spacing (tracking) value of the parent element
16152 * @return float quantity to increases or decreases the space between characters in a text.
16153 * @protected
16154 * @since 5.9.000 (2010-10-02)
16155 */
16156 protected function getCSSFontSpacing($spacing, $parent=0) {
16157 $val = 0; // value to be returned
16158 $spacing = trim($spacing);
16159 switch ($spacing) {
16160 case 'normal': {
16161 $val = 0;
16162 break;
16163 }
16164 case 'inherit': {
16165 if ($parent == 'normal') {
16166 $val = 0;
16167 } else {
16168 $val = $parent;
16169 }
16170 break;
16171 }
16172 default: {
16173 $val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
16174 }
16175 }
16176 return $val;
16177 }
16178
16179 /**
16180 * Returns the percentage of font stretching from CSS value
16181 * @param $stretch (string) stretch mode
16182 * @param $parent (float) stretch value of the parent element
16183 * @return float font stretching percentage
16184 * @protected
16185 * @since 5.9.000 (2010-10-02)
16186 */
16187 protected function getCSSFontStretching($stretch, $parent=100) {
16188 $val = 100; // value to be returned
16189 $stretch = trim($stretch);
16190 switch ($stretch) {
16191 case 'ultra-condensed': {
16192 $val = 40;
16193 break;
16194 }
16195 case 'extra-condensed': {
16196 $val = 55;
16197 break;
16198 }
16199 case 'condensed': {
16200 $val = 70;
16201 break;
16202 }
16203 case 'semi-condensed': {
16204 $val = 85;
16205 break;
16206 }
16207 case 'normal': {
16208 $val = 100;
16209 break;
16210 }
16211 case 'semi-expanded': {
16212 $val = 115;
16213 break;
16214 }
16215 case 'expanded': {
16216 $val = 130;
16217 break;
16218 }
16219 case 'extra-expanded': {
16220 $val = 145;
16221 break;
16222 }
16223 case 'ultra-expanded': {
16224 $val = 160;
16225 break;
16226 }
16227 case 'wider': {
16228 $val = ($parent + 10);
16229 break;
16230 }
16231 case 'narrower': {
16232 $val = ($parent - 10);
16233 break;
16234 }
16235 case 'inherit': {
16236 if ($parent == 'normal') {
16237 $val = 100;
16238 } else {
16239 $val = $parent;
16240 }
16241 break;
16242 }
16243 default: {
16244 $val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
16245 }
16246 }
16247 return $val;
16248 }
16249
16250 /**
16251 * Convert HTML string containing font size value to points
16252 * @param $val (string) String containing font size value and unit.
16253 * @param $refsize (float) Reference font size in points.
16254 * @param $parent_size (float) Parent font size in points.
16255 * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
16256 * @return float value in points
16257 * @public
16258 */
16259 public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
16260 $refsize = TCPDF_FONTS::getFontRefSize($refsize);
16261 $parent_size = TCPDF_FONTS::getFontRefSize($parent_size, $refsize);
16262 switch ($val) {
16263 case 'xx-small': {
16264 $size = ($refsize - 4);
16265 break;
16266 }
16267 case 'x-small': {
16268 $size = ($refsize - 3);
16269 break;
16270 }
16271 case 'small': {
16272 $size = ($refsize - 2);
16273 break;
16274 }
16275 case 'medium': {
16276 $size = $refsize;
16277 break;
16278 }
16279 case 'large': {
16280 $size = ($refsize + 2);
16281 break;
16282 }
16283 case 'x-large': {
16284 $size = ($refsize + 4);
16285 break;
16286 }
16287 case 'xx-large': {
16288 $size = ($refsize + 6);
16289 break;
16290 }
16291 case 'smaller': {
16292 $size = ($parent_size - 3);
16293 break;
16294 }
16295 case 'larger': {
16296 $size = ($parent_size + 3);
16297 break;
16298 }
16299 default: {
16300 $size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
16301 }
16302 }
16303 return $size;
16304 }
16305
16306 /**
16307 * Returns the HTML DOM array.
16308 * @param $html (string) html code
16309 * @return array
16310 * @protected
16311 * @since 3.2.000 (2008-06-20)
16312 */
16313 protected function getHtmlDomArray($html) {
16314 // array of CSS styles ( selector => properties).
16315 $css = array();
16316 // get CSS array defined at previous call
16317 $matches = array();
16318 if (preg_match_all('/<cssarray>([^\<]*)<\/cssarray>/isU', $html, $matches) > 0) {
16319 if (isset($matches[1][0])) {
16320 $css = array_merge($css, json_decode($this->unhtmlentities($matches[1][0]), true));
16321 }
16322 $html = preg_replace('/<cssarray>(.*?)<\/cssarray>/isU', '', $html);
16323 }
16324 // extract external CSS files
16325 $matches = array();
16326 if (preg_match_all('/<link([^\>]*)>/isU', $html, $matches) > 0) {
16327 foreach ($matches[1] as $key => $link) {
16328 $type = array();
16329 if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
16330 $type = array();
16331 preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
16332 // get 'all' and 'print' media, other media types are discarded
16333 // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16334 if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16335 $type = array();
16336 if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
16337 // read CSS data file
16338 $cssdata = TCPDF_STATIC::fileGetContents(trim($type[1]));
16339 if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
16340 $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16341 }
16342 }
16343 }
16344 }
16345 }
16346 }
16347 // extract style tags
16348 $matches = array();
16349 if (preg_match_all('/<style([^\>]*)>([^\<]*)<\/style>/isU', $html, $matches) > 0) {
16350 foreach ($matches[1] as $key => $media) {
16351 $type = array();
16352 preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
16353 // get 'all' and 'print' media, other media types are discarded
16354 // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16355 if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16356 $cssdata = $matches[2][$key];
16357 $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16358 }
16359 }
16360 }
16361 // create a special tag to contain the CSS array (used for table content)
16362 $csstagarray = '<cssarray>' . htmlentities( json_encode( $css ), ENT_COMPAT ) . '</cssarray>';
16363 // remove head and style blocks
16364 $html = preg_replace('/<head([^\>]*)>(.*?)<\/head>/siU', '', $html);
16365 $html = preg_replace('/<style([^\>]*)>([^\<]*)<\/style>/isU', '', $html);
16366 // define block tags
16367 $blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
16368 // define self-closing tags
16369 $selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
16370 // remove all unsupported tags (the line below lists all supported tags)
16371 $html = strip_tags($html, '<marker><a><b><blockquote><body><br><dd><del><div><dl><dt><em><font><form><h1><h2><h3><h4><h5><h6><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>');
16372 //replace some blank characters
16373 $html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
16374 $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);
16375 $html = preg_replace('@(\r\n|\r)@', "\n", $html);
16376 $repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
16377 $html = strtr($html, $repTable);
16378 $offset = 0;
16379 while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
16380 $html_a = substr($html, 0, $offset);
16381 $html_b = substr($html, $offset, ($pos - $offset + 6));
16382 while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
16383 // preserve newlines on <pre> tag
16384 $html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
16385 }
16386 while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
16387 // preserve spaces on <pre> tag
16388 $html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
16389 }
16390 $html = $html_a.$html_b.substr($html, $pos + 6);
16391 $offset = strlen($html_a.$html_b);
16392 }
16393 $offset = 0;
16394 while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
16395 $html_a = substr($html, 0, $offset);
16396 $html_b = substr($html, $offset, ($pos - $offset + 11));
16397 while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
16398 // preserve newlines on <textarea> tag
16399 $html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
16400 $html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
16401 }
16402 $html = $html_a.$html_b.substr($html, $pos + 11);
16403 $offset = strlen($html_a.$html_b);
16404 }
16405 $html = preg_replace('/([\s]*)<option/si', '<option', $html);
16406 $html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
16407 $offset = 0;
16408 while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
16409 $html_a = substr($html, 0, $offset);
16410 $html_b = substr($html, $offset, ($pos - $offset + 9));
16411 while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
16412 $html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
16413 $html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
16414 }
16415 $html = $html_a.$html_b.substr($html, $pos + 9);
16416 $offset = strlen($html_a.$html_b);
16417 }
16418 if (preg_match("'</select'si", $html)) {
16419 $html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
16420 $html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
16421 }
16422 $html = str_replace("\n", ' ', $html);
16423 // restore textarea newlines
16424 $html = str_replace('<TBR>', "\n", $html);
16425 // remove extra spaces from code
16426 $html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
16427 $html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
16428 $html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
16429 $html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
16430 $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);
16431 $html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
16432 $html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
16433 $html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
16434 $html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
16435 $html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
16436 $html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
16437 $html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
16438 $html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
16439 $html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
16440 $html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
16441 $html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
16442 $html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
16443 $html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
16444 $html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
16445 // trim string
16446 $html = $this->stringTrim($html);
16447 // fix br tag after li
16448 $html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
16449 // fix first image tag alignment
16450 $html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
16451 // pattern for generic tag
16452 $tagpattern = '/(<[^>]+>)/';
16453 // explodes the string
16454 $a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
16455 // count elements
16456 $maxel = count($a);
16457 $elkey = 0;
16458 $key = 0;
16459 // create an array of elements
16460 $dom = array();
16461 $dom[$key] = array();
16462 // set inheritable properties fot the first void element
16463 // 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
16464 $dom[$key]['tag'] = false;
16465 $dom[$key]['block'] = false;
16466 $dom[$key]['value'] = '';
16467 $dom[$key]['parent'] = 0;
16468 $dom[$key]['hide'] = false;
16469 $dom[$key]['fontname'] = $this->FontFamily;
16470 $dom[$key]['fontstyle'] = $this->FontStyle;
16471 $dom[$key]['fontsize'] = $this->FontSizePt;
16472 $dom[$key]['font-stretch'] = $this->font_stretching;
16473 $dom[$key]['letter-spacing'] = $this->font_spacing;
16474 $dom[$key]['stroke'] = $this->textstrokewidth;
16475 $dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
16476 $dom[$key]['clip'] = ($this->textrendermode > 3);
16477 $dom[$key]['line-height'] = $this->cell_height_ratio;
16478 $dom[$key]['bgcolor'] = false;
16479 $dom[$key]['fgcolor'] = $this->fgcolor; // color
16480 $dom[$key]['strokecolor'] = $this->strokecolor;
16481 $dom[$key]['align'] = '';
16482 $dom[$key]['listtype'] = '';
16483 $dom[$key]['text-indent'] = 0;
16484 $dom[$key]['text-transform'] = '';
16485 $dom[$key]['border'] = array();
16486 $dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
16487 $thead = false; // true when we are inside the THEAD tag
16488 ++$key;
16489 $level = array();
16490 array_push($level, 0); // root
16491 while ($elkey < $maxel) {
16492 $dom[$key] = array();
16493 $element = $a[$elkey];
16494 $dom[$key]['elkey'] = $elkey;
16495 if (preg_match($tagpattern, $element)) {
16496 // html tag
16497 $element = substr($element, 1, -1);
16498 // get tag name
16499 preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
16500 $tagname = strtolower($tag[1]);
16501 // check if we are inside a table header
16502 if ($tagname == 'thead') {
16503 if ($element[0] == '/') {
16504 $thead = false;
16505 } else {
16506 $thead = true;
16507 }
16508 ++$elkey;
16509 continue;
16510 }
16511 $dom[$key]['tag'] = true;
16512 $dom[$key]['value'] = $tagname;
16513 if (in_array($dom[$key]['value'], $blocktags)) {
16514 $dom[$key]['block'] = true;
16515 } else {
16516 $dom[$key]['block'] = false;
16517 }
16518 if ($element[0] == '/') {
16519 // *** closing html tag
16520 $dom[$key]['opening'] = false;
16521 $dom[$key]['parent'] = end($level);
16522 array_pop($level);
16523 $dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
16524 $dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
16525 $dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
16526 $dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
16527 $dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
16528 $dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
16529 $dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
16530 $dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
16531 $dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
16532 $dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
16533 $dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
16534 $dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
16535 $dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
16536 $dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
16537 $dom[$key]['text-transform'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['text-transform'];
16538 $dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
16539 if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
16540 $dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
16541 }
16542 // set the number of columns in table tag
16543 if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
16544 $dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
16545 }
16546 if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
16547 $dom[($dom[$key]['parent'])]['content'] = $csstagarray;
16548 for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
16549 $dom[($dom[$key]['parent'])]['content'] .= stripslashes($a[$dom[$i]['elkey']]);
16550 }
16551 $key = $i;
16552 // mark nested tables
16553 $dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
16554 // remove thead sections from nested tables
16555 $dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
16556 $dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
16557 }
16558 // store header rows on a new table
16559 if (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['thead'] === true)) {
16560 if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
16561 $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
16562 }
16563 for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
16564 $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
16565 }
16566 if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
16567 $dom[($dom[$key]['parent'])]['attribute'] = array();
16568 }
16569 // header elements must be always contained in a single page
16570 $dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
16571 }
16572 if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
16573 // remove the nobr attributes from the table header
16574 $dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
16575 $dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
16576 }
16577 } else {
16578 // *** opening or self-closing html tag
16579 $dom[$key]['opening'] = true;
16580 $dom[$key]['parent'] = end($level);
16581 if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
16582 // self-closing tag
16583 $dom[$key]['self'] = true;
16584 } else {
16585 // opening tag
16586 array_push($level, $key);
16587 $dom[$key]['self'] = false;
16588 }
16589 // copy some values from parent
16590 $parentkey = 0;
16591 if ($key > 0) {
16592 $parentkey = $dom[$key]['parent'];
16593 $dom[$key]['hide'] = $dom[$parentkey]['hide'];
16594 $dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
16595 $dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
16596 $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
16597 $dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
16598 $dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
16599 $dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
16600 $dom[$key]['fill'] = $dom[$parentkey]['fill'];
16601 $dom[$key]['clip'] = $dom[$parentkey]['clip'];
16602 $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16603 $dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
16604 $dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
16605 $dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
16606 $dom[$key]['align'] = $dom[$parentkey]['align'];
16607 $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16608 $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16609 $dom[$key]['text-transform'] = $dom[$parentkey]['text-transform'];
16610 $dom[$key]['border'] = array();
16611 $dom[$key]['dir'] = $dom[$parentkey]['dir'];
16612 }
16613 // get attributes
16614 preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
16615 $dom[$key]['attribute'] = array(); // reset attribute array
16616 foreach($attr_array[1] as $id => $name) {
16617 $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
16618 }
16619 if (!empty($css)) {
16620 // merge CSS style to current style
16621 list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC::getCSSdataArray($dom, $key, $css);
16622 $dom[$key]['attribute']['style'] = TCPDF_STATIC::getTagStyleFromCSSarray($dom[$key]['cssdata']);
16623 }
16624 // split style attributes
16625 if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
16626 // get style attributes
16627 preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
16628 $dom[$key]['style'] = array(); // reset style attribute array
16629 foreach($style_array[1] as $id => $name) {
16630 // in case of duplicate attribute the last replace the previous
16631 $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
16632 }
16633 // --- get some style attributes ---
16634 // text direction
16635 if (isset($dom[$key]['style']['direction'])) {
16636 $dom[$key]['dir'] = $dom[$key]['style']['direction'];
16637 }
16638 // display
16639 if (isset($dom[$key]['style']['display'])) {
16640 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
16641 }
16642 // font family
16643 if (isset($dom[$key]['style']['font-family'])) {
16644 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
16645 }
16646 // list-style-type
16647 if (isset($dom[$key]['style']['list-style-type'])) {
16648 $dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
16649 if ($dom[$key]['listtype'] == 'inherit') {
16650 $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16651 }
16652 }
16653 // text-indent
16654 if (isset($dom[$key]['style']['text-indent'])) {
16655 $dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
16656 if ($dom[$key]['text-indent'] == 'inherit') {
16657 $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16658 }
16659 }
16660 // text-transform
16661 if (isset($dom[$key]['style']['text-transform'])) {
16662 $dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
16663 }
16664 // font size
16665 if (isset($dom[$key]['style']['font-size'])) {
16666 $fsize = trim($dom[$key]['style']['font-size']);
16667 $dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
16668 }
16669 // font-stretch
16670 if (isset($dom[$key]['style']['font-stretch'])) {
16671 $dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
16672 }
16673 // letter-spacing
16674 if (isset($dom[$key]['style']['letter-spacing'])) {
16675 $dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
16676 }
16677 // line-height (internally is the cell height ratio)
16678 if (isset($dom[$key]['style']['line-height'])) {
16679 $lineheight = trim($dom[$key]['style']['line-height']);
16680 switch ($lineheight) {
16681 // A normal line height. This is default
16682 case 'normal': {
16683 $dom[$key]['line-height'] = $dom[0]['line-height'];
16684 break;
16685 }
16686 case 'inherit': {
16687 $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16688 }
16689 default: {
16690 if (is_numeric($lineheight)) {
16691 // convert to percentage of font height
16692 $lineheight = ($lineheight * 100).'%';
16693 }
16694 $dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
16695 if (substr($lineheight, -1) !== '%') {
16696 if ($dom[$key]['fontsize'] <= 0) {
16697 $dom[$key]['line-height'] = 1;
16698 } else {
16699 $dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding['T'] - $this->cell_padding['B']) / $dom[$key]['fontsize']);
16700 }
16701 }
16702 }
16703 }
16704 }
16705 // font style
16706 if (isset($dom[$key]['style']['font-weight'])) {
16707 if (strtolower($dom[$key]['style']['font-weight'][0]) == 'n') {
16708 if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
16709 $dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
16710 }
16711 } elseif (strtolower($dom[$key]['style']['font-weight'][0]) == 'b') {
16712 $dom[$key]['fontstyle'] .= 'B';
16713 }
16714 }
16715 if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style'][0]) == 'i')) {
16716 $dom[$key]['fontstyle'] .= 'I';
16717 }
16718 // font color
16719 if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['color']))) {
16720 $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors);
16721 } elseif ($dom[$key]['value'] == 'a') {
16722 $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16723 }
16724 // background color
16725 if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['background-color']))) {
16726 $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors);
16727 }
16728 // text-decoration
16729 if (isset($dom[$key]['style']['text-decoration'])) {
16730 $decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
16731 foreach ($decors as $dec) {
16732 $dec = trim($dec);
16733 if (!TCPDF_STATIC::empty_string($dec)) {
16734 if ($dec[0] == 'u') {
16735 // underline
16736 $dom[$key]['fontstyle'] .= 'U';
16737 } elseif ($dec[0] == 'l') {
16738 // line-through
16739 $dom[$key]['fontstyle'] .= 'D';
16740 } elseif ($dec[0] == 'o') {
16741 // overline
16742 $dom[$key]['fontstyle'] .= 'O';
16743 }
16744 }
16745 }
16746 } elseif ($dom[$key]['value'] == 'a') {
16747 $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16748 }
16749 // check for width attribute
16750 if (isset($dom[$key]['style']['width'])) {
16751 $dom[$key]['width'] = $dom[$key]['style']['width'];
16752 }
16753 // check for height attribute
16754 if (isset($dom[$key]['style']['height'])) {
16755 $dom[$key]['height'] = $dom[$key]['style']['height'];
16756 }
16757 // check for text alignment
16758 if (isset($dom[$key]['style']['text-align'])) {
16759 $dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align'][0]);
16760 }
16761 // check for CSS border properties
16762 if (isset($dom[$key]['style']['border'])) {
16763 $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
16764 if (!empty($borderstyle)) {
16765 $dom[$key]['border']['LTRB'] = $borderstyle;
16766 }
16767 }
16768 if (isset($dom[$key]['style']['border-color'])) {
16769 $brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
16770 if (isset($brd_colors[3])) {
16771 $dom[$key]['border']['L']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[3], $this->spot_colors);
16772 }
16773 if (isset($brd_colors[1])) {
16774 $dom[$key]['border']['R']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[1], $this->spot_colors);
16775 }
16776 if (isset($brd_colors[0])) {
16777 $dom[$key]['border']['T']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[0], $this->spot_colors);
16778 }
16779 if (isset($brd_colors[2])) {
16780 $dom[$key]['border']['B']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[2], $this->spot_colors);
16781 }
16782 }
16783 if (isset($dom[$key]['style']['border-width'])) {
16784 $brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
16785 if (isset($brd_widths[3])) {
16786 $dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
16787 }
16788 if (isset($brd_widths[1])) {
16789 $dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
16790 }
16791 if (isset($brd_widths[0])) {
16792 $dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
16793 }
16794 if (isset($brd_widths[2])) {
16795 $dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
16796 }
16797 }
16798 if (isset($dom[$key]['style']['border-style'])) {
16799 $brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
16800 if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
16801 $dom[$key]['border']['L']['cap'] = 'square';
16802 $dom[$key]['border']['L']['join'] = 'miter';
16803 $dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
16804 if ($dom[$key]['border']['L']['dash'] < 0) {
16805 $dom[$key]['border']['L'] = array();
16806 }
16807 }
16808 if (isset($brd_styles[1])) {
16809 $dom[$key]['border']['R']['cap'] = 'square';
16810 $dom[$key]['border']['R']['join'] = 'miter';
16811 $dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
16812 if ($dom[$key]['border']['R']['dash'] < 0) {
16813 $dom[$key]['border']['R'] = array();
16814 }
16815 }
16816 if (isset($brd_styles[0])) {
16817 $dom[$key]['border']['T']['cap'] = 'square';
16818 $dom[$key]['border']['T']['join'] = 'miter';
16819 $dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
16820 if ($dom[$key]['border']['T']['dash'] < 0) {
16821 $dom[$key]['border']['T'] = array();
16822 }
16823 }
16824 if (isset($brd_styles[2])) {
16825 $dom[$key]['border']['B']['cap'] = 'square';
16826 $dom[$key]['border']['B']['join'] = 'miter';
16827 $dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
16828 if ($dom[$key]['border']['B']['dash'] < 0) {
16829 $dom[$key]['border']['B'] = array();
16830 }
16831 }
16832 }
16833 $cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
16834 foreach ($cellside as $bsk => $bsv) {
16835 if (isset($dom[$key]['style']['border-'.$bsv])) {
16836 $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
16837 if (!empty($borderstyle)) {
16838 $dom[$key]['border'][$bsk] = $borderstyle;
16839 }
16840 }
16841 if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
16842 $dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors);
16843 }
16844 if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
16845 $dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
16846 }
16847 if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
16848 $dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
16849 if ($dom[$key]['border'][$bsk]['dash'] < 0) {
16850 $dom[$key]['border'][$bsk] = array();
16851 }
16852 }
16853 }
16854 // check for CSS padding properties
16855 if (isset($dom[$key]['style']['padding'])) {
16856 $dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
16857 } else {
16858 $dom[$key]['padding'] = $this->cell_padding;
16859 }
16860 foreach ($cellside as $psk => $psv) {
16861 if (isset($dom[$key]['style']['padding-'.$psv])) {
16862 $dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
16863 }
16864 }
16865 // check for CSS margin properties
16866 if (isset($dom[$key]['style']['margin'])) {
16867 $dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
16868 } else {
16869 $dom[$key]['margin'] = $this->cell_margin;
16870 }
16871 foreach ($cellside as $psk => $psv) {
16872 if (isset($dom[$key]['style']['margin-'.$psv])) {
16873 $dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
16874 }
16875 }
16876 // check for CSS border-spacing properties
16877 if (isset($dom[$key]['style']['border-spacing'])) {
16878 $dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
16879 }
16880 // page-break-inside
16881 if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
16882 $dom[$key]['attribute']['nobr'] = 'true';
16883 }
16884 // page-break-before
16885 if (isset($dom[$key]['style']['page-break-before'])) {
16886 if ($dom[$key]['style']['page-break-before'] == 'always') {
16887 $dom[$key]['attribute']['pagebreak'] = 'true';
16888 } elseif ($dom[$key]['style']['page-break-before'] == 'left') {
16889 $dom[$key]['attribute']['pagebreak'] = 'left';
16890 } elseif ($dom[$key]['style']['page-break-before'] == 'right') {
16891 $dom[$key]['attribute']['pagebreak'] = 'right';
16892 }
16893 }
16894 // page-break-after
16895 if (isset($dom[$key]['style']['page-break-after'])) {
16896 if ($dom[$key]['style']['page-break-after'] == 'always') {
16897 $dom[$key]['attribute']['pagebreakafter'] = 'true';
16898 } elseif ($dom[$key]['style']['page-break-after'] == 'left') {
16899 $dom[$key]['attribute']['pagebreakafter'] = 'left';
16900 } elseif ($dom[$key]['style']['page-break-after'] == 'right') {
16901 $dom[$key]['attribute']['pagebreakafter'] = 'right';
16902 }
16903 }
16904 }
16905 if (isset($dom[$key]['attribute']['display'])) {
16906 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
16907 }
16908 if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
16909 $borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
16910 if (!empty($borderstyle)) {
16911 $dom[$key]['border']['LTRB'] = $borderstyle;
16912 }
16913 }
16914 // check for font tag
16915 if ($dom[$key]['value'] == 'font') {
16916 // font family
16917 if (isset($dom[$key]['attribute']['face'])) {
16918 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
16919 }
16920 // font size
16921 if (isset($dom[$key]['attribute']['size'])) {
16922 if ($key > 0) {
16923 if ($dom[$key]['attribute']['size'][0] == '+') {
16924 $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
16925 } elseif ($dom[$key]['attribute']['size'][0] == '-') {
16926 $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
16927 } else {
16928 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16929 }
16930 } else {
16931 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16932 }
16933 }
16934 }
16935 // force natural alignment for lists
16936 if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
16937 AND (!isset($dom[$key]['align']) OR TCPDF_STATIC::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
16938 if ($this->rtl) {
16939 $dom[$key]['align'] = 'R';
16940 } else {
16941 $dom[$key]['align'] = 'L';
16942 }
16943 }
16944 if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
16945 if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
16946 $dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
16947 }
16948 }
16949 if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
16950 $dom[$key]['fontstyle'] .= 'B';
16951 }
16952 if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
16953 $dom[$key]['fontstyle'] .= 'I';
16954 }
16955 if ($dom[$key]['value'] == 'u') {
16956 $dom[$key]['fontstyle'] .= 'U';
16957 }
16958 if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
16959 $dom[$key]['fontstyle'] .= 'D';
16960 }
16961 if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
16962 $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16963 }
16964 if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
16965 $dom[$key]['fontname'] = $this->default_monospaced_font;
16966 }
16967 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)) {
16968 // headings h1, h2, h3, h4, h5, h6
16969 if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
16970 $headsize = (4 - intval($dom[$key]['value'][1])) * 2;
16971 $dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
16972 }
16973 if (!isset($dom[$key]['style']['font-weight'])) {
16974 $dom[$key]['fontstyle'] .= 'B';
16975 }
16976 }
16977 if (($dom[$key]['value'] == 'table')) {
16978 $dom[$key]['rows'] = 0; // number of rows
16979 $dom[$key]['trids'] = array(); // IDs of TR elements
16980 $dom[$key]['thead'] = ''; // table header rows
16981 }
16982 if (($dom[$key]['value'] == 'tr')) {
16983 $dom[$key]['cols'] = 0;
16984 if ($thead) {
16985 $dom[$key]['thead'] = true;
16986 // rows on thead block are printed as a separate table
16987 } else {
16988 $dom[$key]['thead'] = false;
16989 // store the number of rows on table element
16990 ++$dom[($dom[$key]['parent'])]['rows'];
16991 // store the TR elements IDs on table element
16992 array_push($dom[($dom[$key]['parent'])]['trids'], $key);
16993 }
16994 }
16995 if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
16996 if (isset($dom[$key]['attribute']['colspan'])) {
16997 $colspan = intval($dom[$key]['attribute']['colspan']);
16998 } else {
16999 $colspan = 1;
17000 }
17001 $dom[$key]['attribute']['colspan'] = $colspan;
17002 $dom[($dom[$key]['parent'])]['cols'] += $colspan;
17003 }
17004 // text direction
17005 if (isset($dom[$key]['attribute']['dir'])) {
17006 $dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
17007 }
17008 // set foreground color attribute
17009 if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['color']))) {
17010 $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors);
17011 } elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
17012 $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
17013 }
17014 // set background color attribute
17015 if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['bgcolor']))) {
17016 $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors);
17017 }
17018 // set stroke color attribute
17019 if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['strokecolor']))) {
17020 $dom[$key]['strokecolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors);
17021 }
17022 // check for width attribute
17023 if (isset($dom[$key]['attribute']['width'])) {
17024 $dom[$key]['width'] = $dom[$key]['attribute']['width'];
17025 }
17026 // check for height attribute
17027 if (isset($dom[$key]['attribute']['height'])) {
17028 $dom[$key]['height'] = $dom[$key]['attribute']['height'];
17029 }
17030 // check for text alignment
17031 if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
17032 $dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align'][0]);
17033 }
17034 // check for text rendering mode (the following attributes do not exist in HTML)
17035 if (isset($dom[$key]['attribute']['stroke'])) {
17036 // font stroke width
17037 $dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
17038 }
17039 if (isset($dom[$key]['attribute']['fill'])) {
17040 // font fill
17041 if ($dom[$key]['attribute']['fill'] == 'true') {
17042 $dom[$key]['fill'] = true;
17043 } else {
17044 $dom[$key]['fill'] = false;
17045 }
17046 }
17047 if (isset($dom[$key]['attribute']['clip'])) {
17048 // clipping mode
17049 if ($dom[$key]['attribute']['clip'] == 'true') {
17050 $dom[$key]['clip'] = true;
17051 } else {
17052 $dom[$key]['clip'] = false;
17053 }
17054 }
17055 } // end opening tag
17056 } else {
17057 // text
17058 $dom[$key]['tag'] = false;
17059 $dom[$key]['block'] = false;
17060 $dom[$key]['parent'] = end($level);
17061 $dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
17062 if (!empty($dom[$dom[$key]['parent']]['text-transform'])) {
17063 // text-transform for unicode requires mb_convert_case (Multibyte String Functions)
17064 if (function_exists('mb_convert_case')) {
17065 $ttm = array('capitalize' => MB_CASE_TITLE, 'uppercase' => MB_CASE_UPPER, 'lowercase' => MB_CASE_LOWER);
17066 if (isset($ttm[$dom[$dom[$key]['parent']]['text-transform']])) {
17067 $element = mb_convert_case($element, $ttm[$dom[$dom[$key]['parent']]['text-transform']], $this->encoding);
17068 }
17069 } elseif (!$this->isunicode) {
17070 switch ($dom[$dom[$key]['parent']]['text-transform']) {
17071 case 'capitalize': {
17072 $element = ucwords(strtolower($element));
17073 break;
17074 }
17075 case 'uppercase': {
17076 $element = strtoupper($element);
17077 break;
17078 }
17079 case 'lowercase': {
17080 $element = strtolower($element);
17081 break;
17082 }
17083 }
17084 }
17085 }
17086 $dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
17087 }
17088 ++$elkey;
17089 ++$key;
17090 }
17091 return $dom;
17092 }
17093
17094 /**
17095 * Returns the string used to find spaces
17096 * @return string
17097 * @protected
17098 * @author Nicola Asuni
17099 * @since 4.8.024 (2010-01-15)
17100 */
17101 protected function getSpaceString() {
17102 $spacestr = chr(32);
17103 if ($this->isUnicodeFont()) {
17104 $spacestr = chr(0).chr(32);
17105 }
17106 return $spacestr;
17107 }
17108
17109 /**
17110 * Return an hash code used to ensure that the serialized data has been generated by this TCPDF instance.
17111 * @param $data (string) serialized data
17112 * @return string
17113 * @public static
17114 */
17115 protected function getHashForTCPDFtagParams($data) {
17116 return md5(strlen($data).$this->file_id.$data);
17117 }
17118
17119 /**
17120 * Serialize an array of parameters to be used with TCPDF tag in HTML code.
17121 * @param $data (array) parameters array
17122 * @return string containing serialized data
17123 * @public static
17124 */
17125 public function serializeTCPDFtagParameters($data) {
17126 $encoded = urlencode(json_encode($data));
17127 return $this->getHashForTCPDFtagParams($encoded).$encoded;
17128 }
17129
17130 /**
17131 * Unserialize parameters to be used with TCPDF tag in HTML code.
17132 * @param $data (string) serialized data
17133 * @return array containing unserialized data
17134 * @protected static
17135 */
17136 protected function unserializeTCPDFtagParameters($data) {
17137 $hash = substr($data, 0, 32);
17138 $encoded = substr($data, 32);
17139 if ($hash != $this->getHashForTCPDFtagParams($encoded)) {
17140 $this->Error('Invalid parameters');
17141 }
17142 return json_decode(urldecode($encoded), true);
17143 }
17144
17145 /**
17146 * Prints a cell (rectangular area) with optional borders, background color and html text string.
17147 * 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 />
17148 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
17149 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17150 * 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
17151 * NOTE: all the HTML attributes must be enclosed in double-quote.
17152 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
17153 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
17154 * @param $x (float) upper-left corner X coordinate
17155 * @param $y (float) upper-left corner Y coordinate
17156 * @param $html (string) html text to print. Default value: empty string.
17157 * @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)))
17158 * @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>
17159 Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
17160 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
17161 * @param $reseth (boolean) if true reset the last cell height (default true).
17162 * @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>
17163 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
17164 * @see Multicell(), writeHTML()
17165 * @public
17166 */
17167 public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
17168 return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
17169 }
17170
17171 /**
17172 * Allows to preserve some HTML formatting (limited support).<br />
17173 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17174 * 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
17175 * NOTE: all the HTML attributes must be enclosed in double-quote.
17176 * @param $html (string) text to display
17177 * @param $ln (boolean) if true add a new line after text (default = true)
17178 * @param $fill (boolean) Indicates if the background must be painted (true) or transparent (false).
17179 * @param $reseth (boolean) if true reset the last cell height (default false).
17180 * @param $cell (boolean) if true add the current left (or right for RTL) padding to each Write (default false).
17181 * @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>
17182 * @public
17183 */
17184 public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
17185 $gvars = $this->getGraphicVars();
17186 // store current values
17187 $prev_cell_margin = $this->cell_margin;
17188 $prev_cell_padding = $this->cell_padding;
17189 $prevPage = $this->page;
17190 $prevlMargin = $this->lMargin;
17191 $prevrMargin = $this->rMargin;
17192 $curfontname = $this->FontFamily;
17193 $curfontstyle = $this->FontStyle;
17194 $curfontsize = $this->FontSizePt;
17195 $curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
17196 $curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
17197 $curfontstretcing = $this->font_stretching;
17198 $curfonttracking = $this->font_spacing;
17199 $this->newline = true;
17200 $newline = true;
17201 $startlinepage = $this->page;
17202 $minstartliney = $this->y;
17203 $maxbottomliney = 0;
17204 $startlinex = $this->x;
17205 $startliney = $this->y;
17206 $yshift = 0;
17207 $loop = 0;
17208 $curpos = 0;
17209 $this_method_vars = array();
17210 $undo = false;
17211 $fontaligned = false;
17212 $reverse_dir = false; // true when the text direction is reversed
17213 $this->premode = false;
17214 if ($this->inxobj) {
17215 // we are inside an XObject template
17216 $pask = count($this->xobjects[$this->xobjid]['annotations']);
17217 } elseif (isset($this->PageAnnots[$this->page])) {
17218 $pask = count($this->PageAnnots[$this->page]);
17219 } else {
17220 $pask = 0;
17221 }
17222 if ($this->inxobj) {
17223 // we are inside an XObject template
17224 $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17225 } elseif (!$this->InFooter) {
17226 if (isset($this->footerlen[$this->page])) {
17227 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17228 } else {
17229 $this->footerpos[$this->page] = $this->pagelen[$this->page];
17230 }
17231 $startlinepos = $this->footerpos[$this->page];
17232 } else {
17233 // we are inside the footer
17234 $startlinepos = $this->pagelen[$this->page];
17235 }
17236 $lalign = $align;
17237 $plalign = $align;
17238 if ($this->rtl) {
17239 $w = $this->x - $this->lMargin;
17240 } else {
17241 $w = $this->w - $this->rMargin - $this->x;
17242 }
17243 $w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
17244 if ($cell) {
17245 if ($this->rtl) {
17246 $this->x -= $this->cell_padding['R'];
17247 $this->lMargin += $this->cell_padding['L'];
17248 } else {
17249 $this->x += $this->cell_padding['L'];
17250 $this->rMargin += $this->cell_padding['R'];
17251 }
17252 }
17253 if ($this->customlistindent >= 0) {
17254 $this->listindent = $this->customlistindent;
17255 } else {
17256 $this->listindent = $this->GetStringWidth('000000');
17257 }
17258 $this->listindentlevel = 0;
17259 // save previous states
17260 $prev_cell_height_ratio = $this->cell_height_ratio;
17261 $prev_listnum = $this->listnum;
17262 $prev_listordered = $this->listordered;
17263 $prev_listcount = $this->listcount;
17264 $prev_lispacer = $this->lispacer;
17265 $this->listnum = 0;
17266 $this->listordered = array();
17267 $this->listcount = array();
17268 $this->lispacer = '';
17269 if ((TCPDF_STATIC::empty_string($this->lasth)) OR ($reseth)) {
17270 // reset row height
17271 $this->resetLastH();
17272 }
17273 $dom = $this->getHtmlDomArray($html);
17274 $maxel = count($dom);
17275 $key = 0;
17276 while ($key < $maxel) {
17277 if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND $dom[$key]['hide']) {
17278 // store the node key
17279 $hidden_node_key = $key;
17280 if ($dom[$key]['self']) {
17281 // skip just this self-closing tag
17282 ++$key;
17283 } else {
17284 // skip this and all children tags
17285 while (($key < $maxel) AND (!$dom[$key]['tag'] OR $dom[$key]['opening'] OR ($dom[$key]['parent'] != $hidden_node_key))) {
17286 // skip hidden objects
17287 ++$key;
17288 }
17289 ++$key;
17290 }
17291 }
17292 if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
17293 // check for pagebreak
17294 if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
17295 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17296 $this->checkPageBreak($this->PageBreakTrigger + 1);
17297 $this->htmlvspace = ($this->PageBreakTrigger + 1);
17298 }
17299 if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
17300 OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
17301 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17302 $this->checkPageBreak($this->PageBreakTrigger + 1);
17303 $this->htmlvspace = ($this->PageBreakTrigger + 1);
17304 }
17305 }
17306 if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
17307 if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
17308 $dom[$key]['attribute']['nobr'] = false;
17309 } else {
17310 // store current object
17311 $this->startTransaction();
17312 // save this method vars
17313 $this_method_vars['html'] = $html;
17314 $this_method_vars['ln'] = $ln;
17315 $this_method_vars['fill'] = $fill;
17316 $this_method_vars['reseth'] = $reseth;
17317 $this_method_vars['cell'] = $cell;
17318 $this_method_vars['align'] = $align;
17319 $this_method_vars['gvars'] = $gvars;
17320 $this_method_vars['prevPage'] = $prevPage;
17321 $this_method_vars['prev_cell_margin'] = $prev_cell_margin;
17322 $this_method_vars['prev_cell_padding'] = $prev_cell_padding;
17323 $this_method_vars['prevlMargin'] = $prevlMargin;
17324 $this_method_vars['prevrMargin'] = $prevrMargin;
17325 $this_method_vars['curfontname'] = $curfontname;
17326 $this_method_vars['curfontstyle'] = $curfontstyle;
17327 $this_method_vars['curfontsize'] = $curfontsize;
17328 $this_method_vars['curfontascent'] = $curfontascent;
17329 $this_method_vars['curfontdescent'] = $curfontdescent;
17330 $this_method_vars['curfontstretcing'] = $curfontstretcing;
17331 $this_method_vars['curfonttracking'] = $curfonttracking;
17332 $this_method_vars['minstartliney'] = $minstartliney;
17333 $this_method_vars['maxbottomliney'] = $maxbottomliney;
17334 $this_method_vars['yshift'] = $yshift;
17335 $this_method_vars['startlinepage'] = $startlinepage;
17336 $this_method_vars['startlinepos'] = $startlinepos;
17337 $this_method_vars['startlinex'] = $startlinex;
17338 $this_method_vars['startliney'] = $startliney;
17339 $this_method_vars['newline'] = $newline;
17340 $this_method_vars['loop'] = $loop;
17341 $this_method_vars['curpos'] = $curpos;
17342 $this_method_vars['pask'] = $pask;
17343 $this_method_vars['lalign'] = $lalign;
17344 $this_method_vars['plalign'] = $plalign;
17345 $this_method_vars['w'] = $w;
17346 $this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
17347 $this_method_vars['prev_listnum'] = $prev_listnum;
17348 $this_method_vars['prev_listordered'] = $prev_listordered;
17349 $this_method_vars['prev_listcount'] = $prev_listcount;
17350 $this_method_vars['prev_lispacer'] = $prev_lispacer;
17351 $this_method_vars['fontaligned'] = $fontaligned;
17352 $this_method_vars['key'] = $key;
17353 $this_method_vars['dom'] = $dom;
17354 }
17355 }
17356 // print THEAD block
17357 if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
17358 if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC::empty_string($dom[$dom[$key]['parent']]['thead'])) {
17359 $this->inthead = true;
17360 // print table header (thead)
17361 $this->writeHTML($this->thead, false, false, false, false, '');
17362 // check if we are on a new page or on a new column
17363 if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
17364 // we are on a new page or on a new column and the total object height is less than the available vertical space.
17365 // restore previous object
17366 $this->rollbackTransaction(true);
17367 // restore previous values
17368 foreach ($this_method_vars as $vkey => $vval) {
17369 $$vkey = $vval;
17370 }
17371 // disable table header
17372 $tmp_thead = $this->thead;
17373 $this->thead = '';
17374 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17375 $pre_y = $this->y;
17376 if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
17377 // fix for multicolumn mode
17378 $startliney = $this->y;
17379 }
17380 $this->start_transaction_page = $this->page;
17381 $this->start_transaction_y = $this->y;
17382 // restore table header
17383 $this->thead = $tmp_thead;
17384 // fix table border properties
17385 if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
17386 $tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
17387 } elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
17388 $tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
17389 } else {
17390 $tmp_cellspacing = 0;
17391 }
17392 $dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
17393 $dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
17394 $dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
17395 $xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
17396 $dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
17397 $dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
17398 // print table header (thead)
17399 $this->writeHTML($this->thead, false, false, false, false, '');
17400 }
17401 }
17402 // move $key index forward to skip THEAD block
17403 while ( ($key < $maxel) AND (!(
17404 ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
17405 OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
17406 ++$key;
17407 }
17408 }
17409 if ($dom[$key]['tag'] OR ($key == 0)) {
17410 if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
17411 $dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
17412 }
17413 // vertically align image in line
17414 if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
17415 // get image height
17416 $imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], ($dom[$key]['fontsize'] / $this->k), 'px');
17417 $autolinebreak = false;
17418 if (!empty($dom[$key]['width'])) {
17419 $imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], ($dom[$key]['fontsize'] / $this->k), 'px', false);
17420 if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
17421 AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
17422 OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
17423 // add automatic line break
17424 $autolinebreak = true;
17425 $this->Ln('', $cell);
17426 if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
17427 // go back to evaluate this line break
17428 --$key;
17429 }
17430 }
17431 }
17432 if (!$autolinebreak) {
17433 if ($this->inPageBody()) {
17434 $pre_y = $this->y;
17435 // check for page break
17436 if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
17437 // fix for multicolumn mode
17438 $startliney = $this->y;
17439 }
17440 }
17441 if ($this->page > $startlinepage) {
17442 // fix line splitted over two pages
17443 if (isset($this->footerlen[$startlinepage])) {
17444 $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17445 }
17446 // line to be moved one page forward
17447 $pagebuff = $this->getPageBuffer($startlinepage);
17448 $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17449 $tstart = substr($pagebuff, 0, $startlinepos);
17450 $tend = substr($this->getPageBuffer($startlinepage), $curpos);
17451 // remove line from previous page
17452 $this->setPageBuffer($startlinepage, $tstart.''.$tend);
17453 $pagebuff = $this->getPageBuffer($this->page);
17454 $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17455 $tend = substr($pagebuff, $this->cntmrk[$this->page]);
17456 // add line start to current page
17457 $yshift = ($minstartliney - $this->y);
17458 if ($fontaligned) {
17459 $yshift += ($curfontsize / $this->k);
17460 }
17461 $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17462 $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17463 // shift the annotations and links
17464 if (isset($this->PageAnnots[$this->page])) {
17465 $next_pask = count($this->PageAnnots[$this->page]);
17466 } else {
17467 $next_pask = 0;
17468 }
17469 if (isset($this->PageAnnots[$startlinepage])) {
17470 foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17471 if ($pak >= $pask) {
17472 $this->PageAnnots[$this->page][] = $pac;
17473 unset($this->PageAnnots[$startlinepage][$pak]);
17474 $npak = count($this->PageAnnots[$this->page]) - 1;
17475 $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17476 }
17477 }
17478 }
17479 $pask = $next_pask;
17480 $startlinepos = $this->cntmrk[$this->page];
17481 $startlinepage = $this->page;
17482 $startliney = $this->y;
17483 $this->newline = false;
17484 }
17485 $this->y += ($this->getCellHeight($curfontsize / $this->k) - ($curfontdescent * $this->cell_height_ratio) - $imgh);
17486 $minstartliney = min($this->y, $minstartliney);
17487 $maxbottomliney = ($startliney + $this->getCellHeight($curfontsize / $this->k));
17488 }
17489 } elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
17490 // account for different font size
17491 $pfontname = $curfontname;
17492 $pfontstyle = $curfontstyle;
17493 $pfontsize = $curfontsize;
17494 $fontname = (isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname);
17495 $fontstyle = (isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle);
17496 $fontsize = (isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize);
17497 $fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
17498 $fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
17499 if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
17500 OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17501 OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
17502 if (($key < ($maxel - 1)) AND (
17503 ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
17504 OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17505 OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize)
17506 AND ($fontsize >= 0) AND ($curfontsize >= 0)
17507 AND (($fontsize != $curfontsize) OR ($fontstyle != $curfontstyle) OR ($fontname != $curfontname)))
17508 )) {
17509 if ($this->page > $startlinepage) {
17510 // fix lines splitted over two pages
17511 if (isset($this->footerlen[$startlinepage])) {
17512 $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17513 }
17514 // line to be moved one page forward
17515 $pagebuff = $this->getPageBuffer($startlinepage);
17516 $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17517 $tstart = substr($pagebuff, 0, $startlinepos);
17518 $tend = substr($this->getPageBuffer($startlinepage), $curpos);
17519 // remove line start from previous page
17520 $this->setPageBuffer($startlinepage, $tstart.''.$tend);
17521 $pagebuff = $this->getPageBuffer($this->page);
17522 $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17523 $tend = substr($pagebuff, $this->cntmrk[$this->page]);
17524 // add line start to current page
17525 $yshift = ($minstartliney - $this->y);
17526 $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17527 $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17528 // shift the annotations and links
17529 if (isset($this->PageAnnots[$this->page])) {
17530 $next_pask = count($this->PageAnnots[$this->page]);
17531 } else {
17532 $next_pask = 0;
17533 }
17534 if (isset($this->PageAnnots[$startlinepage])) {
17535 foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17536 if ($pak >= $pask) {
17537 $this->PageAnnots[$this->page][] = $pac;
17538 unset($this->PageAnnots[$startlinepage][$pak]);
17539 $npak = count($this->PageAnnots[$this->page]) - 1;
17540 $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17541 }
17542 }
17543 }
17544 $pask = $next_pask;
17545 $startlinepos = $this->cntmrk[$this->page];
17546 $startlinepage = $this->page;
17547 $startliney = $this->y;
17548 }
17549 if (!isset($dom[$key]['line-height'])) {
17550 $dom[$key]['line-height'] = $this->cell_height_ratio;
17551 }
17552 if (!$dom[$key]['block']) {
17553 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']))) {
17554 $this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
17555 }
17556 if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
17557 $current_line_align_data = array($key, $minstartliney, $maxbottomliney);
17558 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)))) {
17559 $minstartliney = min($this->y, $line_align_data[1]);
17560 $maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $line_align_data[2]);
17561 } else {
17562 $minstartliney = min($this->y, $minstartliney);
17563 $maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $maxbottomliney);
17564 }
17565 $line_align_data = $current_line_align_data;
17566 }
17567 }
17568 $this->cell_height_ratio = $dom[$key]['line-height'];
17569 $fontaligned = true;
17570 }
17571 $this->SetFont($fontname, $fontstyle, $fontsize);
17572 // reset row height
17573 $this->resetLastH();
17574 $curfontname = $fontname;
17575 $curfontstyle = $fontstyle;
17576 $curfontsize = $fontsize;
17577 $curfontascent = $fontascent;
17578 $curfontdescent = $fontdescent;
17579 }
17580 }
17581 // set text rendering mode
17582 $textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
17583 $textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
17584 $textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
17585 $this->setTextRenderingMode($textstroke, $textfill, $textclip);
17586 if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
17587 $this->setFontStretching($dom[$key]['font-stretch']);
17588 }
17589 if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
17590 $this->setFontSpacing($dom[$key]['letter-spacing']);
17591 }
17592 if (($plalign == 'J') AND $dom[$key]['block']) {
17593 $plalign = '';
17594 }
17595 // get current position on page buffer
17596 $curpos = $this->pagelen[$startlinepage];
17597 if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
17598 $this->SetFillColorArray($dom[$key]['bgcolor']);
17599 $wfill = true;
17600 } else {
17601 $wfill = $fill | false;
17602 }
17603 if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
17604 $this->SetTextColorArray($dom[$key]['fgcolor']);
17605 }
17606 if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
17607 $this->SetDrawColorArray($dom[$key]['strokecolor']);
17608 }
17609 if (isset($dom[$key]['align'])) {
17610 $lalign = $dom[$key]['align'];
17611 }
17612 if (TCPDF_STATIC::empty_string($lalign)) {
17613 $lalign = $align;
17614 }
17615 }
17616 // align lines
17617 if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
17618 $newline = true;
17619 $fontaligned = false;
17620 // we are at the beginning of a new line
17621 if (isset($startlinex)) {
17622 $yshift = ($minstartliney - $startliney);
17623 if (($yshift > 0) OR ($this->page > $startlinepage)) {
17624 $yshift = 0;
17625 }
17626 $t_x = 0;
17627 // the last line must be shifted to be aligned as requested
17628 $linew = abs($this->endlinex - $startlinex);
17629 if ($this->inxobj) {
17630 // we are inside an XObject template
17631 $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
17632 if (isset($opentagpos)) {
17633 $midpos = $opentagpos;
17634 } else {
17635 $midpos = 0;
17636 }
17637 if ($midpos > 0) {
17638 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
17639 $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
17640 } else {
17641 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
17642 $pend = '';
17643 }
17644 } else {
17645 $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
17646 if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17647 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17648 $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
17649 } elseif (isset($opentagpos)) {
17650 $midpos = $opentagpos;
17651 } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17652 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17653 $midpos = $this->footerpos[$startlinepage];
17654 } else {
17655 $midpos = 0;
17656 }
17657 if ($midpos > 0) {
17658 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
17659 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
17660 } else {
17661 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
17662 $pend = '';
17663 }
17664 }
17665 if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
17666 // calculate shifting amount
17667 $tw = $w;
17668 if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
17669 $tw += $this->cell_padding['R'];
17670 }
17671 if ($this->lMargin != $prevlMargin) {
17672 $tw += ($prevlMargin - $this->lMargin);
17673 }
17674 if ($this->rMargin != $prevrMargin) {
17675 $tw += ($prevrMargin - $this->rMargin);
17676 }
17677 $one_space_width = $this->GetStringWidth(chr(32));
17678 $no = 0; // number of spaces on a line contained on a single block
17679 if ($this->isRTLTextDir()) { // RTL
17680 // remove left space if exist
17681 $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
17682 if ($pos1 > 0) {
17683 $pos1 = intval($pos1);
17684 if ($this->isUnicodeFont()) {
17685 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
17686 $spacelen = 2;
17687 } else {
17688 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
17689 $spacelen = 1;
17690 }
17691 if ($pos1 == $pos2) {
17692 $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
17693 if (substr($pmid, $pos1, 4) == '[()]') {
17694 $linew -= $one_space_width;
17695 } elseif ($pos1 == strpos($pmid, '[(')) {
17696 $no = 1;
17697 }
17698 }
17699 }
17700 } else { // LTR
17701 // remove right space if exist
17702 $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
17703 if ($pos1 > 0) {
17704 $pos1 = intval($pos1);
17705 if ($this->isUnicodeFont()) {
17706 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
17707 $spacelen = 2;
17708 } else {
17709 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
17710 $spacelen = 1;
17711 }
17712 if ($pos1 == $pos2) {
17713 $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
17714 $linew -= $one_space_width;
17715 }
17716 }
17717 }
17718 $mdiff = ($tw - $linew);
17719 if ($plalign == 'C') {
17720 if ($this->rtl) {
17721 $t_x = -($mdiff / 2);
17722 } else {
17723 $t_x = ($mdiff / 2);
17724 }
17725 } elseif ($plalign == 'R') {
17726 // right alignment on LTR document
17727 $t_x = $mdiff;
17728 } elseif ($plalign == 'L') {
17729 // left alignment on RTL document
17730 $t_x = -$mdiff;
17731 } elseif (($plalign == 'J') AND ($plalign == $lalign)) {
17732 // Justification
17733 if ($this->isRTLTextDir()) {
17734 // align text on the left
17735 $t_x = -$mdiff;
17736 }
17737 $ns = 0; // number of spaces
17738 $pmidtemp = $pmid;
17739 // escape special characters
17740 $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
17741 $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
17742 // search spaces
17743 if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
17744 $spacestr = $this->getSpaceString();
17745 $maxkk = count($lnstring[1]) - 1;
17746 for ($kk=0; $kk <= $maxkk; ++$kk) {
17747 // restore special characters
17748 $lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
17749 $lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
17750 // store number of spaces on the strings
17751 $lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
17752 // count total spaces on line
17753 $ns += $lnstring[2][$kk];
17754 $lnstring[3][$kk] = $ns;
17755 }
17756 if ($ns == 0) {
17757 $ns = 1;
17758 }
17759 // calculate additional space to add to each existing space
17760 $spacewidth = ($mdiff / ($ns - $no)) * $this->k;
17761 if ($this->FontSize <= 0) {
17762 $this->FontSize = 1;
17763 }
17764 $spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
17765 if ($this->font_spacing != 0) {
17766 // fixed spacing mode
17767 $osw = -1000 * $this->font_spacing / $this->FontSize;
17768 $spacewidthu += $osw;
17769 }
17770 $nsmax = $ns;
17771 $ns = 0;
17772 reset($lnstring);
17773 $offset = 0;
17774 $strcount = 0;
17775 $prev_epsposbeg = 0;
17776 $textpos = 0;
17777 if ($this->isRTLTextDir()) {
17778 $textpos = $this->wPt;
17779 }
17780 while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
17781 // check if we are inside a string section '[( ... )]'
17782 $stroffset = strpos($pmid, '[(', $offset);
17783 if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
17784 // set offset to the end of string section
17785 $offset = strpos($pmid, ')]', $stroffset);
17786 while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
17787 $offset = strpos($pmid, ')]', ($offset + 1));
17788 }
17789 if ($offset === false) {
17790 $this->Error('HTML Justification: malformed PDF code.');
17791 }
17792 continue;
17793 }
17794 if ($this->isRTLTextDir()) {
17795 $spacew = ($spacewidth * ($nsmax - $ns));
17796 } else {
17797 $spacew = ($spacewidth * $ns);
17798 }
17799 $offset = $strpiece[2][1] + strlen($strpiece[2][0]);
17800 $epsposend = strpos($pmid, $this->epsmarker.'Q', $offset);
17801 if ($epsposend !== null) {
17802 $epsposend += strlen($this->epsmarker.'Q');
17803 $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
17804 if ($epsposbeg === null) {
17805 $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
17806 $prev_epsposbeg = $epsposbeg;
17807 }
17808 if (($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend)) {
17809 // shift EPS images
17810 $trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
17811 $pmid_b = substr($pmid, 0, $epsposbeg);
17812 $pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
17813 $pmid_e = substr($pmid, $epsposend);
17814 $pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
17815 $offset = $epsposend;
17816 continue;
17817 }
17818 }
17819 $currentxpos = 0;
17820 // shift blocks of code
17821 switch ($strpiece[2][0]) {
17822 case 'Td':
17823 case 'cm':
17824 case 'm':
17825 case 'l': {
17826 // get current X position
17827 preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
17828 if (!isset($xmatches[1])) {
17829 break;
17830 }
17831 $currentxpos = $xmatches[1];
17832 $textpos = $currentxpos;
17833 if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
17834 $ns = $lnstring[3][$strcount];
17835 if ($this->isRTLTextDir()) {
17836 $spacew = ($spacewidth * ($nsmax - $ns));
17837 }
17838 ++$strcount;
17839 }
17840 // justify block
17841 if (preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $pmatch) == 1) {
17842 $newpmid = sprintf('%F',(floatval($pmatch[1]) + $spacew)).' '.$pmatch[2].' x*#!#*x'.$pmatch[3].$pmatch[4];
17843 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17844 unset($pmatch, $newpmid);
17845 }
17846 break;
17847 }
17848 case 're': {
17849 // justify block
17850 if (!TCPDF_STATIC::empty_string($this->lispacer)) {
17851 $this->lispacer = '';
17852 break;
17853 }
17854 preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
17855 if (!isset($xmatches[1])) {
17856 break;
17857 }
17858 $currentxpos = $xmatches[1];
17859 $x_diff = 0;
17860 $w_diff = 0;
17861 if ($this->isRTLTextDir()) { // RTL
17862 if ($currentxpos < $textpos) {
17863 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
17864 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
17865 } else {
17866 if ($strcount > 0) {
17867 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
17868 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17869 }
17870 }
17871 } else { // LTR
17872 if ($currentxpos > $textpos) {
17873 if ($strcount > 0) {
17874 $x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
17875 }
17876 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
17877 } else {
17878 if ($strcount > 1) {
17879 $x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
17880 }
17881 if ($strcount > 0) {
17882 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17883 }
17884 }
17885 }
17886 if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $pmatch) == 1) {
17887 $newx = sprintf('%F',(floatval($pmatch[1]) + $x_diff));
17888 $neww = sprintf('%F',(floatval($pmatch[3]) + $w_diff));
17889 $newpmid = $newx.' '.$pmatch[2].' '.$neww.' '.$pmatch[4].' x*#!#*x'.$pmatch[5].$pmatch[6];
17890 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17891 unset($pmatch, $newpmid, $newx, $neww);
17892 }
17893 break;
17894 }
17895 case 'c': {
17896 // get current X position
17897 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);
17898 if (!isset($xmatches[1])) {
17899 break;
17900 }
17901 $currentxpos = $xmatches[1];
17902 // justify block
17903 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) {
17904 $newx1 = sprintf('%F',(floatval($pmatch[1]) + $spacew));
17905 $newx2 = sprintf('%F',(floatval($pmatch[3]) + $spacew));
17906 $newx3 = sprintf('%F',(floatval($pmatch[5]) + $spacew));
17907 $newpmid = $newx1.' '.$pmatch[2].' '.$newx2.' '.$pmatch[4].' '.$newx3.' '.$pmatch[6].' x*#!#*x'.$pmatch[7].$pmatch[8];
17908 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17909 unset($pmatch, $newpmid, $newx1, $newx2, $newx3);
17910 }
17911 break;
17912 }
17913 }
17914 // shift the annotations and links
17915 $cxpos = ($currentxpos / $this->k);
17916 $lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
17917 if ($this->inxobj) {
17918 // we are inside an XObject template
17919 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
17920 if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17921 if ($cxpos > $lmpos) {
17922 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
17923 $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17924 } else {
17925 $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17926 }
17927 break;
17928 }
17929 }
17930 } elseif (isset($this->PageAnnots[$this->page])) {
17931 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
17932 if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17933 if ($cxpos > $lmpos) {
17934 $this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
17935 $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17936 } else {
17937 $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17938 }
17939 break;
17940 }
17941 }
17942 }
17943 } // end of while
17944 // remove markers
17945 $pmid = str_replace('x*#!#*x', '', $pmid);
17946 if ($this->isUnicodeFont()) {
17947 // multibyte characters
17948 $spacew = $spacewidthu;
17949 if ($this->font_stretching != 100) {
17950 // word spacing is affected by stretching
17951 $spacew /= ($this->font_stretching / 100);
17952 }
17953 // escape special characters
17954 $pos = 0;
17955 $pmid = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmid);
17956 $pmid = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmid);
17957 if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmid, $pamatch) > 0) {
17958 foreach($pamatch[0] as $pk => $pmatch) {
17959 $replace = $pamatch[1][$pk];
17960 $replace = str_replace('#!#OP#!#', '(', $replace);
17961 $replace = str_replace('#!#CP#!#', ')', $replace);
17962 $newpmid = '[('.str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacew).' (', $replace).')]';
17963 $pos = strpos($pmid, $pmatch, $pos);
17964 if ($pos !== FALSE) {
17965 $pmid = substr_replace($pmid, $newpmid, $pos, strlen($pmatch));
17966 }
17967 ++$pos;
17968 }
17969 unset($pamatch);
17970 }
17971 if ($this->inxobj) {
17972 // we are inside an XObject template
17973 $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
17974 } else {
17975 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
17976 }
17977 $endlinepos = strlen($pstart."\n".$pmid."\n");
17978 } else {
17979 // non-unicode (single-byte characters)
17980 if ($this->font_stretching != 100) {
17981 // word spacing (Tw) is affected by stretching
17982 $spacewidth /= ($this->font_stretching / 100);
17983 }
17984 $rs = sprintf('%F Tw', $spacewidth);
17985 $pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
17986 if ($this->inxobj) {
17987 // we are inside an XObject template
17988 $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
17989 } else {
17990 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
17991 }
17992 $endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
17993 }
17994 }
17995 } // end of J
17996 } // end if $startlinex
17997 if (($t_x != 0) OR ($yshift < 0)) {
17998 // shift the line
17999 $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18000 $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18001 $endlinepos = strlen($pstart);
18002 if ($this->inxobj) {
18003 // we are inside an XObject template
18004 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18005 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18006 if ($pak >= $pask) {
18007 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18008 $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18009 }
18010 }
18011 } else {
18012 $this->setPageBuffer($startlinepage, $pstart.$pend);
18013 // shift the annotations and links
18014 if (isset($this->PageAnnots[$this->page])) {
18015 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18016 if ($pak >= $pask) {
18017 $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18018 $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18019 }
18020 }
18021 }
18022 }
18023 $this->y -= $yshift;
18024 }
18025 }
18026 $pbrk = $this->checkPageBreak($this->lasth);
18027 $this->newline = false;
18028 $startlinex = $this->x;
18029 $startliney = $this->y;
18030 if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
18031 $startliney -= ((0.3 * $this->FontSizePt) / $this->k);
18032 } elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
18033 $startliney -= (($this->FontSizePt / 0.7) / $this->k);
18034 } else {
18035 $minstartliney = $startliney;
18036 $maxbottomliney = ($this->y + $this->getCellHeight($fontsize / $this->k));
18037 }
18038 $startlinepage = $this->page;
18039 if (isset($endlinepos) AND (!$pbrk)) {
18040 $startlinepos = $endlinepos;
18041 } else {
18042 if ($this->inxobj) {
18043 // we are inside an XObject template
18044 $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
18045 } elseif (!$this->InFooter) {
18046 if (isset($this->footerlen[$this->page])) {
18047 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18048 } else {
18049 $this->footerpos[$this->page] = $this->pagelen[$this->page];
18050 }
18051 $startlinepos = $this->footerpos[$this->page];
18052 } else {
18053 $startlinepos = $this->pagelen[$this->page];
18054 }
18055 }
18056 unset($endlinepos);
18057 $plalign = $lalign;
18058 if (isset($this->PageAnnots[$this->page])) {
18059 $pask = count($this->PageAnnots[$this->page]);
18060 } else {
18061 $pask = 0;
18062 }
18063 if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
18064 AND (isset($this->emptypagemrk[$this->page]))
18065 AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
18066 $this->SetFont($fontname, $fontstyle, $fontsize);
18067 if ($wfill) {
18068 $this->SetFillColorArray($this->bgcolor);
18069 }
18070 }
18071 } // end newline
18072 if (isset($opentagpos)) {
18073 unset($opentagpos);
18074 }
18075 if ($dom[$key]['tag']) {
18076 if ($dom[$key]['opening']) {
18077 // get text indentation (if any)
18078 if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
18079 $this->textindent = $dom[$key]['text-indent'];
18080 $this->newline = true;
18081 }
18082 // table
18083 if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
18084 // available page width
18085 if ($this->rtl) {
18086 $wtmp = $this->x - $this->lMargin;
18087 } else {
18088 $wtmp = $this->w - $this->rMargin - $this->x;
18089 }
18090 // get cell spacing
18091 if (isset($dom[$key]['attribute']['cellspacing'])) {
18092 $clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
18093 $cellspacing = array('H' => $clsp, 'V' => $clsp);
18094 } elseif (isset($dom[$key]['border-spacing'])) {
18095 $cellspacing = $dom[$key]['border-spacing'];
18096 } else {
18097 $cellspacing = array('H' => 0, 'V' => 0);
18098 }
18099 // table width
18100 if (isset($dom[$key]['width'])) {
18101 $table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
18102 } else {
18103 $table_width = $wtmp;
18104 }
18105 $table_width -= (2 * $cellspacing['H']);
18106 if (!$this->inthead) {
18107 $this->y += $cellspacing['V'];
18108 }
18109 if ($this->rtl) {
18110 $cellspacingx = -$cellspacing['H'];
18111 } else {
18112 $cellspacingx = $cellspacing['H'];
18113 }
18114 // total table width without cellspaces
18115 $table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
18116 // minimum column width
18117 $table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
18118 // array of custom column widths
18119 $table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
18120 }
18121 // table row
18122 if ($dom[$key]['value'] == 'tr') {
18123 // reset column counter
18124 $colid = 0;
18125 }
18126 // table cell
18127 if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18128 $trid = $dom[$key]['parent'];
18129 $table_el = $dom[$trid]['parent'];
18130 if (!isset($dom[$table_el]['cols'])) {
18131 $dom[$table_el]['cols'] = $dom[$trid]['cols'];
18132 }
18133 // store border info
18134 $tdborder = 0;
18135 if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
18136 $tdborder = $dom[$key]['border'];
18137 }
18138 $colspan = intval($dom[$key]['attribute']['colspan']);
18139 if ($colspan <= 0) {
18140 $colspan = 1;
18141 }
18142 $old_cell_padding = $this->cell_padding;
18143 if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
18144 $crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
18145 $current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
18146 } elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
18147 $current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
18148 } else {
18149 $current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
18150 }
18151 $this->cell_padding = $current_cell_padding;
18152 if (isset($dom[$key]['height'])) {
18153 // minimum cell height
18154 $cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
18155 } else {
18156 $cellh = 0;
18157 }
18158 if (isset($dom[$key]['content'])) {
18159 $cell_content = $dom[$key]['content'];
18160 } else {
18161 $cell_content = '&nbsp;';
18162 }
18163 $tagtype = $dom[$key]['value'];
18164 $parentid = $key;
18165 while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
18166 // move $key index forward
18167 ++$key;
18168 }
18169 if (!isset($dom[$trid]['startpage'])) {
18170 $dom[$trid]['startpage'] = $this->page;
18171 } else {
18172 $this->setPage($dom[$trid]['startpage']);
18173 }
18174 if (!isset($dom[$trid]['startcolumn'])) {
18175 $dom[$trid]['startcolumn'] = $this->current_column;
18176 } elseif ($this->current_column != $dom[$trid]['startcolumn']) {
18177 $tmpx = $this->x;
18178 $this->selectColumn($dom[$trid]['startcolumn']);
18179 $this->x = $tmpx;
18180 }
18181 if (!isset($dom[$trid]['starty'])) {
18182 $dom[$trid]['starty'] = $this->y;
18183 } else {
18184 $this->y = $dom[$trid]['starty'];
18185 }
18186 if (!isset($dom[$trid]['startx'])) {
18187 $dom[$trid]['startx'] = $this->x;
18188 $this->x += $cellspacingx;
18189 } else {
18190 $this->x += ($cellspacingx / 2);
18191 }
18192 if (isset($dom[$parentid]['attribute']['rowspan'])) {
18193 $rowspan = intval($dom[$parentid]['attribute']['rowspan']);
18194 } else {
18195 $rowspan = 1;
18196 }
18197 // skip row-spanned cells started on the previous rows
18198 if (isset($dom[$table_el]['rowspans'])) {
18199 $rsk = 0;
18200 $rskmax = count($dom[$table_el]['rowspans']);
18201 while ($rsk < $rskmax) {
18202 $trwsp = $dom[$table_el]['rowspans'][$rsk];
18203 $rsstartx = $trwsp['startx'];
18204 $rsendx = $trwsp['endx'];
18205 // account for margin changes
18206 if ($trwsp['startpage'] < $this->page) {
18207 if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
18208 $dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
18209 $rsstartx -= $dl;
18210 $rsendx -= $dl;
18211 } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
18212 $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
18213 $rsstartx += $dl;
18214 $rsendx += $dl;
18215 }
18216 }
18217 if (($trwsp['rowspan'] > 0)
18218 AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
18219 AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
18220 AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
18221 // set the starting X position of the current cell
18222 $this->x = $rsendx + $cellspacingx;
18223 // increment column indicator
18224 $colid += $trwsp['colspan'];
18225 if (($trwsp['rowspan'] == 1)
18226 AND (isset($dom[$trid]['endy']))
18227 AND (isset($dom[$trid]['endpage']))
18228 AND (isset($dom[$trid]['endcolumn']))
18229 AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
18230 AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18231 // set ending Y position for row
18232 $dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18233 $dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
18234 }
18235 $rsk = 0;
18236 } else {
18237 ++$rsk;
18238 }
18239 }
18240 }
18241 if (isset($dom[$parentid]['width'])) {
18242 // user specified width
18243 $cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
18244 $tmpcw = ($cellw / $colspan);
18245 for ($i = 0; $i < $colspan; ++$i) {
18246 $table_colwidths[($colid + $i)] = $tmpcw;
18247 }
18248 } else {
18249 // inherit column width
18250 $cellw = 0;
18251 for ($i = 0; $i < $colspan; ++$i) {
18252 $cellw += (isset($table_colwidths[($colid + $i)]) ? $table_colwidths[($colid + $i)] : 0);
18253 }
18254 }
18255 $cellw += (($colspan - 1) * $cellspacing['H']);
18256 // increment column indicator
18257 $colid += $colspan;
18258 // add rowspan information to table element
18259 if ($rowspan > 1) {
18260 $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));
18261 }
18262 $cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
18263 if ($rowspan > 1) {
18264 $dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
18265 }
18266 // push background colors
18267 if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
18268 $dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
18269 }
18270 // store border info
18271 if (isset($tdborder) AND !empty($tdborder)) {
18272 $dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
18273 }
18274 $prevLastH = $this->lasth;
18275 // store some info for multicolumn mode
18276 if ($this->rtl) {
18277 $this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
18278 } else {
18279 $this->colxshift['x'] = $this->x - $this->lMargin;
18280 }
18281 $this->colxshift['s'] = $cellspacing;
18282 $this->colxshift['p'] = $current_cell_padding;
18283 // ****** write the cell content ******
18284 $this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
18285 // restore some values
18286 $this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
18287 $this->lasth = $prevLastH;
18288 $this->cell_padding = $old_cell_padding;
18289 $dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
18290 // update the end of row position
18291 if ($rowspan <= 1) {
18292 if (isset($dom[$trid]['endy'])) {
18293 if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
18294 $dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
18295 } elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
18296 $dom[$trid]['endy'] = $this->y;
18297 }
18298 } else {
18299 $dom[$trid]['endy'] = $this->y;
18300 }
18301 if (isset($dom[$trid]['endpage'])) {
18302 $dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
18303 } else {
18304 $dom[$trid]['endpage'] = $this->page;
18305 }
18306 if (isset($dom[$trid]['endcolumn'])) {
18307 $dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
18308 } else {
18309 $dom[$trid]['endcolumn'] = $this->current_column;
18310 }
18311 } else {
18312 // account for row-spanned cells
18313 $dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
18314 $dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
18315 $dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
18316 $dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
18317 }
18318 if (isset($dom[$table_el]['rowspans'])) {
18319 // update endy and endpage on rowspanned cells
18320 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
18321 if ($trwsp['rowspan'] > 0) {
18322 if (isset($dom[$trid]['endpage'])) {
18323 if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18324 $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18325 } elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
18326 $dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
18327 $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
18328 $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
18329 } else {
18330 $dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
18331 }
18332 }
18333 }
18334 }
18335 }
18336 $this->x += ($cellspacingx / 2);
18337 } else {
18338 // opening tag (or self-closing tag)
18339 if (!isset($opentagpos)) {
18340 if ($this->inxobj) {
18341 // we are inside an XObject template
18342 $opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
18343 } elseif (!$this->InFooter) {
18344 if (isset($this->footerlen[$this->page])) {
18345 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18346 } else {
18347 $this->footerpos[$this->page] = $this->pagelen[$this->page];
18348 }
18349 $opentagpos = $this->footerpos[$this->page];
18350 }
18351 }
18352 $dom = $this->openHTMLTagHandler($dom, $key, $cell);
18353 }
18354 } else { // closing tag
18355 $prev_numpages = $this->numpages;
18356 $old_bordermrk = $this->bordermrk[$this->page];
18357 $dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
18358 if ($this->bordermrk[$this->page] > $old_bordermrk) {
18359 $startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
18360 }
18361 if ($prev_numpages > $this->numpages) {
18362 $startlinepage = $this->page;
18363 }
18364 }
18365 } elseif (strlen($dom[$key]['value']) > 0) {
18366 // print list-item
18367 if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) {
18368 $this->SetFont($pfontname, $pfontstyle, $pfontsize);
18369 $this->resetLastH();
18370 $minstartliney = $this->y;
18371 $maxbottomliney = ($startliney + $this->getCellHeight($this->FontSize));
18372 if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
18373 $this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
18374 }
18375 $this->SetFont($curfontname, $curfontstyle, $curfontsize);
18376 $this->resetLastH();
18377 if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
18378 $pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
18379 $pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
18380 $this->y += ($this->getCellHeight(($pfontsize - $curfontsize) / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
18381 $minstartliney = min($this->y, $minstartliney);
18382 $maxbottomliney = max(($this->y + $this->getCellHeight($pfontsize / $this->k)), $maxbottomliney);
18383 }
18384 }
18385 // text
18386 $this->htmlvspace = 0;
18387 $isRTLString = preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $dom[$key]['value']) || preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $dom[$key]['value']);
18388 if ((!$this->premode) AND $this->isRTLTextDir() AND !$isRTLString) {
18389 // reverse spaces order
18390 $lsp = ''; // left spaces
18391 $rsp = ''; // right spaces
18392 if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18393 $lsp = $matches[1];
18394 }
18395 if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18396 $rsp = $matches[1];
18397 }
18398 $dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
18399 }
18400 if ($newline) {
18401 if (!$this->premode) {
18402 $prelen = strlen($dom[$key]['value']);
18403 if ($this->isRTLTextDir() AND !$isRTLString) {
18404 // right trim except non-breaking space
18405 $dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
18406 } else {
18407 // left trim except non-breaking space
18408 $dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
18409 }
18410 $postlen = strlen($dom[$key]['value']);
18411 if (($postlen == 0) AND ($prelen > 0)) {
18412 $dom[$key]['trimmed_space'] = true;
18413 }
18414 }
18415 $newline = false;
18416 $firstblock = true;
18417 } else {
18418 $firstblock = false;
18419 // replace empty multiple spaces string with a single space
18420 $dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
18421 }
18422 $strrest = '';
18423 if ($this->rtl) {
18424 $this->x -= $this->textindent;
18425 } else {
18426 $this->x += $this->textindent;
18427 }
18428 if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
18429 $strlinelen = $this->GetStringWidth($dom[$key]['value']);
18430 if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
18431 // HTML <a> Link
18432 $hrefcolor = '';
18433 if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
18434 $hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
18435 }
18436 $hrefstyle = -1;
18437 if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
18438 $hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
18439 }
18440 $strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
18441 } else {
18442 $wadj = 0; // space to leave for block continuity
18443 if ($this->rtl) {
18444 $cwa = ($this->x - $this->lMargin);
18445 } else {
18446 $cwa = ($this->w - $this->rMargin - $this->x);
18447 }
18448 if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
18449 // check the next text blocks for continuity
18450 $nkey = ($key + 1);
18451 $write_block = true;
18452 $same_textdir = true;
18453 $tmp_fontname = $this->FontFamily;
18454 $tmp_fontstyle = $this->FontStyle;
18455 $tmp_fontsize = $this->FontSizePt;
18456 while ($write_block AND isset($dom[$nkey])) {
18457 if ($dom[$nkey]['tag']) {
18458 if ($dom[$nkey]['block']) {
18459 // end of block
18460 $write_block = false;
18461 }
18462 $tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
18463 $tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
18464 $tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
18465 $same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
18466 } else {
18467 $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'+/', $this->re_space['m'], $dom[$nkey]['value']);
18468 if (isset($nextstr[0]) AND $same_textdir) {
18469 $wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
18470 if (isset($nextstr[1])) {
18471 $write_block = false;
18472 }
18473 }
18474 }
18475 ++$nkey;
18476 }
18477 }
18478 if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
18479 $wadj = 0;
18480 $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $dom[$key]['value']);
18481 $numblks = count($nextstr);
18482 if ($numblks > 1) {
18483 // try to split on blank spaces
18484 $wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
18485 } else {
18486 // set the entire block on new line
18487 $wadj = $this->GetStringWidth($nextstr[0]);
18488 }
18489 }
18490 // check for reversed text direction
18491 if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
18492 // LTR text on RTL direction or RTL text on LTR direction
18493 $reverse_dir = true;
18494 $this->rtl = !$this->rtl;
18495 $revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
18496 if ($this->rtl) {
18497 $this->x += $revshift;
18498 } else {
18499 $this->x -= $revshift;
18500 }
18501 $xws = $this->x;
18502 }
18503 // ****** write only until the end of the line and get the rest ******
18504 $strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
18505 // restore default direction
18506 if ($reverse_dir AND ($wadj == 0)) {
18507 $this->x = $xws;
18508 $this->rtl = !$this->rtl;
18509 $reverse_dir = false;
18510 }
18511 }
18512 }
18513 $this->textindent = 0;
18514 if (strlen($strrest) > 0) {
18515 // store the remaining string on the previous $key position
18516 $this->newline = true;
18517 if ($strrest == $dom[$key]['value']) {
18518 // used to avoid infinite loop
18519 ++$loop;
18520 } else {
18521 $loop = 0;
18522 }
18523 $dom[$key]['value'] = $strrest;
18524 if ($cell) {
18525 if ($this->rtl) {
18526 $this->x -= $this->cell_padding['R'];
18527 } else {
18528 $this->x += $this->cell_padding['L'];
18529 }
18530 }
18531 if ($loop < 3) {
18532 --$key;
18533 }
18534 } else {
18535 $loop = 0;
18536 // add the positive font spacing of the last character (if any)
18537 if ($this->font_spacing > 0) {
18538 if ($this->rtl) {
18539 $this->x -= $this->font_spacing;
18540 } else {
18541 $this->x += $this->font_spacing;
18542 }
18543 }
18544 }
18545 }
18546 ++$key;
18547 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')) {
18548 // check if we are on a new page or on a new column
18549 if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
18550 // we are on a new page or on a new column and the total object height is less than the available vertical space.
18551 // restore previous object
18552 $this->rollbackTransaction(true);
18553 // restore previous values
18554 foreach ($this_method_vars as $vkey => $vval) {
18555 $$vkey = $vval;
18556 }
18557 if (!empty($dom[$key]['thead'])) {
18558 $this->inthead = true;
18559 }
18560 // add a page (or trig AcceptPageBreak() for multicolumn mode)
18561 $pre_y = $this->y;
18562 if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
18563 $startliney = $this->y;
18564 }
18565 $undo = true; // avoid infinite loop
18566 } else {
18567 $undo = false;
18568 }
18569 }
18570 } // end for each $key
18571 // align the last line
18572 if (isset($startlinex)) {
18573 $yshift = ($minstartliney - $startliney);
18574 if (($yshift > 0) OR ($this->page > $startlinepage)) {
18575 $yshift = 0;
18576 }
18577 $t_x = 0;
18578 // the last line must be shifted to be aligned as requested
18579 $linew = abs($this->endlinex - $startlinex);
18580 if ($this->inxobj) {
18581 // we are inside an XObject template
18582 $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
18583 if (isset($opentagpos)) {
18584 $midpos = $opentagpos;
18585 } else {
18586 $midpos = 0;
18587 }
18588 if ($midpos > 0) {
18589 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
18590 $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
18591 } else {
18592 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
18593 $pend = '';
18594 }
18595 } else {
18596 $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
18597 if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18598 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18599 $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
18600 } elseif (isset($opentagpos)) {
18601 $midpos = $opentagpos;
18602 } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18603 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18604 $midpos = $this->footerpos[$startlinepage];
18605 } else {
18606 $midpos = 0;
18607 }
18608 if ($midpos > 0) {
18609 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
18610 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
18611 } else {
18612 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
18613 $pend = '';
18614 }
18615 }
18616 if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
18617 // calculate shifting amount
18618 $tw = $w;
18619 if ($this->lMargin != $prevlMargin) {
18620 $tw += ($prevlMargin - $this->lMargin);
18621 }
18622 if ($this->rMargin != $prevrMargin) {
18623 $tw += ($prevrMargin - $this->rMargin);
18624 }
18625 $one_space_width = $this->GetStringWidth(chr(32));
18626 $no = 0; // number of spaces on a line contained on a single block
18627 if ($this->isRTLTextDir()) { // RTL
18628 // remove left space if exist
18629 $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
18630 if ($pos1 > 0) {
18631 $pos1 = intval($pos1);
18632 if ($this->isUnicodeFont()) {
18633 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
18634 $spacelen = 2;
18635 } else {
18636 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
18637 $spacelen = 1;
18638 }
18639 if ($pos1 == $pos2) {
18640 $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
18641 if (substr($pmid, $pos1, 4) == '[()]') {
18642 $linew -= $one_space_width;
18643 } elseif ($pos1 == strpos($pmid, '[(')) {
18644 $no = 1;
18645 }
18646 }
18647 }
18648 } else { // LTR
18649 // remove right space if exist
18650 $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
18651 if ($pos1 > 0) {
18652 $pos1 = intval($pos1);
18653 if ($this->isUnicodeFont()) {
18654 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
18655 $spacelen = 2;
18656 } else {
18657 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
18658 $spacelen = 1;
18659 }
18660 if ($pos1 == $pos2) {
18661 $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
18662 $linew -= $one_space_width;
18663 }
18664 }
18665 }
18666 $mdiff = ($tw - $linew);
18667 if ($plalign == 'C') {
18668 if ($this->rtl) {
18669 $t_x = -($mdiff / 2);
18670 } else {
18671 $t_x = ($mdiff / 2);
18672 }
18673 } elseif ($plalign == 'R') {
18674 // right alignment on LTR document
18675 $t_x = $mdiff;
18676 } elseif ($plalign == 'L') {
18677 // left alignment on RTL document
18678 $t_x = -$mdiff;
18679 }
18680 } // end if startlinex
18681 if (($t_x != 0) OR ($yshift < 0)) {
18682 // shift the line
18683 $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18684 $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18685 $endlinepos = strlen($pstart);
18686 if ($this->inxobj) {
18687 // we are inside an XObject template
18688 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18689 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18690 if ($pak >= $pask) {
18691 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18692 $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18693 }
18694 }
18695 } else {
18696 $this->setPageBuffer($startlinepage, $pstart.$pend);
18697 // shift the annotations and links
18698 if (isset($this->PageAnnots[$this->page])) {
18699 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18700 if ($pak >= $pask) {
18701 $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18702 $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18703 }
18704 }
18705 }
18706 }
18707 $this->y -= $yshift;
18708 $yshift = 0;
18709 }
18710 }
18711 // restore previous values
18712 $this->setGraphicVars($gvars);
18713 if ($this->num_columns > 1) {
18714 $this->selectColumn();
18715 } elseif ($this->page > $prevPage) {
18716 $this->lMargin = $this->pagedim[$this->page]['olm'];
18717 $this->rMargin = $this->pagedim[$this->page]['orm'];
18718 }
18719 // restore previous list state
18720 $this->cell_height_ratio = $prev_cell_height_ratio;
18721 $this->listnum = $prev_listnum;
18722 $this->listordered = $prev_listordered;
18723 $this->listcount = $prev_listcount;
18724 $this->lispacer = $prev_lispacer;
18725 if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
18726 $this->Ln($this->lasth);
18727 if (($this->y < $maxbottomliney) AND ($startlinepage == $this->page)) {
18728 $this->y = $maxbottomliney;
18729 }
18730 }
18731 unset($dom);
18732 }
18733
18734 /**
18735 * Process opening tags.
18736 * @param $dom (array) html dom array
18737 * @param $key (int) current element id
18738 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
18739 * @return $dom array
18740 * @protected
18741 */
18742 protected function openHTMLTagHandler($dom, $key, $cell) {
18743 $tag = $dom[$key];
18744 $parent = $dom[($dom[$key]['parent'])];
18745 $firsttag = ($key == 1);
18746 // check for text direction attribute
18747 if (isset($tag['dir'])) {
18748 $this->setTempRTL($tag['dir']);
18749 } else {
18750 $this->tmprtl = false;
18751 }
18752 if ($tag['block']) {
18753 $hbz = 0; // distance from y to line bottom
18754 $hb = 0; // vertical space between block tags
18755 // calculate vertical space for block tags
18756 if (isset($this->tagvspaces[$tag['value']][0]['h']) && !empty($this->tagvspaces[$tag['value']][0]['h']) && ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
18757 $cur_h = $this->tagvspaces[$tag['value']][0]['h'];
18758 } elseif (isset($tag['fontsize'])) {
18759 $cur_h = $this->getCellHeight($tag['fontsize'] / $this->k);
18760 } else {
18761 $cur_h = $this->getCellHeight($this->FontSize);
18762 }
18763 if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
18764 $on = $this->tagvspaces[$tag['value']][0]['n'];
18765 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18766 $on = 0.6;
18767 } else {
18768 $on = 1;
18769 }
18770 if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br', 'hr')))) {
18771 $hb = 0;
18772 } else {
18773 $hb = ($on * $cur_h);
18774 }
18775 if (($this->htmlvspace <= 0) AND ($on > 0)) {
18776 if (isset($parent['fontsize'])) {
18777 $hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
18778 } else {
18779 $hbz = $this->getCellHeight($this->FontSize);
18780 }
18781 }
18782 if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
18783 // fix vertical space after table
18784 $hbz = 0;
18785 }
18786 // closing vertical space
18787 $hbc = 0;
18788 if (isset($this->tagvspaces[$tag['value']][1]['h']) && !empty($this->tagvspaces[$tag['value']][1]['h']) && ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
18789 $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
18790 } elseif (isset($parent['fontsize'])) {
18791 $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
18792 } else {
18793 $pre_h = $this->getCellHeight($this->FontSize);
18794 }
18795 if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
18796 $cn = $this->tagvspaces[$tag['value']][1]['n'];
18797 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18798 $cn = 0.6;
18799 } else {
18800 $cn = 1;
18801 }
18802 if (isset($this->tagvspaces[$tag['value']][1])) {
18803 $hbc = ($cn * $pre_h);
18804 }
18805 }
18806 // Opening tag
18807 switch($tag['value']) {
18808 case 'table': {
18809 $cp = 0;
18810 $cs = 0;
18811 $dom[$key]['rowspans'] = array();
18812 if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
18813 $this->htmlvspace = 0;
18814 // set table header
18815 if (!TCPDF_STATIC::empty_string($dom[$key]['thead'])) {
18816 // set table header
18817 $this->thead = $dom[$key]['thead'];
18818 if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
18819 $this->theadMargins = array();
18820 $this->theadMargins['cell_padding'] = $this->cell_padding;
18821 $this->theadMargins['lmargin'] = $this->lMargin;
18822 $this->theadMargins['rmargin'] = $this->rMargin;
18823 $this->theadMargins['page'] = $this->page;
18824 $this->theadMargins['cell'] = $cell;
18825 $this->theadMargins['gvars'] = $this->getGraphicVars();
18826 }
18827 }
18828 }
18829 // store current margins and page
18830 $dom[$key]['old_cell_padding'] = $this->cell_padding;
18831 if (isset($tag['attribute']['cellpadding'])) {
18832 $pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
18833 $this->SetCellPadding($pad);
18834 } elseif (isset($tag['padding'])) {
18835 $this->cell_padding = $tag['padding'];
18836 }
18837 if (isset($tag['attribute']['cellspacing'])) {
18838 $cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
18839 } elseif (isset($tag['border-spacing'])) {
18840 $cs = $tag['border-spacing']['V'];
18841 }
18842 $prev_y = $this->y;
18843 if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
18844 $this->inthead = true;
18845 // add a page (or trig AcceptPageBreak() for multicolumn mode)
18846 $this->checkPageBreak($this->PageBreakTrigger + 1);
18847 }
18848 break;
18849 }
18850 case 'tr': {
18851 // array of columns positions
18852 $dom[$key]['cellpos'] = array();
18853 break;
18854 }
18855 case 'hr': {
18856 if ((isset($tag['height'])) AND ($tag['height'] != '')) {
18857 $hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
18858 } else {
18859 $hrHeight = $this->GetLineWidth();
18860 }
18861 $this->addHTMLVertSpace($hbz, max($hb, ($hrHeight / 2)), $cell, $firsttag);
18862 $x = $this->GetX();
18863 $y = $this->GetY();
18864 $wtmp = $this->w - $this->lMargin - $this->rMargin;
18865 if ($cell) {
18866 $wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
18867 }
18868 if ((isset($tag['width'])) AND ($tag['width'] != '')) {
18869 $hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
18870 } else {
18871 $hrWidth = $wtmp;
18872 }
18873 $prevlinewidth = $this->GetLineWidth();
18874 $this->SetLineWidth($hrHeight);
18875 $this->Line($x, $y, $x + $hrWidth, $y);
18876 $this->SetLineWidth($prevlinewidth);
18877 $this->addHTMLVertSpace(max($hbc, ($hrHeight / 2)), 0, $cell, !isset($dom[($key + 1)]));
18878 break;
18879 }
18880 case 'a': {
18881 if (array_key_exists('href', $tag['attribute'])) {
18882 $this->HREF['url'] = $tag['attribute']['href'];
18883 }
18884 break;
18885 }
18886 case 'img': {
18887 if (empty($tag['attribute']['src'])) {
18888 break;
18889 }
18890 $imgsrc = $tag['attribute']['src'];
18891 if ($imgsrc[0] === '@') {
18892 // data stream
18893 $imgsrc = '@'.base64_decode(substr($imgsrc, 1));
18894 $type = '';
18895 } else {
18896 if (($imgsrc[0] === '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
18897 // fix image path
18898 $findroot = strpos($imgsrc, $_SERVER['DOCUMENT_ROOT']);
18899 if (($findroot === false) OR ($findroot > 1)) {
18900 if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
18901 $imgsrc = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$imgsrc;
18902 } else {
18903 $imgsrc = $_SERVER['DOCUMENT_ROOT'].$imgsrc;
18904 }
18905 }
18906 $imgsrc = urldecode($imgsrc);
18907 $testscrtype = @parse_url($imgsrc);
18908 if (empty($testscrtype['query'])) {
18909 // convert URL to server path
18910 $imgsrc = str_replace(K_PATH_URL, K_PATH_MAIN, $imgsrc);
18911 } elseif (preg_match('|^https?://|', $imgsrc) !== 1) {
18912 // convert URL to server path
18913 $imgsrc = str_replace(K_PATH_MAIN, K_PATH_URL, $imgsrc);
18914 }
18915 }
18916 // get image type
18917 $type = TCPDF_IMAGES::getImageFileType($imgsrc);
18918 }
18919 if (!isset($tag['width'])) {
18920 $tag['width'] = 0;
18921 }
18922 if (!isset($tag['height'])) {
18923 $tag['height'] = 0;
18924 }
18925 //if (!isset($tag['attribute']['align'])) {
18926 // the only alignment supported is "bottom"
18927 // further development is required for other modes.
18928 $tag['attribute']['align'] = 'bottom';
18929 //}
18930 switch($tag['attribute']['align']) {
18931 case 'top': {
18932 $align = 'T';
18933 break;
18934 }
18935 case 'middle': {
18936 $align = 'M';
18937 break;
18938 }
18939 case 'bottom': {
18940 $align = 'B';
18941 break;
18942 }
18943 default: {
18944 $align = 'B';
18945 break;
18946 }
18947 }
18948 $prevy = $this->y;
18949 $xpos = $this->x;
18950 $imglink = '';
18951 if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
18952 $imglink = $this->HREF['url'];
18953 if ($imglink[0] == '#') {
18954 // convert url to internal link
18955 $lnkdata = explode(',', $imglink);
18956 if (isset($lnkdata[0])) {
18957 $page = intval(substr($lnkdata[0], 1));
18958 if (empty($page) OR ($page <= 0)) {
18959 $page = $this->page;
18960 }
18961 if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
18962 $lnky = floatval($lnkdata[1]);
18963 } else {
18964 $lnky = 0;
18965 }
18966 $imglink = $this->AddLink();
18967 $this->SetLink($imglink, $lnky, $page);
18968 }
18969 }
18970 }
18971 $border = 0;
18972 if (isset($tag['border']) AND !empty($tag['border'])) {
18973 // currently only support 1 (frame) or a combination of 'LTRB'
18974 $border = $tag['border'];
18975 }
18976 $iw = '';
18977 if (isset($tag['width'])) {
18978 $iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k), 'px', false);
18979 }
18980 $ih = '';
18981 if (isset($tag['height'])) {
18982 $ih = $this->getHTMLUnitToUnits($tag['height'], ($tag['fontsize'] / $this->k), 'px', false);
18983 }
18984 if (($type == 'eps') OR ($type == 'ai')) {
18985 $this->ImageEps($imgsrc, $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
18986 } elseif ($type == 'svg') {
18987 $this->ImageSVG($imgsrc, $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
18988 } else {
18989 $this->Image($imgsrc, $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
18990 }
18991 switch($align) {
18992 case 'T': {
18993 $this->y = $prevy;
18994 break;
18995 }
18996 case 'M': {
18997 $this->y = (($this->img_rb_y + $prevy - ($this->getCellHeight($tag['fontsize'] / $this->k))) / 2);
18998 break;
18999 }
19000 case 'B': {
19001 $this->y = $this->img_rb_y - ($this->getCellHeight($tag['fontsize'] / $this->k) - ($this->getFontDescent($tag['fontname'], $tag['fontstyle'], $tag['fontsize']) * $this->cell_height_ratio));
19002 break;
19003 }
19004 }
19005 break;
19006 }
19007 case 'dl': {
19008 ++$this->listnum;
19009 if ($this->listnum == 1) {
19010 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19011 } else {
19012 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19013 }
19014 break;
19015 }
19016 case 'dt': {
19017 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19018 break;
19019 }
19020 case 'dd': {
19021 if ($this->rtl) {
19022 $this->rMargin += $this->listindent;
19023 } else {
19024 $this->lMargin += $this->listindent;
19025 }
19026 ++$this->listindentlevel;
19027 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19028 break;
19029 }
19030 case 'ul':
19031 case 'ol': {
19032 ++$this->listnum;
19033 if ($tag['value'] == 'ol') {
19034 $this->listordered[$this->listnum] = true;
19035 } else {
19036 $this->listordered[$this->listnum] = false;
19037 }
19038 if (isset($tag['attribute']['start'])) {
19039 $this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
19040 } else {
19041 $this->listcount[$this->listnum] = 0;
19042 }
19043 if ($this->rtl) {
19044 $this->rMargin += $this->listindent;
19045 $this->x -= $this->listindent;
19046 } else {
19047 $this->lMargin += $this->listindent;
19048 $this->x += $this->listindent;
19049 }
19050 ++$this->listindentlevel;
19051 if ($this->listnum == 1) {
19052 if ($key > 1) {
19053 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19054 }
19055 } else {
19056 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19057 }
19058 break;
19059 }
19060 case 'li': {
19061 if ($key > 2) {
19062 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19063 }
19064 if ($this->listordered[$this->listnum]) {
19065 // ordered item
19066 if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19067 $this->lispacer = $parent['attribute']['type'];
19068 } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19069 $this->lispacer = $parent['listtype'];
19070 } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19071 $this->lispacer = $this->lisymbol;
19072 } else {
19073 $this->lispacer = '#';
19074 }
19075 ++$this->listcount[$this->listnum];
19076 if (isset($tag['attribute']['value'])) {
19077 $this->listcount[$this->listnum] = intval($tag['attribute']['value']);
19078 }
19079 } else {
19080 // unordered item
19081 if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19082 $this->lispacer = $parent['attribute']['type'];
19083 } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19084 $this->lispacer = $parent['listtype'];
19085 } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19086 $this->lispacer = $this->lisymbol;
19087 } else {
19088 $this->lispacer = '!';
19089 }
19090 }
19091 break;
19092 }
19093 case 'blockquote': {
19094 if ($this->rtl) {
19095 $this->rMargin += $this->listindent;
19096 } else {
19097 $this->lMargin += $this->listindent;
19098 }
19099 ++$this->listindentlevel;
19100 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19101 break;
19102 }
19103 case 'br': {
19104 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19105 break;
19106 }
19107 case 'div': {
19108 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19109 break;
19110 }
19111 case 'p': {
19112 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19113 break;
19114 }
19115 case 'pre': {
19116 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19117 $this->premode = true;
19118 break;
19119 }
19120 case 'sup': {
19121 $this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
19122 break;
19123 }
19124 case 'sub': {
19125 $this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
19126 break;
19127 }
19128 case 'h1':
19129 case 'h2':
19130 case 'h3':
19131 case 'h4':
19132 case 'h5':
19133 case 'h6': {
19134 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19135 break;
19136 }
19137 // Form fields (since 4.8.000 - 2009-09-07)
19138 case 'form': {
19139 if (isset($tag['attribute']['action'])) {
19140 $this->form_action = $tag['attribute']['action'];
19141 } else {
19142 $this->Error('Please explicitly set action attribute path!');
19143 }
19144 if (isset($tag['attribute']['enctype'])) {
19145 $this->form_enctype = $tag['attribute']['enctype'];
19146 } else {
19147 $this->form_enctype = 'application/x-www-form-urlencoded';
19148 }
19149 if (isset($tag['attribute']['method'])) {
19150 $this->form_mode = $tag['attribute']['method'];
19151 } else {
19152 $this->form_mode = 'post';
19153 }
19154 break;
19155 }
19156 case 'input': {
19157 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19158 $name = $tag['attribute']['name'];
19159 } else {
19160 break;
19161 }
19162 $prop = array();
19163 $opt = array();
19164 if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19165 $prop['readonly'] = true;
19166 }
19167 if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19168 $value = $tag['attribute']['value'];
19169 }
19170 if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC::empty_string($tag['attribute']['maxlength'])) {
19171 $opt['maxlen'] = intval($tag['attribute']['maxlength']);
19172 }
19173 $h = $this->getCellHeight($this->FontSize);
19174 if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19175 $w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
19176 } else {
19177 $w = $h;
19178 }
19179 if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
19180 $checked = true;
19181 } else {
19182 $checked = false;
19183 }
19184 if (isset($tag['align'])) {
19185 switch ($tag['align']) {
19186 case 'C': {
19187 $opt['q'] = 1;
19188 break;
19189 }
19190 case 'R': {
19191 $opt['q'] = 2;
19192 break;
19193 }
19194 case 'L':
19195 default: {
19196 break;
19197 }
19198 }
19199 }
19200 switch ($tag['attribute']['type']) {
19201 case 'text': {
19202 if (isset($value)) {
19203 $opt['v'] = $value;
19204 }
19205 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19206 break;
19207 }
19208 case 'password': {
19209 if (isset($value)) {
19210 $opt['v'] = $value;
19211 }
19212 $prop['password'] = 'true';
19213 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19214 break;
19215 }
19216 case 'checkbox': {
19217 if (!isset($value)) {
19218 break;
19219 }
19220 $this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
19221 break;
19222 }
19223 case 'radio': {
19224 if (!isset($value)) {
19225 break;
19226 }
19227 $this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
19228 break;
19229 }
19230 case 'submit': {
19231 if (!isset($value)) {
19232 $value = 'submit';
19233 }
19234 $w = $this->GetStringWidth($value) * 1.5;
19235 $h *= 1.6;
19236 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19237 $action = array();
19238 $action['S'] = 'SubmitForm';
19239 $action['F'] = $this->form_action;
19240 if ($this->form_enctype != 'FDF') {
19241 $action['Flags'] = array('ExportFormat');
19242 }
19243 if ($this->form_mode == 'get') {
19244 $action['Flags'] = array('GetMethod');
19245 }
19246 $this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
19247 break;
19248 }
19249 case 'reset': {
19250 if (!isset($value)) {
19251 $value = 'reset';
19252 }
19253 $w = $this->GetStringWidth($value) * 1.5;
19254 $h *= 1.6;
19255 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19256 $this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
19257 break;
19258 }
19259 case 'file': {
19260 $prop['fileSelect'] = 'true';
19261 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19262 if (!isset($value)) {
19263 $value = '*';
19264 }
19265 $w = $this->GetStringWidth($value) * 2;
19266 $h *= 1.2;
19267 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19268 $jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
19269 $this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19270 break;
19271 }
19272 case 'hidden': {
19273 if (isset($value)) {
19274 $opt['v'] = $value;
19275 }
19276 $opt['f'] = array('invisible', 'hidden');
19277 $this->TextField($name, 0, 0, $prop, $opt, '', '', false);
19278 break;
19279 }
19280 case 'image': {
19281 // THIS TYPE MUST BE FIXED
19282 if (isset($tag['attribute']['src']) AND !TCPDF_STATIC::empty_string($tag['attribute']['src'])) {
19283 $img = $tag['attribute']['src'];
19284 } else {
19285 break;
19286 }
19287 $value = 'img';
19288 //$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
19289 if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19290 $jsaction = $tag['attribute']['onclick'];
19291 } else {
19292 $jsaction = '';
19293 }
19294 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19295 break;
19296 }
19297 case 'button': {
19298 if (!isset($value)) {
19299 $value = ' ';
19300 }
19301 $w = $this->GetStringWidth($value) * 1.5;
19302 $h *= 1.6;
19303 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19304 if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19305 $jsaction = $tag['attribute']['onclick'];
19306 } else {
19307 $jsaction = '';
19308 }
19309 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19310 break;
19311 }
19312 }
19313 break;
19314 }
19315 case 'textarea': {
19316 $prop = array();
19317 $opt = array();
19318 if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19319 $prop['readonly'] = true;
19320 }
19321 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19322 $name = $tag['attribute']['name'];
19323 } else {
19324 break;
19325 }
19326 if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19327 $opt['v'] = $tag['attribute']['value'];
19328 }
19329 if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC::empty_string($tag['attribute']['cols'])) {
19330 $w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
19331 } else {
19332 $w = 40;
19333 }
19334 if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
19335 $h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize);
19336 } else {
19337 $h = 10;
19338 }
19339 $prop['multiline'] = 'true';
19340 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19341 break;
19342 }
19343 case 'select': {
19344 $h = $this->getCellHeight($this->FontSize);
19345 if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19346 $h *= ($tag['attribute']['size'] + 1);
19347 }
19348 $prop = array();
19349 $opt = array();
19350 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19351 $name = $tag['attribute']['name'];
19352 } else {
19353 break;
19354 }
19355 $w = 0;
19356 if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
19357 $options = explode('#!NwL!#', $tag['attribute']['opt']);
19358 $values = array();
19359 foreach ($options as $val) {
19360 if (strpos($val, '#!TaB!#') !== false) {
19361 $opts = explode('#!TaB!#', $val);
19362 $values[] = $opts;
19363 $w = max($w, $this->GetStringWidth($opts[1]));
19364 } else {
19365 $values[] = $val;
19366 $w = max($w, $this->GetStringWidth($val));
19367 }
19368 }
19369 } else {
19370 break;
19371 }
19372 $w *= 2;
19373 if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
19374 $prop['multipleSelection'] = 'true';
19375 $this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19376 } else {
19377 $this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19378 }
19379 break;
19380 }
19381 case 'tcpdf': {
19382 if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
19383 // Special tag used to call TCPDF methods
19384 if (isset($tag['attribute']['method'])) {
19385 $tcpdf_method = $tag['attribute']['method'];
19386 if (method_exists($this, $tcpdf_method)) {
19387 if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
19388 $params = $this->unserializeTCPDFtagParameters($tag['attribute']['params']);
19389 call_user_func_array(array($this, $tcpdf_method), $params);
19390 } else {
19391 $this->$tcpdf_method();
19392 }
19393 $this->newline = true;
19394 }
19395 }
19396 }
19397 break;
19398 }
19399 default: {
19400 break;
19401 }
19402 }
19403 // define tags that support borders and background colors
19404 $bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
19405 if (in_array($tag['value'], $bordertags)) {
19406 // set border
19407 $dom[$key]['borderposition'] = $this->getBorderStartPosition();
19408 }
19409 if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
19410 $pba = $dom[$key]['attribute']['pagebreakafter'];
19411 // check for pagebreak
19412 if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19413 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19414 $this->checkPageBreak($this->PageBreakTrigger + 1);
19415 }
19416 if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19417 OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19418 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19419 $this->checkPageBreak($this->PageBreakTrigger + 1);
19420 }
19421 }
19422 return $dom;
19423 }
19424
19425 /**
19426 * Process closing tags.
19427 * @param $dom (array) html dom array
19428 * @param $key (int) current element id
19429 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19430 * @param $maxbottomliney (int) maximum y value of current line
19431 * @return $dom array
19432 * @protected
19433 */
19434 protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
19435 $tag = $dom[$key];
19436 $parent = $dom[($dom[$key]['parent'])];
19437 $lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
19438 $in_table_head = false;
19439 // maximum x position (used to draw borders)
19440 if ($this->rtl) {
19441 $xmax = $this->w;
19442 } else {
19443 $xmax = 0;
19444 }
19445 if ($tag['block']) {
19446 $hbz = 0; // distance from y to line bottom
19447 $hb = 0; // vertical space between block tags
19448 // calculate vertical space for block tags
19449 if (isset($this->tagvspaces[$tag['value']][1]['h']) && !empty($this->tagvspaces[$tag['value']][1]['h']) && ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
19450 $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
19451 } elseif (isset($parent['fontsize'])) {
19452 $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
19453 } else {
19454 $pre_h = $this->getCellHeight($this->FontSize);
19455 }
19456 if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
19457 $cn = $this->tagvspaces[$tag['value']][1]['n'];
19458 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19459 $cn = 0.6;
19460 } else {
19461 $cn = 1;
19462 }
19463 if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
19464 $hb = 0;
19465 } else {
19466 $hb = ($cn * $pre_h);
19467 }
19468 if ($maxbottomliney > $this->PageBreakTrigger) {
19469 $hbz = $this->getCellHeight($this->FontSize);
19470 } elseif ($this->y < $maxbottomliney) {
19471 $hbz = ($maxbottomliney - $this->y);
19472 }
19473 }
19474 // Closing tag
19475 switch($tag['value']) {
19476 case 'tr': {
19477 $table_el = $dom[($dom[$key]['parent'])]['parent'];
19478 if (!isset($parent['endy'])) {
19479 $dom[($dom[$key]['parent'])]['endy'] = $this->y;
19480 $parent['endy'] = $this->y;
19481 }
19482 if (!isset($parent['endpage'])) {
19483 $dom[($dom[$key]['parent'])]['endpage'] = $this->page;
19484 $parent['endpage'] = $this->page;
19485 }
19486 if (!isset($parent['endcolumn'])) {
19487 $dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
19488 $parent['endcolumn'] = $this->current_column;
19489 }
19490 // update row-spanned cells
19491 if (isset($dom[$table_el]['rowspans'])) {
19492 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19493 $dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
19494 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19495 if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
19496 $dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
19497 } elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
19498 $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19499 $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19500 $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19501 }
19502 }
19503 }
19504 // report new endy and endpage to the rowspanned cells
19505 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19506 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19507 $dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
19508 $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19509 $dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
19510 $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19511 $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
19512 $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19513 }
19514 }
19515 // update remaining rowspanned cells
19516 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19517 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19518 $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
19519 $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
19520 $dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
19521 }
19522 }
19523 }
19524 $prev_page = $this->page;
19525 $this->setPage($dom[($dom[$key]['parent'])]['endpage']);
19526 if ($this->num_columns > 1) {
19527 if (($prev_page < $this->page)
19528 AND ((($this->current_column == 0) AND ($dom[($dom[$key]['parent'])]['endcolumn'] == ($this->num_columns - 1)))
19529 OR ($this->current_column == $dom[($dom[$key]['parent'])]['endcolumn']))) {
19530 // page jump
19531 $this->selectColumn(0);
19532 $dom[($dom[$key]['parent'])]['endcolumn'] = 0;
19533 $dom[($dom[$key]['parent'])]['endy'] = $this->y;
19534 } else {
19535 $this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
19536 $this->y = $dom[($dom[$key]['parent'])]['endy'];
19537 }
19538 } else {
19539 $this->y = $dom[($dom[$key]['parent'])]['endy'];
19540 }
19541 if (isset($dom[$table_el]['attribute']['cellspacing'])) {
19542 $this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
19543 } elseif (isset($dom[$table_el]['border-spacing'])) {
19544 $this->y += $dom[$table_el]['border-spacing']['V'];
19545 }
19546 $this->Ln(0, $cell);
19547 if ($this->current_column == $parent['startcolumn']) {
19548 $this->x = $parent['startx'];
19549 }
19550 // account for booklet mode
19551 if ($this->page > $parent['startpage']) {
19552 if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
19553 $this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
19554 } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
19555 $this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
19556 }
19557 }
19558 break;
19559 }
19560 case 'tablehead':
19561 // closing tag used for the thead part
19562 $in_table_head = true;
19563 $this->inthead = false;
19564 case 'table': {
19565 $table_el = $parent;
19566 // set default border
19567 if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
19568 // set default border
19569 $border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
19570 } else {
19571 $border = 0;
19572 }
19573 $default_border = $border;
19574 // fix bottom line alignment of last line before page break
19575 foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
19576 // update row-spanned cells
19577 if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19578 foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19579 if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
19580 $dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
19581 }
19582 if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
19583 $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
19584 }
19585 }
19586 }
19587 if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
19588 $pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
19589 $dom[$prevtrkey]['endy'] = $pgendy;
19590 // update row-spanned cells
19591 if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19592 foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19593 if (($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
19594 $dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
19595 $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
19596 }
19597 }
19598 }
19599 }
19600 $prevtrkey = $trkey;
19601 $table_el = $dom[($dom[$key]['parent'])];
19602 }
19603 // for each row
19604 if (count($table_el['trids']) > 0) {
19605 unset($xmax);
19606 }
19607 foreach ($table_el['trids'] as $j => $trkey) {
19608 $parent = $dom[$trkey];
19609 if (!isset($xmax)) {
19610 $xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
19611 }
19612 // for each cell on the row
19613 foreach ($parent['cellpos'] as $k => $cellpos) {
19614 if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
19615 $cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
19616 $cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
19617 $endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
19618 $startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
19619 $endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
19620 $startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
19621 $endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
19622 } else {
19623 $endy = $parent['endy'];
19624 $startpage = $parent['startpage'];
19625 $endpage = $parent['endpage'];
19626 $startcolumn = $parent['startcolumn'];
19627 $endcolumn = $parent['endcolumn'];
19628 }
19629 if ($this->num_columns == 0) {
19630 $this->num_columns = 1;
19631 }
19632 if (isset($cellpos['border'])) {
19633 $border = $cellpos['border'];
19634 }
19635 if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
19636 $this->SetFillColorArray($cellpos['bgcolor']);
19637 $fill = true;
19638 } else {
19639 $fill = false;
19640 }
19641 $x = $cellpos['startx'];
19642 $y = $parent['starty'];
19643 $starty = $y;
19644 $w = abs($cellpos['endx'] - $cellpos['startx']);
19645 // get border modes
19646 $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
19647 $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
19648 $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19649 // design borders around HTML cells.
19650 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
19651 $ccode = '';
19652 $this->setPage($page);
19653 if ($this->num_columns < 2) {
19654 // single-column mode
19655 $this->x = $x;
19656 $this->y = $this->tMargin;
19657 }
19658 // account for margin changes
19659 if ($page > $startpage) {
19660 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
19661 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
19662 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
19663 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
19664 }
19665 }
19666 if ($startpage == $endpage) { // single page
19667 $deltacol = 0;
19668 $deltath = 0;
19669 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
19670 $this->selectColumn($column);
19671 if ($startcolumn == $endcolumn) { // single column
19672 $cborder = $border;
19673 $h = $endy - $parent['starty'];
19674 $this->y = $y;
19675 $this->x = $x;
19676 } elseif ($column == $startcolumn) { // first column
19677 $cborder = $border_start;
19678 $this->y = $starty;
19679 $this->x = $x;
19680 $h = $this->h - $this->y - $this->bMargin;
19681 if ($this->rtl) {
19682 $deltacol = $this->x + $this->rMargin - $this->w;
19683 } else {
19684 $deltacol = $this->x - $this->lMargin;
19685 }
19686 } elseif ($column == $endcolumn) { // end column
19687 $cborder = $border_end;
19688 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19689 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19690 }
19691 $this->x += $deltacol;
19692 $h = $endy - $this->y;
19693 } else { // middle column
19694 $cborder = $border_middle;
19695 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19696 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19697 }
19698 $this->x += $deltacol;
19699 $h = $this->h - $this->y - $this->bMargin;
19700 }
19701 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19702 } // end for each column
19703 } elseif ($page == $startpage) { // first page
19704 $deltacol = 0;
19705 $deltath = 0;
19706 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
19707 $this->selectColumn($column);
19708 if ($column == $startcolumn) { // first column
19709 $cborder = $border_start;
19710 $this->y = $starty;
19711 $this->x = $x;
19712 $h = $this->h - $this->y - $this->bMargin;
19713 if ($this->rtl) {
19714 $deltacol = $this->x + $this->rMargin - $this->w;
19715 } else {
19716 $deltacol = $this->x - $this->lMargin;
19717 }
19718 } else { // middle column
19719 $cborder = $border_middle;
19720 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19721 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19722 }
19723 $this->x += $deltacol;
19724 $h = $this->h - $this->y - $this->bMargin;
19725 }
19726 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19727 } // end for each column
19728 } elseif ($page == $endpage) { // last page
19729 $deltacol = 0;
19730 $deltath = 0;
19731 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
19732 $this->selectColumn($column);
19733 if ($column == $endcolumn) { // end column
19734 $cborder = $border_end;
19735 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19736 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19737 }
19738 $this->x += $deltacol;
19739 $h = $endy - $this->y;
19740 } else { // middle column
19741 $cborder = $border_middle;
19742 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19743 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19744 }
19745 $this->x += $deltacol;
19746 $h = $this->h - $this->y - $this->bMargin;
19747 }
19748 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19749 } // end for each column
19750 } else { // middle page
19751 $deltacol = 0;
19752 $deltath = 0;
19753 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
19754 $this->selectColumn($column);
19755 $cborder = $border_middle;
19756 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19757 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19758 }
19759 $this->x += $deltacol;
19760 $h = $this->h - $this->y - $this->bMargin;
19761 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19762 } // end for each column
19763 }
19764 if (!empty($cborder) OR !empty($fill)) {
19765 $offsetlen = strlen($ccode);
19766 // draw border and fill
19767 if ($this->inxobj) {
19768 // we are inside an XObject template
19769 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
19770 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
19771 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
19772 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
19773 } else {
19774 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
19775 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
19776 }
19777 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
19778 $pstart = substr($pagebuff, 0, $pagemark);
19779 $pend = substr($pagebuff, $pagemark);
19780 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
19781 } else {
19782 // draw border and fill
19783 if (end($this->transfmrk[$this->page]) !== false) {
19784 $pagemarkkey = key($this->transfmrk[$this->page]);
19785 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
19786 } elseif ($this->InFooter) {
19787 $pagemark = $this->footerpos[$this->page];
19788 } else {
19789 $pagemark = $this->intmrk[$this->page];
19790 }
19791 $pagebuff = $this->getPageBuffer($this->page);
19792 $pstart = substr($pagebuff, 0, $pagemark);
19793 $pend = substr($pagebuff, $pagemark);
19794 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
19795 }
19796 }
19797 } // end for each page
19798 // restore default border
19799 $border = $default_border;
19800 } // end for each cell on the row
19801 if (isset($table_el['attribute']['cellspacing'])) {
19802 $this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
19803 } elseif (isset($table_el['border-spacing'])) {
19804 $this->y += $table_el['border-spacing']['V'];
19805 }
19806 $this->Ln(0, $cell);
19807 $this->x = $parent['startx'];
19808 if ($endpage > $startpage) {
19809 if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
19810 $this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
19811 } elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
19812 $this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
19813 }
19814 }
19815 }
19816 if (!$in_table_head) { // we are not inside a thead section
19817 $this->cell_padding = $table_el['old_cell_padding'];
19818 // reset row height
19819 $this->resetLastH();
19820 if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
19821 $plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
19822 if (($plendiff > 0) AND ($plendiff < 60)) {
19823 $pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
19824 if (substr($pagediff, 0, 5) == 'BT /F') {
19825 // the difference is only a font setting
19826 $plendiff = 0;
19827 }
19828 }
19829 if ($plendiff == 0) {
19830 // remove last blank page
19831 $this->deletePage($this->numpages);
19832 }
19833 }
19834 if (isset($this->theadMargins['top'])) {
19835 // restore top margin
19836 $this->tMargin = $this->theadMargins['top'];
19837 }
19838 if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
19839 // reset main table header
19840 $this->thead = '';
19841 $this->theadMargins = array();
19842 $this->pagedim[$this->page]['tm'] = $this->tMargin;
19843 }
19844 }
19845 $parent = $table_el;
19846 break;
19847 }
19848 case 'a': {
19849 $this->HREF = array();
19850 break;
19851 }
19852 case 'sup': {
19853 $this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
19854 break;
19855 }
19856 case 'sub': {
19857 $this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
19858 break;
19859 }
19860 case 'div': {
19861 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19862 break;
19863 }
19864 case 'blockquote': {
19865 if ($this->rtl) {
19866 $this->rMargin -= $this->listindent;
19867 } else {
19868 $this->lMargin -= $this->listindent;
19869 }
19870 --$this->listindentlevel;
19871 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19872 break;
19873 }
19874 case 'p': {
19875 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19876 break;
19877 }
19878 case 'pre': {
19879 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19880 $this->premode = false;
19881 break;
19882 }
19883 case 'dl': {
19884 --$this->listnum;
19885 if ($this->listnum <= 0) {
19886 $this->listnum = 0;
19887 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19888 } else {
19889 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19890 }
19891 $this->resetLastH();
19892 break;
19893 }
19894 case 'dt': {
19895 $this->lispacer = '';
19896 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19897 break;
19898 }
19899 case 'dd': {
19900 $this->lispacer = '';
19901 if ($this->rtl) {
19902 $this->rMargin -= $this->listindent;
19903 } else {
19904 $this->lMargin -= $this->listindent;
19905 }
19906 --$this->listindentlevel;
19907 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19908 break;
19909 }
19910 case 'ul':
19911 case 'ol': {
19912 --$this->listnum;
19913 $this->lispacer = '';
19914 if ($this->rtl) {
19915 $this->rMargin -= $this->listindent;
19916 } else {
19917 $this->lMargin -= $this->listindent;
19918 }
19919 --$this->listindentlevel;
19920 if ($this->listnum <= 0) {
19921 $this->listnum = 0;
19922 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19923 } else {
19924 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19925 }
19926 $this->resetLastH();
19927 break;
19928 }
19929 case 'li': {
19930 $this->lispacer = '';
19931 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19932 break;
19933 }
19934 case 'h1':
19935 case 'h2':
19936 case 'h3':
19937 case 'h4':
19938 case 'h5':
19939 case 'h6': {
19940 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19941 break;
19942 }
19943 // Form fields (since 4.8.000 - 2009-09-07)
19944 case 'form': {
19945 $this->form_action = '';
19946 $this->form_enctype = 'application/x-www-form-urlencoded';
19947 break;
19948 }
19949 default : {
19950 break;
19951 }
19952 }
19953 // draw border and background (if any)
19954 $this->drawHTMLTagBorder($parent, $xmax);
19955 if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
19956 $pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
19957 // check for pagebreak
19958 if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19959 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19960 $this->checkPageBreak($this->PageBreakTrigger + 1);
19961 }
19962 if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19963 OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19964 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19965 $this->checkPageBreak($this->PageBreakTrigger + 1);
19966 }
19967 }
19968 $this->tmprtl = false;
19969 return $dom;
19970 }
19971
19972 /**
19973 * Add vertical spaces if needed.
19974 * @param $hbz (string) Distance between current y and line bottom.
19975 * @param $hb (string) The height of the break.
19976 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19977 * @param $firsttag (boolean) set to true when the tag is the first.
19978 * @param $lasttag (boolean) set to true when the tag is the last.
19979 * @protected
19980 */
19981 protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
19982 if ($firsttag) {
19983 $this->Ln(0, $cell);
19984 $this->htmlvspace = 0;
19985 return;
19986 }
19987 if ($lasttag) {
19988 $this->Ln($hbz, $cell);
19989 $this->htmlvspace = 0;
19990 return;
19991 }
19992 if ($hb < $this->htmlvspace) {
19993 $hd = 0;
19994 } else {
19995 $hd = $hb - $this->htmlvspace;
19996 $this->htmlvspace = $hb;
19997 }
19998 $this->Ln(($hbz + $hd), $cell);
19999 }
20000
20001 /**
20002 * Return the starting coordinates to draw an html border
20003 * @return array containing top-left border coordinates
20004 * @protected
20005 * @since 5.7.000 (2010-08-03)
20006 */
20007 protected function getBorderStartPosition() {
20008 if ($this->rtl) {
20009 $xmax = $this->lMargin;
20010 } else {
20011 $xmax = $this->w - $this->rMargin;
20012 }
20013 return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
20014 }
20015
20016 /**
20017 * Draw an HTML block border and fill
20018 * @param $tag (array) array of tag properties.
20019 * @param $xmax (int) end X coordinate for border.
20020 * @protected
20021 * @since 5.7.000 (2010-08-03)
20022 */
20023 protected function drawHTMLTagBorder($tag, $xmax) {
20024 if (!isset($tag['borderposition'])) {
20025 // nothing to draw
20026 return;
20027 }
20028 $prev_x = $this->x;
20029 $prev_y = $this->y;
20030 $prev_lasth = $this->lasth;
20031 $border = 0;
20032 $fill = false;
20033 $this->lasth = 0;
20034 if (isset($tag['border']) AND !empty($tag['border'])) {
20035 // get border style
20036 $border = $tag['border'];
20037 if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
20038 // border for table header
20039 $border = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20040 }
20041 }
20042 if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
20043 // get background color
20044 $old_bgcolor = $this->bgcolor;
20045 $this->SetFillColorArray($tag['bgcolor']);
20046 $fill = true;
20047 }
20048 if (!$border AND !$fill) {
20049 // nothing to draw
20050 return;
20051 }
20052 if (isset($tag['attribute']['cellspacing'])) {
20053 $clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
20054 $cellspacing = array('H' => $clsp, 'V' => $clsp);
20055 } elseif (isset($tag['border-spacing'])) {
20056 $cellspacing = $tag['border-spacing'];
20057 } else {
20058 $cellspacing = array('H' => 0, 'V' => 0);
20059 }
20060 if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
20061 // draw the border externally respect the sqare edge.
20062 $border['mode'] = 'ext';
20063 }
20064 if ($this->rtl) {
20065 if ($xmax >= $tag['borderposition']['x']) {
20066 $xmax = $tag['borderposition']['xmax'];
20067 }
20068 $w = ($tag['borderposition']['x'] - $xmax);
20069 } else {
20070 if ($xmax <= $tag['borderposition']['x']) {
20071 $xmax = $tag['borderposition']['xmax'];
20072 }
20073 $w = ($xmax - $tag['borderposition']['x']);
20074 }
20075 if ($w <= 0) {
20076 return;
20077 }
20078 $w += $cellspacing['H'];
20079 $startpage = $tag['borderposition']['page'];
20080 $startcolumn = $tag['borderposition']['column'];
20081 $x = $tag['borderposition']['x'];
20082 $y = $tag['borderposition']['y'];
20083 $endpage = $this->page;
20084 $starty = $tag['borderposition']['y'] - $cellspacing['V'];
20085 $currentY = $this->y;
20086 $this->x = $x;
20087 // get latest column
20088 $endcolumn = $this->current_column;
20089 if ($this->num_columns == 0) {
20090 $this->num_columns = 1;
20091 }
20092 // get border modes
20093 $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
20094 $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
20095 $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20096 // temporary disable page regions
20097 $temp_page_regions = $this->page_regions;
20098 $this->page_regions = array();
20099 // design borders around HTML cells.
20100 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
20101 $ccode = '';
20102 $this->setPage($page);
20103 if ($this->num_columns < 2) {
20104 // single-column mode
20105 $this->x = $x;
20106 $this->y = $this->tMargin;
20107 }
20108 // account for margin changes
20109 if ($page > $startpage) {
20110 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
20111 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
20112 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
20113 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
20114 }
20115 }
20116 if ($startpage == $endpage) {
20117 // single page
20118 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
20119 $this->selectColumn($column);
20120 if ($startcolumn == $endcolumn) { // single column
20121 $cborder = $border;
20122 $h = ($currentY - $y) + $cellspacing['V'];
20123 $this->y = $starty;
20124 } elseif ($column == $startcolumn) { // first column
20125 $cborder = $border_start;
20126 $this->y = $starty;
20127 $h = $this->h - $this->y - $this->bMargin;
20128 } elseif ($column == $endcolumn) { // end column
20129 $cborder = $border_end;
20130 $h = $currentY - $this->y;
20131 } else { // middle column
20132 $cborder = $border_middle;
20133 $h = $this->h - $this->y - $this->bMargin;
20134 }
20135 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20136 } // end for each column
20137 } elseif ($page == $startpage) { // first page
20138 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
20139 $this->selectColumn($column);
20140 if ($column == $startcolumn) { // first column
20141 $cborder = $border_start;
20142 $this->y = $starty;
20143 $h = $this->h - $this->y - $this->bMargin;
20144 } else { // middle column
20145 $cborder = $border_middle;
20146 $h = $this->h - $this->y - $this->bMargin;
20147 }
20148 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20149 } // end for each column
20150 } elseif ($page == $endpage) { // last page
20151 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
20152 $this->selectColumn($column);
20153 if ($column == $endcolumn) {
20154 // end column
20155 $cborder = $border_end;
20156 $h = $currentY - $this->y;
20157 } else {
20158 // middle column
20159 $cborder = $border_middle;
20160 $h = $this->h - $this->y - $this->bMargin;
20161 }
20162 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20163 } // end for each column
20164 } else { // middle page
20165 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
20166 $this->selectColumn($column);
20167 $cborder = $border_middle;
20168 $h = $this->h - $this->y - $this->bMargin;
20169 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20170 } // end for each column
20171 }
20172 if ($cborder OR $fill) {
20173 $offsetlen = strlen($ccode);
20174 // draw border and fill
20175 if ($this->inxobj) {
20176 // we are inside an XObject template
20177 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
20178 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
20179 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
20180 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
20181 } else {
20182 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
20183 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
20184 }
20185 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
20186 $pstart = substr($pagebuff, 0, $pagemark);
20187 $pend = substr($pagebuff, $pagemark);
20188 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
20189 } else {
20190 if (end($this->transfmrk[$this->page]) !== false) {
20191 $pagemarkkey = key($this->transfmrk[$this->page]);
20192 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
20193 } elseif ($this->InFooter) {
20194 $pagemark = $this->footerpos[$this->page];
20195 } else {
20196 $pagemark = $this->intmrk[$this->page];
20197 }
20198 $pagebuff = $this->getPageBuffer($this->page);
20199 $pstart = substr($pagebuff, 0, $pagemark);
20200 $pend = substr($pagebuff, $pagemark);
20201 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
20202 $this->bordermrk[$this->page] += $offsetlen;
20203 $this->cntmrk[$this->page] += $offsetlen;
20204 }
20205 }
20206 } // end for each page
20207 // restore page regions
20208 $this->page_regions = $temp_page_regions;
20209 if (isset($old_bgcolor)) {
20210 // restore background color
20211 $this->SetFillColorArray($old_bgcolor);
20212 }
20213 // restore pointer position
20214 $this->x = $prev_x;
20215 $this->y = $prev_y;
20216 $this->lasth = $prev_lasth;
20217 }
20218
20219 /**
20220 * Set the default bullet to be used as LI bullet symbol
20221 * @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')
20222 * @public
20223 * @since 4.0.028 (2008-09-26)
20224 */
20225 public function setLIsymbol($symbol='!') {
20226 // check for custom image symbol
20227 if (substr($symbol, 0, 4) == 'img|') {
20228 $this->lisymbol = $symbol;
20229 return;
20230 }
20231 $symbol = strtolower($symbol);
20232 $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');
20233 if (in_array($symbol, $valid_symbols)) {
20234 $this->lisymbol = $symbol;
20235 } else {
20236 $this->lisymbol = '';
20237 }
20238 }
20239
20240 /**
20241 * Set the booklet mode for double-sided pages.
20242 * @param $booklet (boolean) true set the booklet mode on, false otherwise.
20243 * @param $inner (float) Inner page margin.
20244 * @param $outer (float) Outer page margin.
20245 * @public
20246 * @since 4.2.000 (2008-10-29)
20247 */
20248 public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
20249 $this->booklet = $booklet;
20250 if ($inner >= 0) {
20251 $this->lMargin = $inner;
20252 }
20253 if ($outer >= 0) {
20254 $this->rMargin = $outer;
20255 }
20256 }
20257
20258 /**
20259 * Swap the left and right margins.
20260 * @param $reverse (boolean) if true swap left and right margins.
20261 * @protected
20262 * @since 4.2.000 (2008-10-29)
20263 */
20264 protected function swapMargins($reverse=true) {
20265 if ($reverse) {
20266 // swap left and right margins
20267 $mtemp = $this->original_lMargin;
20268 $this->original_lMargin = $this->original_rMargin;
20269 $this->original_rMargin = $mtemp;
20270 $deltam = $this->original_lMargin - $this->original_rMargin;
20271 $this->lMargin += $deltam;
20272 $this->rMargin -= $deltam;
20273 }
20274 }
20275
20276 /**
20277 * Set the vertical spaces for HTML tags.
20278 * The array must have the following structure (example):
20279 * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
20280 * The first array level contains the tag names,
20281 * the second level contains 0 for opening tags or 1 for closing tags,
20282 * the third level contains the vertical space unit (h) and the number spaces to add (n).
20283 * If the h parameter is not specified, default values are used.
20284 * @param $tagvs (array) array of tags and relative vertical spaces.
20285 * @public
20286 * @since 4.2.001 (2008-10-30)
20287 */
20288 public function setHtmlVSpace($tagvs) {
20289 $this->tagvspaces = $tagvs;
20290 }
20291
20292 /**
20293 * Set custom width for list indentation.
20294 * @param $width (float) width of the indentation. Use negative value to disable it.
20295 * @public
20296 * @since 4.2.007 (2008-11-12)
20297 */
20298 public function setListIndentWidth($width) {
20299 return $this->customlistindent = floatval($width);
20300 }
20301
20302 /**
20303 * Set the top/bottom cell sides to be open or closed when the cell cross the page.
20304 * @param $isopen (boolean) if true keeps the top/bottom border open for the cell sides that cross the page.
20305 * @public
20306 * @since 4.2.010 (2008-11-14)
20307 */
20308 public function setOpenCell($isopen) {
20309 $this->opencell = $isopen;
20310 }
20311
20312 /**
20313 * Set the color and font style for HTML links.
20314 * @param $color (array) RGB array of colors
20315 * @param $fontstyle (string) additional font styles to add
20316 * @public
20317 * @since 4.4.003 (2008-12-09)
20318 */
20319 public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
20320 $this->htmlLinkColorArray = $color;
20321 $this->htmlLinkFontStyle = $fontstyle;
20322 }
20323
20324 /**
20325 * Convert HTML string containing value and unit of measure to user's units or points.
20326 * @param $htmlval (string) String containing values and unit.
20327 * @param $refsize (string) Reference value in points.
20328 * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
20329 * @param $points (boolean) If true returns points, otherwise returns value in user's units.
20330 * @return float value in user's unit or point if $points=true
20331 * @public
20332 * @since 4.4.004 (2008-12-10)
20333 */
20334 public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
20335 $supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
20336 $retval = 0;
20337 $value = 0;
20338 $unit = 'px';
20339 if ($points) {
20340 $k = 1;
20341 } else {
20342 $k = $this->k;
20343 }
20344 if (in_array($defaultunit, $supportedunits)) {
20345 $unit = $defaultunit;
20346 }
20347 if (is_numeric($htmlval)) {
20348 $value = floatval($htmlval);
20349 } elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
20350 $value = floatval($mnum[1]);
20351 if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
20352 if (in_array($munit[1], $supportedunits)) {
20353 $unit = $munit[1];
20354 }
20355 }
20356 }
20357 switch ($unit) {
20358 // percentage
20359 case '%': {
20360 $retval = (($value * $refsize) / 100);
20361 break;
20362 }
20363 // relative-size
20364 case 'em': {
20365 $retval = ($value * $refsize);
20366 break;
20367 }
20368 // height of lower case 'x' (about half the font-size)
20369 case 'ex': {
20370 $retval = ($value * ($refsize / 2));
20371 break;
20372 }
20373 // absolute-size
20374 case 'in': {
20375 $retval = (($value * $this->dpi) / $k);
20376 break;
20377 }
20378 // centimeters
20379 case 'cm': {
20380 $retval = (($value / 2.54 * $this->dpi) / $k);
20381 break;
20382 }
20383 // millimeters
20384 case 'mm': {
20385 $retval = (($value / 25.4 * $this->dpi) / $k);
20386 break;
20387 }
20388 // one pica is 12 points
20389 case 'pc': {
20390 $retval = (($value * 12) / $k);
20391 break;
20392 }
20393 // points
20394 case 'pt': {
20395 $retval = ($value / $k);
20396 break;
20397 }
20398 // pixels
20399 case 'px': {
20400 $retval = $this->pixelsToUnits($value);
20401 if ($points) {
20402 $retval *= $this->k;
20403 }
20404 break;
20405 }
20406 }
20407 return $retval;
20408 }
20409
20410 /**
20411 * Output an HTML list bullet or ordered item symbol
20412 * @param $listdepth (int) list nesting level
20413 * @param $listtype (string) type of list
20414 * @param $size (float) current font size
20415 * @protected
20416 * @since 4.4.004 (2008-12-10)
20417 */
20418 protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
20419 if ($this->state != 2) {
20420 return;
20421 }
20422 $size /= $this->k;
20423 $fill = '';
20424 $bgcolor = $this->bgcolor;
20425 $color = $this->fgcolor;
20426 $strokecolor = $this->strokecolor;
20427 $width = 0;
20428 $textitem = '';
20429 $tmpx = $this->x;
20430 $lspace = $this->GetStringWidth(' ');
20431 if ($listtype == '^') {
20432 // special symbol used for avoid justification of rect bullet
20433 $this->lispacer = '';
20434 return;
20435 } elseif ($listtype == '!') {
20436 // set default list type for unordered list
20437 $deftypes = array('disc', 'circle', 'square');
20438 $listtype = $deftypes[($listdepth - 1) % 3];
20439 } elseif ($listtype == '#') {
20440 // set default list type for ordered list
20441 $listtype = 'decimal';
20442 } elseif (substr($listtype, 0, 4) == 'img|') {
20443 // custom image type ('img|type|width|height|image.ext')
20444 $img = explode('|', $listtype);
20445 $listtype = 'img';
20446 }
20447 switch ($listtype) {
20448 // unordered types
20449 case 'none': {
20450 break;
20451 }
20452 case 'disc': {
20453 $r = $size / 6;
20454 $lspace += (2 * $r);
20455 if ($this->rtl) {
20456 $this->x += $lspace;
20457 } else {
20458 $this->x -= $lspace;
20459 }
20460 $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
20461 break;
20462 }
20463 case 'circle': {
20464 $r = $size / 6;
20465 $lspace += (2 * $r);
20466 if ($this->rtl) {
20467 $this->x += $lspace;
20468 } else {
20469 $this->x -= $lspace;
20470 }
20471 $prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
20472 $new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
20473 $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
20474 $this->_out($prev_line_style); // restore line settings
20475 break;
20476 }
20477 case 'square': {
20478 $l = $size / 3;
20479 $lspace += $l;
20480 if ($this->rtl) {;
20481 $this->x += $lspace;
20482 } else {
20483 $this->x -= $lspace;
20484 }
20485 $this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
20486 break;
20487 }
20488 case 'img': {
20489 // 1=>type, 2=>width, 3=>height, 4=>image.ext
20490 $lspace += $img[2];
20491 if ($this->rtl) {;
20492 $this->x += $lspace;
20493 } else {
20494 $this->x -= $lspace;
20495 }
20496 $imgtype = strtolower($img[1]);
20497 $prev_y = $this->y;
20498 switch ($imgtype) {
20499 case 'svg': {
20500 $this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
20501 break;
20502 }
20503 case 'ai':
20504 case 'eps': {
20505 $this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
20506 break;
20507 }
20508 default: {
20509 $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);
20510 break;
20511 }
20512 }
20513 $this->y = $prev_y;
20514 break;
20515 }
20516 // ordered types
20517 // $this->listcount[$this->listnum];
20518 // $textitem
20519 case '1':
20520 case 'decimal': {
20521 $textitem = $this->listcount[$this->listnum];
20522 break;
20523 }
20524 case 'decimal-leading-zero': {
20525 $textitem = sprintf('%02d', $this->listcount[$this->listnum]);
20526 break;
20527 }
20528 case 'i':
20529 case 'lower-roman': {
20530 $textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
20531 break;
20532 }
20533 case 'I':
20534 case 'upper-roman': {
20535 $textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
20536 break;
20537 }
20538 case 'a':
20539 case 'lower-alpha':
20540 case 'lower-latin': {
20541 $textitem = chr(97 + $this->listcount[$this->listnum] - 1);
20542 break;
20543 }
20544 case 'A':
20545 case 'upper-alpha':
20546 case 'upper-latin': {
20547 $textitem = chr(65 + $this->listcount[$this->listnum] - 1);
20548 break;
20549 }
20550 case 'lower-greek': {
20551 $textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
20552 break;
20553 }
20554 /*
20555 // Types to be implemented (special handling)
20556 case 'hebrew': {
20557 break;
20558 }
20559 case 'armenian': {
20560 break;
20561 }
20562 case 'georgian': {
20563 break;
20564 }
20565 case 'cjk-ideographic': {
20566 break;
20567 }
20568 case 'hiragana': {
20569 break;
20570 }
20571 case 'katakana': {
20572 break;
20573 }
20574 case 'hiragana-iroha': {
20575 break;
20576 }
20577 case 'katakana-iroha': {
20578 break;
20579 }
20580 */
20581 default: {
20582 $textitem = $this->listcount[$this->listnum];
20583 }
20584 }
20585 if (!TCPDF_STATIC::empty_string($textitem)) {
20586 // Check whether we need a new page or new column
20587 $prev_y = $this->y;
20588 $h = $this->getCellHeight($this->FontSize);
20589 if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
20590 $tmpx = $this->x;
20591 }
20592 // print ordered item
20593 if ($this->rtl) {
20594 $textitem = '.'.$textitem;
20595 } else {
20596 $textitem = $textitem.'.';
20597 }
20598 $lspace += $this->GetStringWidth($textitem);
20599 if ($this->rtl) {
20600 $this->x += $lspace;
20601 } else {
20602 $this->x -= $lspace;
20603 }
20604 $this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
20605 }
20606 $this->x = $tmpx;
20607 $this->lispacer = '^';
20608 // restore colors
20609 $this->SetFillColorArray($bgcolor);
20610 $this->SetDrawColorArray($strokecolor);
20611 $this->SettextColorArray($color);
20612 }
20613
20614 /**
20615 * Returns current graphic variables as array.
20616 * @return array of graphic variables
20617 * @protected
20618 * @since 4.2.010 (2008-11-14)
20619 */
20620 protected function getGraphicVars() {
20621 $grapvars = array(
20622 'FontFamily' => $this->FontFamily,
20623 'FontStyle' => $this->FontStyle,
20624 'FontSizePt' => $this->FontSizePt,
20625 'rMargin' => $this->rMargin,
20626 'lMargin' => $this->lMargin,
20627 'cell_padding' => $this->cell_padding,
20628 'cell_margin' => $this->cell_margin,
20629 'LineWidth' => $this->LineWidth,
20630 'linestyleWidth' => $this->linestyleWidth,
20631 'linestyleCap' => $this->linestyleCap,
20632 'linestyleJoin' => $this->linestyleJoin,
20633 'linestyleDash' => $this->linestyleDash,
20634 'textrendermode' => $this->textrendermode,
20635 'textstrokewidth' => $this->textstrokewidth,
20636 'DrawColor' => $this->DrawColor,
20637 'FillColor' => $this->FillColor,
20638 'TextColor' => $this->TextColor,
20639 'ColorFlag' => $this->ColorFlag,
20640 'bgcolor' => $this->bgcolor,
20641 'fgcolor' => $this->fgcolor,
20642 'htmlvspace' => $this->htmlvspace,
20643 'listindent' => $this->listindent,
20644 'listindentlevel' => $this->listindentlevel,
20645 'listnum' => $this->listnum,
20646 'listordered' => $this->listordered,
20647 'listcount' => $this->listcount,
20648 'lispacer' => $this->lispacer,
20649 'cell_height_ratio' => $this->cell_height_ratio,
20650 'font_stretching' => $this->font_stretching,
20651 'font_spacing' => $this->font_spacing,
20652 'alpha' => $this->alpha,
20653 // extended
20654 'lasth' => $this->lasth,
20655 'tMargin' => $this->tMargin,
20656 'bMargin' => $this->bMargin,
20657 'AutoPageBreak' => $this->AutoPageBreak,
20658 'PageBreakTrigger' => $this->PageBreakTrigger,
20659 'x' => $this->x,
20660 'y' => $this->y,
20661 'w' => $this->w,
20662 'h' => $this->h,
20663 'wPt' => $this->wPt,
20664 'hPt' => $this->hPt,
20665 'fwPt' => $this->fwPt,
20666 'fhPt' => $this->fhPt,
20667 'page' => $this->page,
20668 'current_column' => $this->current_column,
20669 'num_columns' => $this->num_columns
20670 );
20671 return $grapvars;
20672 }
20673
20674 /**
20675 * Set graphic variables.
20676 * @param $gvars (array) array of graphic variablesto restore
20677 * @param $extended (boolean) if true restore extended graphic variables
20678 * @protected
20679 * @since 4.2.010 (2008-11-14)
20680 */
20681 protected function setGraphicVars($gvars, $extended=false) {
20682 if ($this->state != 2) {
20683 return;
20684 }
20685 $this->FontFamily = $gvars['FontFamily'];
20686 $this->FontStyle = $gvars['FontStyle'];
20687 $this->FontSizePt = $gvars['FontSizePt'];
20688 $this->rMargin = $gvars['rMargin'];
20689 $this->lMargin = $gvars['lMargin'];
20690 $this->cell_padding = $gvars['cell_padding'];
20691 $this->cell_margin = $gvars['cell_margin'];
20692 $this->LineWidth = $gvars['LineWidth'];
20693 $this->linestyleWidth = $gvars['linestyleWidth'];
20694 $this->linestyleCap = $gvars['linestyleCap'];
20695 $this->linestyleJoin = $gvars['linestyleJoin'];
20696 $this->linestyleDash = $gvars['linestyleDash'];
20697 $this->textrendermode = $gvars['textrendermode'];
20698 $this->textstrokewidth = $gvars['textstrokewidth'];
20699 $this->DrawColor = $gvars['DrawColor'];
20700 $this->FillColor = $gvars['FillColor'];
20701 $this->TextColor = $gvars['TextColor'];
20702 $this->ColorFlag = $gvars['ColorFlag'];
20703 $this->bgcolor = $gvars['bgcolor'];
20704 $this->fgcolor = $gvars['fgcolor'];
20705 $this->htmlvspace = $gvars['htmlvspace'];
20706 $this->listindent = $gvars['listindent'];
20707 $this->listindentlevel = $gvars['listindentlevel'];
20708 $this->listnum = $gvars['listnum'];
20709 $this->listordered = $gvars['listordered'];
20710 $this->listcount = $gvars['listcount'];
20711 $this->lispacer = $gvars['lispacer'];
20712 $this->cell_height_ratio = $gvars['cell_height_ratio'];
20713 $this->font_stretching = $gvars['font_stretching'];
20714 $this->font_spacing = $gvars['font_spacing'];
20715 $this->alpha = $gvars['alpha'];
20716 if ($extended) {
20717 // restore extended values
20718 $this->lasth = $gvars['lasth'];
20719 $this->tMargin = $gvars['tMargin'];
20720 $this->bMargin = $gvars['bMargin'];
20721 $this->AutoPageBreak = $gvars['AutoPageBreak'];
20722 $this->PageBreakTrigger = $gvars['PageBreakTrigger'];
20723 $this->x = $gvars['x'];
20724 $this->y = $gvars['y'];
20725 $this->w = $gvars['w'];
20726 $this->h = $gvars['h'];
20727 $this->wPt = $gvars['wPt'];
20728 $this->hPt = $gvars['hPt'];
20729 $this->fwPt = $gvars['fwPt'];
20730 $this->fhPt = $gvars['fhPt'];
20731 $this->page = $gvars['page'];
20732 $this->current_column = $gvars['current_column'];
20733 $this->num_columns = $gvars['num_columns'];
20734 }
20735 $this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
20736 if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
20737 $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
20738 }
20739 }
20740
20741 /**
20742 * Outputs the "save graphics state" operator 'q'
20743 * @protected
20744 */
20745 protected function _outSaveGraphicsState() {
20746 $this->_out('q');
20747 }
20748
20749 /**
20750 * Outputs the "restore graphics state" operator 'Q'
20751 * @protected
20752 */
20753 protected function _outRestoreGraphicsState() {
20754 $this->_out('Q');
20755 }
20756
20757 /**
20758 * Set buffer content (always append data).
20759 * @param $data (string) data
20760 * @protected
20761 * @since 4.5.000 (2009-01-02)
20762 */
20763 protected function setBuffer($data) {
20764 $this->bufferlen += strlen($data);
20765 $this->buffer .= $data;
20766 }
20767
20768 /**
20769 * Replace the buffer content
20770 * @param $data (string) data
20771 * @protected
20772 * @since 5.5.000 (2010-06-22)
20773 */
20774 protected function replaceBuffer($data) {
20775 $this->bufferlen = strlen($data);
20776 $this->buffer = $data;
20777 }
20778
20779 /**
20780 * Get buffer content.
20781 * @return string buffer content
20782 * @protected
20783 * @since 4.5.000 (2009-01-02)
20784 */
20785 protected function getBuffer() {
20786 return $this->buffer;
20787 }
20788
20789 /**
20790 * Set page buffer content.
20791 * @param $page (int) page number
20792 * @param $data (string) page data
20793 * @param $append (boolean) if true append data, false replace.
20794 * @protected
20795 * @since 4.5.000 (2008-12-31)
20796 */
20797 protected function setPageBuffer($page, $data, $append=false) {
20798 if ($append) {
20799 $this->pages[$page] .= $data;
20800 } else {
20801 $this->pages[$page] = $data;
20802 }
20803 if ($append AND isset($this->pagelen[$page])) {
20804 $this->pagelen[$page] += strlen($data);
20805 } else {
20806 $this->pagelen[$page] = strlen($data);
20807 }
20808 }
20809
20810 /**
20811 * Get page buffer content.
20812 * @param $page (int) page number
20813 * @return string page buffer content or false in case of error
20814 * @protected
20815 * @since 4.5.000 (2008-12-31)
20816 */
20817 protected function getPageBuffer($page) {
20818 if (isset($this->pages[$page])) {
20819 return $this->pages[$page];
20820 }
20821 return false;
20822 }
20823
20824 /**
20825 * Set image buffer content.
20826 * @param $image (string) image key
20827 * @param $data (array) image data
20828 * @return int image index number
20829 * @protected
20830 * @since 4.5.000 (2008-12-31)
20831 */
20832 protected function setImageBuffer($image, $data) {
20833 if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
20834 $this->imagekeys[$this->numimages] = $image;
20835 $data['i'] = $this->numimages;
20836 ++$this->numimages;
20837 }
20838 $this->images[$image] = $data;
20839 return $data['i'];
20840 }
20841
20842 /**
20843 * Set image buffer content for a specified sub-key.
20844 * @param $image (string) image key
20845 * @param $key (string) image sub-key
20846 * @param $data (array) image data
20847 * @protected
20848 * @since 4.5.000 (2008-12-31)
20849 */
20850 protected function setImageSubBuffer($image, $key, $data) {
20851 if (!isset($this->images[$image])) {
20852 $this->setImageBuffer($image, array());
20853 }
20854 $this->images[$image][$key] = $data;
20855 }
20856
20857 /**
20858 * Get image buffer content.
20859 * @param $image (string) image key
20860 * @return string image buffer content or false in case of error
20861 * @protected
20862 * @since 4.5.000 (2008-12-31)
20863 */
20864 protected function getImageBuffer($image) {
20865 if (isset($this->images[$image])) {
20866 return $this->images[$image];
20867 }
20868 return false;
20869 }
20870
20871 /**
20872 * Set font buffer content.
20873 * @param $font (string) font key
20874 * @param $data (array) font data
20875 * @protected
20876 * @since 4.5.000 (2009-01-02)
20877 */
20878 protected function setFontBuffer($font, $data) {
20879 $this->fonts[$font] = $data;
20880 if (!in_array($font, $this->fontkeys)) {
20881 $this->fontkeys[] = $font;
20882 // store object ID for current font
20883 ++$this->n;
20884 $this->font_obj_ids[$font] = $this->n;
20885 $this->setFontSubBuffer($font, 'n', $this->n);
20886 }
20887 }
20888
20889 /**
20890 * Set font buffer content.
20891 * @param $font (string) font key
20892 * @param $key (string) font sub-key
20893 * @param $data (array) font data
20894 * @protected
20895 * @since 4.5.000 (2009-01-02)
20896 */
20897 protected function setFontSubBuffer($font, $key, $data) {
20898 if (!isset($this->fonts[$font])) {
20899 $this->setFontBuffer($font, array());
20900 }
20901 $this->fonts[$font][$key] = $data;
20902 }
20903
20904 /**
20905 * Get font buffer content.
20906 * @param $font (string) font key
20907 * @return string font buffer content or false in case of error
20908 * @protected
20909 * @since 4.5.000 (2009-01-02)
20910 */
20911 protected function getFontBuffer($font) {
20912 if (isset($this->fonts[$font])) {
20913 return $this->fonts[$font];
20914 }
20915 return false;
20916 }
20917
20918 /**
20919 * Move a page to a previous position.
20920 * @param $frompage (int) number of the source page
20921 * @param $topage (int) number of the destination page (must be less than $frompage)
20922 * @return true in case of success, false in case of error.
20923 * @public
20924 * @since 4.5.000 (2009-01-02)
20925 */
20926 public function movePage($frompage, $topage) {
20927 if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
20928 return false;
20929 }
20930 if ($frompage == $this->page) {
20931 // close the page before moving it
20932 $this->endPage();
20933 }
20934 // move all page-related states
20935 $tmppage = $this->getPageBuffer($frompage);
20936 $tmppagedim = $this->pagedim[$frompage];
20937 $tmppagelen = $this->pagelen[$frompage];
20938 $tmpintmrk = $this->intmrk[$frompage];
20939 $tmpbordermrk = $this->bordermrk[$frompage];
20940 $tmpcntmrk = $this->cntmrk[$frompage];
20941 $tmppageobjects = $this->pageobjects[$frompage];
20942 if (isset($this->footerpos[$frompage])) {
20943 $tmpfooterpos = $this->footerpos[$frompage];
20944 }
20945 if (isset($this->footerlen[$frompage])) {
20946 $tmpfooterlen = $this->footerlen[$frompage];
20947 }
20948 if (isset($this->transfmrk[$frompage])) {
20949 $tmptransfmrk = $this->transfmrk[$frompage];
20950 }
20951 if (isset($this->PageAnnots[$frompage])) {
20952 $tmpannots = $this->PageAnnots[$frompage];
20953 }
20954 if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
20955 for ($i = $frompage; $i > $topage; --$i) {
20956 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
20957 --$this->pagegroups[$this->newpagegroup[$i]];
20958 break;
20959 }
20960 }
20961 for ($i = $topage; $i > 0; --$i) {
20962 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
20963 ++$this->pagegroups[$this->newpagegroup[$i]];
20964 break;
20965 }
20966 }
20967 }
20968 for ($i = $frompage; $i > $topage; --$i) {
20969 $j = $i - 1;
20970 // shift pages down
20971 $this->setPageBuffer($i, $this->getPageBuffer($j));
20972 $this->pagedim[$i] = $this->pagedim[$j];
20973 $this->pagelen[$i] = $this->pagelen[$j];
20974 $this->intmrk[$i] = $this->intmrk[$j];
20975 $this->bordermrk[$i] = $this->bordermrk[$j];
20976 $this->cntmrk[$i] = $this->cntmrk[$j];
20977 $this->pageobjects[$i] = $this->pageobjects[$j];
20978 if (isset($this->footerpos[$j])) {
20979 $this->footerpos[$i] = $this->footerpos[$j];
20980 } elseif (isset($this->footerpos[$i])) {
20981 unset($this->footerpos[$i]);
20982 }
20983 if (isset($this->footerlen[$j])) {
20984 $this->footerlen[$i] = $this->footerlen[$j];
20985 } elseif (isset($this->footerlen[$i])) {
20986 unset($this->footerlen[$i]);
20987 }
20988 if (isset($this->transfmrk[$j])) {
20989 $this->transfmrk[$i] = $this->transfmrk[$j];
20990 } elseif (isset($this->transfmrk[$i])) {
20991 unset($this->transfmrk[$i]);
20992 }
20993 if (isset($this->PageAnnots[$j])) {
20994 $this->PageAnnots[$i] = $this->PageAnnots[$j];
20995 } elseif (isset($this->PageAnnots[$i])) {
20996 unset($this->PageAnnots[$i]);
20997 }
20998 if (isset($this->newpagegroup[$j])) {
20999 $this->newpagegroup[$i] = $this->newpagegroup[$j];
21000 unset($this->newpagegroup[$j]);
21001 }
21002 if ($this->currpagegroup == $j) {
21003 $this->currpagegroup = $i;
21004 }
21005 }
21006 $this->setPageBuffer($topage, $tmppage);
21007 $this->pagedim[$topage] = $tmppagedim;
21008 $this->pagelen[$topage] = $tmppagelen;
21009 $this->intmrk[$topage] = $tmpintmrk;
21010 $this->bordermrk[$topage] = $tmpbordermrk;
21011 $this->cntmrk[$topage] = $tmpcntmrk;
21012 $this->pageobjects[$topage] = $tmppageobjects;
21013 if (isset($tmpfooterpos)) {
21014 $this->footerpos[$topage] = $tmpfooterpos;
21015 } elseif (isset($this->footerpos[$topage])) {
21016 unset($this->footerpos[$topage]);
21017 }
21018 if (isset($tmpfooterlen)) {
21019 $this->footerlen[$topage] = $tmpfooterlen;
21020 } elseif (isset($this->footerlen[$topage])) {
21021 unset($this->footerlen[$topage]);
21022 }
21023 if (isset($tmptransfmrk)) {
21024 $this->transfmrk[$topage] = $tmptransfmrk;
21025 } elseif (isset($this->transfmrk[$topage])) {
21026 unset($this->transfmrk[$topage]);
21027 }
21028 if (isset($tmpannots)) {
21029 $this->PageAnnots[$topage] = $tmpannots;
21030 } elseif (isset($this->PageAnnots[$topage])) {
21031 unset($this->PageAnnots[$topage]);
21032 }
21033 // adjust outlines
21034 $tmpoutlines = $this->outlines;
21035 foreach ($tmpoutlines as $key => $outline) {
21036 if (!$outline['f']) {
21037 if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
21038 $this->outlines[$key]['p'] = ($outline['p'] + 1);
21039 } elseif ($outline['p'] == $frompage) {
21040 $this->outlines[$key]['p'] = $topage;
21041 }
21042 }
21043 }
21044 // adjust dests
21045 $tmpdests = $this->dests;
21046 foreach ($tmpdests as $key => $dest) {
21047 if (!$dest['f']) {
21048 if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
21049 $this->dests[$key]['p'] = ($dest['p'] + 1);
21050 } elseif ($dest['p'] == $frompage) {
21051 $this->dests[$key]['p'] = $topage;
21052 }
21053 }
21054 }
21055 // adjust links
21056 $tmplinks = $this->links;
21057 foreach ($tmplinks as $key => $link) {
21058 if (!$link['f']) {
21059 if (($link['p'] >= $topage) AND ($link['p'] < $frompage)) {
21060 $this->links[$key]['p'] = ($link['p'] + 1);
21061 } elseif ($link['p'] == $frompage) {
21062 $this->links[$key]['p'] = $topage;
21063 }
21064 }
21065 }
21066 // adjust javascript
21067 $jfrompage = $frompage;
21068 $jtopage = $topage;
21069 if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21070 foreach($pamatch[0] as $pk => $pmatch) {
21071 $pagenum = intval($pamatch[3][$pk]) + 1;
21072 if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
21073 $newpage = ($pagenum + 1);
21074 } elseif ($pagenum == $jfrompage) {
21075 $newpage = $jtopage;
21076 } else {
21077 $newpage = $pagenum;
21078 }
21079 --$newpage;
21080 $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21081 $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21082 }
21083 unset($pamatch);
21084 }
21085 // return to last page
21086 $this->lastPage(true);
21087 return true;
21088 }
21089
21090 /**
21091 * Remove the specified page.
21092 * @param $page (int) page to remove
21093 * @return true in case of success, false in case of error.
21094 * @public
21095 * @since 4.6.004 (2009-04-23)
21096 */
21097 public function deletePage($page) {
21098 if (($page < 1) OR ($page > $this->numpages)) {
21099 return false;
21100 }
21101 // delete current page
21102 unset($this->pages[$page]);
21103 unset($this->pagedim[$page]);
21104 unset($this->pagelen[$page]);
21105 unset($this->intmrk[$page]);
21106 unset($this->bordermrk[$page]);
21107 unset($this->cntmrk[$page]);
21108 foreach ($this->pageobjects[$page] as $oid) {
21109 if (isset($this->offsets[$oid])){
21110 unset($this->offsets[$oid]);
21111 }
21112 }
21113 unset($this->pageobjects[$page]);
21114 if (isset($this->footerpos[$page])) {
21115 unset($this->footerpos[$page]);
21116 }
21117 if (isset($this->footerlen[$page])) {
21118 unset($this->footerlen[$page]);
21119 }
21120 if (isset($this->transfmrk[$page])) {
21121 unset($this->transfmrk[$page]);
21122 }
21123 if (isset($this->PageAnnots[$page])) {
21124 unset($this->PageAnnots[$page]);
21125 }
21126 if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21127 for ($i = $page; $i > 0; --$i) {
21128 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
21129 --$this->pagegroups[$this->newpagegroup[$i]];
21130 break;
21131 }
21132 }
21133 }
21134 if (isset($this->pageopen[$page])) {
21135 unset($this->pageopen[$page]);
21136 }
21137 if ($page < $this->numpages) {
21138 // update remaining pages
21139 for ($i = $page; $i < $this->numpages; ++$i) {
21140 $j = $i + 1;
21141 // shift pages
21142 $this->setPageBuffer($i, $this->getPageBuffer($j));
21143 $this->pagedim[$i] = $this->pagedim[$j];
21144 $this->pagelen[$i] = $this->pagelen[$j];
21145 $this->intmrk[$i] = $this->intmrk[$j];
21146 $this->bordermrk[$i] = $this->bordermrk[$j];
21147 $this->cntmrk[$i] = $this->cntmrk[$j];
21148 $this->pageobjects[$i] = $this->pageobjects[$j];
21149 if (isset($this->footerpos[$j])) {
21150 $this->footerpos[$i] = $this->footerpos[$j];
21151 } elseif (isset($this->footerpos[$i])) {
21152 unset($this->footerpos[$i]);
21153 }
21154 if (isset($this->footerlen[$j])) {
21155 $this->footerlen[$i] = $this->footerlen[$j];
21156 } elseif (isset($this->footerlen[$i])) {
21157 unset($this->footerlen[$i]);
21158 }
21159 if (isset($this->transfmrk[$j])) {
21160 $this->transfmrk[$i] = $this->transfmrk[$j];
21161 } elseif (isset($this->transfmrk[$i])) {
21162 unset($this->transfmrk[$i]);
21163 }
21164 if (isset($this->PageAnnots[$j])) {
21165 $this->PageAnnots[$i] = $this->PageAnnots[$j];
21166 } elseif (isset($this->PageAnnots[$i])) {
21167 unset($this->PageAnnots[$i]);
21168 }
21169 if (isset($this->newpagegroup[$j])) {
21170 $this->newpagegroup[$i] = $this->newpagegroup[$j];
21171 unset($this->newpagegroup[$j]);
21172 }
21173 if ($this->currpagegroup == $j) {
21174 $this->currpagegroup = $i;
21175 }
21176 if (isset($this->pageopen[$j])) {
21177 $this->pageopen[$i] = $this->pageopen[$j];
21178 } elseif (isset($this->pageopen[$i])) {
21179 unset($this->pageopen[$i]);
21180 }
21181 }
21182 // remove last page
21183 unset($this->pages[$this->numpages]);
21184 unset($this->pagedim[$this->numpages]);
21185 unset($this->pagelen[$this->numpages]);
21186 unset($this->intmrk[$this->numpages]);
21187 unset($this->bordermrk[$this->numpages]);
21188 unset($this->cntmrk[$this->numpages]);
21189 foreach ($this->pageobjects[$this->numpages] as $oid) {
21190 if (isset($this->offsets[$oid])){
21191 unset($this->offsets[$oid]);
21192 }
21193 }
21194 unset($this->pageobjects[$this->numpages]);
21195 if (isset($this->footerpos[$this->numpages])) {
21196 unset($this->footerpos[$this->numpages]);
21197 }
21198 if (isset($this->footerlen[$this->numpages])) {
21199 unset($this->footerlen[$this->numpages]);
21200 }
21201 if (isset($this->transfmrk[$this->numpages])) {
21202 unset($this->transfmrk[$this->numpages]);
21203 }
21204 if (isset($this->PageAnnots[$this->numpages])) {
21205 unset($this->PageAnnots[$this->numpages]);
21206 }
21207 if (isset($this->newpagegroup[$this->numpages])) {
21208 unset($this->newpagegroup[$this->numpages]);
21209 }
21210 if ($this->currpagegroup == $this->numpages) {
21211 $this->currpagegroup = ($this->numpages - 1);
21212 }
21213 if (isset($this->pagegroups[$this->numpages])) {
21214 unset($this->pagegroups[$this->numpages]);
21215 }
21216 if (isset($this->pageopen[$this->numpages])) {
21217 unset($this->pageopen[$this->numpages]);
21218 }
21219 }
21220 --$this->numpages;
21221 $this->page = $this->numpages;
21222 // adjust outlines
21223 $tmpoutlines = $this->outlines;
21224 foreach ($tmpoutlines as $key => $outline) {
21225 if (!$outline['f']) {
21226 if ($outline['p'] > $page) {
21227 $this->outlines[$key]['p'] = $outline['p'] - 1;
21228 } elseif ($outline['p'] == $page) {
21229 unset($this->outlines[$key]);
21230 }
21231 }
21232 }
21233 // adjust dests
21234 $tmpdests = $this->dests;
21235 foreach ($tmpdests as $key => $dest) {
21236 if (!$dest['f']) {
21237 if ($dest['p'] > $page) {
21238 $this->dests[$key]['p'] = $dest['p'] - 1;
21239 } elseif ($dest['p'] == $page) {
21240 unset($this->dests[$key]);
21241 }
21242 }
21243 }
21244 // adjust links
21245 $tmplinks = $this->links;
21246 foreach ($tmplinks as $key => $link) {
21247 if (!$link['f']) {
21248 if ($link['p'] > $page) {
21249 $this->links[$key]['p'] = $link['p'] - 1;
21250 } elseif ($link['p'] == $page) {
21251 unset($this->links[$key]);
21252 }
21253 }
21254 }
21255 // adjust javascript
21256 $jpage = $page;
21257 if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21258 foreach($pamatch[0] as $pk => $pmatch) {
21259 $pagenum = intval($pamatch[3][$pk]) + 1;
21260 if ($pagenum >= $jpage) {
21261 $newpage = ($pagenum - 1);
21262 } elseif ($pagenum == $jpage) {
21263 $newpage = 1;
21264 } else {
21265 $newpage = $pagenum;
21266 }
21267 --$newpage;
21268 $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21269 $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21270 }
21271 unset($pamatch);
21272 }
21273 // return to last page
21274 if ($this->numpages > 0) {
21275 $this->lastPage(true);
21276 }
21277 return true;
21278 }
21279
21280 /**
21281 * Clone the specified page to a new page.
21282 * @param $page (int) number of page to copy (0 = current page)
21283 * @return true in case of success, false in case of error.
21284 * @public
21285 * @since 4.9.015 (2010-04-20)
21286 */
21287 public function copyPage($page=0) {
21288 if ($page == 0) {
21289 // default value
21290 $page = $this->page;
21291 }
21292 if (($page < 1) OR ($page > $this->numpages)) {
21293 return false;
21294 }
21295 // close the last page
21296 $this->endPage();
21297 // copy all page-related states
21298 ++$this->numpages;
21299 $this->page = $this->numpages;
21300 $this->setPageBuffer($this->page, $this->getPageBuffer($page));
21301 $this->pagedim[$this->page] = $this->pagedim[$page];
21302 $this->pagelen[$this->page] = $this->pagelen[$page];
21303 $this->intmrk[$this->page] = $this->intmrk[$page];
21304 $this->bordermrk[$this->page] = $this->bordermrk[$page];
21305 $this->cntmrk[$this->page] = $this->cntmrk[$page];
21306 $this->pageobjects[$this->page] = $this->pageobjects[$page];
21307 $this->pageopen[$this->page] = false;
21308 if (isset($this->footerpos[$page])) {
21309 $this->footerpos[$this->page] = $this->footerpos[$page];
21310 }
21311 if (isset($this->footerlen[$page])) {
21312 $this->footerlen[$this->page] = $this->footerlen[$page];
21313 }
21314 if (isset($this->transfmrk[$page])) {
21315 $this->transfmrk[$this->page] = $this->transfmrk[$page];
21316 }
21317 if (isset($this->PageAnnots[$page])) {
21318 $this->PageAnnots[$this->page] = $this->PageAnnots[$page];
21319 }
21320 if (isset($this->newpagegroup[$page])) {
21321 // start a new group
21322 $this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
21323 $this->currpagegroup = $this->newpagegroup[$this->page];
21324 $this->pagegroups[$this->currpagegroup] = 1;
21325 } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
21326 ++$this->pagegroups[$this->currpagegroup];
21327 }
21328 // copy outlines
21329 $tmpoutlines = $this->outlines;
21330 foreach ($tmpoutlines as $key => $outline) {
21331 if ($outline['p'] == $page) {
21332 $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']);
21333 }
21334 }
21335 // copy links
21336 $tmplinks = $this->links;
21337 foreach ($tmplinks as $key => $link) {
21338 if ($link['p'] == $page) {
21339 $this->links[] = array('p' => $this->page, 'y' => $link['y'], 'f' => $link['f']);
21340 }
21341 }
21342 // return to last page
21343 $this->lastPage(true);
21344 return true;
21345 }
21346
21347 /**
21348 * Output a Table of Content Index (TOC).
21349 * This method must be called after all Bookmarks were set.
21350 * Before calling this method you have to open the page using the addTOCPage() method.
21351 * After calling this method you have to call endTOCPage() to close the TOC page.
21352 * You can override this method to achieve different styles.
21353 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21354 * @param $numbersfont (string) set the font for page numbers (please use monospaced font for better alignment).
21355 * @param $filler (string) string used to fill the space between text and page number.
21356 * @param $toc_name (string) name to use for TOC bookmark.
21357 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21358 * @param $color (array) RGB color array for bookmark title (values from 0 to 255).
21359 * @public
21360 * @author Nicola Asuni
21361 * @since 4.5.000 (2009-01-02)
21362 * @see addTOCPage(), endTOCPage(), addHTMLTOC()
21363 */
21364 public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
21365 $fontsize = $this->FontSizePt;
21366 $fontfamily = $this->FontFamily;
21367 $fontstyle = $this->FontStyle;
21368 $w = $this->w - $this->lMargin - $this->rMargin;
21369 $spacer = $this->GetStringWidth(chr(32)) * 4;
21370 $lmargin = $this->lMargin;
21371 $rmargin = $this->rMargin;
21372 $x_start = $this->GetX();
21373 $page_first = $this->page;
21374 $current_page = $this->page;
21375 $page_fill_start = false;
21376 $page_fill_end = false;
21377 $current_column = $this->current_column;
21378 if (TCPDF_STATIC::empty_string($numbersfont)) {
21379 $numbersfont = $this->default_monospaced_font;
21380 }
21381 if (TCPDF_STATIC::empty_string($filler)) {
21382 $filler = ' ';
21383 }
21384 if (TCPDF_STATIC::empty_string($page)) {
21385 $gap = ' ';
21386 } else {
21387 $gap = '';
21388 if ($page < 1) {
21389 $page = 1;
21390 }
21391 }
21392 $this->SetFont($numbersfont, $fontstyle, $fontsize);
21393 $numwidth = $this->GetStringWidth('00000');
21394 $maxpage = 0; //used for pages on attached documents
21395 foreach ($this->outlines as $key => $outline) {
21396 // check for extra pages (used for attachments)
21397 if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
21398 $outline['p'] += ($this->page - $page_first);
21399 }
21400 if ($this->rtl) {
21401 $aligntext = 'R';
21402 $alignnum = 'L';
21403 } else {
21404 $aligntext = 'L';
21405 $alignnum = 'R';
21406 }
21407 if ($outline['l'] == 0) {
21408 $this->SetFont($fontfamily, $outline['s'].'B', $fontsize);
21409 } else {
21410 $this->SetFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
21411 }
21412 $this->SetTextColorArray($outline['c']);
21413 // check for page break
21414 $this->checkPageBreak(2 * $this->getCellHeight($this->FontSize));
21415 // set margins and X position
21416 if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
21417 $this->lMargin = $lmargin;
21418 $this->rMargin = $rmargin;
21419 } else {
21420 if ($this->current_column != $current_column) {
21421 if ($this->rtl) {
21422 $x_start = $this->w - $this->columns[$this->current_column]['x'];
21423 } else {
21424 $x_start = $this->columns[$this->current_column]['x'];
21425 }
21426 }
21427 $lmargin = $this->lMargin;
21428 $rmargin = $this->rMargin;
21429 $current_page = $this->page;
21430 $current_column = $this->current_column;
21431 }
21432 $this->SetX($x_start);
21433 $indent = ($spacer * $outline['l']);
21434 if ($this->rtl) {
21435 $this->x -= $indent;
21436 $this->rMargin = $this->w - $this->x;
21437 } else {
21438 $this->x += $indent;
21439 $this->lMargin = $this->x;
21440 }
21441 $link = $this->AddLink();
21442 $this->SetLink($link, $outline['y'], $outline['p']);
21443 // write the text
21444 if ($this->rtl) {
21445 $txt = ' '.$outline['t'];
21446 } else {
21447 $txt = $outline['t'].' ';
21448 }
21449 $this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
21450 if ($this->rtl) {
21451 $tw = $this->x - $this->lMargin;
21452 } else {
21453 $tw = $this->w - $this->rMargin - $this->x;
21454 }
21455 $this->SetFont($numbersfont, $fontstyle, $fontsize);
21456 if (TCPDF_STATIC::empty_string($page)) {
21457 $pagenum = $outline['p'];
21458 } else {
21459 // placemark to be replaced with the correct number
21460 $pagenum = '{#'.($outline['p']).'}';
21461 if ($this->isUnicodeFont()) {
21462 $pagenum = '{'.$pagenum.'}';
21463 }
21464 $maxpage = max($maxpage, $outline['p']);
21465 }
21466 $fw = ($tw - $this->GetStringWidth($pagenum.$filler));
21467 $wfiller = $this->GetStringWidth($filler);
21468 if ($wfiller > 0) {
21469 $numfills = floor($fw / $wfiller);
21470 } else {
21471 $numfills = 0;
21472 }
21473 if ($numfills > 0) {
21474 $rowfill = str_repeat($filler, $numfills);
21475 } else {
21476 $rowfill = '';
21477 }
21478 if ($this->rtl) {
21479 $pagenum = $pagenum.$gap.$rowfill;
21480 } else {
21481 $pagenum = $rowfill.$gap.$pagenum;
21482 }
21483 // write the number
21484 $this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
21485 }
21486 $page_last = $this->getPage();
21487 $numpages = ($page_last - $page_first + 1);
21488 // account for booklet mode
21489 if ($this->booklet) {
21490 // check if a blank page is required before TOC
21491 $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21492 $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21493 if ($page_fill_start) {
21494 // add a page at the end (to be moved before TOC)
21495 $this->addPage();
21496 ++$page_last;
21497 ++$numpages;
21498 }
21499 if ($page_fill_end) {
21500 // add a page at the end
21501 $this->addPage();
21502 ++$page_last;
21503 ++$numpages;
21504 }
21505 }
21506 $maxpage = max($maxpage, $page_last);
21507 if (!TCPDF_STATIC::empty_string($page)) {
21508 for ($p = $page_first; $p <= $page_last; ++$p) {
21509 // get page data
21510 $temppage = $this->getPageBuffer($p);
21511 for ($n = 1; $n <= $maxpage; ++$n) {
21512 // update page numbers
21513 $a = '{#'.$n.'}';
21514 // get page number aliases
21515 $pnalias = $this->getInternalPageNumberAliases($a);
21516 // calculate replacement number
21517 if (($n >= $page) AND ($n <= $this->numpages)) {
21518 $np = $n + $numpages;
21519 } else {
21520 $np = $n;
21521 }
21522 $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21523 $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21524 // replace aliases with numbers
21525 foreach ($pnalias['u'] as $u) {
21526 $sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
21527 if ($this->rtl) {
21528 $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21529 } else {
21530 $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21531 }
21532 $temppage = str_replace($u, $nr, $temppage);
21533 }
21534 foreach ($pnalias['a'] as $a) {
21535 $sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
21536 if ($this->rtl) {
21537 $nr = $na.' '.$sfill;
21538 } else {
21539 $nr = $sfill.' '.$na;
21540 }
21541 $temppage = str_replace($a, $nr, $temppage);
21542 }
21543 }
21544 // save changes
21545 $this->setPageBuffer($p, $temppage);
21546 }
21547 // move pages
21548 $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21549 if ($page_fill_start) {
21550 $this->movePage($page_last, $page_first);
21551 }
21552 for ($i = 0; $i < $numpages; ++$i) {
21553 $this->movePage($page_last, $page);
21554 }
21555 }
21556 }
21557
21558 /**
21559 * Output a Table Of Content Index (TOC) using HTML templates.
21560 * This method must be called after all Bookmarks were set.
21561 * Before calling this method you have to open the page using the addTOCPage() method.
21562 * After calling this method you have to call endTOCPage() to close the TOC page.
21563 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21564 * @param $toc_name (string) name to use for TOC bookmark.
21565 * @param $templates (array) array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
21566 * @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)
21567 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21568 * @param $color (array) RGB color array for title (values from 0 to 255).
21569 * @public
21570 * @author Nicola Asuni
21571 * @since 5.0.001 (2010-05-06)
21572 * @see addTOCPage(), endTOCPage(), addTOC()
21573 */
21574 public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
21575 $filler = ' ';
21576 $prev_htmlLinkColorArray = $this->htmlLinkColorArray;
21577 $prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
21578 // set new style for link
21579 $this->htmlLinkColorArray = array();
21580 $this->htmlLinkFontStyle = '';
21581 $page_first = $this->getPage();
21582 $page_fill_start = false;
21583 $page_fill_end = false;
21584 // get the font type used for numbers in each template
21585 $current_font = $this->FontFamily;
21586 foreach ($templates as $level => $html) {
21587 $dom = $this->getHtmlDomArray($html);
21588 foreach ($dom as $key => $value) {
21589 if ($value['value'] == '#TOC_PAGE_NUMBER#') {
21590 $this->SetFont($dom[($key - 1)]['fontname']);
21591 $templates['F'.$level] = $this->isUnicodeFont();
21592 }
21593 }
21594 }
21595 $this->SetFont($current_font);
21596 $maxpage = 0; //used for pages on attached documents
21597 foreach ($this->outlines as $key => $outline) {
21598 // get HTML template
21599 $row = $templates[$outline['l']];
21600 if (TCPDF_STATIC::empty_string($page)) {
21601 $pagenum = $outline['p'];
21602 } else {
21603 // placemark to be replaced with the correct number
21604 $pagenum = '{#'.($outline['p']).'}';
21605 if (isset($templates['F'.$outline['l']]) && $templates['F'.$outline['l']]) {
21606 $pagenum = '{'.$pagenum.'}';
21607 }
21608 $maxpage = max($maxpage, $outline['p']);
21609 }
21610 // replace templates with current values
21611 $row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
21612 $row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
21613 // add link to page
21614 $row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
21615 // write bookmark entry
21616 $this->writeHTML($row, false, false, true, false, '');
21617 }
21618 // restore link styles
21619 $this->htmlLinkColorArray = $prev_htmlLinkColorArray;
21620 $this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
21621 // move TOC page and replace numbers
21622 $page_last = $this->getPage();
21623 $numpages = ($page_last - $page_first + 1);
21624 // account for booklet mode
21625 if ($this->booklet) {
21626 // check if a blank page is required before TOC
21627 $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21628 $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21629 if ($page_fill_start) {
21630 // add a page at the end (to be moved before TOC)
21631 $this->addPage();
21632 ++$page_last;
21633 ++$numpages;
21634 }
21635 if ($page_fill_end) {
21636 // add a page at the end
21637 $this->addPage();
21638 ++$page_last;
21639 ++$numpages;
21640 }
21641 }
21642 $maxpage = max($maxpage, $page_last);
21643 if (!TCPDF_STATIC::empty_string($page)) {
21644 for ($p = $page_first; $p <= $page_last; ++$p) {
21645 // get page data
21646 $temppage = $this->getPageBuffer($p);
21647 for ($n = 1; $n <= $maxpage; ++$n) {
21648 // update page numbers
21649 $a = '{#'.$n.'}';
21650 // get page number aliases
21651 $pnalias = $this->getInternalPageNumberAliases($a);
21652 // calculate replacement number
21653 if ($n >= $page) {
21654 $np = $n + $numpages;
21655 } else {
21656 $np = $n;
21657 }
21658 $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21659 $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21660 // replace aliases with numbers
21661 foreach ($pnalias['u'] as $u) {
21662 if ($correct_align) {
21663 $sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
21664 if ($this->rtl) {
21665 $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21666 } else {
21667 $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21668 }
21669 } else {
21670 $nr = $nu;
21671 }
21672 $temppage = str_replace($u, $nr, $temppage);
21673 }
21674 foreach ($pnalias['a'] as $a) {
21675 if ($correct_align) {
21676 $sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
21677 if ($this->rtl) {
21678 $nr = $na.' '.$sfill;
21679 } else {
21680 $nr = $sfill.' '.$na;
21681 }
21682 } else {
21683 $nr = $na;
21684 }
21685 $temppage = str_replace($a, $nr, $temppage);
21686 }
21687 }
21688 // save changes
21689 $this->setPageBuffer($p, $temppage);
21690 }
21691 // move pages
21692 $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21693 if ($page_fill_start) {
21694 $this->movePage($page_last, $page_first);
21695 }
21696 for ($i = 0; $i < $numpages; ++$i) {
21697 $this->movePage($page_last, $page);
21698 }
21699 }
21700 }
21701
21702 /**
21703 * Stores a copy of the current TCPDF object used for undo operation.
21704 * @public
21705 * @since 4.5.029 (2009-03-19)
21706 */
21707 public function startTransaction() {
21708 if (isset($this->objcopy)) {
21709 // remove previous copy
21710 $this->commitTransaction();
21711 }
21712 // record current page number and Y position
21713 $this->start_transaction_page = $this->page;
21714 $this->start_transaction_y = $this->y;
21715 // clone current object
21716 $this->objcopy = TCPDF_STATIC::objclone($this);
21717 }
21718
21719 /**
21720 * Delete the copy of the current TCPDF object used for undo operation.
21721 * @public
21722 * @since 4.5.029 (2009-03-19)
21723 */
21724 public function commitTransaction() {
21725 if (isset($this->objcopy)) {
21726 $this->objcopy->_destroy(true, true);
21727 unset($this->objcopy);
21728 }
21729 }
21730
21731 /**
21732 * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
21733 * @param $self (boolean) if true restores current class object to previous state without the need of reassignment via the returned value.
21734 * @return TCPDF object.
21735 * @public
21736 * @since 4.5.029 (2009-03-19)
21737 */
21738 public function rollbackTransaction($self=false) {
21739 if (isset($this->objcopy)) {
21740 $this->_destroy(true, true);
21741 if ($self) {
21742 $objvars = get_object_vars($this->objcopy);
21743 foreach ($objvars as $key => $value) {
21744 $this->$key = $value;
21745 }
21746 }
21747 return $this->objcopy;
21748 }
21749 return $this;
21750 }
21751
21752 // --- MULTI COLUMNS METHODS -----------------------
21753
21754 /**
21755 * Set multiple columns of the same size
21756 * @param $numcols (int) number of columns (set to zero to disable columns mode)
21757 * @param $width (int) column width
21758 * @param $y (int) column starting Y position (leave empty for current Y position)
21759 * @public
21760 * @since 4.9.001 (2010-03-28)
21761 */
21762 public function setEqualColumns($numcols=0, $width=0, $y='') {
21763 $this->columns = array();
21764 if ($numcols < 2) {
21765 $numcols = 0;
21766 $this->columns = array();
21767 } else {
21768 // maximum column width
21769 $maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
21770 if (($width == 0) OR ($width > $maxwidth)) {
21771 $width = $maxwidth;
21772 }
21773 if (TCPDF_STATIC::empty_string($y)) {
21774 $y = $this->y;
21775 }
21776 // space between columns
21777 $space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
21778 // fill the columns array (with, space, starting Y position)
21779 for ($i = 0; $i < $numcols; ++$i) {
21780 $this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
21781 }
21782 }
21783 $this->num_columns = $numcols;
21784 $this->current_column = 0;
21785 $this->column_start_page = $this->page;
21786 $this->selectColumn(0);
21787 }
21788
21789 /**
21790 * Remove columns and reset page margins.
21791 * @public
21792 * @since 5.9.072 (2011-04-26)
21793 */
21794 public function resetColumns() {
21795 $this->lMargin = $this->original_lMargin;
21796 $this->rMargin = $this->original_rMargin;
21797 $this->setEqualColumns();
21798 }
21799
21800 /**
21801 * Set columns array.
21802 * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
21803 * @param $columns (array)
21804 * @public
21805 * @since 4.9.001 (2010-03-28)
21806 */
21807 public function setColumnsArray($columns) {
21808 $this->columns = $columns;
21809 $this->num_columns = count($columns);
21810 $this->current_column = 0;
21811 $this->column_start_page = $this->page;
21812 $this->selectColumn(0);
21813 }
21814
21815 /**
21816 * Set position at a given column
21817 * @param $col (int) column number (from 0 to getNumberOfColumns()-1); empty string = current column.
21818 * @public
21819 * @since 4.9.001 (2010-03-28)
21820 */
21821 public function selectColumn($col='') {
21822 if (is_string($col)) {
21823 $col = $this->current_column;
21824 } elseif ($col >= $this->num_columns) {
21825 $col = 0;
21826 }
21827 $xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
21828 $enable_thead = false;
21829 if ($this->num_columns > 1) {
21830 if ($col != $this->current_column) {
21831 // move Y pointer at the top of the column
21832 if ($this->column_start_page == $this->page) {
21833 $this->y = $this->columns[$col]['y'];
21834 } else {
21835 $this->y = $this->tMargin;
21836 }
21837 // Avoid to write table headers more than once
21838 if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
21839 $enable_thead = true;
21840 $this->maxselcol['page'] = $this->page;
21841 $this->maxselcol['column'] = $col;
21842 }
21843 }
21844 $xshift = $this->colxshift;
21845 // set X position of the current column by case
21846 $listindent = ($this->listindentlevel * $this->listindent);
21847 // calculate column X position
21848 $colpos = 0;
21849 for ($i = 0; $i < $col; ++$i) {
21850 $colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
21851 }
21852 if ($this->rtl) {
21853 $x = $this->w - $this->original_rMargin - $colpos;
21854 $this->rMargin = ($this->w - $x + $listindent);
21855 $this->lMargin = ($x - $this->columns[$col]['w']);
21856 $this->x = $x - $listindent;
21857 } else {
21858 $x = $this->original_lMargin + $colpos;
21859 $this->lMargin = ($x + $listindent);
21860 $this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
21861 $this->x = $x + $listindent;
21862 }
21863 $this->columns[$col]['x'] = $x;
21864 }
21865 $this->current_column = $col;
21866 // fix for HTML mode
21867 $this->newline = true;
21868 // print HTML table header (if any)
21869 if ((!TCPDF_STATIC::empty_string($this->thead)) AND (!$this->inthead)) {
21870 if ($enable_thead) {
21871 // print table header
21872 $this->writeHTML($this->thead, false, false, false, false, '');
21873 $this->y += $xshift['s']['V'];
21874 // store end of header position
21875 if (!isset($this->columns[$col]['th'])) {
21876 $this->columns[$col]['th'] = array();
21877 }
21878 $this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
21879 $this->lasth = 0;
21880 } elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
21881 $this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
21882 }
21883 }
21884 // account for an html table cell over multiple columns
21885 if ($this->rtl) {
21886 $this->rMargin += $xshift['x'];
21887 $this->x -= ($xshift['x'] + $xshift['p']['R']);
21888 } else {
21889 $this->lMargin += $xshift['x'];
21890 $this->x += $xshift['x'] + $xshift['p']['L'];
21891 }
21892 }
21893
21894 /**
21895 * Return the current column number
21896 * @return int current column number
21897 * @public
21898 * @since 5.5.011 (2010-07-08)
21899 */
21900 public function getColumn() {
21901 return $this->current_column;
21902 }
21903
21904 /**
21905 * Return the current number of columns.
21906 * @return int number of columns
21907 * @public
21908 * @since 5.8.018 (2010-08-25)
21909 */
21910 public function getNumberOfColumns() {
21911 return $this->num_columns;
21912 }
21913
21914 /**
21915 * Set Text rendering mode.
21916 * @param $stroke (int) outline size in user units (0 = disable).
21917 * @param $fill (boolean) if true fills the text (default).
21918 * @param $clip (boolean) if true activate clipping mode
21919 * @public
21920 * @since 4.9.008 (2009-04-02)
21921 */
21922 public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
21923 // Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
21924 // convert text rendering parameters
21925 if ($stroke < 0) {
21926 $stroke = 0;
21927 }
21928 if ($fill === true) {
21929 if ($stroke > 0) {
21930 if ($clip === true) {
21931 // Fill, then stroke text and add to path for clipping
21932 $textrendermode = 6;
21933 } else {
21934 // Fill, then stroke text
21935 $textrendermode = 2;
21936 }
21937 $textstrokewidth = $stroke;
21938 } else {
21939 if ($clip === true) {
21940 // Fill text and add to path for clipping
21941 $textrendermode = 4;
21942 } else {
21943 // Fill text
21944 $textrendermode = 0;
21945 }
21946 }
21947 } else {
21948 if ($stroke > 0) {
21949 if ($clip === true) {
21950 // Stroke text and add to path for clipping
21951 $textrendermode = 5;
21952 } else {
21953 // Stroke text
21954 $textrendermode = 1;
21955 }
21956 $textstrokewidth = $stroke;
21957 } else {
21958 if ($clip === true) {
21959 // Add text to path for clipping
21960 $textrendermode = 7;
21961 } else {
21962 // Neither fill nor stroke text (invisible)
21963 $textrendermode = 3;
21964 }
21965 }
21966 }
21967 $this->textrendermode = $textrendermode;
21968 $this->textstrokewidth = $stroke;
21969 }
21970
21971 /**
21972 * Set parameters for drop shadow effect for text.
21973 * @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.
21974 * @since 5.9.174 (2012-07-25)
21975 * @public
21976 */
21977 public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
21978 if (isset($params['enabled'])) {
21979 $this->txtshadow['enabled'] = $params['enabled']?true:false;
21980 } else {
21981 $this->txtshadow['enabled'] = false;
21982 }
21983 if (isset($params['depth_w'])) {
21984 $this->txtshadow['depth_w'] = floatval($params['depth_w']);
21985 } else {
21986 $this->txtshadow['depth_w'] = 0;
21987 }
21988 if (isset($params['depth_h'])) {
21989 $this->txtshadow['depth_h'] = floatval($params['depth_h']);
21990 } else {
21991 $this->txtshadow['depth_h'] = 0;
21992 }
21993 if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
21994 $this->txtshadow['color'] = $params['color'];
21995 } else {
21996 $this->txtshadow['color'] = $this->strokecolor;
21997 }
21998 if (isset($params['opacity'])) {
21999 $this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
22000 } else {
22001 $this->txtshadow['opacity'] = 1;
22002 }
22003 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'))) {
22004 $this->txtshadow['blend_mode'] = $params['blend_mode'];
22005 } else {
22006 $this->txtshadow['blend_mode'] = 'Normal';
22007 }
22008 if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
22009 $this->txtshadow['enabled'] = false;
22010 }
22011 }
22012
22013 /**
22014 * Return the text shadow parameters array.
22015 * @return Array of parameters.
22016 * @since 5.9.174 (2012-07-25)
22017 * @public
22018 */
22019 public function getTextShadow() {
22020 return $this->txtshadow;
22021 }
22022
22023 /**
22024 * Returns an array of chars containing soft hyphens.
22025 * @param $word (array) array of chars
22026 * @param $patterns (array) Array of hypenation patterns.
22027 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algorithm.
22028 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
22029 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
22030 * @param $charmin (int) Minimum word length to apply the hyphenation algorithm.
22031 * @param $charmax (int) Maximum length of broken piece of word.
22032 * @return array text with soft hyphens
22033 * @author Nicola Asuni
22034 * @since 4.9.012 (2010-04-12)
22035 * @protected
22036 */
22037 protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22038 $hyphenword = array(); // hyphens positions
22039 $numchars = count($word);
22040 if ($numchars <= $charmin) {
22041 return $word;
22042 }
22043 $word_string = TCPDF_FONTS::UTF8ArrSubString($word, '', '', $this->isunicode);
22044 // some words will be returned as-is
22045 $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})(\]?)$/';
22046 if (preg_match($pattern, $word_string) > 0) {
22047 // email
22048 return $word;
22049 }
22050 $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})(\]?)$/';
22051 if (preg_match($pattern, $word_string) > 0) {
22052 // URL
22053 return $word;
22054 }
22055 if (isset($dictionary[$word_string])) {
22056 return TCPDF_FONTS::UTF8StringToArray($dictionary[$word_string], $this->isunicode, $this->CurrentFont);
22057 }
22058 // surround word with '_' characters
22059 $tmpword = array_merge(array(46), $word, array(46));
22060 $tmpnumchars = $numchars + 2;
22061 $maxpos = $tmpnumchars - 1;
22062 for ($pos = 0; $pos < $maxpos; ++$pos) {
22063 $imax = min(($tmpnumchars - $pos), $charmax);
22064 for ($i = 1; $i <= $imax; ++$i) {
22065 $subword = strtolower(TCPDF_FONTS::UTF8ArrSubString($tmpword, $pos, ($pos + $i), $this->isunicode));
22066 if (isset($patterns[$subword])) {
22067 $pattern = TCPDF_FONTS::UTF8StringToArray($patterns[$subword], $this->isunicode, $this->CurrentFont);
22068 $pattern_length = count($pattern);
22069 $digits = 1;
22070 for ($j = 0; $j < $pattern_length; ++$j) {
22071 // check if $pattern[$j] is a number = hyphenation level (only numbers from 1 to 5 are valid)
22072 if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
22073 if ($j == 0) {
22074 $zero = $pos - 1;
22075 } else {
22076 $zero = $pos + $j - $digits;
22077 }
22078 // get hyphenation level
22079 $level = ($pattern[$j] - 48);
22080 // if two levels from two different patterns match at the same point, the higher one is selected.
22081 if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] < $level)) {
22082 $hyphenword[$zero] = $level;
22083 }
22084 ++$digits;
22085 }
22086 }
22087 }
22088 }
22089 }
22090 $inserted = 0;
22091 $maxpos = $numchars - $rightmin;
22092 for ($i = $leftmin; $i <= $maxpos; ++$i) {
22093 // only odd levels indicate allowed hyphenation points
22094 if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
22095 // 173 = soft hyphen character
22096 array_splice($word, $i + $inserted, 0, 173);
22097 ++$inserted;
22098 }
22099 }
22100 return $word;
22101 }
22102
22103 /**
22104 * Returns text with soft hyphens.
22105 * @param $text (string) text to process
22106 * @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/
22107 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algorithm.
22108 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
22109 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
22110 * @param $charmin (int) Minimum word length to apply the hyphenation algorithm.
22111 * @param $charmax (int) Maximum length of broken piece of word.
22112 * @return array text with soft hyphens
22113 * @author Nicola Asuni
22114 * @since 4.9.012 (2010-04-12)
22115 * @public
22116 */
22117 public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22118 $text = $this->unhtmlentities($text);
22119 $word = array(); // last word
22120 $txtarr = array(); // text to be returned
22121 $intag = false; // true if we are inside an HTML tag
22122 $skip = false; // true to skip hyphenation
22123 if (!is_array($patterns)) {
22124 $patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patterns);
22125 }
22126 // get array of characters
22127 $unichars = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
22128 // for each char
22129 foreach ($unichars as $char) {
22130 if ((!$intag) AND (!$skip) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
22131 // letter character
22132 $word[] = $char;
22133 } else {
22134 // other type of character
22135 if (!TCPDF_STATIC::empty_string($word)) {
22136 // hypenate the word
22137 $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22138 $word = array();
22139 }
22140 $txtarr[] = $char;
22141 if (chr($char) == '<') {
22142 // we are inside an HTML tag
22143 $intag = true;
22144 } elseif ($intag AND (chr($char) == '>')) {
22145 // end of HTML tag
22146 $intag = false;
22147 // check for style tag
22148 $expected = array(115, 116, 121, 108, 101); // = 'style'
22149 $current = array_slice($txtarr, -6, 5); // last 5 chars
22150 $compare = array_diff($expected, $current);
22151 if (empty($compare)) {
22152 // check if it is a closing tag
22153 $expected = array(47); // = '/'
22154 $current = array_slice($txtarr, -7, 1);
22155 $compare = array_diff($expected, $current);
22156 if (empty($compare)) {
22157 // closing style tag
22158 $skip = false;
22159 } else {
22160 // opening style tag
22161 $skip = true;
22162 }
22163 }
22164 }
22165 }
22166 }
22167 if (!TCPDF_STATIC::empty_string($word)) {
22168 // hypenate the word
22169 $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22170 }
22171 // convert char array to string and return
22172 return TCPDF_FONTS::UTF8ArrSubString($txtarr, '', '', $this->isunicode);
22173 }
22174
22175 /**
22176 * Enable/disable rasterization of vector images using ImageMagick library.
22177 * @param $mode (boolean) if true enable rasterization, false otherwise.
22178 * @public
22179 * @since 5.0.000 (2010-04-27)
22180 */
22181 public function setRasterizeVectorImages($mode) {
22182 $this->rasterize_vector_images = $mode;
22183 }
22184
22185 /**
22186 * Enable or disable default option for font subsetting.
22187 * @param $enable (boolean) if true enable font subsetting by default.
22188 * @author Nicola Asuni
22189 * @public
22190 * @since 5.3.002 (2010-06-07)
22191 */
22192 public function setFontSubsetting($enable=true) {
22193 if ($this->pdfa_mode) {
22194 $this->font_subsetting = false;
22195 } else {
22196 $this->font_subsetting = $enable ? true : false;
22197 }
22198 }
22199
22200 /**
22201 * Return the default option for font subsetting.
22202 * @return boolean default font subsetting state.
22203 * @author Nicola Asuni
22204 * @public
22205 * @since 5.3.002 (2010-06-07)
22206 */
22207 public function getFontSubsetting() {
22208 return $this->font_subsetting;
22209 }
22210
22211 /**
22212 * Left trim the input string
22213 * @param $str (string) string to trim
22214 * @param $replace (string) string that replace spaces.
22215 * @return left trimmed string
22216 * @author Nicola Asuni
22217 * @public
22218 * @since 5.8.000 (2010-08-11)
22219 */
22220 public function stringLeftTrim($str, $replace='') {
22221 return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
22222 }
22223
22224 /**
22225 * Right trim the input string
22226 * @param $str (string) string to trim
22227 * @param $replace (string) string that replace spaces.
22228 * @return right trimmed string
22229 * @author Nicola Asuni
22230 * @public
22231 * @since 5.8.000 (2010-08-11)
22232 */
22233 public function stringRightTrim($str, $replace='') {
22234 return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
22235 }
22236
22237 /**
22238 * Trim the input string
22239 * @param $str (string) string to trim
22240 * @param $replace (string) string that replace spaces.
22241 * @return trimmed string
22242 * @author Nicola Asuni
22243 * @public
22244 * @since 5.8.000 (2010-08-11)
22245 */
22246 public function stringTrim($str, $replace='') {
22247 $str = $this->stringLeftTrim($str, $replace);
22248 $str = $this->stringRightTrim($str, $replace);
22249 return $str;
22250 }
22251
22252 /**
22253 * Return true if the current font is unicode type.
22254 * @return true for unicode font, false otherwise.
22255 * @author Nicola Asuni
22256 * @public
22257 * @since 5.8.002 (2010-08-14)
22258 */
22259 public function isUnicodeFont() {
22260 return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
22261 }
22262
22263 /**
22264 * Return normalized font name
22265 * @param $fontfamily (string) property string containing font family names
22266 * @return string normalized font name
22267 * @author Nicola Asuni
22268 * @public
22269 * @since 5.8.004 (2010-08-17)
22270 */
22271 public function getFontFamilyName($fontfamily) {
22272 // remove spaces and symbols
22273 $fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
22274 // extract all font names
22275 $fontslist = preg_split('/[,]/', $fontfamily);
22276 // find first valid font name
22277 foreach ($fontslist as $font) {
22278 // replace font variations
22279 $font = preg_replace('/regular$/', '', $font);
22280 $font = preg_replace('/italic$/', 'I', $font);
22281 $font = preg_replace('/oblique$/', 'I', $font);
22282 $font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
22283 // replace common family names and core fonts
22284 $pattern = array();
22285 $replacement = array();
22286 $pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
22287 $replacement[] = 'times';
22288 $pattern[] = '/^sansserif/';
22289 $replacement[] = 'helvetica';
22290 $pattern[] = '/^monospace/';
22291 $replacement[] = 'courier';
22292 $font = preg_replace($pattern, $replacement, $font);
22293 if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
22294 return $font;
22295 }
22296 }
22297 // return current font as default
22298 return $this->CurrentFont['fontkey'];
22299 }
22300
22301 /**
22302 * Start a new XObject Template.
22303 * 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).
22304 * 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.
22305 * Note: X,Y coordinates will be reset to 0,0.
22306 * @param $w (int) Template width in user units (empty string or zero = page width less margins).
22307 * @param $h (int) Template height in user units (empty string or zero = page height less margins).
22308 * @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).
22309 * @return int the XObject Template ID in case of success or false in case of error.
22310 * @author Nicola Asuni
22311 * @public
22312 * @since 5.8.017 (2010-08-24)
22313 * @see endTemplate(), printTemplate()
22314 */
22315 public function startTemplate($w=0, $h=0, $group=false) {
22316 if ($this->inxobj) {
22317 // we are already inside an XObject template
22318 return false;
22319 }
22320 $this->inxobj = true;
22321 ++$this->n;
22322 // XObject ID
22323 $this->xobjid = 'XT'.$this->n;
22324 // object ID
22325 $this->xobjects[$this->xobjid] = array('n' => $this->n);
22326 // store current graphic state
22327 $this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
22328 // initialize data
22329 $this->xobjects[$this->xobjid]['intmrk'] = 0;
22330 $this->xobjects[$this->xobjid]['transfmrk'] = array();
22331 $this->xobjects[$this->xobjid]['outdata'] = '';
22332 $this->xobjects[$this->xobjid]['xobjects'] = array();
22333 $this->xobjects[$this->xobjid]['images'] = array();
22334 $this->xobjects[$this->xobjid]['fonts'] = array();
22335 $this->xobjects[$this->xobjid]['annotations'] = array();
22336 $this->xobjects[$this->xobjid]['extgstates'] = array();
22337 $this->xobjects[$this->xobjid]['gradients'] = array();
22338 $this->xobjects[$this->xobjid]['spot_colors'] = array();
22339 // set new environment
22340 $this->num_columns = 1;
22341 $this->current_column = 0;
22342 $this->SetAutoPageBreak(false);
22343 if (($w === '') OR ($w <= 0)) {
22344 $w = $this->w - $this->lMargin - $this->rMargin;
22345 }
22346 if (($h === '') OR ($h <= 0)) {
22347 $h = $this->h - $this->tMargin - $this->bMargin;
22348 }
22349 $this->xobjects[$this->xobjid]['x'] = 0;
22350 $this->xobjects[$this->xobjid]['y'] = 0;
22351 $this->xobjects[$this->xobjid]['w'] = $w;
22352 $this->xobjects[$this->xobjid]['h'] = $h;
22353 $this->w = $w;
22354 $this->h = $h;
22355 $this->wPt = $this->w * $this->k;
22356 $this->hPt = $this->h * $this->k;
22357 $this->fwPt = $this->wPt;
22358 $this->fhPt = $this->hPt;
22359 $this->x = 0;
22360 $this->y = 0;
22361 $this->lMargin = 0;
22362 $this->rMargin = 0;
22363 $this->tMargin = 0;
22364 $this->bMargin = 0;
22365 // set group mode
22366 $this->xobjects[$this->xobjid]['group'] = $group;
22367 return $this->xobjid;
22368 }
22369
22370 /**
22371 * End the current XObject Template started with startTemplate() and restore the previous graphic state.
22372 * 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).
22373 * 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.
22374 * @return int the XObject Template ID in case of success or false in case of error.
22375 * @author Nicola Asuni
22376 * @public
22377 * @since 5.8.017 (2010-08-24)
22378 * @see startTemplate(), printTemplate()
22379 */
22380 public function endTemplate() {
22381 if (!$this->inxobj) {
22382 // we are not inside a template
22383 return false;
22384 }
22385 $this->inxobj = false;
22386 // restore previous graphic state
22387 $this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
22388 return $this->xobjid;
22389 }
22390
22391 /**
22392 * Print an XObject Template.
22393 * You can print an XObject Template inside the currently opened Template.
22394 * 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).
22395 * 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.
22396 * @param $id (string) The ID of XObject Template to print.
22397 * @param $x (int) X position in user units (empty string = current x position)
22398 * @param $y (int) Y position in user units (empty string = current y position)
22399 * @param $w (int) Width in user units (zero = remaining page width)
22400 * @param $h (int) Height in user units (zero = remaining page height)
22401 * @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>
22402 * @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>
22403 * @param $fitonpage (boolean) If true the template is resized to not exceed page dimensions.
22404 * @author Nicola Asuni
22405 * @public
22406 * @since 5.8.017 (2010-08-24)
22407 * @see startTemplate(), endTemplate()
22408 */
22409 public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
22410 if ($this->state != 2) {
22411 return;
22412 }
22413 if (!isset($this->xobjects[$id])) {
22414 $this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
22415 }
22416 if ($this->inxobj) {
22417 if ($id == $this->xobjid) {
22418 // close current template
22419 $this->endTemplate();
22420 } else {
22421 // use the template as resource for the template currently opened
22422 $this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
22423 }
22424 }
22425 // set default values
22426 if ($x === '') {
22427 $x = $this->x;
22428 }
22429 if ($y === '') {
22430 $y = $this->y;
22431 }
22432 // check page for no-write regions and adapt page margins if necessary
22433 list($x, $y) = $this->checkPageRegions($h, $x, $y);
22434 $ow = $this->xobjects[$id]['w'];
22435 if ($ow <= 0) {
22436 $ow = 1;
22437 }
22438 $oh = $this->xobjects[$id]['h'];
22439 if ($oh <= 0) {
22440 $oh = 1;
22441 }
22442 // calculate template width and height on document
22443 if (($w <= 0) AND ($h <= 0)) {
22444 $w = $ow;
22445 $h = $oh;
22446 } elseif ($w <= 0) {
22447 $w = $h * $ow / $oh;
22448 } elseif ($h <= 0) {
22449 $h = $w * $oh / $ow;
22450 }
22451 // fit the template on available space
22452 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22453 // set page alignment
22454 $rb_y = $y + $h;
22455 // set alignment
22456 if ($this->rtl) {
22457 if ($palign == 'L') {
22458 $xt = $this->lMargin;
22459 } elseif ($palign == 'C') {
22460 $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22461 } elseif ($palign == 'R') {
22462 $xt = $this->w - $this->rMargin - $w;
22463 } else {
22464 $xt = $x - $w;
22465 }
22466 $rb_x = $xt;
22467 } else {
22468 if ($palign == 'L') {
22469 $xt = $this->lMargin;
22470 } elseif ($palign == 'C') {
22471 $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22472 } elseif ($palign == 'R') {
22473 $xt = $this->w - $this->rMargin - $w;
22474 } else {
22475 $xt = $x;
22476 }
22477 $rb_x = $xt + $w;
22478 }
22479 // print XObject Template + Transformation matrix
22480 $this->StartTransform();
22481 // translate and scale
22482 $sx = ($w / $ow);
22483 $sy = ($h / $oh);
22484 $tm = array();
22485 $tm[0] = $sx;
22486 $tm[1] = 0;
22487 $tm[2] = 0;
22488 $tm[3] = $sy;
22489 $tm[4] = $xt * $this->k;
22490 $tm[5] = ($this->h - $h - $y) * $this->k;
22491 $this->Transform($tm);
22492 // set object
22493 $this->_out('/'.$id.' Do');
22494 $this->StopTransform();
22495 // add annotations
22496 if (!empty($this->xobjects[$id]['annotations'])) {
22497 foreach ($this->xobjects[$id]['annotations'] as $annot) {
22498 // transform original coordinates
22499 $coordlt = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
22500 $ax = ($coordlt[4] / $this->k);
22501 $ay = ($this->h - $h - ($coordlt[5] / $this->k));
22502 $coordrb = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
22503 $aw = ($coordrb[4] / $this->k) - $ax;
22504 $ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
22505 $this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
22506 }
22507 }
22508 // set pointer to align the next text/objects
22509 switch($align) {
22510 case 'T': {
22511 $this->y = $y;
22512 $this->x = $rb_x;
22513 break;
22514 }
22515 case 'M': {
22516 $this->y = $y + round($h/2);
22517 $this->x = $rb_x;
22518 break;
22519 }
22520 case 'B': {
22521 $this->y = $rb_y;
22522 $this->x = $rb_x;
22523 break;
22524 }
22525 case 'N': {
22526 $this->SetY($rb_y);
22527 break;
22528 }
22529 default:{
22530 break;
22531 }
22532 }
22533 }
22534
22535 /**
22536 * Set the percentage of character stretching.
22537 * @param $perc (int) percentage of stretching (100 = no stretching)
22538 * @author Nicola Asuni
22539 * @public
22540 * @since 5.9.000 (2010-09-29)
22541 */
22542 public function setFontStretching($perc=100) {
22543 $this->font_stretching = $perc;
22544 }
22545
22546 /**
22547 * Get the percentage of character stretching.
22548 * @return float stretching value
22549 * @author Nicola Asuni
22550 * @public
22551 * @since 5.9.000 (2010-09-29)
22552 */
22553 public function getFontStretching() {
22554 return $this->font_stretching;
22555 }
22556
22557 /**
22558 * Set the amount to increase or decrease the space between characters in a text.
22559 * @param $spacing (float) amount to increase or decrease the space between characters in a text (0 = default spacing)
22560 * @author Nicola Asuni
22561 * @public
22562 * @since 5.9.000 (2010-09-29)
22563 */
22564 public function setFontSpacing($spacing=0) {
22565 $this->font_spacing = $spacing;
22566 }
22567
22568 /**
22569 * Get the amount to increase or decrease the space between characters in a text.
22570 * @return int font spacing (tracking) value
22571 * @author Nicola Asuni
22572 * @public
22573 * @since 5.9.000 (2010-09-29)
22574 */
22575 public function getFontSpacing() {
22576 return $this->font_spacing;
22577 }
22578
22579 /**
22580 * Return an array of no-write page regions
22581 * @return array of no-write page regions
22582 * @author Nicola Asuni
22583 * @public
22584 * @since 5.9.003 (2010-10-13)
22585 * @see setPageRegions(), addPageRegion()
22586 */
22587 public function getPageRegions() {
22588 return $this->page_regions;
22589 }
22590
22591 /**
22592 * Set no-write regions on page.
22593 * 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.
22594 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22595 * You can set multiple regions for the same page.
22596 * @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.
22597 * @author Nicola Asuni
22598 * @public
22599 * @since 5.9.003 (2010-10-13)
22600 * @see addPageRegion(), getPageRegions()
22601 */
22602 public function setPageRegions($regions=array()) {
22603 // empty current regions array
22604 $this->page_regions = array();
22605 // add regions
22606 foreach ($regions as $data) {
22607 $this->addPageRegion($data);
22608 }
22609 }
22610
22611 /**
22612 * Add a single no-write region on selected page.
22613 * 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.
22614 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22615 * You can set multiple regions for the same page.
22616 * @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).
22617 * @author Nicola Asuni
22618 * @public
22619 * @since 5.9.003 (2010-10-13)
22620 * @see setPageRegions(), getPageRegions()
22621 */
22622 public function addPageRegion($region) {
22623 if (!isset($region['page']) OR empty($region['page'])) {
22624 $region['page'] = $this->page;
22625 }
22626 if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
22627 AND isset($region['yt']) AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
22628 AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
22629 $this->page_regions[] = $region;
22630 }
22631 }
22632
22633 /**
22634 * Remove a single no-write region.
22635 * @param $key (int) region key
22636 * @author Nicola Asuni
22637 * @public
22638 * @since 5.9.003 (2010-10-13)
22639 * @see setPageRegions(), getPageRegions()
22640 */
22641 public function removePageRegion($key) {
22642 if (isset($this->page_regions[$key])) {
22643 unset($this->page_regions[$key]);
22644 }
22645 }
22646
22647 /**
22648 * Check page for no-write regions and adapt current coordinates and page margins if necessary.
22649 * 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.
22650 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22651 * @param $h (float) height of the text/image/object to print in user units
22652 * @param $x (float) current X coordinate in user units
22653 * @param $y (float) current Y coordinate in user units
22654 * @return array($x, $y)
22655 * @author Nicola Asuni
22656 * @protected
22657 * @since 5.9.003 (2010-10-13)
22658 */
22659 protected function checkPageRegions($h, $x, $y) {
22660 // set default values
22661 if ($x === '') {
22662 $x = $this->x;
22663 }
22664 if ($y === '') {
22665 $y = $this->y;
22666 }
22667 if (!$this->check_page_regions OR empty($this->page_regions)) {
22668 // no page regions defined
22669 return array($x, $y);
22670 }
22671 if (empty($h)) {
22672 $h = $this->getCellHeight($this->FontSize);
22673 }
22674 // check for page break
22675 if ($this->checkPageBreak($h, $y)) {
22676 // the content will be printed on a new page
22677 $x = $this->x;
22678 $y = $this->y;
22679 }
22680 if ($this->num_columns > 1) {
22681 if ($this->rtl) {
22682 $this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22683 } else {
22684 $this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22685 }
22686 } else {
22687 if ($this->rtl) {
22688 $this->lMargin = max($this->clMargin, $this->original_lMargin);
22689 } else {
22690 $this->rMargin = max($this->crMargin, $this->original_rMargin);
22691 }
22692 }
22693 // adjust coordinates and page margins
22694 foreach ($this->page_regions as $regid => $regdata) {
22695 if ($regdata['page'] == $this->page) {
22696 // check region boundaries
22697 if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
22698 // Y is inside the region
22699 $minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
22700 $yt = max($y, $regdata['yt']);
22701 $yb = min(($yt + $h), $regdata['yb']);
22702 $xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
22703 $xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
22704 if ($regdata['side'] == 'L') { // left side
22705 $new_margin = max($xt, $xb);
22706 if ($this->lMargin < $new_margin) {
22707 if ($this->rtl) {
22708 // adjust left page margin
22709 $this->lMargin = max(0, $new_margin);
22710 }
22711 if ($x < $new_margin) {
22712 // adjust x position
22713 $x = $new_margin;
22714 if ($new_margin > ($this->w - $this->rMargin)) {
22715 // adjust y position
22716 $y = $regdata['yb'] - $h;
22717 }
22718 }
22719 }
22720 } elseif ($regdata['side'] == 'R') { // right side
22721 $new_margin = min($xt, $xb);
22722 if (($this->w - $this->rMargin) > $new_margin) {
22723 if (!$this->rtl) {
22724 // adjust right page margin
22725 $this->rMargin = max(0, ($this->w - $new_margin));
22726 }
22727 if ($x > $new_margin) {
22728 // adjust x position
22729 $x = $new_margin;
22730 if ($new_margin > $this->lMargin) {
22731 // adjust y position
22732 $y = $regdata['yb'] - $h;
22733 }
22734 }
22735 }
22736 }
22737 }
22738 }
22739 }
22740 return array($x, $y);
22741 }
22742
22743 // --- SVG METHODS ---------------------------------------------------------
22744
22745 /**
22746 * Embedd a Scalable Vector Graphics (SVG) image.
22747 * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
22748 * @param $file (string) Name of the SVG file or a '@' character followed by the SVG data string.
22749 * @param $x (float) Abscissa of the upper-left corner.
22750 * @param $y (float) Ordinate of the upper-left corner.
22751 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
22752 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
22753 * @param $link (mixed) URL or identifier returned by AddLink().
22754 * @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.
22755 * @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>
22756 * @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)))
22757 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
22758 * @author Nicola Asuni
22759 * @since 5.0.000 (2010-05-02)
22760 * @public
22761 */
22762 public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
22763 if ($this->state != 2) {
22764 return;
22765 }
22766 // reset SVG vars
22767 $this->svggradients = array();
22768 $this->svggradientid = 0;
22769 $this->svgdefsmode = false;
22770 $this->svgdefs = array();
22771 $this->svgclipmode = false;
22772 $this->svgclippaths = array();
22773 $this->svgcliptm = array();
22774 $this->svgclipid = 0;
22775 $this->svgtext = '';
22776 $this->svgtextmode = array();
22777 if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
22778 // convert SVG to raster image using GD or ImageMagick libraries
22779 return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22780 }
22781 if ($file[0] === '@') { // image from string
22782 $this->svgdir = '';
22783 $svgdata = substr($file, 1);
22784 } else { // SVG file
22785 $this->svgdir = dirname($file);
22786 $svgdata = TCPDF_STATIC::fileGetContents($file);
22787 }
22788 if ($svgdata === FALSE) {
22789 $this->Error('SVG file not found: '.$file);
22790 }
22791 if ($x === '') {
22792 $x = $this->x;
22793 }
22794 if ($y === '') {
22795 $y = $this->y;
22796 }
22797 // check page for no-write regions and adapt page margins if necessary
22798 list($x, $y) = $this->checkPageRegions($h, $x, $y);
22799 $k = $this->k;
22800 $ox = 0;
22801 $oy = 0;
22802 $ow = $w;
22803 $oh = $h;
22804 $aspect_ratio_align = 'xMidYMid';
22805 $aspect_ratio_ms = 'meet';
22806 $regs = array();
22807 // get original image width and height
22808 preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
22809 if (isset($regs[1]) AND !empty($regs[1])) {
22810 $tmp = array();
22811 if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22812 $ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22813 }
22814 $tmp = array();
22815 if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22816 $oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22817 }
22818 $tmp = array();
22819 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22820 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22821 }
22822 $tmp = array();
22823 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22824 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22825 }
22826 $tmp = array();
22827 $view_box = array();
22828 if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
22829 if (count($tmp) == 5) {
22830 array_shift($tmp);
22831 foreach ($tmp as $key => $val) {
22832 $view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
22833 }
22834 $ox = $view_box[0];
22835 $oy = $view_box[1];
22836 }
22837 // get aspect ratio
22838 $tmp = array();
22839 if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22840 $aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
22841 switch (count($aspect_ratio)) {
22842 case 3: {
22843 $aspect_ratio_align = $aspect_ratio[1];
22844 $aspect_ratio_ms = $aspect_ratio[2];
22845 break;
22846 }
22847 case 2: {
22848 $aspect_ratio_align = $aspect_ratio[0];
22849 $aspect_ratio_ms = $aspect_ratio[1];
22850 break;
22851 }
22852 case 1: {
22853 $aspect_ratio_align = $aspect_ratio[0];
22854 $aspect_ratio_ms = 'meet';
22855 break;
22856 }
22857 }
22858 }
22859 }
22860 }
22861 if ($ow <= 0) {
22862 $ow = 1;
22863 }
22864 if ($oh <= 0) {
22865 $oh = 1;
22866 }
22867 // calculate image width and height on document
22868 if (($w <= 0) AND ($h <= 0)) {
22869 // convert image size to document unit
22870 $w = $ow;
22871 $h = $oh;
22872 } elseif ($w <= 0) {
22873 $w = $h * $ow / $oh;
22874 } elseif ($h <= 0) {
22875 $h = $w * $oh / $ow;
22876 }
22877 // fit the image on available space
22878 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22879 if ($this->rasterize_vector_images) {
22880 // convert SVG to raster image using GD or ImageMagick libraries
22881 return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22882 }
22883 // set alignment
22884 $this->img_rb_y = $y + $h;
22885 // set alignment
22886 if ($this->rtl) {
22887 if ($palign == 'L') {
22888 $ximg = $this->lMargin;
22889 } elseif ($palign == 'C') {
22890 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22891 } elseif ($palign == 'R') {
22892 $ximg = $this->w - $this->rMargin - $w;
22893 } else {
22894 $ximg = $x - $w;
22895 }
22896 $this->img_rb_x = $ximg;
22897 } else {
22898 if ($palign == 'L') {
22899 $ximg = $this->lMargin;
22900 } elseif ($palign == 'C') {
22901 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22902 } elseif ($palign == 'R') {
22903 $ximg = $this->w - $this->rMargin - $w;
22904 } else {
22905 $ximg = $x;
22906 }
22907 $this->img_rb_x = $ximg + $w;
22908 }
22909 // store current graphic vars
22910 $gvars = $this->getGraphicVars();
22911 // store SVG position and scale factors
22912 $svgoffset_x = ($ximg - $ox) * $this->k;
22913 $svgoffset_y = -($y - $oy) * $this->k;
22914 if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
22915 $ow = $view_box[2];
22916 $oh = $view_box[3];
22917 } else {
22918 if ($ow <= 0) {
22919 $ow = $w;
22920 }
22921 if ($oh <= 0) {
22922 $oh = $h;
22923 }
22924 }
22925 $svgscale_x = $w / $ow;
22926 $svgscale_y = $h / $oh;
22927 // scaling and alignment
22928 if ($aspect_ratio_align != 'none') {
22929 // store current scaling values
22930 $svgscale_old_x = $svgscale_x;
22931 $svgscale_old_y = $svgscale_y;
22932 // force uniform scaling
22933 if ($aspect_ratio_ms == 'slice') {
22934 // the entire viewport is covered by the viewBox
22935 if ($svgscale_x > $svgscale_y) {
22936 $svgscale_y = $svgscale_x;
22937 } elseif ($svgscale_x < $svgscale_y) {
22938 $svgscale_x = $svgscale_y;
22939 }
22940 } else { // meet
22941 // the entire viewBox is visible within the viewport
22942 if ($svgscale_x < $svgscale_y) {
22943 $svgscale_y = $svgscale_x;
22944 } elseif ($svgscale_x > $svgscale_y) {
22945 $svgscale_x = $svgscale_y;
22946 }
22947 }
22948 // correct X alignment
22949 switch (substr($aspect_ratio_align, 1, 3)) {
22950 case 'Min': {
22951 // do nothing
22952 break;
22953 }
22954 case 'Max': {
22955 $svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
22956 break;
22957 }
22958 default:
22959 case 'Mid': {
22960 $svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
22961 break;
22962 }
22963 }
22964 // correct Y alignment
22965 switch (substr($aspect_ratio_align, 5)) {
22966 case 'Min': {
22967 // do nothing
22968 break;
22969 }
22970 case 'Max': {
22971 $svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
22972 break;
22973 }
22974 default:
22975 case 'Mid': {
22976 $svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
22977 break;
22978 }
22979 }
22980 }
22981 // store current page break mode
22982 $page_break_mode = $this->AutoPageBreak;
22983 $page_break_margin = $this->getBreakMargin();
22984 $cell_padding = $this->cell_padding;
22985 $this->SetCellPadding(0);
22986 $this->SetAutoPageBreak(false);
22987 // save the current graphic state
22988 $this->_out('q'.$this->epsmarker);
22989 // set initial clipping mask
22990 $this->Rect($ximg, $y, $w, $h, 'CNZ', array(), array());
22991 // scale and translate
22992 $e = $ox * $this->k * (1 - $svgscale_x);
22993 $f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
22994 $this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
22995 // creates a new XML parser to be used by the other XML functions
22996 $this->parser = xml_parser_create('UTF-8');
22997 // the following function allows to use parser inside object
22998 xml_set_object($this->parser, $this);
22999 // disable case-folding for this XML parser
23000 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
23001 // sets the element handler functions for the XML parser
23002 xml_set_element_handler($this->parser, 'startSVGElementHandler', 'endSVGElementHandler');
23003 // sets the character data handler function for the XML parser
23004 xml_set_character_data_handler($this->parser, 'segSVGContentHandler');
23005 // start parsing an XML document
23006 if (!xml_parse($this->parser, $svgdata)) {
23007 $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));
23008 $this->Error($error_message);
23009 }
23010 // free this XML parser
23011 xml_parser_free($this->parser);
23012 // restore previous graphic state
23013 $this->_out($this->epsmarker.'Q');
23014 // restore graphic vars
23015 $this->setGraphicVars($gvars);
23016 $this->lasth = $gvars['lasth'];
23017 if (!empty($border)) {
23018 $bx = $this->x;
23019 $by = $this->y;
23020 $this->x = $ximg;
23021 if ($this->rtl) {
23022 $this->x += $w;
23023 }
23024 $this->y = $y;
23025 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
23026 $this->x = $bx;
23027 $this->y = $by;
23028 }
23029 if ($link) {
23030 $this->Link($ximg, $y, $w, $h, $link, 0);
23031 }
23032 // set pointer to align the next text/objects
23033 switch($align) {
23034 case 'T':{
23035 $this->y = $y;
23036 $this->x = $this->img_rb_x;
23037 break;
23038 }
23039 case 'M':{
23040 $this->y = $y + round($h/2);
23041 $this->x = $this->img_rb_x;
23042 break;
23043 }
23044 case 'B':{
23045 $this->y = $this->img_rb_y;
23046 $this->x = $this->img_rb_x;
23047 break;
23048 }
23049 case 'N':{
23050 $this->SetY($this->img_rb_y);
23051 break;
23052 }
23053 default:{
23054 // restore pointer to starting position
23055 $this->x = $gvars['x'];
23056 $this->y = $gvars['y'];
23057 $this->page = $gvars['page'];
23058 $this->current_column = $gvars['current_column'];
23059 $this->tMargin = $gvars['tMargin'];
23060 $this->bMargin = $gvars['bMargin'];
23061 $this->w = $gvars['w'];
23062 $this->h = $gvars['h'];
23063 $this->wPt = $gvars['wPt'];
23064 $this->hPt = $gvars['hPt'];
23065 $this->fwPt = $gvars['fwPt'];
23066 $this->fhPt = $gvars['fhPt'];
23067 break;
23068 }
23069 }
23070 $this->endlinex = $this->img_rb_x;
23071 // restore page break
23072 $this->SetAutoPageBreak($page_break_mode, $page_break_margin);
23073 $this->cell_padding = $cell_padding;
23074 }
23075
23076 /**
23077 * Convert SVG transformation matrix to PDF.
23078 * @param $tm (array) original SVG transformation matrix
23079 * @return array transformation matrix
23080 * @protected
23081 * @since 5.0.000 (2010-05-02)
23082 */
23083 protected function convertSVGtMatrix($tm) {
23084 $a = $tm[0];
23085 $b = -$tm[1];
23086 $c = -$tm[2];
23087 $d = $tm[3];
23088 $e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
23089 $f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
23090 $x = 0;
23091 $y = $this->h * $this->k;
23092 $e = ($x * (1 - $a)) - ($y * $c) + $e;
23093 $f = ($y * (1 - $d)) - ($x * $b) + $f;
23094 return array($a, $b, $c, $d, $e, $f);
23095 }
23096
23097 /**
23098 * Apply SVG graphic transformation matrix.
23099 * @param $tm (array) original SVG transformation matrix
23100 * @protected
23101 * @since 5.0.000 (2010-05-02)
23102 */
23103 protected function SVGTransform($tm) {
23104 $this->Transform($this->convertSVGtMatrix($tm));
23105 }
23106
23107 /**
23108 * Apply the requested SVG styles (*** TO BE COMPLETED ***)
23109 * @param $svgstyle (array) array of SVG styles to apply
23110 * @param $prevsvgstyle (array) array of previous SVG style
23111 * @param $x (int) X origin of the bounding box
23112 * @param $y (int) Y origin of the bounding box
23113 * @param $w (int) width of the bounding box
23114 * @param $h (int) height of the bounding box
23115 * @param $clip_function (string) clip function
23116 * @param $clip_params (array) array of parameters for clipping function
23117 * @return object style
23118 * @author Nicola Asuni
23119 * @since 5.0.000 (2010-05-02)
23120 * @protected
23121 */
23122 protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
23123 if ($this->state != 2) {
23124 return;
23125 }
23126 $objstyle = '';
23127 $minlen = (0.01 / $this->k); // minimum acceptable length
23128 if (!isset($svgstyle['opacity'])) {
23129 return $objstyle;
23130 }
23131 // clip-path
23132 $regs = array();
23133 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
23134 $clip_path = $this->svgclippaths[$regs[1]];
23135 foreach ($clip_path as $cp) {
23136 $this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
23137 }
23138 }
23139 // opacity
23140 if ($svgstyle['opacity'] != 1) {
23141 $this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
23142 }
23143 // color
23144 $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
23145 $this->SetFillColorArray($fill_color);
23146 // text color
23147 $text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
23148 $this->SetTextColorArray($text_color);
23149 // clip
23150 if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
23151 $top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
23152 $right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
23153 $bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
23154 $left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
23155 $cx = $x + $left;
23156 $cy = $y + $top;
23157 $cw = $w - $left - $right;
23158 $ch = $h - $top - $bottom;
23159 if ($svgstyle['clip-rule'] == 'evenodd') {
23160 $clip_rule = 'CNZ';
23161 } else {
23162 $clip_rule = 'CEO';
23163 }
23164 $this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
23165 }
23166 // fill
23167 $regs = array();
23168 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
23169 // gradient
23170 $gradient = $this->svggradients[$regs[1]];
23171 if (isset($gradient['xref'])) {
23172 // reference to another gradient definition
23173 $newgradient = $this->svggradients[$gradient['xref']];
23174 $newgradient['coords'] = $gradient['coords'];
23175 $newgradient['mode'] = $gradient['mode'];
23176 $newgradient['type'] = $gradient['type'];
23177 $newgradient['gradientUnits'] = $gradient['gradientUnits'];
23178 if (isset($gradient['gradientTransform'])) {
23179 $newgradient['gradientTransform'] = $gradient['gradientTransform'];
23180 }
23181 $gradient = $newgradient;
23182 }
23183 //save current Graphic State
23184 $this->_outSaveGraphicsState();
23185 //set clipping area
23186 if (!empty($clip_function) AND method_exists($this, $clip_function)) {
23187 $bbox = call_user_func_array(array($this, $clip_function), $clip_params);
23188 if ((!isset($gradient['type']) OR ($gradient['type'] != 3)) AND is_array($bbox) AND (count($bbox) == 4)) {
23189 list($x, $y, $w, $h) = $bbox;
23190 }
23191 }
23192 if ($gradient['mode'] == 'measure') {
23193 if (!isset($gradient['coords'][4])) {
23194 $gradient['coords'][4] = 0.5;
23195 }
23196 if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
23197 $gtm = $gradient['gradientTransform'];
23198 // apply transformation matrix
23199 $xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
23200 $ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
23201 $xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
23202 $yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
23203 $r = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
23204 $gradient['coords'][0] = $xa;
23205 $gradient['coords'][1] = $ya;
23206 $gradient['coords'][2] = $xb;
23207 $gradient['coords'][3] = $yb;
23208 $gradient['coords'][4] = $r;
23209 }
23210 // convert SVG coordinates to user units
23211 $gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
23212 $gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
23213 $gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
23214 $gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
23215 $gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
23216 if ($w <= $minlen) {
23217 $w = $minlen;
23218 }
23219 if ($h <= $minlen) {
23220 $h = $minlen;
23221 }
23222 // shift units
23223 if ($gradient['gradientUnits'] == 'objectBoundingBox') {
23224 // convert to SVG coordinate system
23225 $gradient['coords'][0] += $x;
23226 $gradient['coords'][1] += $y;
23227 $gradient['coords'][2] += $x;
23228 $gradient['coords'][3] += $y;
23229 }
23230 // calculate percentages
23231 $gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
23232 $gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
23233 $gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
23234 $gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
23235 $gradient['coords'][4] /= $w;
23236 } elseif ($gradient['mode'] == 'percentage') {
23237 foreach($gradient['coords'] as $key => $val) {
23238 $gradient['coords'][$key] = (intval($val) / 100);
23239 if ($val < 0) {
23240 $gradient['coords'][$key] = 0;
23241 } elseif ($val > 1) {
23242 $gradient['coords'][$key] = 1;
23243 }
23244 }
23245 }
23246 if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
23247 // single color (no shading)
23248 $gradient['coords'][0] = 1;
23249 $gradient['coords'][1] = 0;
23250 $gradient['coords'][2] = 0.999;
23251 $gradient['coords'][3] = 0;
23252 }
23253 // swap Y coordinates
23254 $tmp = $gradient['coords'][1];
23255 $gradient['coords'][1] = $gradient['coords'][3];
23256 $gradient['coords'][3] = $tmp;
23257 // set transformation map for gradient
23258 $cy = ($this->h - $y);
23259 if ($gradient['type'] == 3) {
23260 // circular gradient
23261 $cy -= ($gradient['coords'][1] * ($w + $h));
23262 $h = $w = max($w, $h);
23263 } else {
23264 $cy -= $h;
23265 }
23266 $this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), ($cy * $this->k)));
23267 if (count($gradient['stops']) > 1) {
23268 $this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
23269 }
23270 } elseif ($svgstyle['fill'] != 'none') {
23271 $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
23272 if ($svgstyle['fill-opacity'] != 1) {
23273 $this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
23274 }
23275 $this->SetFillColorArray($fill_color);
23276 if ($svgstyle['fill-rule'] == 'evenodd') {
23277 $objstyle .= 'F*';
23278 } else {
23279 $objstyle .= 'F';
23280 }
23281 }
23282 // stroke
23283 if ($svgstyle['stroke'] != 'none') {
23284 if ($svgstyle['stroke-opacity'] != 1) {
23285 $this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
23286 } elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['stroke'], $rgba_matches)) {
23287 $this->setAlpha($rgba_matches[1], 'Normal', $this->alpha['ca'], false);
23288 }
23289 $stroke_style = array(
23290 'color' => TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors),
23291 'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
23292 'cap' => $svgstyle['stroke-linecap'],
23293 'join' => $svgstyle['stroke-linejoin']
23294 );
23295 if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
23296 $stroke_style['dash'] = $svgstyle['stroke-dasharray'];
23297 }
23298 $this->SetLineStyle($stroke_style);
23299 $objstyle .= 'D';
23300 }
23301 // font
23302 $regs = array();
23303 if (!empty($svgstyle['font'])) {
23304 if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
23305 $font_family = $this->getFontFamilyName($regs[1]);
23306 } else {
23307 $font_family = $svgstyle['font-family'];
23308 }
23309 if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23310 $font_size = trim($regs[1]);
23311 } else {
23312 $font_size = $svgstyle['font-size'];
23313 }
23314 if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23315 $font_style = trim($regs[1]);
23316 } else {
23317 $font_style = $svgstyle['font-style'];
23318 }
23319 if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23320 $font_weight = trim($regs[1]);
23321 } else {
23322 $font_weight = $svgstyle['font-weight'];
23323 }
23324 if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23325 $font_stretch = trim($regs[1]);
23326 } else {
23327 $font_stretch = $svgstyle['font-stretch'];
23328 }
23329 if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23330 $font_spacing = trim($regs[1]);
23331 } else {
23332 $font_spacing = $svgstyle['letter-spacing'];
23333 }
23334 } else {
23335 $font_family = $this->getFontFamilyName($svgstyle['font-family']);
23336 $font_size = $svgstyle['font-size'];
23337 $font_style = $svgstyle['font-style'];
23338 $font_weight = $svgstyle['font-weight'];
23339 $font_stretch = $svgstyle['font-stretch'];
23340 $font_spacing = $svgstyle['letter-spacing'];
23341 }
23342 $font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit);
23343 $font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
23344 $font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
23345 switch ($font_style) {
23346 case 'italic': {
23347 $font_style = 'I';
23348 break;
23349 }
23350 case 'oblique': {
23351 $font_style = 'I';
23352 break;
23353 }
23354 default:
23355 case 'normal': {
23356 $font_style = '';
23357 break;
23358 }
23359 }
23360 switch ($font_weight) {
23361 case 'bold':
23362 case 'bolder': {
23363 $font_style .= 'B';
23364 break;
23365 }
23366 case 'normal': {
23367 if ((substr($font_family, -1) == 'I') AND (substr($font_family, -2, 1) == 'B')) {
23368 $font_family = substr($font_family, 0, -2).'I';
23369 } elseif (substr($font_family, -1) == 'B') {
23370 $font_family = substr($font_family, 0, -1);
23371 }
23372 break;
23373 }
23374 }
23375 switch ($svgstyle['text-decoration']) {
23376 case 'underline': {
23377 $font_style .= 'U';
23378 break;
23379 }
23380 case 'overline': {
23381 $font_style .= 'O';
23382 break;
23383 }
23384 case 'line-through': {
23385 $font_style .= 'D';
23386 break;
23387 }
23388 default:
23389 case 'none': {
23390 break;
23391 }
23392 }
23393 $this->SetFont($font_family, $font_style, $font_size);
23394 $this->setFontStretching($font_stretch);
23395 $this->setFontSpacing($font_spacing);
23396 return $objstyle;
23397 }
23398
23399 /**
23400 * Draws an SVG path
23401 * @param $d (string) attribute d of the path SVG element
23402 * @param $style (string) Style of rendering. Possible values are:
23403 * <ul>
23404 * <li>D or empty string: Draw (default).</li>
23405 * <li>F: Fill.</li>
23406 * <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23407 * <li>DF or FD: Draw and fill.</li>
23408 * <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23409 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
23410 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
23411 * </ul>
23412 * @return array of container box measures (x, y, w, h)
23413 * @author Nicola Asuni
23414 * @since 5.0.000 (2010-05-02)
23415 * @protected
23416 */
23417 protected function SVGPath($d, $style='') {
23418 if ($this->state != 2) {
23419 return;
23420 }
23421 // set fill/stroke style
23422 $op = TCPDF_STATIC::getPathPaintOperator($style, '');
23423 if (empty($op)) {
23424 return;
23425 }
23426 $paths = array();
23427 $d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
23428 preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
23429 $x = 0;
23430 $y = 0;
23431 $x1 = 0;
23432 $y1 = 0;
23433 $x2 = 0;
23434 $y2 = 0;
23435 $xmin = 2147483647;
23436 $xmax = 0;
23437 $ymin = 2147483647;
23438 $ymax = 0;
23439 $xinitial = 0;
23440 $yinitial = 0;
23441 $relcoord = false;
23442 $minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
23443 $firstcmd = true; // used to print first point
23444 // draw curve pieces
23445 foreach ($paths as $key => $val) {
23446 // get curve type
23447 $cmd = trim($val[1]);
23448 if (strtolower($cmd) == $cmd) {
23449 // use relative coordinated instead of absolute
23450 $relcoord = true;
23451 $xoffset = $x;
23452 $yoffset = $y;
23453 } else {
23454 $relcoord = false;
23455 $xoffset = 0;
23456 $yoffset = 0;
23457 }
23458 $params = array();
23459 if (isset($val[2])) {
23460 // get curve parameters
23461 $rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
23462 $params = array();
23463 foreach ($rawparams as $ck => $cp) {
23464 $params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
23465 if (abs($params[$ck]) < $minlen) {
23466 // approximate little values to zero
23467 $params[$ck] = 0;
23468 }
23469 }
23470 }
23471 // store current origin point
23472 $x0 = $x;
23473 $y0 = $y;
23474 switch (strtoupper($cmd)) {
23475 case 'M': { // moveto
23476 foreach ($params as $ck => $cp) {
23477 if (($ck % 2) == 0) {
23478 $x = $cp + $xoffset;
23479 } else {
23480 $y = $cp + $yoffset;
23481 if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23482 if ($ck == 1) {
23483 $this->_outPoint($x, $y);
23484 $firstcmd = false;
23485 $xinitial = $x;
23486 $yinitial = $y;
23487 } else {
23488 $this->_outLine($x, $y);
23489 }
23490 $x0 = $x;
23491 $y0 = $y;
23492 }
23493 $xmin = min($xmin, $x);
23494 $ymin = min($ymin, $y);
23495 $xmax = max($xmax, $x);
23496 $ymax = max($ymax, $y);
23497 if ($relcoord) {
23498 $xoffset = $x;
23499 $yoffset = $y;
23500 }
23501 }
23502 }
23503 break;
23504 }
23505 case 'L': { // lineto
23506 foreach ($params as $ck => $cp) {
23507 if (($ck % 2) == 0) {
23508 $x = $cp + $xoffset;
23509 } else {
23510 $y = $cp + $yoffset;
23511 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23512 $this->_outLine($x, $y);
23513 $x0 = $x;
23514 $y0 = $y;
23515 }
23516 $xmin = min($xmin, $x);
23517 $ymin = min($ymin, $y);
23518 $xmax = max($xmax, $x);
23519 $ymax = max($ymax, $y);
23520 if ($relcoord) {
23521 $xoffset = $x;
23522 $yoffset = $y;
23523 }
23524 }
23525 }
23526 break;
23527 }
23528 case 'H': { // horizontal lineto
23529 foreach ($params as $ck => $cp) {
23530 $x = $cp + $xoffset;
23531 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23532 $this->_outLine($x, $y);
23533 $x0 = $x;
23534 $y0 = $y;
23535 }
23536 $xmin = min($xmin, $x);
23537 $xmax = max($xmax, $x);
23538 if ($relcoord) {
23539 $xoffset = $x;
23540 }
23541 }
23542 break;
23543 }
23544 case 'V': { // vertical lineto
23545 foreach ($params as $ck => $cp) {
23546 $y = $cp + $yoffset;
23547 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23548 $this->_outLine($x, $y);
23549 $x0 = $x;
23550 $y0 = $y;
23551 }
23552 $ymin = min($ymin, $y);
23553 $ymax = max($ymax, $y);
23554 if ($relcoord) {
23555 $yoffset = $y;
23556 }
23557 }
23558 break;
23559 }
23560 case 'C': { // curveto
23561 foreach ($params as $ck => $cp) {
23562 $params[$ck] = $cp;
23563 if ((($ck + 1) % 6) == 0) {
23564 $x1 = $params[($ck - 5)] + $xoffset;
23565 $y1 = $params[($ck - 4)] + $yoffset;
23566 $x2 = $params[($ck - 3)] + $xoffset;
23567 $y2 = $params[($ck - 2)] + $yoffset;
23568 $x = $params[($ck - 1)] + $xoffset;
23569 $y = $params[($ck)] + $yoffset;
23570 $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23571 $xmin = min($xmin, $x, $x1, $x2);
23572 $ymin = min($ymin, $y, $y1, $y2);
23573 $xmax = max($xmax, $x, $x1, $x2);
23574 $ymax = max($ymax, $y, $y1, $y2);
23575 if ($relcoord) {
23576 $xoffset = $x;
23577 $yoffset = $y;
23578 }
23579 }
23580 }
23581 break;
23582 }
23583 case 'S': { // shorthand/smooth curveto
23584 foreach ($params as $ck => $cp) {
23585 $params[$ck] = $cp;
23586 if ((($ck + 1) % 4) == 0) {
23587 if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
23588 $x1 = (2 * $x) - $x2;
23589 $y1 = (2 * $y) - $y2;
23590 } else {
23591 $x1 = $x;
23592 $y1 = $y;
23593 }
23594 $x2 = $params[($ck - 3)] + $xoffset;
23595 $y2 = $params[($ck - 2)] + $yoffset;
23596 $x = $params[($ck - 1)] + $xoffset;
23597 $y = $params[($ck)] + $yoffset;
23598 $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23599 $xmin = min($xmin, $x, $x1, $x2);
23600 $ymin = min($ymin, $y, $y1, $y2);
23601 $xmax = max($xmax, $x, $x1, $x2);
23602 $ymax = max($ymax, $y, $y1, $y2);
23603 if ($relcoord) {
23604 $xoffset = $x;
23605 $yoffset = $y;
23606 }
23607 }
23608 }
23609 break;
23610 }
23611 case 'Q': { // quadratic Bezier curveto
23612 foreach ($params as $ck => $cp) {
23613 $params[$ck] = $cp;
23614 if ((($ck + 1) % 4) == 0) {
23615 // convert quadratic points to cubic points
23616 $x1 = $params[($ck - 3)] + $xoffset;
23617 $y1 = $params[($ck - 2)] + $yoffset;
23618 $xa = ($x + (2 * $x1)) / 3;
23619 $ya = ($y + (2 * $y1)) / 3;
23620 $x = $params[($ck - 1)] + $xoffset;
23621 $y = $params[($ck)] + $yoffset;
23622 $xb = ($x + (2 * $x1)) / 3;
23623 $yb = ($y + (2 * $y1)) / 3;
23624 $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23625 $xmin = min($xmin, $x, $xa, $xb);
23626 $ymin = min($ymin, $y, $ya, $yb);
23627 $xmax = max($xmax, $x, $xa, $xb);
23628 $ymax = max($ymax, $y, $ya, $yb);
23629 if ($relcoord) {
23630 $xoffset = $x;
23631 $yoffset = $y;
23632 }
23633 }
23634 }
23635 break;
23636 }
23637 case 'T': { // shorthand/smooth quadratic Bezier curveto
23638 foreach ($params as $ck => $cp) {
23639 $params[$ck] = $cp;
23640 if (($ck % 2) != 0) {
23641 if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
23642 $x1 = (2 * $x) - $x1;
23643 $y1 = (2 * $y) - $y1;
23644 } else {
23645 $x1 = $x;
23646 $y1 = $y;
23647 }
23648 // convert quadratic points to cubic points
23649 $xa = ($x + (2 * $x1)) / 3;
23650 $ya = ($y + (2 * $y1)) / 3;
23651 $x = $params[($ck - 1)] + $xoffset;
23652 $y = $params[($ck)] + $yoffset;
23653 $xb = ($x + (2 * $x1)) / 3;
23654 $yb = ($y + (2 * $y1)) / 3;
23655 $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23656 $xmin = min($xmin, $x, $xa, $xb);
23657 $ymin = min($ymin, $y, $ya, $yb);
23658 $xmax = max($xmax, $x, $xa, $xb);
23659 $ymax = max($ymax, $y, $ya, $yb);
23660 if ($relcoord) {
23661 $xoffset = $x;
23662 $yoffset = $y;
23663 }
23664 }
23665 }
23666 break;
23667 }
23668 case 'A': { // elliptical arc
23669 foreach ($params as $ck => $cp) {
23670 $params[$ck] = $cp;
23671 if ((($ck + 1) % 7) == 0) {
23672 $x0 = $x;
23673 $y0 = $y;
23674 $rx = max(abs($params[($ck - 6)]), .000000001);
23675 $ry = max(abs($params[($ck - 5)]), .000000001);
23676 $ang = -$rawparams[($ck - 4)];
23677 $angle = deg2rad($ang);
23678 $fa = $rawparams[($ck - 3)]; // large-arc-flag
23679 $fs = $rawparams[($ck - 2)]; // sweep-flag
23680 $x = $params[($ck - 1)] + $xoffset;
23681 $y = $params[$ck] + $yoffset;
23682 if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
23683 // endpoints are almost identical
23684 $xmin = min($xmin, $x);
23685 $ymin = min($ymin, $y);
23686 $xmax = max($xmax, $x);
23687 $ymax = max($ymax, $y);
23688 } else {
23689 $cos_ang = cos($angle);
23690 $sin_ang = sin($angle);
23691 $a = (($x0 - $x) / 2);
23692 $b = (($y0 - $y) / 2);
23693 $xa = ($a * $cos_ang) - ($b * $sin_ang);
23694 $ya = ($a * $sin_ang) + ($b * $cos_ang);
23695 $rx2 = $rx * $rx;
23696 $ry2 = $ry * $ry;
23697 $xa2 = $xa * $xa;
23698 $ya2 = $ya * $ya;
23699 $delta = ($xa2 / $rx2) + ($ya2 / $ry2);
23700 if ($delta > 1) {
23701 $rx *= sqrt($delta);
23702 $ry *= sqrt($delta);
23703 $rx2 = $rx * $rx;
23704 $ry2 = $ry * $ry;
23705 }
23706 $numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
23707 if ($numerator < 0) {
23708 $root = 0;
23709 } else {
23710 $root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
23711 }
23712 if ($fa == $fs){
23713 $root *= -1;
23714 }
23715 $cax = $root * (($rx * $ya) / $ry);
23716 $cay = -$root * (($ry * $xa) / $rx);
23717 // coordinates of ellipse center
23718 $cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
23719 $cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
23720 // get angles
23721 $angs = TCPDF_STATIC::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
23722 $dang = TCPDF_STATIC::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
23723 if (($fs == 0) AND ($dang > 0)) {
23724 $dang -= (2 * M_PI);
23725 } elseif (($fs == 1) AND ($dang < 0)) {
23726 $dang += (2 * M_PI);
23727 }
23728 $angf = $angs - $dang;
23729 if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
23730 // reverse angles
23731 $tmp = $angs;
23732 $angs = $angf;
23733 $angf = $tmp;
23734 }
23735 $angs = round(rad2deg($angs), 6);
23736 $angf = round(rad2deg($angf), 6);
23737 // covent angles to positive values
23738 if (($angs < 0) AND ($angf < 0)) {
23739 $angs += 360;
23740 $angf += 360;
23741 }
23742 $pie = false;
23743 if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
23744 $pie = true;
23745 }
23746 list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
23747 $xmin = min($xmin, $x, $axmin);
23748 $ymin = min($ymin, $y, $aymin);
23749 $xmax = max($xmax, $x, $axmax);
23750 $ymax = max($ymax, $y, $aymax);
23751 }
23752 if ($relcoord) {
23753 $xoffset = $x;
23754 $yoffset = $y;
23755 }
23756 }
23757 }
23758 break;
23759 }
23760 case 'Z': {
23761 $this->_out('h');
23762 $x = $x0 = $xinitial;
23763 $y = $y0 = $yinitial;
23764 break;
23765 }
23766 }
23767 $firstcmd = false;
23768 } // end foreach
23769 if (!empty($op)) {
23770 $this->_out($op);
23771 }
23772 return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
23773 }
23774
23775 /**
23776 * Return the tag name without the namespace
23777 * @param $name (string) Tag name
23778 * @protected
23779 */
23780 protected function removeTagNamespace($name) {
23781 if(strpos($name, ':') !== false) {
23782 $parts = explode(':', $name);
23783 return $parts[(sizeof($parts) - 1)];
23784 }
23785 return $name;
23786 }
23787
23788 /**
23789 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
23790 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
23791 * @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.
23792 * @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.
23793 * @param $ctm (array) tranformation matrix for clipping mode (starting transformation matrix).
23794 * @author Nicola Asuni
23795 * @since 5.0.000 (2010-05-02)
23796 * @protected
23797 */
23798 protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
23799 $name = $this->removeTagNamespace($name);
23800 // check if we are in clip mode
23801 if ($this->svgclipmode) {
23802 $this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
23803 return;
23804 }
23805 if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
23806 if (isset($attribs['id'])) {
23807 $attribs['child_elements'] = array();
23808 $this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23809 return;
23810 }
23811 if (end($this->svgdefs) !== FALSE) {
23812 $last_svgdefs_id = key($this->svgdefs);
23813 if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
23814 $attribs['id'] = 'DF_'.(count($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements']) + 1);
23815 $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23816 return;
23817 }
23818 }
23819 return;
23820 }
23821 $clipping = false;
23822 if ($parser == 'clip-path') {
23823 // set clipping mode
23824 $clipping = true;
23825 }
23826 // get styling properties
23827 $prev_svgstyle = $this->svgstyles[max(0,(count($this->svgstyles) - 1))]; // previous style
23828 $svgstyle = $this->svgstyles[0]; // set default style
23829 if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
23830 // default fill attribute for clipping
23831 $attribs['fill'] = 'none';
23832 }
23833 if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
23834 // fix style for regular expression
23835 $attribs['style'] = ';'.$attribs['style'];
23836 }
23837 foreach ($prev_svgstyle as $key => $val) {
23838 if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
23839 // inherit previous value
23840 $svgstyle[$key] = $val;
23841 }
23842 if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
23843 // specific attribute settings
23844 if ($attribs[$key] == 'inherit') {
23845 $svgstyle[$key] = $val;
23846 } else {
23847 $svgstyle[$key] = $attribs[$key];
23848 }
23849 } elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
23850 // CSS style syntax
23851 $attrval = array();
23852 if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
23853 if ($attrval[1] == 'inherit') {
23854 $svgstyle[$key] = $val;
23855 } else {
23856 $svgstyle[$key] = $attrval[1];
23857 }
23858 }
23859 }
23860 }
23861 // transformation matrix
23862 if (!empty($ctm)) {
23863 $tm = $ctm;
23864 } else {
23865 $tm = array(1,0,0,1,0,0);
23866 }
23867 if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
23868 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
23869 }
23870 $svgstyle['transfmatrix'] = $tm;
23871 $invisible = false;
23872 if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
23873 // the current graphics element is invisible (nothing is painted)
23874 $invisible = true;
23875 }
23876 // process tag
23877 switch($name) {
23878 case 'defs': {
23879 $this->svgdefsmode = true;
23880 break;
23881 }
23882 // clipPath
23883 case 'clipPath': {
23884 if ($invisible) {
23885 break;
23886 }
23887 $this->svgclipmode = true;
23888 if (!isset($attribs['id'])) {
23889 $attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
23890 }
23891 $this->svgclipid = $attribs['id'];
23892 $this->svgclippaths[$this->svgclipid] = array();
23893 $this->svgcliptm[$this->svgclipid] = $tm;
23894 break;
23895 }
23896 case 'svg': {
23897 // start of SVG object
23898 if(++$this->svg_tag_depth <= 1) {
23899 break;
23900 }
23901 // inner SVG
23902 array_push($this->svgstyles, $svgstyle);
23903 $this->StartTransform();
23904 $svgX = (isset($attribs['x'])?$attribs['x']:0);
23905 $svgY = (isset($attribs['y'])?$attribs['y']:0);
23906 $svgW = (isset($attribs['width'])?$attribs['width']:0);
23907 $svgH = (isset($attribs['height'])?$attribs['height']:0);
23908 // set x, y position using transform matrix
23909 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array( 1, 0, 0, 1, $svgX, $svgY));
23910 $this->SVGTransform($tm);
23911 // set clipping for width and height
23912 $x = 0;
23913 $y = 0;
23914 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):$this->w);
23915 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):$this->h);
23916 // draw clipping rect
23917 $this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
23918 // parse viewbox, calculate extra transformation matrix
23919 if (isset($attribs['viewBox'])) {
23920 $tmp = array();
23921 preg_match_all("/[0-9]+/", $attribs['viewBox'], $tmp);
23922 $tmp = $tmp[0];
23923 if (sizeof($tmp) == 4) {
23924 $vx = $tmp[0];
23925 $vy = $tmp[1];
23926 $vw = $tmp[2];
23927 $vh = $tmp[3];
23928 // get aspect ratio
23929 $tmp = array();
23930 $aspectX = 'xMid';
23931 $aspectY = 'YMid';
23932 $fit = 'meet';
23933 if (isset($attribs['preserveAspectRatio'])) {
23934 if($attribs['preserveAspectRatio'] == 'none') {
23935 $fit = 'none';
23936 } else {
23937 preg_match_all('/[a-zA-Z]+/', $attribs['preserveAspectRatio'], $tmp);
23938 $tmp = $tmp[0];
23939 if ((sizeof($tmp) == 2) AND (strlen($tmp[0]) == 8) AND (in_array($tmp[1], array('meet', 'slice', 'none')))) {
23940 $aspectX = substr($tmp[0], 0, 4);
23941 $aspectY = substr($tmp[0], 4, 4);
23942 $fit = $tmp[1];
23943 }
23944 }
23945 }
23946 $wr = ($svgW / $vw);
23947 $hr = ($svgH / $vh);
23948 $ax = $ay = 0;
23949 if ((($fit == 'meet') AND ($hr < $wr)) OR (($fit == 'slice') AND ($hr > $wr))) {
23950 if ($aspectX == 'xMax') {
23951 $ax = (($vw * ($wr / $hr)) - $vw);
23952 }
23953 if ($aspectX == 'xMid') {
23954 $ax = ((($vw * ($wr / $hr)) - $vw) / 2);
23955 }
23956 $wr = $hr;
23957 } elseif ((($fit == 'meet') AND ($hr > $wr)) OR (($fit == 'slice') AND ($hr < $wr))) {
23958 if ($aspectY == 'YMax') {
23959 $ay = (($vh * ($hr / $wr)) - $vh);
23960 }
23961 if ($aspectY == 'YMid') {
23962 $ay = ((($vh * ($hr / $wr)) - $vh) / 2);
23963 }
23964 $hr = $wr;
23965 }
23966 $newtm = array($wr, 0, 0, $hr, (($wr * ($ax - $vx)) - $svgX), (($hr * ($ay - $vy)) - $svgY));
23967 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, $newtm);
23968 $this->SVGTransform($tm);
23969 }
23970 }
23971 $this->setSVGStyles($svgstyle, $prev_svgstyle);
23972 break;
23973 }
23974 case 'g': {
23975 // group together related graphics elements
23976 array_push($this->svgstyles, $svgstyle);
23977 $this->StartTransform();
23978 $x = (isset($attribs['x'])?$attribs['x']:0);
23979 $y = (isset($attribs['y'])?$attribs['y']:0);
23980 $w = 1;//(isset($attribs['width'])?$attribs['width']:1);
23981 $h = 1;//(isset($attribs['height'])?$attribs['height']:1);
23982 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
23983 $this->SVGTransform($tm);
23984 $this->setSVGStyles($svgstyle, $prev_svgstyle);
23985 break;
23986 }
23987 case 'linearGradient': {
23988 if ($this->pdfa_mode) {
23989 break;
23990 }
23991 if (!isset($attribs['id'])) {
23992 $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
23993 }
23994 $this->svggradientid = $attribs['id'];
23995 $this->svggradients[$this->svggradientid] = array();
23996 $this->svggradients[$this->svggradientid]['type'] = 2;
23997 $this->svggradients[$this->svggradientid]['stops'] = array();
23998 if (isset($attribs['gradientUnits'])) {
23999 $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24000 } else {
24001 $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24002 }
24003 //$attribs['spreadMethod']
24004 if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
24005 OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
24006 OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
24007 OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
24008 OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
24009 $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24010 } else {
24011 $this->svggradients[$this->svggradientid]['mode'] = 'measure';
24012 }
24013 $x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
24014 $y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
24015 $x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
24016 $y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
24017 if (isset($attribs['gradientTransform'])) {
24018 $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24019 }
24020 $this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
24021 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24022 // gradient is defined on another place
24023 $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24024 }
24025 break;
24026 }
24027 case 'radialGradient': {
24028 if ($this->pdfa_mode) {
24029 break;
24030 }
24031 if (!isset($attribs['id'])) {
24032 $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24033 }
24034 $this->svggradientid = $attribs['id'];
24035 $this->svggradients[$this->svggradientid] = array();
24036 $this->svggradients[$this->svggradientid]['type'] = 3;
24037 $this->svggradients[$this->svggradientid]['stops'] = array();
24038 if (isset($attribs['gradientUnits'])) {
24039 $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24040 } else {
24041 $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24042 }
24043 //$attribs['spreadMethod']
24044 if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
24045 OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
24046 OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')))) {
24047 $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24048 } elseif (isset($attribs['r']) AND is_numeric($attribs['r']) AND ($attribs['r']) <= 1) {
24049 $this->svggradients[$this->svggradientid]['mode'] = 'ratio';
24050 } else {
24051 $this->svggradients[$this->svggradientid]['mode'] = 'measure';
24052 }
24053 $cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
24054 $cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
24055 $fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
24056 $fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
24057 $r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
24058 if (isset($attribs['gradientTransform'])) {
24059 $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24060 }
24061 $this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
24062 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24063 // gradient is defined on another place
24064 $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24065 }
24066 break;
24067 }
24068 case 'stop': {
24069 // gradient stops
24070 if (substr($attribs['offset'], -1) == '%') {
24071 $offset = floatval(substr($attribs['offset'], 0, -1)) / 100;
24072 } else {
24073 $offset = floatval($attribs['offset']);
24074 if ($offset > 1) {
24075 $offset /= 100;
24076 }
24077 }
24078 $stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
24079 $opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
24080 $this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
24081 break;
24082 }
24083 // paths
24084 case 'path': {
24085 if ($invisible) {
24086 break;
24087 }
24088 if (isset($attribs['d'])) {
24089 $d = trim($attribs['d']);
24090 if (!empty($d)) {
24091 $x = (isset($attribs['x'])?$attribs['x']:0);
24092 $y = (isset($attribs['y'])?$attribs['y']:0);
24093 $w = (isset($attribs['width'])?$attribs['width']:1);
24094 $h = (isset($attribs['height'])?$attribs['height']:1);
24095 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24096 if ($clipping) {
24097 $this->SVGTransform($tm);
24098 $this->SVGPath($d, 'CNZ');
24099 } else {
24100 $this->StartTransform();
24101 $this->SVGTransform($tm);
24102 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
24103 if (!empty($obstyle)) {
24104 $this->SVGPath($d, $obstyle);
24105 }
24106 $this->StopTransform();
24107 }
24108 }
24109 }
24110 break;
24111 }
24112 // shapes
24113 case 'rect': {
24114 if ($invisible) {
24115 break;
24116 }
24117 $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24118 $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24119 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24120 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24121 $rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
24122 $ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
24123 if ($clipping) {
24124 $this->SVGTransform($tm);
24125 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
24126 } else {
24127 $this->StartTransform();
24128 $this->SVGTransform($tm);
24129 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
24130 if (!empty($obstyle)) {
24131 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
24132 }
24133 $this->StopTransform();
24134 }
24135 break;
24136 }
24137 case 'circle': {
24138 if ($invisible) {
24139 break;
24140 }
24141 $r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
24142 $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24143 $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24144 $x = ($cx - $r);
24145 $y = ($cy - $r);
24146 $w = (2 * $r);
24147 $h = $w;
24148 if ($clipping) {
24149 $this->SVGTransform($tm);
24150 $this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
24151 } else {
24152 $this->StartTransform();
24153 $this->SVGTransform($tm);
24154 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
24155 if (!empty($obstyle)) {
24156 $this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
24157 }
24158 $this->StopTransform();
24159 }
24160 break;
24161 }
24162 case 'ellipse': {
24163 if ($invisible) {
24164 break;
24165 }
24166 $rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
24167 $ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
24168 $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24169 $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24170 $x = ($cx - $rx);
24171 $y = ($cy - $ry);
24172 $w = (2 * $rx);
24173 $h = (2 * $ry);
24174 if ($clipping) {
24175 $this->SVGTransform($tm);
24176 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
24177 } else {
24178 $this->StartTransform();
24179 $this->SVGTransform($tm);
24180 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
24181 if (!empty($obstyle)) {
24182 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
24183 }
24184 $this->StopTransform();
24185 }
24186 break;
24187 }
24188 case 'line': {
24189 if ($invisible) {
24190 break;
24191 }
24192 $x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
24193 $y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
24194 $x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
24195 $y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
24196 $x = $x1;
24197 $y = $y1;
24198 $w = abs($x2 - $x1);
24199 $h = abs($y2 - $y1);
24200 if (!$clipping) {
24201 $this->StartTransform();
24202 $this->SVGTransform($tm);
24203 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
24204 $this->Line($x1, $y1, $x2, $y2);
24205 $this->StopTransform();
24206 }
24207 break;
24208 }
24209 case 'polyline':
24210 case 'polygon': {
24211 if ($invisible) {
24212 break;
24213 }
24214 $points = (isset($attribs['points'])?$attribs['points']:'0 0');
24215 $points = trim($points);
24216 // note that point may use a complex syntax not covered here
24217 $points = preg_split('/[\,\s]+/si', $points);
24218 if (count($points) < 4) {
24219 break;
24220 }
24221 $p = array();
24222 $xmin = 2147483647;
24223 $xmax = 0;
24224 $ymin = 2147483647;
24225 $ymax = 0;
24226 foreach ($points as $key => $val) {
24227 $p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24228 if (($key % 2) == 0) {
24229 // X coordinate
24230 $xmin = min($xmin, $p[$key]);
24231 $xmax = max($xmax, $p[$key]);
24232 } else {
24233 // Y coordinate
24234 $ymin = min($ymin, $p[$key]);
24235 $ymax = max($ymax, $p[$key]);
24236 }
24237 }
24238 $x = $xmin;
24239 $y = $ymin;
24240 $w = ($xmax - $xmin);
24241 $h = ($ymax - $ymin);
24242 if ($name == 'polyline') {
24243 $this->StartTransform();
24244 $this->SVGTransform($tm);
24245 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
24246 if (!empty($obstyle)) {
24247 $this->PolyLine($p, $obstyle, array(), array());
24248 }
24249 $this->StopTransform();
24250 } else { // polygon
24251 if ($clipping) {
24252 $this->SVGTransform($tm);
24253 $this->Polygon($p, 'CNZ', array(), array(), true);
24254 } else {
24255 $this->StartTransform();
24256 $this->SVGTransform($tm);
24257 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
24258 if (!empty($obstyle)) {
24259 $this->Polygon($p, $obstyle, array(), array(), true);
24260 }
24261 $this->StopTransform();
24262 }
24263 }
24264 break;
24265 }
24266 // image
24267 case 'image': {
24268 if ($invisible) {
24269 break;
24270 }
24271 if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
24272 break;
24273 }
24274 $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24275 $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24276 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24277 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24278 $img = $attribs['xlink:href'];
24279 if (!$clipping) {
24280 $this->StartTransform();
24281 $this->SVGTransform($tm);
24282 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
24283 if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
24284 // embedded image encoded as base64
24285 $img = '@'.base64_decode(substr($img, strlen($m[0])));
24286 } else {
24287 // fix image path
24288 if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
24289 // replace relative path with full server path
24290 $img = $this->svgdir.'/'.$img;
24291 }
24292 if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
24293 $findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
24294 if (($findroot === false) OR ($findroot > 1)) {
24295 if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
24296 $img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
24297 } else {
24298 $img = $_SERVER['DOCUMENT_ROOT'].$img;
24299 }
24300 }
24301 }
24302 $img = urldecode($img);
24303 $testscrtype = @parse_url($img);
24304 if (empty($testscrtype['query'])) {
24305 // convert URL to server path
24306 $img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
24307 } elseif (preg_match('|^https?://|', $img) !== 1) {
24308 // convert server path to URL
24309 $img = str_replace(K_PATH_MAIN, K_PATH_URL, $img);
24310 }
24311 }
24312 // get image type
24313 $imgtype = TCPDF_IMAGES::getImageFileType($img);
24314 if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
24315 $this->ImageEps($img, $x, $y, $w, $h);
24316 } elseif ($imgtype == 'svg') {
24317 // store SVG vars
24318 $svggradients = $this->svggradients;
24319 $svggradientid = $this->svggradientid;
24320 $svgdefsmode = $this->svgdefsmode;
24321 $svgdefs = $this->svgdefs;
24322 $svgclipmode = $this->svgclipmode;
24323 $svgclippaths = $this->svgclippaths;
24324 $svgcliptm = $this->svgcliptm;
24325 $svgclipid = $this->svgclipid;
24326 $svgtext = $this->svgtext;
24327 $svgtextmode = $this->svgtextmode;
24328 $this->ImageSVG($img, $x, $y, $w, $h);
24329 // restore SVG vars
24330 $this->svggradients = $svggradients;
24331 $this->svggradientid = $svggradientid;
24332 $this->svgdefsmode = $svgdefsmode;
24333 $this->svgdefs = $svgdefs;
24334 $this->svgclipmode = $svgclipmode;
24335 $this->svgclippaths = $svgclippaths;
24336 $this->svgcliptm = $svgcliptm;
24337 $this->svgclipid = $svgclipid;
24338 $this->svgtext = $svgtext;
24339 $this->svgtextmode = $svgtextmode;
24340 } else {
24341 $this->Image($img, $x, $y, $w, $h);
24342 }
24343 $this->StopTransform();
24344 }
24345 break;
24346 }
24347 // text
24348 case 'text':
24349 case 'tspan': {
24350 if (isset($this->svgtextmode['text-anchor']) AND !empty($this->svgtext)) {
24351 // @TODO: unsupported feature
24352 }
24353 // only basic support - advanced features must be implemented
24354 $this->svgtextmode['invisible'] = $invisible;
24355 if ($invisible) {
24356 break;
24357 }
24358 array_push($this->svgstyles, $svgstyle);
24359 if (isset($attribs['x'])) {
24360 $x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
24361 } elseif ($name == 'tspan') {
24362 $x = $this->x;
24363 } else {
24364 $x = 0;
24365 }
24366 if (isset($attribs['dx'])) {
24367 $x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
24368 }
24369 if (isset($attribs['y'])) {
24370 $y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
24371 } elseif ($name == 'tspan') {
24372 $y = $this->y;
24373 } else {
24374 $y = 0;
24375 }
24376 if (isset($attribs['dy'])) {
24377 $y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
24378 }
24379 $svgstyle['text-color'] = $svgstyle['fill'];
24380 $this->svgtext = '';
24381 if (isset($svgstyle['text-anchor'])) {
24382 $this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
24383 } else {
24384 $this->svgtextmode['text-anchor'] = 'start';
24385 }
24386 if (isset($svgstyle['direction'])) {
24387 if ($svgstyle['direction'] == 'rtl') {
24388 $this->svgtextmode['rtl'] = true;
24389 } else {
24390 $this->svgtextmode['rtl'] = false;
24391 }
24392 } else {
24393 $this->svgtextmode['rtl'] = false;
24394 }
24395 if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
24396 $this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
24397 } else {
24398 $this->svgtextmode['stroke'] = false;
24399 }
24400 $this->StartTransform();
24401 $this->SVGTransform($tm);
24402 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
24403 $this->x = $x;
24404 $this->y = $y;
24405 break;
24406 }
24407 // use
24408 case 'use': {
24409 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24410 $svgdefid = substr($attribs['xlink:href'], 1);
24411 if (isset($this->svgdefs[$svgdefid])) {
24412 $use = $this->svgdefs[$svgdefid];
24413 if (isset($attribs['xlink:href'])) {
24414 unset($attribs['xlink:href']);
24415 }
24416 if (isset($attribs['id'])) {
24417 unset($attribs['id']);
24418 }
24419 if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
24420 $attribs['x'] += $use['attribs']['x'];
24421 }
24422 if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
24423 $attribs['y'] += $use['attribs']['y'];
24424 }
24425 if (empty($attribs['style'])) {
24426 $attribs['style'] = '';
24427 }
24428 if (!empty($use['attribs']['style'])) {
24429 // merge styles
24430 $attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
24431 }
24432 $attribs = array_merge($use['attribs'], $attribs);
24433 $this->startSVGElementHandler($parser, $use['name'], $attribs);
24434 return;
24435 }
24436 }
24437 break;
24438 }
24439 default: {
24440 break;
24441 }
24442 } // end of switch
24443 // process child elements
24444 if (!empty($attribs['child_elements'])) {
24445 $child_elements = $attribs['child_elements'];
24446 unset($attribs['child_elements']);
24447 foreach($child_elements as $child_element) {
24448 if (empty($child_element['attribs']['closing_tag'])) {
24449 $this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
24450 } else {
24451 if (isset($child_element['attribs']['content'])) {
24452 $this->svgtext = $child_element['attribs']['content'];
24453 }
24454 $this->endSVGElementHandler('child-tag', $child_element['name']);
24455 }
24456 }
24457 }
24458 }
24459
24460 /**
24461 * Sets the closing SVG element handler function for the XML parser.
24462 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24463 * @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.
24464 * @author Nicola Asuni
24465 * @since 5.0.000 (2010-05-02)
24466 * @protected
24467 */
24468 protected function endSVGElementHandler($parser, $name) {
24469 $name = $this->removeTagNamespace($name);
24470 if ($this->svgdefsmode AND !in_array($name, array('defs', 'clipPath', 'linearGradient', 'radialGradient', 'stop'))) {;
24471 if (end($this->svgdefs) !== FALSE) {
24472 $last_svgdefs_id = key($this->svgdefs);
24473 if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
24474 foreach($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'] as $child_element) {
24475 if (isset($child_element['attribs']['id']) AND ($child_element['name'] == $name)) {
24476 $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$child_element['attribs']['id'].'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24477 return;
24478 }
24479 }
24480 if ($this->svgdefs[$last_svgdefs_id]['name'] == $name) {
24481 $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$last_svgdefs_id.'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24482 return;
24483 }
24484 }
24485 }
24486 return;
24487 }
24488 switch($name) {
24489 case 'defs': {
24490 $this->svgdefsmode = false;
24491 break;
24492 }
24493 // clipPath
24494 case 'clipPath': {
24495 $this->svgclipmode = false;
24496 break;
24497 }
24498 case 'svg': {
24499 if (--$this->svg_tag_depth <= 0) {
24500 break;
24501 }
24502 }
24503 case 'g': {
24504 // ungroup: remove last style from array
24505 array_pop($this->svgstyles);
24506 $this->StopTransform();
24507 break;
24508 }
24509 case 'text':
24510 case 'tspan': {
24511 if ($this->svgtextmode['invisible']) {
24512 // This implementation must be fixed to following the rule:
24513 // 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.
24514 break;
24515 }
24516 // print text
24517 $text = $this->svgtext;
24518 //$text = $this->stringTrim($text);
24519 $textlen = $this->GetStringWidth($text);
24520 if ($this->svgtextmode['text-anchor'] != 'start') {
24521 // check if string is RTL text
24522 if ($this->svgtextmode['text-anchor'] == 'end') {
24523 if ($this->svgtextmode['rtl']) {
24524 $this->x += $textlen;
24525 } else {
24526 $this->x -= $textlen;
24527 }
24528 } elseif ($this->svgtextmode['text-anchor'] == 'middle') {
24529 if ($this->svgtextmode['rtl']) {
24530 $this->x += ($textlen / 2);
24531 } else {
24532 $this->x -= ($textlen / 2);
24533 }
24534 }
24535 }
24536 $textrendermode = $this->textrendermode;
24537 $textstrokewidth = $this->textstrokewidth;
24538 $this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
24539 if ($name == 'text') {
24540 // store current coordinates
24541 $tmpx = $this->x;
24542 $tmpy = $this->y;
24543 }
24544 // print the text
24545 $this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
24546 if ($name == 'text') {
24547 // restore coordinates
24548 $this->x = $tmpx;
24549 $this->y = $tmpy;
24550 }
24551 // restore previous rendering mode
24552 $this->textrendermode = $textrendermode;
24553 $this->textstrokewidth = $textstrokewidth;
24554 $this->svgtext = '';
24555 $this->StopTransform();
24556 if (!$this->svgdefsmode) {
24557 array_pop($this->svgstyles);
24558 }
24559 break;
24560 }
24561 default: {
24562 break;
24563 }
24564 }
24565 }
24566
24567 /**
24568 * Sets the character data handler function for the XML parser.
24569 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24570 * @param $data (string) The second parameter, data, contains the character data as a string.
24571 * @author Nicola Asuni
24572 * @since 5.0.000 (2010-05-02)
24573 * @protected
24574 */
24575 protected function segSVGContentHandler($parser, $data) {
24576 $this->svgtext .= $data;
24577 }
24578
24579 // --- END SVG METHODS -----------------------------------------------------
24580
24581 } // END OF TCPDF CLASS
24582
24583 //============================================================+
24584 // END OF FILE
24585 //============================================================+
24586