PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.15.0
GiveWP – Donation Plugin and Fundraising Platform v4.15.0
4.16.9 4.16.8.1 4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 All 255 releases
give / vendor / tecnickcom / tcpdf / tcpdf.php

tcpdf.php in GiveWP – Donation Plugin and Fundraising Platform 4.15.0, at vendor/tecnickcom/tcpdf/tcpdf.php

24,900 lines 889.7 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.11.3
5 // Begin : 2002-08-03
6 // Last Update : 2026-03-03
7 // Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
8 // License : GNU-LGPL v3 (https://www.gnu.org/copyleft/lesser.html)
9 // -------------------------------------------------------------------
10 // Copyright (C) 2002-2026 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.11.3
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.11.3
132 * @author Nicola Asuni - info@tecnick.com
133 * @IgnoreAnnotation("protected")
134 * @IgnoreAnnotation("public")
135 * @IgnoreAnnotation("pre")
136 */
137 class TCPDF {
138
139 // Protected properties
140
141 /**
142 * Current page number.
143 * @protected
144 */
145 protected $page;
146
147 /**
148 * Current object number.
149 * @protected
150 */
151 protected $n;
152
153 /**
154 * Array of object offsets.
155 * @protected
156 */
157 protected $offsets = array();
158
159 /**
160 * Array of object IDs for each page.
161 * @protected
162 */
163 protected $pageobjects = array();
164
165 /**
166 * Buffer holding in-memory PDF.
167 * @protected
168 */
169 protected $buffer;
170
171 /**
172 * Array containing pages.
173 * @protected
174 */
175 protected $pages = array();
176
177 /**
178 * Current document state.
179 * @protected
180 */
181 protected $state;
182
183 /**
184 * Compression flag.
185 * @protected
186 */
187 protected $compress;
188
189 /**
190 * Current page orientation (P = Portrait, L = Landscape).
191 * @protected
192 */
193 protected $CurOrientation;
194
195 /**
196 * Page dimensions.
197 * @protected
198 */
199 protected $pagedim = array();
200
201 /**
202 * Scale factor (number of points in user unit).
203 * @protected
204 */
205 protected $k;
206
207 /**
208 * Width of page format in points.
209 * @protected
210 */
211 protected $fwPt;
212
213 /**
214 * Height of page format in points.
215 * @protected
216 */
217 protected $fhPt;
218
219 /**
220 * Current width of page in points.
221 * @protected
222 */
223 protected $wPt;
224
225 /**
226 * Current height of page in points.
227 * @protected
228 */
229 protected $hPt;
230
231 /**
232 * Current width of page in user unit.
233 * @protected
234 */
235 protected $w;
236
237 /**
238 * Current height of page in user unit.
239 * @protected
240 */
241 protected $h;
242
243 /**
244 * Left margin.
245 * @protected
246 */
247 protected $lMargin;
248
249 /**
250 * Right margin.
251 * @protected
252 */
253 protected $rMargin;
254
255 /**
256 * Cell left margin (used by regions).
257 * @protected
258 */
259 protected $clMargin;
260
261 /**
262 * Cell right margin (used by regions).
263 * @protected
264 */
265 protected $crMargin;
266
267 /**
268 * Top margin.
269 * @protected
270 */
271 protected $tMargin;
272
273 /**
274 * Page break margin.
275 * @protected
276 */
277 protected $bMargin;
278
279 /**
280 * Array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
281 * @since 5.9.000 (2010-10-03)
282 * @protected
283 */
284 protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
285
286 /**
287 * Array of cell margins ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
288 * @since 5.9.000 (2010-10-04)
289 * @protected
290 */
291 protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
292
293 /**
294 * Current horizontal position in user unit for cell positioning.
295 * @protected
296 */
297 protected $x;
298
299 /**
300 * Current vertical position in user unit for cell positioning.
301 * @protected
302 */
303 protected $y;
304
305 /**
306 * Height of last cell printed.
307 * @protected
308 */
309 protected $lasth;
310
311 /**
312 * Line width in user unit.
313 * @protected
314 */
315 protected $LineWidth;
316
317 /**
318 * Array of standard font names.
319 * @protected
320 */
321 protected $CoreFonts;
322
323 /**
324 * Array of used fonts.
325 * @protected
326 */
327 protected $fonts = array();
328
329 /**
330 * Array of font files.
331 * @protected
332 */
333 protected $FontFiles = array();
334
335 /**
336 * Array of encoding differences.
337 * @protected
338 */
339 protected $diffs = array();
340
341 /**
342 * Array of used images.
343 * @protected
344 */
345 protected $images = array();
346
347 /**
348 * Depth of the svg tag, to keep track if the svg tag is a subtag or the root tag.
349 * @protected
350 */
351 protected $svg_tag_depth = 0;
352
353 /**
354 * Array of Annotations in pages.
355 * @protected
356 */
357 protected $PageAnnots = array();
358
359 /**
360 * Array of internal links.
361 * @protected
362 */
363 protected $links = array();
364
365 /**
366 * Current font family.
367 * @protected
368 */
369 protected $FontFamily;
370
371 /**
372 * Current font style.
373 * @protected
374 */
375 protected $FontStyle;
376
377 /**
378 * Current font ascent (distance between font top and baseline).
379 * @protected
380 * @since 2.8.000 (2007-03-29)
381 */
382 protected $FontAscent;
383
384 /**
385 * Current font descent (distance between font bottom and baseline).
386 * @protected
387 * @since 2.8.000 (2007-03-29)
388 */
389 protected $FontDescent;
390
391 /**
392 * Underlining flag.
393 * @protected
394 */
395 protected $underline;
396
397 /**
398 * Overlining flag.
399 * @protected
400 */
401 protected $overline;
402
403 /**
404 * Current font info.
405 * @protected
406 */
407 protected $CurrentFont;
408
409 /**
410 * Current font size in points.
411 * @protected
412 */
413 protected $FontSizePt;
414
415 /**
416 * Current font size in user unit.
417 * @protected
418 */
419 protected $FontSize;
420
421 /**
422 * Commands for drawing color.
423 * @protected
424 */
425 protected $DrawColor;
426
427 /**
428 * Commands for filling color.
429 * @protected
430 */
431 protected $FillColor;
432
433 /**
434 * Commands for text color.
435 * @protected
436 */
437 protected $TextColor;
438
439 /**
440 * Indicates whether fill and text colors are different.
441 * @protected
442 */
443 protected $ColorFlag;
444
445 /**
446 * Automatic page breaking.
447 * @protected
448 */
449 protected $AutoPageBreak;
450
451 /**
452 * Threshold used to trigger page breaks.
453 * @protected
454 */
455 protected $PageBreakTrigger;
456
457 /**
458 * Flag set when processing page header.
459 * @protected
460 */
461 protected $InHeader = false;
462
463 /**
464 * Flag set when processing page footer.
465 * @protected
466 */
467 protected $InFooter = false;
468
469 /**
470 * Zoom display mode.
471 * @protected
472 */
473 protected $ZoomMode;
474
475 /**
476 * Layout display mode.
477 * @protected
478 */
479 protected $LayoutMode;
480
481 /**
482 * If true set the document information dictionary in Unicode.
483 * @protected
484 */
485 protected $docinfounicode = true;
486
487 /**
488 * Document title.
489 * @protected
490 */
491 protected $title = '';
492
493 /**
494 * Document subject.
495 * @protected
496 */
497 protected $subject = '';
498
499 /**
500 * Document author.
501 * @protected
502 */
503 protected $author = '';
504
505 /**
506 * Document keywords.
507 * @protected
508 */
509 protected $keywords = '';
510
511 /**
512 * Document creator.
513 * @protected
514 */
515 protected $creator = '';
516
517 /**
518 * Starting page number.
519 * @protected
520 */
521 protected $starting_page_number = 1;
522
523 /**
524 * The right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image.
525 * @since 2002-07-31
526 * @author Nicola Asuni
527 * @protected
528 */
529 protected $img_rb_x;
530
531 /**
532 * The right-bottom corner Y coordinate of last inserted image.
533 * @since 2002-07-31
534 * @author Nicola Asuni
535 * @protected
536 */
537 protected $img_rb_y;
538
539 /**
540 * Adjusting factor to convert pixels to user units.
541 * @since 2004-06-14
542 * @author Nicola Asuni
543 * @protected
544 */
545 protected $imgscale = 1;
546
547 /**
548 * Boolean flag set to true when the input text is unicode (require unicode fonts).
549 * @since 2005-01-02
550 * @author Nicola Asuni
551 * @protected
552 */
553 protected $isunicode = false;
554
555 /**
556 * PDF version.
557 * @since 1.5.3
558 * @protected
559 */
560 protected $PDFVersion = '1.7';
561
562 /**
563 * ID of the stored default header template (-1 = not set).
564 * @protected
565 */
566 protected $header_xobjid = false;
567
568 /**
569 * If true reset the Header Xobject template at each page
570 * @protected
571 */
572 protected $header_xobj_autoreset = false;
573
574 /**
575 * Minimum distance between header and top page margin.
576 * @protected
577 * @var float
578 */
579 protected $header_margin;
580
581 /**
582 * Minimum distance between footer and bottom page margin.
583 * @protected
584 * @var float
585 */
586 protected $footer_margin;
587
588 /**
589 * Original left margin value.
590 * @protected
591 * @since 1.53.0.TC013
592 */
593 protected $original_lMargin;
594
595 /**
596 * Original right margin value.
597 * @protected
598 * @since 1.53.0.TC013
599 */
600 protected $original_rMargin;
601
602 /**
603 * Default font used on page header.
604 * @protected
605 * @var array<int,string|float|null>
606 * @phpstan-var array{0: string, 1: string, 2: float|null}
607 */
608 protected $header_font;
609
610 /**
611 * Default font used on page footer.
612 * @protected
613 * @var array<int,string|float|null>
614 * @phpstan-var array{0: string, 1: string, 2: float|null}
615 */
616 protected $footer_font;
617
618 /**
619 * Language templates.
620 * @protected
621 */
622 protected $l;
623
624 /**
625 * Barcode to print on page footer (only if set).
626 * @protected
627 */
628 protected $barcode = false;
629
630 /**
631 * Boolean flag to print/hide page header.
632 * @protected
633 */
634 protected $print_header = true;
635
636 /**
637 * Boolean flag to print/hide page footer.
638 * @protected
639 */
640 protected $print_footer = true;
641
642 /**
643 * Header image logo.
644 * @protected
645 */
646 protected $header_logo = '';
647
648 /**
649 * Width of header image logo in user units.
650 * @protected
651 */
652 protected $header_logo_width = 30;
653
654 /**
655 * Title to be printed on default page header.
656 * @protected
657 */
658 protected $header_title = '';
659
660 /**
661 * String to print on page header after title.
662 * @protected
663 */
664 protected $header_string = '';
665
666 /**
667 * Color for header text (RGB array).
668 * @since 5.9.174 (2012-07-25)
669 * @protected
670 * @var int[]
671 * @phpstan-var array{0: int, 1: int, 2: int}
672 */
673 protected $header_text_color = array(0,0,0);
674
675 /**
676 * Color for header line (RGB array).
677 * @since 5.9.174 (2012-07-25)
678 * @protected
679 * @var int[]
680 * @phpstan-var array{0: int, 1: int, 2: int}
681 */
682 protected $header_line_color = array(0,0,0);
683
684 /**
685 * Color for footer text (RGB array).
686 * @since 5.9.174 (2012-07-25)
687 * @protected
688 * @var int[]
689 * @phpstan-var array{0: int, 1: int, 2: int}
690 */
691 protected $footer_text_color = array(0,0,0);
692
693 /**
694 * Color for footer line (RGB array).
695 * @since 5.9.174 (2012-07-25)
696 * @protected
697 * @var int[]
698 * @phpstan-var array{0: int, 1: int, 2: int}
699 */
700 protected $footer_line_color = array(0,0,0);
701
702 /**
703 * Text shadow data array.
704 * @since 5.9.174 (2012-07-25)
705 * @protected
706 */
707 protected $txtshadow = array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal');
708
709 /**
710 * Default number of columns for html table.
711 * @protected
712 */
713 protected $default_table_columns = 4;
714
715 // variables for html parser
716
717 /**
718 * HTML PARSER: array to store current link and rendering styles.
719 * @protected
720 */
721 protected $HREF = array();
722
723 /**
724 * List of available fonts on filesystem.
725 * @protected
726 */
727 protected $fontlist = array();
728
729 /**
730 * Current foreground color.
731 * @protected
732 */
733 protected $fgcolor;
734
735 /**
736 * HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
737 * @protected
738 */
739 protected $listordered = array();
740
741 /**
742 * HTML PARSER: array count list items on nested lists.
743 * @protected
744 */
745 protected $listcount = array();
746
747 /**
748 * HTML PARSER: current list nesting level.
749 * @protected
750 */
751 protected $listnum = 0;
752
753 /**
754 * HTML PARSER: indent amount for lists.
755 * @protected
756 */
757 protected $listindent = 0;
758
759 /**
760 * HTML PARSER: current list indententation level.
761 * @protected
762 */
763 protected $listindentlevel = 0;
764
765 /**
766 * Current background color.
767 * @protected
768 */
769 protected $bgcolor;
770
771 /**
772 * Temporary font size in points.
773 * @protected
774 */
775 protected $tempfontsize = 10;
776
777 /**
778 * Spacer string for LI tags.
779 * @protected
780 */
781 protected $lispacer = '';
782
783 /**
784 * Default encoding.
785 * @protected
786 * @since 1.53.0.TC010
787 */
788 protected $encoding = 'UTF-8';
789
790 /**
791 * Boolean flag to indicate if the document language is Right-To-Left.
792 * @protected
793 * @since 2.0.000
794 */
795 protected $rtl = false;
796
797 /**
798 * Boolean flag used to force RTL or LTR string direction.
799 * @protected
800 * @since 2.0.000
801 */
802 protected $tmprtl = false;
803
804 // --- Variables used for document encryption:
805
806 /**
807 * IBoolean flag indicating whether document is protected.
808 * @protected
809 * @since 2.0.000 (2008-01-02)
810 */
811 protected $encrypted;
812
813 /**
814 * Array containing encryption settings.
815 * @protected
816 * @since 5.0.005 (2010-05-11)
817 */
818 protected $encryptdata = array();
819
820 /**
821 * Last RC4 key encrypted (cached for optimisation).
822 * @protected
823 * @since 2.0.000 (2008-01-02)
824 */
825 protected $last_enc_key;
826
827 /**
828 * Last RC4 computed key.
829 * @protected
830 * @since 2.0.000 (2008-01-02)
831 */
832 protected $last_enc_key_c;
833
834 /**
835 * File ID (used on document trailer).
836 * @protected
837 * @since 5.0.005 (2010-05-12)
838 */
839 protected $file_id;
840
841 /**
842 * Internal secret used to encrypt data.
843 * @protected
844 * @since 6.7.5 (2024-03-21)
845 */
846 protected $hash_key;
847
848 // --- bookmark ---
849
850 /**
851 * Outlines for bookmark.
852 * @protected
853 * @since 2.1.002 (2008-02-12)
854 */
855 protected $outlines = array();
856
857 /**
858 * Outline root for bookmark.
859 * @protected
860 * @since 2.1.002 (2008-02-12)
861 */
862 protected $OutlineRoot;
863
864 // --- javascript and form ---
865
866 /**
867 * Javascript code.
868 * @protected
869 * @since 2.1.002 (2008-02-12)
870 */
871 protected $javascript = '';
872
873 /**
874 * Javascript counter.
875 * @protected
876 * @since 2.1.002 (2008-02-12)
877 */
878 protected $n_js;
879
880 /**
881 * line through state
882 * @protected
883 * @since 2.8.000 (2008-03-19)
884 */
885 protected $linethrough;
886
887 /**
888 * Array with additional document-wide usage rights for the document.
889 * @protected
890 * @since 5.8.014 (2010-08-23)
891 */
892 protected $ur = array();
893
894 /**
895 * DPI (Dot Per Inch) Document Resolution (do not change).
896 * @protected
897 * @since 3.0.000 (2008-03-27)
898 */
899 protected $dpi = 72;
900
901 /**
902 * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
903 * @protected
904 * @since 3.0.000 (2008-03-27)
905 */
906 protected $newpagegroup = array();
907
908 /**
909 * Array that contains the number of pages in each page group.
910 * @protected
911 * @since 3.0.000 (2008-03-27)
912 */
913 protected $pagegroups = array();
914
915 /**
916 * Current page group number.
917 * @protected
918 * @since 3.0.000 (2008-03-27)
919 */
920 protected $currpagegroup = 0;
921
922 /**
923 * Array of transparency objects and parameters.
924 * @protected
925 * @since 3.0.000 (2008-03-27)
926 */
927 protected $extgstates;
928
929 /**
930 * Set the default JPEG compression quality (1-100).
931 * @protected
932 * @since 3.0.000 (2008-03-27)
933 */
934 protected $jpeg_quality;
935
936 /**
937 * Default cell height ratio.
938 * @protected
939 * @since 3.0.014 (2008-05-23)
940 * @var float
941 */
942 protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
943
944 /**
945 * PDF viewer preferences.
946 * @protected
947 * @since 3.1.000 (2008-06-09)
948 */
949 protected $viewer_preferences;
950
951 /**
952 * A name object specifying how the document should be displayed when opened.
953 * @protected
954 * @since 3.1.000 (2008-06-09)
955 */
956 protected $PageMode;
957
958 /**
959 * Array for storing gradient information.
960 * @protected
961 * @since 3.1.000 (2008-06-09)
962 */
963 protected $gradients = array();
964
965 /**
966 * Array used to store positions inside the pages buffer (keys are the page numbers).
967 * @protected
968 * @since 3.2.000 (2008-06-26)
969 */
970 protected $intmrk = array();
971
972 /**
973 * Array used to store positions inside the pages buffer (keys are the page numbers).
974 * @protected
975 * @since 5.7.000 (2010-08-03)
976 */
977 protected $bordermrk = array();
978
979 /**
980 * Array used to store page positions to track empty pages (keys are the page numbers).
981 * @protected
982 * @since 5.8.007 (2010-08-18)
983 */
984 protected $emptypagemrk = array();
985
986 /**
987 * Array used to store content positions inside the pages buffer (keys are the page numbers).
988 * @protected
989 * @since 4.6.021 (2009-07-20)
990 */
991 protected $cntmrk = array();
992
993 /**
994 * Array used to store footer positions of each page.
995 * @protected
996 * @since 3.2.000 (2008-07-01)
997 */
998 protected $footerpos = array();
999
1000 /**
1001 * Array used to store footer length of each page.
1002 * @protected
1003 * @since 4.0.014 (2008-07-29)
1004 */
1005 protected $footerlen = array();
1006
1007 /**
1008 * Boolean flag to indicate if a new line is created.
1009 * @protected
1010 * @since 3.2.000 (2008-07-01)
1011 */
1012 protected $newline = true;
1013
1014 /**
1015 * End position of the latest inserted line.
1016 * @protected
1017 * @since 3.2.000 (2008-07-01)
1018 */
1019 protected $endlinex = 0;
1020
1021 /**
1022 * PDF string for width value of the last line.
1023 * @protected
1024 * @since 4.0.006 (2008-07-16)
1025 */
1026 protected $linestyleWidth = '';
1027
1028 /**
1029 * PDF string for CAP value of the last line.
1030 * @protected
1031 * @since 4.0.006 (2008-07-16)
1032 */
1033 protected $linestyleCap = '0 J';
1034
1035 /**
1036 * PDF string for join value of the last line.
1037 * @protected
1038 * @since 4.0.006 (2008-07-16)
1039 */
1040 protected $linestyleJoin = '0 j';
1041
1042 /**
1043 * PDF string for dash value of the last line.
1044 * @protected
1045 * @since 4.0.006 (2008-07-16)
1046 */
1047 protected $linestyleDash = '[] 0 d';
1048
1049 /**
1050 * Boolean flag to indicate if marked-content sequence is open.
1051 * @protected
1052 * @since 4.0.013 (2008-07-28)
1053 */
1054 protected $openMarkedContent = false;
1055
1056 /**
1057 * Count the latest inserted vertical spaces on HTML.
1058 * @protected
1059 * @since 4.0.021 (2008-08-24)
1060 */
1061 protected $htmlvspace = 0;
1062
1063 /**
1064 * Array of Spot colors.
1065 * @protected
1066 * @since 4.0.024 (2008-09-12)
1067 */
1068 protected $spot_colors = array();
1069
1070 /**
1071 * Symbol used for HTML unordered list items.
1072 * @protected
1073 * @since 4.0.028 (2008-09-26)
1074 */
1075 protected $lisymbol = '';
1076
1077 /**
1078 * String used to mark the beginning and end of EPS image blocks.
1079 * @protected
1080 * @since 4.1.000 (2008-10-18)
1081 */
1082 protected $epsmarker = 'x#!#EPS#!#x';
1083
1084 /**
1085 * Array of transformation matrix.
1086 * @protected
1087 * @since 4.2.000 (2008-10-29)
1088 */
1089 protected $transfmatrix = array();
1090
1091 /**
1092 * Current key for transformation matrix.
1093 * @protected
1094 * @since 4.8.005 (2009-09-17)
1095 */
1096 protected $transfmatrix_key = 0;
1097
1098 /**
1099 * Booklet mode for double-sided pages.
1100 * @protected
1101 * @since 4.2.000 (2008-10-29)
1102 */
1103 protected $booklet = false;
1104
1105 /**
1106 * Epsilon value used for float calculations.
1107 * @protected
1108 * @since 4.2.000 (2008-10-29)
1109 */
1110 protected $feps = 0.005;
1111
1112 /**
1113 * Array used for custom vertical spaces for HTML tags.
1114 * @protected
1115 * @since 4.2.001 (2008-10-30)
1116 */
1117 protected $tagvspaces = array();
1118
1119 /**
1120 * HTML PARSER: custom indent amount for lists. Negative value means disabled.
1121 * @protected
1122 * @since 4.2.007 (2008-11-12)
1123 */
1124 protected $customlistindent = -1;
1125
1126 /**
1127 * Boolean flag to indicate if the border of the cell sides that cross the page should be removed.
1128 * @protected
1129 * @since 4.2.010 (2008-11-14)
1130 */
1131 protected $opencell = true;
1132
1133 /**
1134 * Array of files to embed.
1135 * @protected
1136 * @since 4.4.000 (2008-12-07)
1137 */
1138 protected $embeddedfiles = array();
1139
1140 /**
1141 * Boolean flag to indicate if we are inside a PRE tag.
1142 * @protected
1143 * @since 4.4.001 (2008-12-08)
1144 */
1145 protected $premode = false;
1146
1147 /**
1148 * Array used to store positions of graphics transformation blocks inside the page buffer.
1149 * keys are the page numbers
1150 * @protected
1151 * @since 4.4.002 (2008-12-09)
1152 */
1153 protected $transfmrk = array();
1154
1155 /**
1156 * Default color for html links.
1157 * @protected
1158 * @since 4.4.003 (2008-12-09)
1159 */
1160 protected $htmlLinkColorArray = array(0, 0, 255);
1161
1162 /**
1163 * Default font style to add to html links.
1164 * @protected
1165 * @since 4.4.003 (2008-12-09)
1166 */
1167 protected $htmlLinkFontStyle = 'U';
1168
1169 /**
1170 * Counts the number of pages.
1171 * @protected
1172 * @since 4.5.000 (2008-12-31)
1173 */
1174 protected $numpages = 0;
1175
1176 /**
1177 * Array containing page lengths in bytes.
1178 * @protected
1179 * @since 4.5.000 (2008-12-31)
1180 */
1181 protected $pagelen = array();
1182
1183 /**
1184 * Counts the number of pages.
1185 * @protected
1186 * @since 4.5.000 (2008-12-31)
1187 */
1188 protected $numimages = 0;
1189
1190 /**
1191 * Store the image keys.
1192 * @protected
1193 * @since 4.5.000 (2008-12-31)
1194 */
1195 protected $imagekeys = array();
1196
1197 /**
1198 * Length of the buffer in bytes.
1199 * @protected
1200 * @since 4.5.000 (2008-12-31)
1201 */
1202 protected $bufferlen = 0;
1203
1204 /**
1205 * Counts the number of fonts.
1206 * @protected
1207 * @since 4.5.000 (2009-01-02)
1208 */
1209 protected $numfonts = 0;
1210
1211 /**
1212 * Store the font keys.
1213 * @protected
1214 * @since 4.5.000 (2009-01-02)
1215 */
1216 protected $fontkeys = array();
1217
1218 /**
1219 * Store the font object IDs.
1220 * @protected
1221 * @since 4.8.001 (2009-09-09)
1222 */
1223 protected $font_obj_ids = array();
1224
1225 /**
1226 * Store the fage status (true when opened, false when closed).
1227 * @protected
1228 * @since 4.5.000 (2009-01-02)
1229 */
1230 protected $pageopen = array();
1231
1232 /**
1233 * Default monospace font.
1234 * @protected
1235 * @since 4.5.025 (2009-03-10)
1236 */
1237 protected $default_monospaced_font = 'courier';
1238
1239 /**
1240 * Cloned copy of the current class object.
1241 * @protected
1242 * @since 4.5.029 (2009-03-19)
1243 */
1244 protected $objcopy;
1245
1246 /**
1247 * Array used to store the lengths of cache files.
1248 * @protected
1249 * @since 4.5.029 (2009-03-19)
1250 */
1251 protected $cache_file_length = array();
1252
1253 /**
1254 * Table header content to be repeated on each new page.
1255 * @protected
1256 * @since 4.5.030 (2009-03-20)
1257 */
1258 protected $thead = '';
1259
1260 /**
1261 * Margins used for table header.
1262 * @protected
1263 * @since 4.5.030 (2009-03-20)
1264 */
1265 protected $theadMargins = array();
1266
1267 /**
1268 * Boolean flag to enable document digital signature.
1269 * @protected
1270 * @since 4.6.005 (2009-04-24)
1271 */
1272 protected $sign = false;
1273
1274 /**
1275 * Digital signature data.
1276 * @protected
1277 * @since 4.6.005 (2009-04-24)
1278 */
1279 protected $signature_data = array();
1280
1281 /**
1282 * Digital signature max length.
1283 * @protected
1284 * @since 4.6.005 (2009-04-24)
1285 */
1286 protected $signature_max_length = 11742;
1287
1288 /**
1289 * Data for digital signature appearance.
1290 * @protected
1291 * @since 5.3.011 (2010-06-16)
1292 */
1293 protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1294
1295 /**
1296 * Array of empty digital signature appearances.
1297 * @protected
1298 * @since 5.9.101 (2011-07-06)
1299 */
1300 protected $empty_signature_appearance = array();
1301
1302 /**
1303 * Boolean flag to enable document timestamping with TSA.
1304 * @protected
1305 * @since 6.0.085 (2014-06-19)
1306 */
1307 protected $tsa_timestamp = false;
1308
1309 /**
1310 * Timestamping data.
1311 * @protected
1312 * @since 6.0.085 (2014-06-19)
1313 */
1314 protected $tsa_data = array();
1315
1316 /**
1317 * Regular expression used to find blank characters (required for word-wrapping).
1318 * @protected
1319 * @since 4.6.006 (2009-04-28)
1320 */
1321 protected $re_spaces = '/[^\S\xa0]/';
1322
1323 /**
1324 * Array of $re_spaces parts.
1325 * @protected
1326 * @since 5.5.011 (2010-07-09)
1327 */
1328 protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1329
1330 /**
1331 * Digital signature object ID.
1332 * @protected
1333 * @since 4.6.022 (2009-06-23)
1334 */
1335 protected $sig_obj_id = 0;
1336
1337 /**
1338 * ID of page objects.
1339 * @protected
1340 * @since 4.7.000 (2009-08-29)
1341 */
1342 protected $page_obj_id = array();
1343
1344 /**
1345 * List of form annotations IDs.
1346 * @protected
1347 * @since 4.8.000 (2009-09-07)
1348 */
1349 protected $form_obj_id = array();
1350
1351 /**
1352 * Deafult Javascript field properties. Possible values are described on official Javascript for Acrobat API reference. Annotation options can be directly specified using the 'aopt' entry.
1353 * @protected
1354 * @since 4.8.000 (2009-09-07)
1355 */
1356 protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1357
1358 /**
1359 * Javascript objects array.
1360 * @protected
1361 * @since 4.8.000 (2009-09-07)
1362 */
1363 protected $js_objects = array();
1364
1365 /**
1366 * Current form action (used during XHTML rendering).
1367 * @protected
1368 * @since 4.8.000 (2009-09-07)
1369 */
1370 protected $form_action = '';
1371
1372 /**
1373 * Current form encryption type (used during XHTML rendering).
1374 * @protected
1375 * @since 4.8.000 (2009-09-07)
1376 */
1377 protected $form_enctype = 'application/x-www-form-urlencoded';
1378
1379 /**
1380 * Current method to submit forms.
1381 * @protected
1382 * @since 4.8.000 (2009-09-07)
1383 */
1384 protected $form_mode = 'post';
1385
1386 /**
1387 * List of fonts used on form fields (fontname => fontkey).
1388 * @protected
1389 * @since 4.8.001 (2009-09-09)
1390 */
1391 protected $annotation_fonts = array();
1392
1393 /**
1394 * List of radio buttons parent objects.
1395 * @protected
1396 * @since 4.8.001 (2009-09-09)
1397 */
1398 protected $radiobutton_groups = array();
1399
1400 /**
1401 * List of radio group objects IDs.
1402 * @protected
1403 * @since 4.8.001 (2009-09-09)
1404 */
1405 protected $radio_groups = array();
1406
1407 /**
1408 * Text indentation value (used for text-indent CSS attribute).
1409 * @protected
1410 * @since 4.8.006 (2009-09-23)
1411 */
1412 protected $textindent = 0;
1413
1414 /**
1415 * Store page number when startTransaction() is called.
1416 * @protected
1417 * @since 4.8.006 (2009-09-23)
1418 */
1419 protected $start_transaction_page = 0;
1420
1421 /**
1422 * Store Y position when startTransaction() is called.
1423 * @protected
1424 * @since 4.9.001 (2010-03-28)
1425 */
1426 protected $start_transaction_y = 0;
1427
1428 /**
1429 * True when we are printing the thead section on a new page.
1430 * @protected
1431 * @since 4.8.027 (2010-01-25)
1432 */
1433 protected $inthead = false;
1434
1435 /**
1436 * Array of column measures (width, space, starting Y position).
1437 * @protected
1438 * @since 4.9.001 (2010-03-28)
1439 */
1440 protected $columns = array();
1441
1442 /**
1443 * Number of colums.
1444 * @protected
1445 * @since 4.9.001 (2010-03-28)
1446 */
1447 protected $num_columns = 1;
1448
1449 /**
1450 * Current column number.
1451 * @protected
1452 * @since 4.9.001 (2010-03-28)
1453 */
1454 protected $current_column = 0;
1455
1456 /**
1457 * Starting page for columns.
1458 * @protected
1459 * @since 4.9.001 (2010-03-28)
1460 */
1461 protected $column_start_page = 0;
1462
1463 /**
1464 * Maximum page and column selected.
1465 * @protected
1466 * @since 5.8.000 (2010-08-11)
1467 */
1468 protected $maxselcol = array('page' => 0, 'column' => 0);
1469
1470 /**
1471 * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
1472 * @protected
1473 * @since 5.8.000 (2010-08-11)
1474 */
1475 protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1476
1477 /**
1478 * Text rendering mode: 0 = Fill text; 1 = Stroke text; 2 = Fill, then stroke text; 3 = Neither fill nor stroke text (invisible); 4 = Fill text and add to path for clipping; 5 = Stroke text and add to path for clipping; 6 = Fill, then stroke text and add to path for clipping; 7 = Add text to path for clipping.
1479 * @protected
1480 * @since 4.9.008 (2010-04-03)
1481 */
1482 protected $textrendermode = 0;
1483
1484 /**
1485 * Text stroke width in doc units.
1486 * @protected
1487 * @since 4.9.008 (2010-04-03)
1488 */
1489 protected $textstrokewidth = 0;
1490
1491 /**
1492 * Current stroke color.
1493 * @protected
1494 * @since 4.9.008 (2010-04-03)
1495 */
1496 protected $strokecolor;
1497
1498 /**
1499 * Default unit of measure for document.
1500 * @protected
1501 * @since 5.0.000 (2010-04-22)
1502 */
1503 protected $pdfunit = 'mm';
1504
1505 /**
1506 * Boolean flag true when we are on TOC (Table Of Content) page.
1507 * @protected
1508 */
1509 protected $tocpage = false;
1510
1511 /**
1512 * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1513 * @protected
1514 * @since 5.0.000 (2010-04-26)
1515 */
1516 protected $rasterize_vector_images = false;
1517
1518 /**
1519 * Boolean flag: if true enables font subsetting by default.
1520 * @protected
1521 * @since 5.3.002 (2010-06-07)
1522 */
1523 protected $font_subsetting = true;
1524
1525 /**
1526 * Array of default graphic settings.
1527 * @protected
1528 * @since 5.5.008 (2010-07-02)
1529 */
1530 protected $default_graphic_vars = array();
1531
1532 /**
1533 * Array of XObjects.
1534 * @protected
1535 * @since 5.8.014 (2010-08-23)
1536 */
1537 protected $xobjects = array();
1538
1539 /**
1540 * Boolean value true when we are inside an XObject.
1541 * @protected
1542 * @since 5.8.017 (2010-08-24)
1543 */
1544 protected $inxobj = false;
1545
1546 /**
1547 * Current XObject ID.
1548 * @protected
1549 * @since 5.8.017 (2010-08-24)
1550 */
1551 protected $xobjid = '';
1552
1553 /**
1554 * Percentage of character stretching.
1555 * @protected
1556 * @since 5.9.000 (2010-09-29)
1557 */
1558 protected $font_stretching = 100;
1559
1560 /**
1561 * Increases or decreases the space between characters in a text by the specified amount (tracking).
1562 * @protected
1563 * @since 5.9.000 (2010-09-29)
1564 */
1565 protected $font_spacing = 0;
1566
1567 /**
1568 * Array of no-write regions.
1569 * ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right)
1570 * @protected
1571 * @since 5.9.003 (2010-10-14)
1572 */
1573 protected $page_regions = array();
1574
1575 /**
1576 * Boolean value true when page region check is active.
1577 * @protected
1578 */
1579 protected $check_page_regions = true;
1580
1581 /**
1582 * Array of PDF layers data.
1583 * @protected
1584 * @since 5.9.102 (2011-07-13)
1585 */
1586 protected $pdflayers = array();
1587
1588 /**
1589 * A dictionary of names and corresponding destinations (Dests key on document Catalog).
1590 * @protected
1591 * @since 5.9.097 (2011-06-23)
1592 */
1593 protected $dests = array();
1594
1595 /**
1596 * Object ID for Named Destinations
1597 * @protected
1598 * @since 5.9.097 (2011-06-23)
1599 */
1600 protected $n_dests;
1601
1602 /**
1603 * Embedded Files Names
1604 * @protected
1605 * @since 5.9.204 (2013-01-23)
1606 */
1607 protected $efnames = array();
1608
1609 /**
1610 * Directory used for the last SVG image.
1611 * @protected
1612 * @since 5.0.000 (2010-05-05)
1613 */
1614 protected $svgdir = '';
1615
1616 /**
1617 * Deafult unit of measure for SVG.
1618 * @protected
1619 * @since 5.0.000 (2010-05-02)
1620 */
1621 protected $svgunit = 'px';
1622
1623 /**
1624 * Array of SVG gradients.
1625 * @protected
1626 * @since 5.0.000 (2010-05-02)
1627 */
1628 protected $svggradients = array();
1629
1630 /**
1631 * ID of last SVG gradient.
1632 * @protected
1633 * @since 5.0.000 (2010-05-02)
1634 */
1635 protected $svggradientid = 0;
1636
1637 /**
1638 * Boolean value true when in SVG defs group.
1639 * @protected
1640 * @since 5.0.000 (2010-05-02)
1641 */
1642 protected $svgdefsmode = false;
1643
1644 /**
1645 * Array of SVG defs.
1646 * @protected
1647 * @since 5.0.000 (2010-05-02)
1648 */
1649 protected $svgdefs = array();
1650
1651 /**
1652 * Boolean value true when in SVG clipPath tag.
1653 * @protected
1654 * @since 5.0.000 (2010-04-26)
1655 */
1656 protected $svgclipmode = false;
1657
1658 /**
1659 * Array of SVG clipPath commands.
1660 * @protected
1661 * @since 5.0.000 (2010-05-02)
1662 */
1663 protected $svgclippaths = array();
1664
1665 /**
1666 * Array of SVG clipPath tranformation matrix.
1667 * @protected
1668 * @since 5.8.022 (2010-08-31)
1669 */
1670 protected $svgcliptm = array();
1671
1672 /**
1673 * ID of last SVG clipPath.
1674 * @protected
1675 * @since 5.0.000 (2010-05-02)
1676 */
1677 protected $svgclipid = 0;
1678
1679 /**
1680 * SVG text.
1681 * @protected
1682 * @since 5.0.000 (2010-05-02)
1683 */
1684 protected $svgtext = '';
1685
1686 /**
1687 * SVG text properties.
1688 * @protected
1689 * @since 5.8.013 (2010-08-23)
1690 */
1691 protected $svgtextmode = array();
1692
1693 /**
1694 * Array of SVG properties.
1695 * @protected
1696 * @since 5.0.000 (2010-05-02)
1697 */
1698 protected $svgstyles = array(array(
1699 'alignment-baseline' => 'auto',
1700 'baseline-shift' => 'baseline',
1701 'clip' => 'auto',
1702 'clip-path' => 'none',
1703 'clip-rule' => 'nonzero',
1704 'color' => 'black',
1705 'color-interpolation' => 'sRGB',
1706 'color-interpolation-filters' => 'linearRGB',
1707 'color-profile' => 'auto',
1708 'color-rendering' => 'auto',
1709 'cursor' => 'auto',
1710 'direction' => 'ltr',
1711 'display' => 'inline',
1712 'dominant-baseline' => 'auto',
1713 'enable-background' => 'accumulate',
1714 'fill' => 'black',
1715 'fill-opacity' => 1,
1716 'fill-rule' => 'nonzero',
1717 'filter' => 'none',
1718 'flood-color' => 'black',
1719 'flood-opacity' => 1,
1720 'font' => '',
1721 'font-family' => 'helvetica',
1722 'font-size' => 'medium',
1723 'font-size-adjust' => 'none',
1724 'font-stretch' => 'normal',
1725 'font-style' => 'normal',
1726 'font-variant' => 'normal',
1727 'font-weight' => 'normal',
1728 'glyph-orientation-horizontal' => '0deg',
1729 'glyph-orientation-vertical' => 'auto',
1730 'image-rendering' => 'auto',
1731 'kerning' => 'auto',
1732 'letter-spacing' => 'normal',
1733 'lighting-color' => 'white',
1734 'marker' => '',
1735 'marker-end' => 'none',
1736 'marker-mid' => 'none',
1737 'marker-start' => 'none',
1738 'mask' => 'none',
1739 'opacity' => 1,
1740 'overflow' => 'auto',
1741 'pointer-events' => 'visiblePainted',
1742 'shape-rendering' => 'auto',
1743 'stop-color' => 'black',
1744 'stop-opacity' => 1,
1745 'stroke' => 'none',
1746 'stroke-dasharray' => 'none',
1747 'stroke-dashoffset' => 0,
1748 'stroke-linecap' => 'butt',
1749 'stroke-linejoin' => 'miter',
1750 'stroke-miterlimit' => 4,
1751 'stroke-opacity' => 1,
1752 'stroke-width' => 1,
1753 'text-anchor' => 'start',
1754 'text-decoration' => 'none',
1755 'text-rendering' => 'auto',
1756 'unicode-bidi' => 'normal',
1757 'visibility' => 'visible',
1758 'word-spacing' => 'normal',
1759 'writing-mode' => 'lr-tb',
1760 'text-color' => 'black',
1761 'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1762 ));
1763
1764 /**
1765 * If true force sRGB color profile for all document.
1766 * @protected
1767 * @since 5.9.121 (2011-09-28)
1768 */
1769 protected $force_srgb = false;
1770
1771 /**
1772 * If true set the document to PDF/A mode.
1773 * @protected
1774 * @since 5.9.121 (2011-09-27)
1775 */
1776 protected $pdfa_mode = false;
1777
1778 /**
1779 * version of PDF/A mode (1 - 3).
1780 * @protected
1781 * @since 6.2.26 (2019-03-12)
1782 */
1783 protected $pdfa_version = 1;
1784
1785 /**
1786 * Document creation date-time
1787 * @protected
1788 * @since 5.9.152 (2012-03-22)
1789 */
1790 protected $doc_creation_timestamp;
1791
1792 /**
1793 * Document modification date-time
1794 * @protected
1795 * @since 5.9.152 (2012-03-22)
1796 */
1797 protected $doc_modification_timestamp;
1798
1799 /**
1800 * Custom XMP data.
1801 * @protected
1802 * @since 5.9.128 (2011-10-06)
1803 */
1804 protected $custom_xmp = '';
1805
1806 /**
1807 * Custom XMP RDF data.
1808 * @protected
1809 * @since 6.3.0 (2019-09-19)
1810 */
1811 protected $custom_xmp_rdf = '';
1812
1813 /**
1814 * Custom XMP RDF pdfaextension data.
1815 * @protected
1816 * @since 6.9.0 (2025-02-11)
1817 */
1818 protected $custom_xmp_rdf_pdfaExtension = '';
1819
1820 /**
1821 * Overprint mode array.
1822 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1823 * @protected
1824 * @since 5.9.152 (2012-03-23)
1825 * @var array<string,bool|int>
1826 */
1827 protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1828
1829 /**
1830 * Alpha mode array.
1831 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1832 * @protected
1833 * @since 5.9.152 (2012-03-23)
1834 */
1835 protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1836
1837 /**
1838 * Define the page boundaries boxes to be set on document.
1839 * @protected
1840 * @since 5.9.152 (2012-03-23)
1841 */
1842 protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1843
1844 /**
1845 * If true print TCPDF meta link.
1846 * @protected
1847 * @since 5.9.152 (2012-03-23)
1848 */
1849 protected $tcpdflink = true;
1850
1851 /**
1852 * Cache array for computed GD gamma values.
1853 * @protected
1854 * @since 5.9.1632 (2012-06-05)
1855 */
1856 protected $gdgammacache = array();
1857
1858 /**
1859 * Cache array for file content
1860 * @protected
1861 * @var array
1862 * @since 6.3.5 (2020-09-28)
1863 */
1864 protected $fileContentCache = array();
1865
1866 /**
1867 * Whether to allow local file path in image html tags, when prefixed with file://
1868 *
1869 * @var bool
1870 * @protected
1871 * @since 6.4 (2020-07-23)
1872 */
1873 protected $allowLocalFiles = false;
1874
1875 //------------------------------------------------------------
1876 // METHODS
1877 //------------------------------------------------------------
1878
1879 /**
1880 * This is the class constructor.
1881 * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1882 *
1883 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
1884 * @param string $unit User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
1885 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
1886 * @param boolean $unicode TRUE means that the input text is unicode (default = true)
1887 * @param string $encoding Charset encoding (used only when converting back html entities); default is UTF-8.
1888 * @param boolean $diskcache DEPRECATED FEATURE
1889 * @param false|integer $pdfa If not false, set the document to PDF/A mode and the good version (1 or 3).
1890 * @public
1891 * @see getPageSizeFromFormat(), setPageFormat()
1892 */
1893 public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
1894 // set file ID for trailer
1895 $serformat = (is_array($format) ? json_encode($format) : $format);
1896 $this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
1897 $this->hash_key = hash_hmac('sha256', TCPDF_STATIC::getRandomSeed($this->file_id), TCPDF_STATIC::getRandomSeed('TCPDF'), false);
1898 $this->font_obj_ids = array();
1899 $this->page_obj_id = array();
1900 $this->form_obj_id = array();
1901 // set pdf/a mode
1902 if ($pdfa != false) {
1903 $this->pdfa_mode = true;
1904 $this->pdfa_version = $pdfa; // 1 or 3
1905 } else
1906 $this->pdfa_mode = false;
1907
1908 $this->force_srgb = false;
1909 // set language direction
1910 $this->rtl = false;
1911 $this->tmprtl = false;
1912 // some checks
1913 $this->_dochecks();
1914 // initialization of properties
1915 $this->isunicode = $unicode;
1916 $this->page = 0;
1917 $this->transfmrk[0] = array();
1918 $this->pagedim = array();
1919 $this->n = 2;
1920 $this->buffer = '';
1921 $this->pages = array();
1922 $this->state = 0;
1923 $this->fonts = array();
1924 $this->FontFiles = array();
1925 $this->diffs = array();
1926 $this->images = array();
1927 $this->links = array();
1928 $this->gradients = array();
1929 $this->InFooter = false;
1930 $this->lasth = 0;
1931 $this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
1932 $this->FontStyle = '';
1933 $this->FontSizePt = 12;
1934 $this->underline = false;
1935 $this->overline = false;
1936 $this->linethrough = false;
1937 $this->DrawColor = '0 G';
1938 $this->FillColor = '0 g';
1939 $this->TextColor = '0 g';
1940 $this->ColorFlag = false;
1941 $this->pdflayers = array();
1942 // encryption values
1943 $this->encrypted = false;
1944 $this->last_enc_key = '';
1945 // standard Unicode fonts
1946 $this->CoreFonts = array(
1947 'courier'=>'Courier',
1948 'courierB'=>'Courier-Bold',
1949 'courierI'=>'Courier-Oblique',
1950 'courierBI'=>'Courier-BoldOblique',
1951 'helvetica'=>'Helvetica',
1952 'helveticaB'=>'Helvetica-Bold',
1953 'helveticaI'=>'Helvetica-Oblique',
1954 'helveticaBI'=>'Helvetica-BoldOblique',
1955 'times'=>'Times-Roman',
1956 'timesB'=>'Times-Bold',
1957 'timesI'=>'Times-Italic',
1958 'timesBI'=>'Times-BoldItalic',
1959 'symbol'=>'Symbol',
1960 'zapfdingbats'=>'ZapfDingbats'
1961 );
1962 // set scale factor
1963 $this->setPageUnit($unit);
1964 // set page format and orientation
1965 $this->setPageFormat($format, $orientation);
1966 // page margins (1 cm)
1967 $margin = 28.35 / $this->k;
1968 $this->setMargins($margin, $margin);
1969 $this->clMargin = $this->lMargin;
1970 $this->crMargin = $this->rMargin;
1971 // internal cell padding
1972 $cpadding = $margin / 10;
1973 $this->setCellPaddings($cpadding, 0, $cpadding, 0);
1974 // cell margins
1975 $this->setCellMargins(0, 0, 0, 0);
1976 // line width (0.2 mm)
1977 $this->LineWidth = 0.57 / $this->k;
1978 $this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
1979 $this->linestyleCap = '0 J';
1980 $this->linestyleJoin = '0 j';
1981 $this->linestyleDash = '[] 0 d';
1982 // automatic page break
1983 $this->setAutoPageBreak(true, (2 * $margin));
1984 // full width display mode
1985 $this->setDisplayMode('fullwidth');
1986 // compression
1987 $this->setCompression();
1988 // set default PDF version number
1989 $this->setPDFVersion();
1990 $this->tcpdflink = true;
1991 $this->encoding = $encoding;
1992 $this->HREF = array();
1993 $this->getFontsList();
1994 $this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
1995 $this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
1996 $this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
1997 $this->extgstates = array();
1998 $this->setTextShadow();
1999 // signature
2000 $this->sign = false;
2001 $this->tsa_timestamp = false;
2002 $this->tsa_data = array();
2003 $this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
2004 $this->empty_signature_appearance = array();
2005 // user's rights
2006 $this->ur['enabled'] = false;
2007 $this->ur['document'] = '/FullSave';
2008 $this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
2009 $this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
2010 $this->ur['signature'] = '/Modify';
2011 $this->ur['ef'] = '/Create/Delete/Modify/Import';
2012 $this->ur['formex'] = '';
2013 // set default JPEG quality
2014 $this->jpeg_quality = 75;
2015 // initialize some settings
2016 TCPDF_FONTS::utf8Bidi(array(), '', false, $this->isunicode, $this->CurrentFont);
2017 // set default font
2018 $this->setFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
2019 $this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
2020 $this->setFooterFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
2021 // check if PCRE Unicode support is enabled
2022 if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
2023 // PCRE unicode support is turned ON
2024 // \s : any whitespace character
2025 // \p{Z} : any separator
2026 // \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2027 // \xa0 : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2028 //$this->setSpacesRE('/(?!\xa0)[\s\p{Z}\p{Lo}]/u');
2029 $this->setSpacesRE('/(?!\xa0)[\s\p{Z}]/u');
2030 } else {
2031 // PCRE unicode support is turned OFF
2032 $this->setSpacesRE('/[^\S\xa0]/');
2033 }
2034 $this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
2035 // set document creation and modification timestamp
2036 $this->doc_creation_timestamp = time();
2037 $this->doc_modification_timestamp = $this->doc_creation_timestamp;
2038 // get default graphic vars
2039 $this->default_graphic_vars = $this->getGraphicVars();
2040 $this->header_xobj_autoreset = false;
2041 $this->custom_xmp = '';
2042 $this->custom_xmp_rdf = '';
2043 }
2044
2045 /**
2046 * Default destructor.
2047 * @public
2048 * @since 1.53.0.TC016
2049 */
2050 public function __destruct() {
2051 // cleanup
2052 $this->_destroy(true);
2053 }
2054
2055 /**
2056 * Set the units of measure for the document.
2057 * @param string $unit User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
2058 * @public
2059 * @since 3.0.015 (2008-06-06)
2060 */
2061 public function setPageUnit($unit) {
2062 $unit = strtolower($unit);
2063 //Set scale factor
2064 switch ($unit) {
2065 // points
2066 case 'px':
2067 case 'pt': {
2068 $this->k = 1;
2069 break;
2070 }
2071 // millimeters
2072 case 'mm': {
2073 $this->k = $this->dpi / 25.4;
2074 break;
2075 }
2076 // centimeters
2077 case 'cm': {
2078 $this->k = $this->dpi / 2.54;
2079 break;
2080 }
2081 // inches
2082 case 'in': {
2083 $this->k = $this->dpi;
2084 break;
2085 }
2086 // unsupported unit
2087 default : {
2088 $this->Error('Incorrect unit: '.$unit);
2089 break;
2090 }
2091 }
2092 $this->pdfunit = $unit;
2093 if (isset($this->CurOrientation)) {
2094 $this->setPageOrientation($this->CurOrientation);
2095 }
2096 }
2097
2098 /**
2099 * Change the format of the current page
2100 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() documentation or an array of two numbers (width, height) or an array containing the following measures and options:<ul>
2101 * <li>['format'] = page format name (one of the above);</li>
2102 * <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>
2103 * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2104 * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2105 * <li>['MediaBox']['llx'] : lower-left x coordinate</li>
2106 * <li>['MediaBox']['lly'] : lower-left y coordinate</li>
2107 * <li>['MediaBox']['urx'] : upper-right x coordinate</li>
2108 * <li>['MediaBox']['ury'] : upper-right y coordinate</li>
2109 * <li>['CropBox'] : the visible region of default user space:</li>
2110 * <li>['CropBox']['llx'] : lower-left x coordinate</li>
2111 * <li>['CropBox']['lly'] : lower-left y coordinate</li>
2112 * <li>['CropBox']['urx'] : upper-right x coordinate</li>
2113 * <li>['CropBox']['ury'] : upper-right y coordinate</li>
2114 * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2115 * <li>['BleedBox']['llx'] : lower-left x coordinate</li>
2116 * <li>['BleedBox']['lly'] : lower-left y coordinate</li>
2117 * <li>['BleedBox']['urx'] : upper-right x coordinate</li>
2118 * <li>['BleedBox']['ury'] : upper-right y coordinate</li>
2119 * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2120 * <li>['TrimBox']['llx'] : lower-left x coordinate</li>
2121 * <li>['TrimBox']['lly'] : lower-left y coordinate</li>
2122 * <li>['TrimBox']['urx'] : upper-right x coordinate</li>
2123 * <li>['TrimBox']['ury'] : upper-right y coordinate</li>
2124 * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2125 * <li>['ArtBox']['llx'] : lower-left x coordinate</li>
2126 * <li>['ArtBox']['lly'] : lower-left y coordinate</li>
2127 * <li>['ArtBox']['urx'] : upper-right x coordinate</li>
2128 * <li>['ArtBox']['ury'] : upper-right y coordinate</li>
2129 * <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>
2130 * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2131 * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2132 * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2133 * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2134 * <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>
2135 * <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>
2136 * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2137 * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2138 * <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>
2139 * <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>
2140 * <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>
2141 * <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>
2142 * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2143 * </ul>
2144 * @param string $orientation page orientation. Possible values are (case insensitive):<ul>
2145 * <li>P or Portrait (default)</li>
2146 * <li>L or Landscape</li>
2147 * <li>'' (empty string) for automatic orientation</li>
2148 * </ul>
2149 * @protected
2150 * @since 3.0.015 (2008-06-06)
2151 * @see getPageSizeFromFormat()
2152 */
2153 protected function setPageFormat($format, $orientation='P') {
2154 if (!empty($format) AND isset($this->pagedim[$this->page])) {
2155 // remove inherited values
2156 unset($this->pagedim[$this->page]);
2157 }
2158 if (is_string($format)) {
2159 // get page measures from format name
2160 $pf = TCPDF_STATIC::getPageSizeFromFormat($format);
2161 $this->fwPt = $pf[0];
2162 $this->fhPt = $pf[1];
2163 } else {
2164 // the boundaries of the physical medium on which the page shall be displayed or printed
2165 if (isset($format['MediaBox'])) {
2166 $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);
2167 $this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2168 $this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2169 } else {
2170 if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2171 $pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2172 } else {
2173 if (!isset($format['format'])) {
2174 // default value
2175 $format['format'] = 'A4';
2176 }
2177 $pf = TCPDF_STATIC::getPageSizeFromFormat($format['format']);
2178 }
2179 $this->fwPt = $pf[0];
2180 $this->fhPt = $pf[1];
2181 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2182 }
2183 // the visible region of default user space
2184 if (isset($format['CropBox'])) {
2185 $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);
2186 }
2187 // the region to which the contents of the page shall be clipped when output in a production environment
2188 if (isset($format['BleedBox'])) {
2189 $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);
2190 }
2191 // the intended dimensions of the finished page after trimming
2192 if (isset($format['TrimBox'])) {
2193 $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);
2194 }
2195 // the page's meaningful content (including potential white space)
2196 if (isset($format['ArtBox'])) {
2197 $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);
2198 }
2199 // specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2200 if (isset($format['BoxColorInfo'])) {
2201 $this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2202 }
2203 if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2204 // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2205 $this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2206 }
2207 if (isset($format['PZ'])) {
2208 // The page's preferred zoom (magnification) factor
2209 $this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2210 }
2211 if (isset($format['trans'])) {
2212 // The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2213 if (isset($format['trans']['Dur'])) {
2214 // The page's display duration
2215 $this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2216 }
2217 $stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2218 if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2219 // The transition style that shall be used when moving to this page from another during a presentation
2220 $this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2221 $valid_effect = array('Split', 'Blinds');
2222 $valid_vals = array('H', 'V');
2223 if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2224 $this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2225 }
2226 $valid_effect = array('Split', 'Box', 'Fly');
2227 $valid_vals = array('I', 'O');
2228 if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2229 $this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2230 }
2231 $valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2232 if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2233 if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2234 OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2235 OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2236 $this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2237 }
2238 }
2239 if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2240 $this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2241 }
2242 if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2243 $this->pagedim[$this->page]['trans']['B'] = 'true';
2244 }
2245 } else {
2246 $this->pagedim[$this->page]['trans']['S'] = 'R';
2247 }
2248 if (isset($format['trans']['D'])) {
2249 // The duration of the transition effect, in seconds
2250 $this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2251 } else {
2252 $this->pagedim[$this->page]['trans']['D'] = 1;
2253 }
2254 }
2255 }
2256 $this->setPageOrientation($orientation);
2257 }
2258
2259 /**
2260 * Set page orientation.
2261 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
2262 * @param boolean|null $autopagebreak Boolean indicating if auto-page-break mode should be on or off.
2263 * @param float|null $bottommargin bottom margin of the page.
2264 * @public
2265 * @since 3.0.015 (2008-06-06)
2266 */
2267 public function setPageOrientation($orientation, $autopagebreak=null, $bottommargin=null) {
2268 if (!isset($this->pagedim[$this->page]['MediaBox'])) {
2269 // the boundaries of the physical medium on which the page shall be displayed or printed
2270 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2271 }
2272 if (!isset($this->pagedim[$this->page]['CropBox'])) {
2273 // the visible region of default user space
2274 $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);
2275 }
2276 if (!isset($this->pagedim[$this->page]['BleedBox'])) {
2277 // the region to which the contents of the page shall be clipped when output in a production environment
2278 $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);
2279 }
2280 if (!isset($this->pagedim[$this->page]['TrimBox'])) {
2281 // the intended dimensions of the finished page after trimming
2282 $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);
2283 }
2284 if (!isset($this->pagedim[$this->page]['ArtBox'])) {
2285 // the page's meaningful content (including potential white space)
2286 $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);
2287 }
2288 if (!isset($this->pagedim[$this->page]['Rotate'])) {
2289 // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2290 $this->pagedim[$this->page]['Rotate'] = 0;
2291 }
2292 if (!isset($this->pagedim[$this->page]['PZ'])) {
2293 // The page's preferred zoom (magnification) factor
2294 $this->pagedim[$this->page]['PZ'] = 1;
2295 }
2296 if ($this->fwPt > $this->fhPt) {
2297 // landscape
2298 $default_orientation = 'L';
2299 } else {
2300 // portrait
2301 $default_orientation = 'P';
2302 }
2303 $valid_orientations = array('P', 'L');
2304 if (empty($orientation)) {
2305 $orientation = $default_orientation;
2306 } else {
2307 $orientation = strtoupper($orientation[0]);
2308 }
2309 if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2310 $this->CurOrientation = $orientation;
2311 $this->wPt = $this->fhPt;
2312 $this->hPt = $this->fwPt;
2313 } else {
2314 $this->CurOrientation = $default_orientation;
2315 $this->wPt = $this->fwPt;
2316 $this->hPt = $this->fhPt;
2317 }
2318 if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
2319 // swap X and Y coordinates (change page orientation)
2320 $this->pagedim = TCPDF_STATIC::swapPageBoxCoordinates($this->page, $this->pagedim);
2321 }
2322 $this->w = ($this->wPt / $this->k);
2323 $this->h = ($this->hPt / $this->k);
2324 if (TCPDF_STATIC::empty_string($autopagebreak)) {
2325 if (isset($this->AutoPageBreak)) {
2326 $autopagebreak = $this->AutoPageBreak;
2327 } else {
2328 $autopagebreak = true;
2329 }
2330 }
2331 if (TCPDF_STATIC::empty_string($bottommargin)) {
2332 if (isset($this->bMargin)) {
2333 $bottommargin = $this->bMargin;
2334 } else {
2335 // default value = 2 cm
2336 $bottommargin = 2 * 28.35 / $this->k;
2337 }
2338 }
2339 $this->setAutoPageBreak($autopagebreak, $bottommargin);
2340 // store page dimensions
2341 $this->pagedim[$this->page]['w'] = $this->wPt;
2342 $this->pagedim[$this->page]['h'] = $this->hPt;
2343 $this->pagedim[$this->page]['wk'] = $this->w;
2344 $this->pagedim[$this->page]['hk'] = $this->h;
2345 $this->pagedim[$this->page]['tm'] = $this->tMargin;
2346 $this->pagedim[$this->page]['bm'] = $bottommargin;
2347 $this->pagedim[$this->page]['lm'] = $this->lMargin;
2348 $this->pagedim[$this->page]['rm'] = $this->rMargin;
2349 $this->pagedim[$this->page]['pb'] = $autopagebreak;
2350 $this->pagedim[$this->page]['or'] = $this->CurOrientation;
2351 $this->pagedim[$this->page]['olm'] = $this->original_lMargin;
2352 $this->pagedim[$this->page]['orm'] = $this->original_rMargin;
2353 }
2354
2355 /**
2356 * Set regular expression to detect withespaces or word separators.
2357 * The pattern delimiter must be the forward-slash character "/".
2358 * Some example patterns are:
2359 * <pre>
2360 * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
2361 * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
2362 * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
2363 * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
2364 * \s : any whitespace character
2365 * \p{Z} : any separator
2366 * \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2367 * \xa0 : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2368 * </pre>
2369 * @param string $re regular expression (leave empty for default).
2370 * @public
2371 * @since 4.6.016 (2009-06-15)
2372 */
2373 public function setSpacesRE($re='/[^\S\xa0]/') {
2374 $this->re_spaces = $re;
2375 $re_parts = explode('/', $re);
2376 // get pattern parts
2377 $this->re_space = array();
2378 if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2379 $this->re_space['p'] = $re_parts[1];
2380 } else {
2381 $this->re_space['p'] = '[\s]';
2382 }
2383 // set pattern modifiers
2384 if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2385 $this->re_space['m'] = $re_parts[2];
2386 } else {
2387 $this->re_space['m'] = '';
2388 }
2389 }
2390
2391 /**
2392 * Enable or disable Right-To-Left language mode
2393 * @param boolean $enable if true enable Right-To-Left language mode.
2394 * @param boolean $resetx if true reset the X position on direction change.
2395 * @public
2396 * @since 2.0.000 (2008-01-03)
2397 */
2398 public function setRTL($enable, $resetx=true) {
2399 $enable = $enable ? true : false;
2400 $resetx = ($resetx AND ($enable != $this->rtl));
2401 $this->rtl = $enable;
2402 $this->tmprtl = false;
2403 if ($resetx) {
2404 $this->Ln(0);
2405 }
2406 }
2407
2408 /**
2409 * Return the RTL status
2410 * @return bool
2411 * @public
2412 * @since 4.0.012 (2008-07-24)
2413 */
2414 public function getRTL() {
2415 return $this->rtl;
2416 }
2417
2418 /**
2419 * Force temporary RTL language direction
2420 * @param false|string $mode can be false, 'L' for LTR or 'R' for RTL
2421 * @public
2422 * @since 2.1.000 (2008-01-09)
2423 */
2424 public function setTempRTL($mode) {
2425 $newmode = false;
2426 switch (strtoupper($mode)) {
2427 case 'LTR':
2428 case 'L': {
2429 if ($this->rtl) {
2430 $newmode = 'L';
2431 }
2432 break;
2433 }
2434 case 'RTL':
2435 case 'R': {
2436 if (!$this->rtl) {
2437 $newmode = 'R';
2438 }
2439 break;
2440 }
2441 case false:
2442 default: {
2443 $newmode = false;
2444 break;
2445 }
2446 }
2447 $this->tmprtl = $newmode;
2448 }
2449
2450 /**
2451 * Return the current temporary RTL status
2452 * @return bool
2453 * @public
2454 * @since 4.8.014 (2009-11-04)
2455 */
2456 public function isRTLTextDir() {
2457 return ($this->rtl OR ($this->tmprtl == 'R'));
2458 }
2459
2460 /**
2461 * Set the last cell height.
2462 * @param float $h cell height.
2463 * @author Nicola Asuni
2464 * @public
2465 * @since 1.53.0.TC034
2466 */
2467 public function setLastH($h) {
2468 $this->lasth = $h;
2469 }
2470
2471 /**
2472 * Return the cell height
2473 * @param int $fontsize Font size in internal units
2474 * @param boolean $padding If true add cell padding
2475 * @public
2476 * @return float
2477 */
2478 public function getCellHeight($fontsize, $padding=TRUE) {
2479 $height = ($fontsize * $this->cell_height_ratio);
2480 if ($padding && !empty($this->cell_padding)) {
2481 $height += ($this->cell_padding['T'] + $this->cell_padding['B']);
2482 }
2483 return round($height, 6);
2484 }
2485
2486 /**
2487 * Reset the last cell height.
2488 * @public
2489 * @since 5.9.000 (2010-10-03)
2490 */
2491 public function resetLastH() {
2492 $this->lasth = $this->getCellHeight($this->FontSize);
2493 }
2494
2495 /**
2496 * Get the last cell height.
2497 * @return float last cell height
2498 * @public
2499 * @since 4.0.017 (2008-08-05)
2500 */
2501 public function getLastH() {
2502 return $this->lasth;
2503 }
2504
2505 /**
2506 * Set the adjusting factor to convert pixels to user units.
2507 * @param float $scale adjusting factor to convert pixels to user units.
2508 * @author Nicola Asuni
2509 * @public
2510 * @since 1.5.2
2511 */
2512 public function setImageScale($scale) {
2513 $this->imgscale = $scale;
2514 }
2515
2516 /**
2517 * Returns the adjusting factor to convert pixels to user units.
2518 * @return float adjusting factor to convert pixels to user units.
2519 * @author Nicola Asuni
2520 * @public
2521 * @since 1.5.2
2522 */
2523 public function getImageScale() {
2524 return $this->imgscale;
2525 }
2526
2527 /**
2528 * Returns an array of page dimensions:
2529 * <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>
2530 * @param int|null $pagenum page number (empty = current page)
2531 * @return array of page dimensions.
2532 * @author Nicola Asuni
2533 * @public
2534 * @since 4.5.027 (2009-03-16)
2535 */
2536 public function getPageDimensions($pagenum=null) {
2537 if (empty($pagenum)) {
2538 $pagenum = $this->page;
2539 }
2540 return $this->pagedim[$pagenum];
2541 }
2542
2543 /**
2544 * Returns the page width in units.
2545 * @param int|null $pagenum page number (empty = current page)
2546 * @return int|float page width.
2547 * @author Nicola Asuni
2548 * @public
2549 * @since 1.5.2
2550 * @see getPageDimensions()
2551 */
2552 public function getPageWidth($pagenum=null) {
2553 if (empty($pagenum)) {
2554 return $this->w;
2555 }
2556 return $this->pagedim[$pagenum]['w'];
2557 }
2558
2559 /**
2560 * Returns the page height in units.
2561 * @param int|null $pagenum page number (empty = current page)
2562 * @return int|float page height.
2563 * @author Nicola Asuni
2564 * @public
2565 * @since 1.5.2
2566 * @see getPageDimensions()
2567 */
2568 public function getPageHeight($pagenum=null) {
2569 if (empty($pagenum)) {
2570 return $this->h;
2571 }
2572 return $this->pagedim[$pagenum]['h'];
2573 }
2574
2575 /**
2576 * Returns the page break margin.
2577 * @param int|null $pagenum page number (empty = current page)
2578 * @return int|float page break margin.
2579 * @author Nicola Asuni
2580 * @public
2581 * @since 1.5.2
2582 * @see getPageDimensions()
2583 */
2584 public function getBreakMargin($pagenum=null) {
2585 if (empty($pagenum)) {
2586 return $this->bMargin;
2587 }
2588 return $this->pagedim[$pagenum]['bm'];
2589 }
2590
2591 /**
2592 * Returns the scale factor (number of points in user unit).
2593 * @return int scale factor.
2594 * @author Nicola Asuni
2595 * @public
2596 * @since 1.5.2
2597 */
2598 public function getScaleFactor() {
2599 return $this->k;
2600 }
2601
2602 /**
2603 * Defines the left, top and right margins.
2604 * @param int|float $left Left margin.
2605 * @param int|float $top Top margin.
2606 * @param int|float|null $right Right margin. Default value is the left one.
2607 * @param boolean $keepmargins if true overwrites the default page margins
2608 * @public
2609 * @since 1.0
2610 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
2611 */
2612 public function setMargins($left, $top, $right=null, $keepmargins=false) {
2613 //Set left, top and right margins
2614 $this->lMargin = $left;
2615 $this->tMargin = $top;
2616 if ($right == -1 OR $right === null) {
2617 $right = $left;
2618 }
2619 $this->rMargin = $right;
2620 if ($keepmargins) {
2621 // overwrite original values
2622 $this->original_lMargin = $this->lMargin;
2623 $this->original_rMargin = $this->rMargin;
2624 }
2625 }
2626
2627 /**
2628 * 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.
2629 * @param int|float $margin The margin.
2630 * @public
2631 * @since 1.4
2632 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2633 */
2634 public function setLeftMargin($margin) {
2635 //Set left margin
2636 $this->lMargin = $margin;
2637 if (($this->page > 0) AND ($this->x < $margin)) {
2638 $this->x = $margin;
2639 }
2640 }
2641
2642 /**
2643 * Defines the top margin. The method can be called before creating the first page.
2644 * @param int|float $margin The margin.
2645 * @public
2646 * @since 1.5
2647 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2648 */
2649 public function setTopMargin($margin) {
2650 //Set top margin
2651 $this->tMargin = $margin;
2652 if (($this->page > 0) AND ($this->y < $margin)) {
2653 $this->y = $margin;
2654 }
2655 }
2656
2657 /**
2658 * Defines the right margin. The method can be called before creating the first page.
2659 * @param int|float $margin The margin.
2660 * @public
2661 * @since 1.5
2662 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
2663 */
2664 public function setRightMargin($margin) {
2665 $this->rMargin = $margin;
2666 if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
2667 $this->x = $this->w - $margin;
2668 }
2669 }
2670
2671 /**
2672 * Set the same internal Cell padding for top, right, bottom, left-
2673 * @param int|float $pad internal padding.
2674 * @public
2675 * @since 2.1.000 (2008-01-09)
2676 * @see getCellPaddings(), setCellPaddings()
2677 */
2678 public function setCellPadding($pad) {
2679 if ($pad >= 0) {
2680 $this->cell_padding['L'] = $pad;
2681 $this->cell_padding['T'] = $pad;
2682 $this->cell_padding['R'] = $pad;
2683 $this->cell_padding['B'] = $pad;
2684 }
2685 }
2686
2687 /**
2688 * Set the internal Cell paddings.
2689 * @param int|float|null $left left padding
2690 * @param int|float|null $top top padding
2691 * @param int|float|null $right right padding
2692 * @param int|float|null $bottom bottom padding
2693 * @public
2694 * @since 5.9.000 (2010-10-03)
2695 * @see getCellPaddings(), SetCellPadding()
2696 */
2697 public function setCellPaddings($left=null, $top=null, $right=null, $bottom=null) {
2698 if (!TCPDF_STATIC::empty_string($left) AND ($left >= 0)) {
2699 $this->cell_padding['L'] = $left;
2700 }
2701 if (!TCPDF_STATIC::empty_string($top) AND ($top >= 0)) {
2702 $this->cell_padding['T'] = $top;
2703 }
2704 if (!TCPDF_STATIC::empty_string($right) AND ($right >= 0)) {
2705 $this->cell_padding['R'] = $right;
2706 }
2707 if (!TCPDF_STATIC::empty_string($bottom) AND ($bottom >= 0)) {
2708 $this->cell_padding['B'] = $bottom;
2709 }
2710 }
2711
2712 /**
2713 * Get the internal Cell padding array.
2714 * @return array of padding values
2715 * @public
2716 * @since 5.9.000 (2010-10-03)
2717 * @see setCellPaddings(), SetCellPadding()
2718 */
2719 public function getCellPaddings() {
2720 return $this->cell_padding;
2721 }
2722
2723 /**
2724 * Set the internal Cell margins.
2725 * @param int|float|null $left left margin
2726 * @param int|float|null $top top margin
2727 * @param int|float|null $right right margin
2728 * @param int|float|null $bottom bottom margin
2729 * @public
2730 * @since 5.9.000 (2010-10-03)
2731 * @see getCellMargins()
2732 */
2733 public function setCellMargins($left=null, $top=null, $right=null, $bottom=null) {
2734 if (!TCPDF_STATIC::empty_string($left) AND ($left >= 0)) {
2735 $this->cell_margin['L'] = $left;
2736 }
2737 if (!TCPDF_STATIC::empty_string($top) AND ($top >= 0)) {
2738 $this->cell_margin['T'] = $top;
2739 }
2740 if (!TCPDF_STATIC::empty_string($right) AND ($right >= 0)) {
2741 $this->cell_margin['R'] = $right;
2742 }
2743 if (!TCPDF_STATIC::empty_string($bottom) AND ($bottom >= 0)) {
2744 $this->cell_margin['B'] = $bottom;
2745 }
2746 }
2747
2748 /**
2749 * Get the internal Cell margin array.
2750 * @return array of margin values
2751 * @public
2752 * @since 5.9.000 (2010-10-03)
2753 * @see setCellMargins()
2754 */
2755 public function getCellMargins() {
2756 return $this->cell_margin;
2757 }
2758
2759 /**
2760 * Adjust the internal Cell padding array to take account of the line width.
2761 * @param string|array|int|bool $brd Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
2762 * @return void|array array of adjustments
2763 * @public
2764 * @since 5.9.000 (2010-10-03)
2765 */
2766 protected function adjustCellPadding($brd=0) {
2767 if (empty($brd)) {
2768 return;
2769 }
2770 if (is_string($brd)) {
2771 // convert string to array
2772 $slen = strlen($brd);
2773 $newbrd = array();
2774 for ($i = 0; $i < $slen; ++$i) {
2775 $newbrd[$brd[$i]] = true;
2776 }
2777 $brd = $newbrd;
2778 } elseif (
2779 ($brd === 1)
2780 || ($brd === true)
2781 || (is_numeric($brd) && ((int)$brd > 0))
2782 ) {
2783 $brd = array('LRTB' => true);
2784 }
2785 if (!is_array($brd)) {
2786 return;
2787 }
2788 // store current cell padding
2789 $cp = $this->cell_padding;
2790 // select border mode
2791 if (isset($brd['mode'])) {
2792 $mode = $brd['mode'];
2793 unset($brd['mode']);
2794 } else {
2795 $mode = 'normal';
2796 }
2797 // process borders
2798 foreach ($brd as $border => $style) {
2799 $line_width = $this->LineWidth;
2800 if (is_array($style) && isset($style['width'])) {
2801 // get border width
2802 $line_width = $style['width'];
2803 }
2804 $adj = 0; // line width inside the cell
2805 switch ($mode) {
2806 case 'ext': {
2807 $adj = 0;
2808 break;
2809 }
2810 case 'int': {
2811 $adj = $line_width;
2812 break;
2813 }
2814 case 'normal':
2815 default: {
2816 $adj = ($line_width / 2);
2817 break;
2818 }
2819 }
2820 // correct internal cell padding if required to avoid overlap between text and lines
2821 if (
2822 is_numeric($this->cell_padding['T'])
2823 && ($this->cell_padding['T'] < $adj)
2824 && (strpos($border, 'T') !== false)
2825 ) {
2826 $this->cell_padding['T'] = $adj;
2827 }
2828 if (
2829 is_numeric($this->cell_padding['R'])
2830 && ($this->cell_padding['R'] < $adj)
2831 && (strpos($border, 'R') !== false)
2832 ) {
2833 $this->cell_padding['R'] = $adj;
2834 }
2835 if (
2836 is_numeric($this->cell_padding['B'])
2837 && ($this->cell_padding['B'] < $adj)
2838 && (strpos($border, 'B') !== false)
2839 ) {
2840 $this->cell_padding['B'] = $adj;
2841 }
2842 if (
2843 is_numeric($this->cell_padding['L'])
2844 && ($this->cell_padding['L'] < $adj)
2845 && (strpos($border, 'L') !== false)
2846 ) {
2847 $this->cell_padding['L'] = $adj;
2848 }
2849
2850 }
2851
2852 return array(
2853 'T' => ($this->cell_padding['T'] - $cp['T']),
2854 'R' => ($this->cell_padding['R'] - $cp['R']),
2855 'B' => ($this->cell_padding['B'] - $cp['B']),
2856 'L' => ($this->cell_padding['L'] - $cp['L']),
2857 );
2858 }
2859
2860 /**
2861 * 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.
2862 * @param boolean $auto Boolean indicating if mode should be on or off.
2863 * @param float $margin Distance from the bottom of the page.
2864 * @public
2865 * @since 1.0
2866 * @see Cell(), MultiCell(), AcceptPageBreak()
2867 */
2868 public function setAutoPageBreak($auto, $margin=0) {
2869 $this->AutoPageBreak = $auto ? true : false;
2870 $this->bMargin = $margin;
2871 $this->PageBreakTrigger = $this->h - $margin;
2872 }
2873
2874 /**
2875 * Return the auto-page-break mode (true or false).
2876 * @return bool auto-page-break mode
2877 * @public
2878 * @since 5.9.088
2879 */
2880 public function getAutoPageBreak() {
2881 return $this->AutoPageBreak;
2882 }
2883
2884 /**
2885 * Defines the way the document is to be displayed by the viewer.
2886 * @param mixed $zoom The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. <ul><li>fullpage: displays the entire page on screen </li><li>fullwidth: uses maximum width of window</li><li>real: uses real size (equivalent to 100% zoom)</li><li>default: uses viewer default mode</li></ul>
2887 * @param string $layout The page layout. Possible values are:<ul><li>SinglePage Display one page at a time</li><li>OneColumn Display the pages in one column</li><li>TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left</li><li>TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right</li><li>TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left</li><li>TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right</li></ul>
2888 * @param string $mode A name object specifying how the document should be displayed when opened:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible</li><li>UseOC (PDF 1.5) Optional content group panel visible</li><li>UseAttachments (PDF 1.6) Attachments panel visible</li></ul>
2889 * @public
2890 * @since 1.2
2891 */
2892 public function setDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
2893 if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
2894 $this->ZoomMode = $zoom;
2895 } else {
2896 $this->Error('Incorrect zoom display mode: '.$zoom);
2897 }
2898 $this->LayoutMode = TCPDF_STATIC::getPageLayoutMode($layout);
2899 $this->PageMode = TCPDF_STATIC::getPageMode($mode);
2900 }
2901
2902 /**
2903 * 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.
2904 * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
2905 * @param boolean $compress Boolean indicating if compression must be enabled.
2906 * @public
2907 * @since 1.4
2908 */
2909 public function setCompression($compress=true) {
2910 $this->compress = ($compress && function_exists('gzcompress'));
2911 }
2912
2913 /**
2914 * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
2915 * @param boolean $mode If true force sRGB output intent.
2916 * @public
2917 * @since 5.9.121 (2011-09-28)
2918 */
2919 public function setSRGBmode($mode=false) {
2920 $this->force_srgb = $mode ? true : false;
2921 }
2922
2923 /**
2924 * Turn on/off Unicode mode for document information dictionary (meta tags).
2925 * This has effect only when unicode mode is set to false.
2926 * @param boolean $unicode if true set the meta information in Unicode
2927 * @since 5.9.027 (2010-12-01)
2928 * @public
2929 */
2930 public function setDocInfoUnicode($unicode=true) {
2931 $this->docinfounicode = $unicode ? true : false;
2932 }
2933
2934 /**
2935 * Defines the title of the document.
2936 * @param string $title The title.
2937 * @public
2938 * @since 1.2
2939 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
2940 */
2941 public function setTitle($title) {
2942 $this->title = $title;
2943 }
2944
2945 /**
2946 * Defines the subject of the document.
2947 * @param string $subject The subject.
2948 * @public
2949 * @since 1.2
2950 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
2951 */
2952 public function setSubject($subject) {
2953 $this->subject = $subject;
2954 }
2955
2956 /**
2957 * Defines the author of the document.
2958 * @param string $author The name of the author.
2959 * @public
2960 * @since 1.2
2961 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
2962 */
2963 public function setAuthor($author) {
2964 $this->author = $author;
2965 }
2966
2967 /**
2968 * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
2969 * @param string $keywords The list of keywords.
2970 * @public
2971 * @since 1.2
2972 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
2973 */
2974 public function setKeywords($keywords) {
2975 $this->keywords = $keywords;
2976 }
2977
2978 /**
2979 * Defines the creator of the document. This is typically the name of the application that generates the PDF.
2980 * @param string $creator The name of the creator.
2981 * @public
2982 * @since 1.2
2983 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
2984 */
2985 public function setCreator($creator) {
2986 $this->creator = $creator;
2987 }
2988
2989 /**
2990 * Whether to allow local file path in image html tags, when prefixed with file://
2991 *
2992 * @param bool $allowLocalFiles true, when local files should be allowed. Otherwise false.
2993 * @public
2994 * @since 6.4
2995 */
2996 public function setAllowLocalFiles($allowLocalFiles) {
2997 $this->allowLocalFiles = (bool) $allowLocalFiles;
2998 }
2999
3000
3001 /**
3002 * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
3003 * @param string $msg The error message
3004 * @public
3005 * @since 1.0
3006 */
3007 public function Error($msg) {
3008 // unset all class variables
3009 $this->_destroy(true);
3010 $msg = htmlspecialchars($msg, ENT_QUOTES, 'UTF-8');
3011 if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
3012 die('<strong>TCPDF ERROR: </strong>'.$msg);
3013 } else {
3014 throw new Exception('TCPDF ERROR: '.$msg);
3015 }
3016 }
3017
3018 /**
3019 * This method begins the generation of the PDF document.
3020 * It is not necessary to call it explicitly because AddPage() does it automatically.
3021 * Note: no page is created by this method
3022 * @public
3023 * @since 1.0
3024 * @see AddPage(), Close()
3025 */
3026 public function Open() {
3027 $this->state = 1;
3028 }
3029
3030 /**
3031 * Terminates the PDF document.
3032 * It is not necessary to call this method explicitly because Output() does it automatically.
3033 * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
3034 * @public
3035 * @since 1.0
3036 * @see Open(), Output()
3037 */
3038 public function Close() {
3039 if ($this->state == 3) {
3040 return;
3041 }
3042 if ($this->page == 0) {
3043 $this->AddPage();
3044 }
3045 $this->endLayer();
3046 if ($this->tcpdflink) {
3047 // save current graphic settings
3048 $gvars = $this->getGraphicVars();
3049 $this->setEqualColumns();
3050 $this->lastpage(true);
3051 $this->setAutoPageBreak(false);
3052 $this->x = 0;
3053 $this->y = $this->h - (1 / $this->k);
3054 $this->lMargin = 0;
3055 $this->_outSaveGraphicsState();
3056 $font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
3057 $this->setFont($font, '', 1);
3058 $this->setTextRenderingMode(0, false, false);
3059 $msg = "\x50\x6f\x77\x65\x72\x65\x64\x20\x62\x79\x20\x54\x43\x50\x44\x46\x20\x28\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67\x29";
3060 $lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
3061 $this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
3062 $this->_outRestoreGraphicsState();
3063 // restore graphic settings
3064 $this->setGraphicVars($gvars);
3065 }
3066 // close page
3067 $this->endPage();
3068 // close document
3069 $this->_enddoc();
3070 // unset all class variables (except critical ones)
3071 $this->_destroy(false);
3072 }
3073
3074 /**
3075 * Move pointer at the specified document page and update page dimensions.
3076 * @param int $pnum page number (1 ... numpages)
3077 * @param boolean $resetmargins if true reset left, right, top margins and Y position.
3078 * @public
3079 * @since 2.1.000 (2008-01-07)
3080 * @see getPage(), lastpage(), getNumPages()
3081 */
3082 public function setPage($pnum, $resetmargins=false) {
3083 if (($pnum == $this->page) AND ($this->state == 2)) {
3084 return;
3085 }
3086 if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3087 $this->state = 2;
3088 // save current graphic settings
3089 //$gvars = $this->getGraphicVars();
3090 $oldpage = $this->page;
3091 $this->page = $pnum;
3092 $this->wPt = $this->pagedim[$this->page]['w'];
3093 $this->hPt = $this->pagedim[$this->page]['h'];
3094 $this->w = $this->pagedim[$this->page]['wk'];
3095 $this->h = $this->pagedim[$this->page]['hk'];
3096 $this->tMargin = $this->pagedim[$this->page]['tm'];
3097 $this->bMargin = $this->pagedim[$this->page]['bm'];
3098 $this->original_lMargin = $this->pagedim[$this->page]['olm'];
3099 $this->original_rMargin = $this->pagedim[$this->page]['orm'];
3100 $this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3101 $this->CurOrientation = $this->pagedim[$this->page]['or'];
3102 $this->setAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3103 // restore graphic settings
3104 //$this->setGraphicVars($gvars);
3105 if ($resetmargins) {
3106 $this->lMargin = $this->pagedim[$this->page]['olm'];
3107 $this->rMargin = $this->pagedim[$this->page]['orm'];
3108 $this->setY($this->tMargin);
3109 } else {
3110 // account for booklet mode
3111 if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3112 $deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3113 $this->lMargin += $deltam;
3114 $this->rMargin -= $deltam;
3115 }
3116 }
3117 } else {
3118 $this->Error('Wrong page number on setPage() function: '.$pnum);
3119 }
3120 }
3121
3122 /**
3123 * Reset pointer to the last document page.
3124 * @param boolean $resetmargins if true reset left, right, top margins and Y position.
3125 * @public
3126 * @since 2.0.000 (2008-01-04)
3127 * @see setPage(), getPage(), getNumPages()
3128 */
3129 public function lastPage($resetmargins=false) {
3130 $this->setPage($this->getNumPages(), $resetmargins);
3131 }
3132
3133 /**
3134 * Get current document page number.
3135 * @return int page number
3136 * @public
3137 * @since 2.1.000 (2008-01-07)
3138 * @see setPage(), lastpage(), getNumPages()
3139 */
3140 public function getPage() {
3141 return $this->page;
3142 }
3143
3144 /**
3145 * Get the total number of insered pages.
3146 * @return int number of pages
3147 * @public
3148 * @since 2.1.000 (2008-01-07)
3149 * @see setPage(), getPage(), lastpage()
3150 */
3151 public function getNumPages() {
3152 return $this->numpages;
3153 }
3154
3155 /**
3156 * Adds a new TOC (Table Of Content) page to the document.
3157 * @param string $orientation page orientation.
3158 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3159 * @param boolean $keepmargins if true overwrites the default page margins with the current margins
3160 * @public
3161 * @since 5.0.001 (2010-05-06)
3162 * @see AddPage(), startPage(), endPage(), endTOCPage()
3163 */
3164 public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3165 $this->AddPage($orientation, $format, $keepmargins, true);
3166 }
3167
3168 /**
3169 * Terminate the current TOC (Table Of Content) page
3170 * @public
3171 * @since 5.0.001 (2010-05-06)
3172 * @see AddPage(), startPage(), endPage(), addTOCPage()
3173 */
3174 public function endTOCPage() {
3175 $this->endPage(true);
3176 }
3177
3178 /**
3179 * Adds a new page to the document. If a page is already present, the Footer() method is called first to output the footer (if enabled). Then the page is added, the current position set to the top-left corner according to the left and top margins (or top-right if in RTL mode), and Header() is called to display the header (if enabled).
3180 * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3181 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3182 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3183 * @param boolean $keepmargins if true overwrites the default page margins with the current margins
3184 * @param boolean $tocpage if true set the tocpage state to true (the added page will be used to display Table Of Content).
3185 * @public
3186 * @since 1.0
3187 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3188 */
3189 public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3190 if ($this->inxobj) {
3191 // we are inside an XObject template
3192 return;
3193 }
3194 if (!isset($this->original_lMargin) OR $keepmargins) {
3195 $this->original_lMargin = $this->lMargin;
3196 }
3197 if (!isset($this->original_rMargin) OR $keepmargins) {
3198 $this->original_rMargin = $this->rMargin;
3199 }
3200 // terminate previous page
3201 $this->endPage();
3202 // start new page
3203 $this->startPage($orientation, $format, $tocpage);
3204 }
3205
3206 /**
3207 * Terminate the current page
3208 * @param boolean $tocpage if true set the tocpage state to false (end the page used to display Table Of Content).
3209 * @public
3210 * @since 4.2.010 (2008-11-14)
3211 * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3212 */
3213 public function endPage($tocpage=false) {
3214 // check if page is already closed
3215 if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3216 return;
3217 }
3218 // print page footer
3219 $this->setFooter();
3220 // close page
3221 $this->_endpage();
3222 // mark page as closed
3223 $this->pageopen[$this->page] = false;
3224 if ($tocpage) {
3225 $this->tocpage = false;
3226 }
3227 }
3228
3229 /**
3230 * Starts a new page to the document. The page must be closed using the endPage() function.
3231 * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3232 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3233 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3234 * @param boolean $tocpage if true the page is designated to contain the Table-Of-Content.
3235 * @since 4.2.010 (2008-11-14)
3236 * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3237 * @public
3238 */
3239 public function startPage($orientation='', $format='', $tocpage=false) {
3240 if ($tocpage) {
3241 $this->tocpage = true;
3242 }
3243 // move page numbers of documents to be attached
3244 if ($this->tocpage) {
3245 // move reference to unexistent pages (used for page attachments)
3246 // adjust outlines
3247 $tmpoutlines = $this->outlines;
3248 foreach ($tmpoutlines as $key => $outline) {
3249 if (!$outline['f'] AND ($outline['p'] > $this->numpages)) {
3250 $this->outlines[$key]['p'] = ($outline['p'] + 1);
3251 }
3252 }
3253 // adjust dests
3254 $tmpdests = $this->dests;
3255 foreach ($tmpdests as $key => $dest) {
3256 if (!$dest['f'] AND ($dest['p'] > $this->numpages)) {
3257 $this->dests[$key]['p'] = ($dest['p'] + 1);
3258 }
3259 }
3260 // adjust links
3261 $tmplinks = $this->links;
3262 foreach ($tmplinks as $key => $link) {
3263 if (!$link['f'] AND ($link['p'] > $this->numpages)) {
3264 $this->links[$key]['p'] = ($link['p'] + 1);
3265 }
3266 }
3267 }
3268 if ($this->numpages > $this->page) {
3269 // this page has been already added
3270 $this->setPage($this->page + 1);
3271 $this->setY($this->tMargin);
3272 return;
3273 }
3274 // start a new page
3275 if ($this->state == 0) {
3276 $this->Open();
3277 }
3278 ++$this->numpages;
3279 $this->swapMargins($this->booklet);
3280 // save current graphic settings
3281 $gvars = $this->getGraphicVars();
3282 // start new page
3283 $this->_beginpage($orientation, $format);
3284 // mark page as open
3285 $this->pageopen[$this->page] = true;
3286 // restore graphic settings
3287 $this->setGraphicVars($gvars);
3288 // mark this point
3289 $this->setPageMark();
3290 // print page header
3291 $this->setHeader();
3292 // restore graphic settings
3293 $this->setGraphicVars($gvars);
3294 // mark this point
3295 $this->setPageMark();
3296 // print table header (if any)
3297 $this->setTableHeader();
3298 // set mark for empty page check
3299 $this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
3300 }
3301
3302 /**
3303 * Set start-writing mark on current page stream used to put borders and fills.
3304 * Borders and fills are always created after content and inserted on the position marked by this method.
3305 * This function must be called after calling Image() function for a background image.
3306 * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
3307 * @public
3308 * @since 4.0.016 (2008-07-30)
3309 */
3310 public function setPageMark() {
3311 $this->intmrk[$this->page] = $this->pagelen[$this->page];
3312 $this->bordermrk[$this->page] = $this->intmrk[$this->page];
3313 $this->setContentMark();
3314 }
3315
3316 /**
3317 * Set start-writing mark on selected page.
3318 * Borders and fills are always created after content and inserted on the position marked by this method.
3319 * @param int $page page number (default is the current page)
3320 * @protected
3321 * @since 4.6.021 (2009-07-20)
3322 */
3323 protected function setContentMark($page=0) {
3324 if ($page <= 0) {
3325 $page = $this->page;
3326 }
3327 if (isset($this->footerlen[$page])) {
3328 $this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3329 } else {
3330 $this->cntmrk[$page] = $this->pagelen[$page];
3331 }
3332 }
3333
3334 /**
3335 * Set header data.
3336 * @param string $ln header image logo
3337 * @param int $lw header image logo width in mm
3338 * @param string $ht string to print as title on document header
3339 * @param string $hs string to print on document header
3340 * @param int[] $tc RGB array color for text.
3341 * @param int[] $lc RGB array color for line.
3342 * @public
3343 */
3344 public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
3345 $this->header_logo = $ln;
3346 $this->header_logo_width = $lw;
3347 $this->header_title = $ht;
3348 $this->header_string = $hs;
3349 $this->header_text_color = $tc;
3350 $this->header_line_color = $lc;
3351 }
3352
3353 /**
3354 * Set footer data.
3355 * @param int[] $tc RGB array color for text.
3356 * @param int[] $lc RGB array color for line.
3357 * @public
3358 */
3359 public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
3360 $this->footer_text_color = $tc;
3361 $this->footer_line_color = $lc;
3362 }
3363
3364 /**
3365 * Returns header data:
3366 * <ul><li>$ret['logo'] = logo image</li><li>$ret['logo_width'] = width of the image logo in user units</li><li>$ret['title'] = header title</li><li>$ret['string'] = header description string</li></ul>
3367 * @return array<string,mixed>
3368 * @public
3369 * @since 4.0.012 (2008-07-24)
3370 */
3371 public function getHeaderData() {
3372 $ret = array();
3373 $ret['logo'] = $this->header_logo;
3374 $ret['logo_width'] = $this->header_logo_width;
3375 $ret['title'] = $this->header_title;
3376 $ret['string'] = $this->header_string;
3377 $ret['text_color'] = $this->header_text_color;
3378 $ret['line_color'] = $this->header_line_color;
3379 return $ret;
3380 }
3381
3382 /**
3383 * Set header margin.
3384 * (minimum distance between header and top page margin)
3385 * @param float $hm distance in user units
3386 * @public
3387 */
3388 public function setHeaderMargin($hm=10) {
3389 $this->header_margin = $hm;
3390 }
3391
3392 /**
3393 * Returns header margin in user units.
3394 * @return float
3395 * @since 4.0.012 (2008-07-24)
3396 * @public
3397 */
3398 public function getHeaderMargin() {
3399 return $this->header_margin;
3400 }
3401
3402 /**
3403 * Set footer margin.
3404 * (minimum distance between footer and bottom page margin)
3405 * @param float $fm distance in user units
3406 * @public
3407 */
3408 public function setFooterMargin($fm=10) {
3409 $this->footer_margin = $fm;
3410 }
3411
3412 /**
3413 * Returns footer margin in user units.
3414 * @return float
3415 * @since 4.0.012 (2008-07-24)
3416 * @public
3417 */
3418 public function getFooterMargin() {
3419 return $this->footer_margin;
3420 }
3421 /**
3422 * Set a flag to print page header.
3423 * @param boolean $val set to true to print the page header (default), false otherwise.
3424 * @public
3425 */
3426 public function setPrintHeader($val=true) {
3427 $this->print_header = $val ? true : false;
3428 }
3429
3430 /**
3431 * Set a flag to print page footer.
3432 * @param boolean $val set to true to print the page footer (default), false otherwise.
3433 * @public
3434 */
3435 public function setPrintFooter($val=true) {
3436 $this->print_footer = $val ? true : false;
3437 }
3438
3439 /**
3440 * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
3441 * @return float
3442 * @public
3443 */
3444 public function getImageRBX() {
3445 return $this->img_rb_x;
3446 }
3447
3448 /**
3449 * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
3450 * @return float
3451 * @public
3452 */
3453 public function getImageRBY() {
3454 return $this->img_rb_y;
3455 }
3456
3457 /**
3458 * Reset the xobject template used by Header() method.
3459 * @public
3460 */
3461 public function resetHeaderTemplate() {
3462 $this->header_xobjid = false;
3463 }
3464
3465 /**
3466 * Set a flag to automatically reset the xobject template used by Header() method at each page.
3467 * @param boolean $val set to true to reset Header xobject template at each page, false otherwise.
3468 * @public
3469 */
3470 public function setHeaderTemplateAutoreset($val=true) {
3471 $this->header_xobj_autoreset = $val ? true : false;
3472 }
3473
3474 /**
3475 * This method is used to render the page header.
3476 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3477 * @public
3478 */
3479 public function Header() {
3480 if ($this->header_xobjid === false) {
3481 // start a new XObject Template
3482 $this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
3483 $headerfont = $this->getHeaderFont();
3484 $headerdata = $this->getHeaderData();
3485 $this->y = $this->header_margin;
3486 if ($this->rtl) {
3487 $this->x = $this->w - $this->original_rMargin;
3488 } else {
3489 $this->x = $this->original_lMargin;
3490 }
3491 if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
3492 $imgtype = TCPDF_IMAGES::getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
3493 if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
3494 $this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3495 } elseif ($imgtype == 'svg') {
3496 $this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3497 } else {
3498 $this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3499 }
3500 $imgy = $this->getImageRBY();
3501 } else {
3502 $imgy = $this->y;
3503 }
3504 $cell_height = $this->getCellHeight($headerfont[2] / $this->k);
3505 // set starting margin for text data cell
3506 if ($this->getRTL()) {
3507 $header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
3508 } else {
3509 $header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
3510 }
3511 $cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
3512 $this->setTextColorArray($this->header_text_color);
3513 // header title
3514 $this->setFont($headerfont[0], 'B', $headerfont[2] + 1);
3515 $this->setX($header_x);
3516 $this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
3517 // header string
3518 $this->setFont($headerfont[0], $headerfont[1], $headerfont[2]);
3519 $this->setX($header_x);
3520 $this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
3521 // print an ending header line
3522 $this->setLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
3523 $this->setY((2.835 / $this->k) + max($imgy, $this->y));
3524 if ($this->rtl) {
3525 $this->setX($this->original_rMargin);
3526 } else {
3527 $this->setX($this->original_lMargin);
3528 }
3529 $this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
3530 $this->endTemplate();
3531 }
3532 // print header template
3533 $x = 0;
3534 $dx = 0;
3535 if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
3536 // adjust margins for booklet mode
3537 $dx = ($this->original_lMargin - $this->original_rMargin);
3538 }
3539 if ($this->rtl) {
3540 $x = $this->w + $dx;
3541 } else {
3542 $x = 0 + $dx;
3543 }
3544 $this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
3545 if ($this->header_xobj_autoreset) {
3546 // reset header xobject template at each page
3547 $this->header_xobjid = false;
3548 }
3549 }
3550
3551 /**
3552 * This method is used to render the page footer.
3553 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3554 * @public
3555 */
3556 public function Footer() {
3557 $cur_y = $this->y;
3558 $this->setTextColorArray($this->footer_text_color);
3559 //set style for cell border
3560 $line_width = (0.85 / $this->k);
3561 $this->setLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
3562 //print document barcode
3563 $barcode = $this->getBarcode();
3564 if (!empty($barcode)) {
3565 $this->Ln($line_width);
3566 $barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
3567 $style = array(
3568 'position' => $this->rtl?'R':'L',
3569 'align' => $this->rtl?'R':'L',
3570 'stretch' => false,
3571 'fitwidth' => true,
3572 'cellfitalign' => '',
3573 'border' => false,
3574 'padding' => 0,
3575 'fgcolor' => array(0,0,0),
3576 'bgcolor' => false,
3577 'text' => false
3578 );
3579 $this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
3580 }
3581 $w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
3582 if (empty($this->pagegroups)) {
3583 $pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
3584 } else {
3585 $pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
3586 }
3587 $this->setY($cur_y);
3588 //Print page number
3589 if ($this->getRTL()) {
3590 $this->setX($this->original_rMargin);
3591 $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
3592 } else {
3593 $this->setX($this->original_lMargin);
3594 $this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
3595 }
3596 }
3597
3598 /**
3599 * This method is used to render the page header.
3600 * @protected
3601 * @since 4.0.012 (2008-07-24)
3602 */
3603 protected function setHeader() {
3604 if (!$this->print_header OR ($this->state != 2)) {
3605 return;
3606 }
3607 $this->InHeader = true;
3608 $this->setGraphicVars($this->default_graphic_vars);
3609 $temp_thead = $this->thead;
3610 $temp_theadMargins = $this->theadMargins;
3611 $lasth = $this->lasth;
3612 $newline = $this->newline;
3613 $this->_outSaveGraphicsState();
3614 $this->rMargin = $this->original_rMargin;
3615 $this->lMargin = $this->original_lMargin;
3616 $this->setCellPadding(0);
3617 //set current position
3618 if ($this->rtl) {
3619 $this->setXY($this->original_rMargin, $this->header_margin);
3620 } else {
3621 $this->setXY($this->original_lMargin, $this->header_margin);
3622 }
3623 $this->setFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
3624 $this->Header();
3625 //restore position
3626 if ($this->rtl) {
3627 $this->setXY($this->original_rMargin, $this->tMargin);
3628 } else {
3629 $this->setXY($this->original_lMargin, $this->tMargin);
3630 }
3631 $this->_outRestoreGraphicsState();
3632 $this->lasth = $lasth;
3633 $this->thead = $temp_thead;
3634 $this->theadMargins = $temp_theadMargins;
3635 $this->newline = $newline;
3636 $this->InHeader = false;
3637 }
3638
3639 /**
3640 * This method is used to render the page footer.
3641 * @protected
3642 * @since 4.0.012 (2008-07-24)
3643 */
3644 protected function setFooter() {
3645 if ($this->state != 2) {
3646 return;
3647 }
3648 $this->InFooter = true;
3649 // save current graphic settings
3650 $gvars = $this->getGraphicVars();
3651 // mark this point
3652 $this->footerpos[$this->page] = $this->pagelen[$this->page];
3653 $this->_out("\n");
3654 if ($this->print_footer) {
3655 $this->setGraphicVars($this->default_graphic_vars);
3656 $this->current_column = 0;
3657 $this->num_columns = 1;
3658 $temp_thead = $this->thead;
3659 $temp_theadMargins = $this->theadMargins;
3660 $lasth = $this->lasth;
3661 $this->_outSaveGraphicsState();
3662 $this->rMargin = $this->original_rMargin;
3663 $this->lMargin = $this->original_lMargin;
3664 $this->setCellPadding(0);
3665 //set current position
3666 $footer_y = $this->h - $this->footer_margin;
3667 if ($this->rtl) {
3668 $this->setXY($this->original_rMargin, $footer_y);
3669 } else {
3670 $this->setXY($this->original_lMargin, $footer_y);
3671 }
3672 $this->setFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
3673 $this->Footer();
3674 //restore position
3675 if ($this->rtl) {
3676 $this->setXY($this->original_rMargin, $this->tMargin);
3677 } else {
3678 $this->setXY($this->original_lMargin, $this->tMargin);
3679 }
3680 $this->_outRestoreGraphicsState();
3681 $this->lasth = $lasth;
3682 $this->thead = $temp_thead;
3683 $this->theadMargins = $temp_theadMargins;
3684 }
3685 // restore graphic settings
3686 $this->setGraphicVars($gvars);
3687 $this->current_column = $gvars['current_column'];
3688 $this->num_columns = $gvars['num_columns'];
3689 // calculate footer length
3690 $this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
3691 $this->InFooter = false;
3692 }
3693
3694 /**
3695 * Check if we are on the page body (excluding page header and footer).
3696 * @return bool true if we are not in page header nor in page footer, false otherwise.
3697 * @protected
3698 * @since 5.9.091 (2011-06-15)
3699 */
3700 protected function inPageBody() {
3701 return (($this->InHeader === false) AND ($this->InFooter === false));
3702 }
3703
3704 /**
3705 * This method is used to render the table header on new page (if any).
3706 * @protected
3707 * @since 4.5.030 (2009-03-25)
3708 */
3709 protected function setTableHeader() {
3710 if ($this->num_columns > 1) {
3711 // multi column mode
3712 return;
3713 }
3714 if (isset($this->theadMargins['top'])) {
3715 // restore the original top-margin
3716 $this->tMargin = $this->theadMargins['top'];
3717 $this->pagedim[$this->page]['tm'] = $this->tMargin;
3718 $this->y = $this->tMargin;
3719 }
3720 if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
3721 // set margins
3722 $prev_lMargin = $this->lMargin;
3723 $prev_rMargin = $this->rMargin;
3724 $prev_cell_padding = $this->cell_padding;
3725 $this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
3726 $this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
3727 $this->cell_padding = $this->theadMargins['cell_padding'];
3728 if ($this->rtl) {
3729 $this->x = $this->w - $this->rMargin;
3730 } else {
3731 $this->x = $this->lMargin;
3732 }
3733 // account for special "cell" mode
3734 if ($this->theadMargins['cell']) {
3735 if ($this->rtl) {
3736 $this->x -= $this->cell_padding['R'];
3737 } else {
3738 $this->x += $this->cell_padding['L'];
3739 }
3740 }
3741 $gvars = $this->getGraphicVars();
3742 if (!empty($this->theadMargins['gvars'])) {
3743 // set the correct graphic style
3744 $this->setGraphicVars($this->theadMargins['gvars']);
3745 $this->rMargin = $gvars['rMargin'];
3746 $this->lMargin = $gvars['lMargin'];
3747 }
3748 // print table header
3749 $this->writeHTML($this->thead, false, false, false, false, '');
3750 $this->setGraphicVars($gvars);
3751 // set new top margin to skip the table headers
3752 if (!isset($this->theadMargins['top'])) {
3753 $this->theadMargins['top'] = $this->tMargin;
3754 }
3755 // store end of header position
3756 if (!isset($this->columns[0]['th'])) {
3757 $this->columns[0]['th'] = array();
3758 }
3759 $this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
3760 $this->tMargin = $this->y;
3761 $this->pagedim[$this->page]['tm'] = $this->tMargin;
3762 $this->lasth = 0;
3763 $this->lMargin = $prev_lMargin;
3764 $this->rMargin = $prev_rMargin;
3765 $this->cell_padding = $prev_cell_padding;
3766 }
3767 }
3768
3769 /**
3770 * Returns the current page number.
3771 * @return int page number
3772 * @public
3773 * @since 1.0
3774 * @see getAliasNbPages()
3775 */
3776 public function PageNo() {
3777 return $this->page;
3778 }
3779
3780 /**
3781 * Returns the array of spot colors.
3782 * @return array Spot colors array.
3783 * @public
3784 * @since 6.0.038 (2013-09-30)
3785 */
3786 public function getAllSpotColors() {
3787 return $this->spot_colors;
3788 }
3789
3790 /**
3791 * Defines a new spot color.
3792 * It can be expressed in RGB components or gray scale.
3793 * The method can be called before the first page is created and the value is retained from page to page.
3794 * @param string $name Full name of the spot color.
3795 * @param float $c Cyan color for CMYK. Value between 0 and 100.
3796 * @param float $m Magenta color for CMYK. Value between 0 and 100.
3797 * @param float $y Yellow color for CMYK. Value between 0 and 100.
3798 * @param float $k Key (Black) color for CMYK. Value between 0 and 100.
3799 * @public
3800 * @since 4.0.024 (2008-09-12)
3801 * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3802 */
3803 public function AddSpotColor($name, $c, $m, $y, $k) {
3804 if (!isset($this->spot_colors[$name])) {
3805 $i = (1 + count($this->spot_colors));
3806 $this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
3807 }
3808 }
3809
3810 /**
3811 * Set the spot color for the specified type ('draw', 'fill', 'text').
3812 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3813 * @param string $name Name of the spot color.
3814 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3815 * @return string PDF color command.
3816 * @public
3817 * @since 5.9.125 (2011-10-03)
3818 */
3819 public function setSpotColor($type, $name, $tint=100) {
3820 $spotcolor = TCPDF_COLORS::getSpotColor($name, $this->spot_colors);
3821 if ($spotcolor === false) {
3822 $this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
3823 }
3824 $tint = (max(0, min(100, $tint)) / 100);
3825 $pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
3826 switch ($type) {
3827 case 'draw': {
3828 $pdfcolor .= sprintf('CS %F SCN', $tint);
3829 $this->DrawColor = $pdfcolor;
3830 $this->strokecolor = $spotcolor;
3831 break;
3832 }
3833 case 'fill': {
3834 $pdfcolor .= sprintf('cs %F scn', $tint);
3835 $this->FillColor = $pdfcolor;
3836 $this->bgcolor = $spotcolor;
3837 break;
3838 }
3839 case 'text': {
3840 $pdfcolor .= sprintf('cs %F scn', $tint);
3841 $this->TextColor = $pdfcolor;
3842 $this->fgcolor = $spotcolor;
3843 break;
3844 }
3845 }
3846 $this->ColorFlag = ($this->FillColor != $this->TextColor);
3847 if ($this->state == 2) {
3848 $this->_out($pdfcolor);
3849 }
3850 if ($this->inxobj) {
3851 // we are inside an XObject template
3852 $this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
3853 }
3854 return $pdfcolor;
3855 }
3856
3857 /**
3858 * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
3859 * @param string $name Name of the spot color.
3860 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3861 * @public
3862 * @since 4.0.024 (2008-09-12)
3863 * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3864 */
3865 public function setDrawSpotColor($name, $tint=100) {
3866 $this->setSpotColor('draw', $name, $tint);
3867 }
3868
3869 /**
3870 * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
3871 * @param string $name Name of the spot color.
3872 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3873 * @public
3874 * @since 4.0.024 (2008-09-12)
3875 * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
3876 */
3877 public function setFillSpotColor($name, $tint=100) {
3878 $this->setSpotColor('fill', $name, $tint);
3879 }
3880
3881 /**
3882 * Defines the spot color used for text.
3883 * @param string $name Name of the spot color.
3884 * @param int $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3885 * @public
3886 * @since 4.0.024 (2008-09-12)
3887 * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
3888 */
3889 public function setTextSpotColor($name, $tint=100) {
3890 $this->setSpotColor('text', $name, $tint);
3891 }
3892
3893 /**
3894 * Set the color array for the specified type ('draw', 'fill', 'text').
3895 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3896 * The method can be called before the first page is created and the value is retained from page to page.
3897 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3898 * @param array $color Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
3899 * @param boolean $ret If true do not send the PDF command.
3900 * @return string The PDF command or empty string.
3901 * @public
3902 * @since 3.1.000 (2008-06-11)
3903 */
3904 public function setColorArray($type, $color, $ret=false) {
3905 if (is_array($color)) {
3906 $color = array_values($color);
3907 // component: grey, RGB red or CMYK cyan
3908 $c = isset($color[0]) ? $color[0] : -1;
3909 // component: RGB green or CMYK magenta
3910 $m = isset($color[1]) ? $color[1] : -1;
3911 // component: RGB blue or CMYK yellow
3912 $y = isset($color[2]) ? $color[2] : -1;
3913 // component: CMYK black
3914 $k = isset($color[3]) ? $color[3] : -1;
3915 // color name
3916 $name = isset($color[4]) ? $color[4] : '';
3917 if ($c >= 0) {
3918 return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
3919 }
3920 }
3921 return '';
3922 }
3923
3924 /**
3925 * Defines the color used for all drawing operations (lines, rectangles and cell borders).
3926 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3927 * The method can be called before the first page is created and the value is retained from page to page.
3928 * @param array $color Array of colors (1, 3 or 4 values).
3929 * @param boolean $ret If true do not send the PDF command.
3930 * @return string the PDF command
3931 * @public
3932 * @since 3.1.000 (2008-06-11)
3933 * @see SetDrawColor()
3934 */
3935 public function setDrawColorArray($color, $ret=false) {
3936 return $this->setColorArray('draw', $color, $ret);
3937 }
3938
3939 /**
3940 * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
3941 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3942 * The method can be called before the first page is created and the value is retained from page to page.
3943 * @param array $color Array of colors (1, 3 or 4 values).
3944 * @param boolean $ret If true do not send the PDF command.
3945 * @public
3946 * @since 3.1.000 (2008-6-11)
3947 * @see SetFillColor()
3948 */
3949 public function setFillColorArray($color, $ret=false) {
3950 return $this->setColorArray('fill', $color, $ret);
3951 }
3952
3953 /**
3954 * Defines the color used for text. It can be expressed in RGB components or gray scale.
3955 * The method can be called before the first page is created and the value is retained from page to page.
3956 * @param array $color Array of colors (1, 3 or 4 values).
3957 * @param boolean $ret If true do not send the PDF command.
3958 * @public
3959 * @since 3.1.000 (2008-6-11)
3960 * @see SetFillColor()
3961 */
3962 public function setTextColorArray($color, $ret=false) {
3963 return $this->setColorArray('text', $color, $ret);
3964 }
3965
3966 /**
3967 * Defines the color used by the specified type ('draw', 'fill', 'text').
3968 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3969 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3970 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3971 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3972 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
3973 * @param boolean $ret If true do not send the command.
3974 * @param string $name spot color name (if any)
3975 * @return string The PDF command or empty string.
3976 * @public
3977 * @since 5.9.125 (2011-10-03)
3978 */
3979 public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3980 // set default values
3981 if (!is_numeric($col1)) {
3982 $col1 = 0;
3983 }
3984 if (!is_numeric($col2)) {
3985 $col2 = -1;
3986 }
3987 if (!is_numeric($col3)) {
3988 $col3 = -1;
3989 }
3990 if (!is_numeric($col4)) {
3991 $col4 = -1;
3992 }
3993 // set color by case
3994 $suffix = '';
3995 if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
3996 // Grey scale
3997 $col1 = max(0, min(255, $col1));
3998 $intcolor = array('G' => $col1);
3999 $pdfcolor = sprintf('%F ', ($col1 / 255));
4000 $suffix = 'g';
4001 } elseif ($col4 == -1) {
4002 // RGB
4003 $col1 = max(0, min(255, $col1));
4004 $col2 = max(0, min(255, $col2));
4005 $col3 = max(0, min(255, $col3));
4006 $intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
4007 $pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
4008 $suffix = 'rg';
4009 } else {
4010 $col1 = max(0, min(100, $col1));
4011 $col2 = max(0, min(100, $col2));
4012 $col3 = max(0, min(100, $col3));
4013 $col4 = max(0, min(100, $col4));
4014 if (empty($name)) {
4015 // CMYK
4016 $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
4017 $pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
4018 $suffix = 'k';
4019 } else {
4020 // SPOT COLOR
4021 $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
4022 $this->AddSpotColor($name, $col1, $col2, $col3, $col4);
4023 $pdfcolor = $this->setSpotColor($type, $name, 100);
4024 }
4025 }
4026 switch ($type) {
4027 case 'draw': {
4028 $pdfcolor .= strtoupper($suffix);
4029 $this->DrawColor = $pdfcolor;
4030 $this->strokecolor = $intcolor;
4031 break;
4032 }
4033 case 'fill': {
4034 $pdfcolor .= $suffix;
4035 $this->FillColor = $pdfcolor;
4036 $this->bgcolor = $intcolor;
4037 break;
4038 }
4039 case 'text': {
4040 $pdfcolor .= $suffix;
4041 $this->TextColor = $pdfcolor;
4042 $this->fgcolor = $intcolor;
4043 break;
4044 }
4045 }
4046 $this->ColorFlag = ($this->FillColor != $this->TextColor);
4047 if (($type != 'text') AND ($this->state == 2) AND $type !== 0) {
4048 if (!$ret) {
4049 $this->_out($pdfcolor);
4050 }
4051 return $pdfcolor;
4052 }
4053 return '';
4054 }
4055
4056 /**
4057 * Defines the color used for all drawing operations (lines, rectangles and cell borders). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
4058 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4059 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4060 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4061 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4062 * @param boolean $ret If true do not send the command.
4063 * @param string $name spot color name (if any)
4064 * @return string the PDF command
4065 * @public
4066 * @since 1.3
4067 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
4068 */
4069 public function setDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4070 return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
4071 }
4072
4073 /**
4074 * Defines the color used for all filling operations (filled rectangles and cell backgrounds). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
4075 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4076 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4077 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4078 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4079 * @param boolean $ret If true do not send the command.
4080 * @param string $name Spot color name (if any).
4081 * @return string The PDF command.
4082 * @public
4083 * @since 1.3
4084 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
4085 */
4086 public function setFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4087 return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
4088 }
4089
4090 /**
4091 * Defines the color used for text. It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
4092 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4093 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4094 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4095 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4096 * @param boolean $ret If true do not send the command.
4097 * @param string $name Spot color name (if any).
4098 * @return string Empty string.
4099 * @public
4100 * @since 1.3
4101 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4102 */
4103 public function setTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4104 return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4105 }
4106
4107 /**
4108 * Returns the length of a string in user unit. A font must be selected.<br>
4109 * @param string $s The string whose length is to be computed
4110 * @param string $fontname Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4111 * @param string $fontstyle Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line-through</li><li>O: overline</li></ul> or any combination. The default value is regular.
4112 * @param float $fontsize Font size in points. The default value is the current size.
4113 * @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
4114 * @return float[]|float total string length or array of characted widths
4115 * @phpstan-return ($getarray is true ? float[] : float) total string length or array of characted widths
4116 * @author Nicola Asuni
4117 * @public
4118 * @since 1.2
4119 */
4120 public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4121 return $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont), $s, $this->tmprtl, $this->isunicode, $this->CurrentFont), $fontname, $fontstyle, $fontsize, $getarray);
4122 }
4123
4124 /**
4125 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4126 * @param array $sa The array of chars whose total length is to be computed
4127 * @param string $fontname Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4128 * @param string $fontstyle Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular.
4129 * @param float $fontsize Font size in points. The default value is the current size.
4130 * @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
4131 * @return float[]|float total string length or array of characted widths
4132 * @phpstan-return ($getarray is true ? float[] : float) total string length or array of characted widths
4133 * @author Nicola Asuni
4134 * @public
4135 * @since 2.4.000 (2008-03-06)
4136 */
4137 public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4138 // store current values
4139 if (!TCPDF_STATIC::empty_string($fontname)) {
4140 $prev_FontFamily = $this->FontFamily;
4141 $prev_FontStyle = $this->FontStyle;
4142 $prev_FontSizePt = $this->FontSizePt;
4143 $this->setFont($fontname, $fontstyle, $fontsize, '', 'default', false);
4144 }
4145 // convert UTF-8 array to Latin1 if required
4146 if ($this->isunicode AND (!$this->isUnicodeFont())) {
4147 $sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
4148 }
4149 $w = 0; // total width
4150 $wa = array(); // array of characters widths
4151 foreach ($sa as $ck => $char) {
4152 // character width
4153 $cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4154 $wa[] = $cw;
4155 $w += $cw;
4156 }
4157 // restore previous values
4158 if (!TCPDF_STATIC::empty_string($fontname)) {
4159 $this->setFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4160 }
4161 if ($getarray) {
4162 return $wa;
4163 }
4164 return $w;
4165 }
4166
4167 /**
4168 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
4169 * @param int $char The char code whose length is to be returned
4170 * @param boolean $notlast If false ignore the font-spacing.
4171 * @return float char width
4172 * @author Nicola Asuni
4173 * @public
4174 * @since 2.4.000 (2008-03-06)
4175 */
4176 public function GetCharWidth($char, $notlast=true) {
4177 // get raw width
4178 $chw = $this->getRawCharWidth($char);
4179 if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
4180 // increase/decrease font spacing
4181 $chw += $this->font_spacing;
4182 }
4183 if ($this->font_stretching != 100) {
4184 // fixed stretching mode
4185 $chw *= ($this->font_stretching / 100);
4186 }
4187 return $chw;
4188 }
4189
4190 /**
4191 * Returns the length of the char in user unit for the current font.
4192 * @param int $char The char code whose length is to be returned
4193 * @return float char width
4194 * @author Nicola Asuni
4195 * @public
4196 * @since 5.9.000 (2010-09-28)
4197 */
4198 public function getRawCharWidth($char) {
4199 if ($char == 173) {
4200 // SHY character will not be printed
4201 return (0);
4202 }
4203 if (isset($this->CurrentFont['cw'][intval($char)])) {
4204 $w = $this->CurrentFont['cw'][intval($char)];
4205 } elseif (isset($this->CurrentFont['dw'])) {
4206 // default width
4207 $w = $this->CurrentFont['dw'];
4208 } elseif (isset($this->CurrentFont['cw'][32])) {
4209 // default width
4210 $w = $this->CurrentFont['cw'][32];
4211 } else {
4212 $w = 600;
4213 }
4214 return $this->getAbsFontMeasure($w);
4215 }
4216
4217 /**
4218 * Returns the numbero of characters in a string.
4219 * @param string $s The input string.
4220 * @return int number of characters
4221 * @public
4222 * @since 2.0.0001 (2008-01-07)
4223 */
4224 public function GetNumChars($s) {
4225 if ($this->isUnicodeFont()) {
4226 return count(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont));
4227 }
4228 return strlen($s);
4229 }
4230
4231 /**
4232 * Fill the list of available fonts ($this->fontlist).
4233 * @protected
4234 * @since 4.0.013 (2008-07-28)
4235 */
4236 protected function getFontsList() {
4237 if (($fontsdir = opendir(TCPDF_FONTS::_getfontpath())) !== false) {
4238 while (($file = readdir($fontsdir)) !== false) {
4239 if (substr($file, -4) == '.php') {
4240 array_push($this->fontlist, strtolower(basename($file, '.php')));
4241 }
4242 }
4243 closedir($fontsdir);
4244 }
4245 }
4246
4247 /**
4248 * Imports a TrueType, Type1, core, or CID0 font and makes it available.
4249 * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
4250 * The definition file (and the font file itself when embedding) must be present either in the current directory or in the one indicated by K_PATH_FONTS if the constant is defined. If it could not be found, the error "Could not include font definition file" is generated.
4251 * @param string $family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
4252 * @param string $style Font style. Possible values are (case insensitive):<ul><li>empty string: regular (default)</li><li>B: bold</li><li>I: italic</li><li>BI or IB: bold italic</li></ul>
4253 * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4254 * @return array|false array containing the font data, or false in case of error.
4255 * @param mixed $subset if true embed only a subset of the font (stores only the information related to the used characters); if false embed full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
4256 * @public
4257 * @since 1.5
4258 * @see SetFont(), setFontSubsetting()
4259 */
4260 public function AddFont($family, $style='', $fontfile='', $subset='default') {
4261 if ($subset === 'default') {
4262 $subset = $this->font_subsetting;
4263 }
4264 if ($this->pdfa_mode) {
4265 $subset = false;
4266 }
4267 if (TCPDF_STATIC::empty_string($family)) {
4268 if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
4269 $family = $this->FontFamily;
4270 } else {
4271 $this->Error('Empty font family');
4272 }
4273 }
4274 // move embedded styles on $style
4275 if (substr($family, -1) == 'I') {
4276 $style .= 'I';
4277 $family = substr($family, 0, -1);
4278 }
4279 if (substr($family, -1) == 'B') {
4280 $style .= 'B';
4281 $family = substr($family, 0, -1);
4282 }
4283 // normalize family name
4284 $family = strtolower($family);
4285 if ((!$this->isunicode) AND ($family == 'arial')) {
4286 $family = 'helvetica';
4287 }
4288 if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4289 $style = '';
4290 }
4291 if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
4292 // all fonts must be embedded
4293 $family = 'pdfa'.$family;
4294 }
4295 $tempstyle = strtoupper($style === null ? '' : $style);
4296 $style = '';
4297 // underline
4298 if (strpos($tempstyle, 'U') !== false) {
4299 $this->underline = true;
4300 } else {
4301 $this->underline = false;
4302 }
4303 // line-through (deleted)
4304 if (strpos($tempstyle, 'D') !== false) {
4305 $this->linethrough = true;
4306 } else {
4307 $this->linethrough = false;
4308 }
4309 // overline
4310 if (strpos($tempstyle, 'O') !== false) {
4311 $this->overline = true;
4312 } else {
4313 $this->overline = false;
4314 }
4315 // bold
4316 if (strpos($tempstyle, 'B') !== false) {
4317 $style .= 'B';
4318 }
4319 // oblique
4320 if (strpos($tempstyle, 'I') !== false) {
4321 $style .= 'I';
4322 }
4323 $bistyle = $style;
4324 $fontkey = $family.$style;
4325 $font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
4326 $fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4327 // check if the font has been already added
4328 $fb = $this->getFontBuffer($fontkey);
4329 if ($fb !== false) {
4330 if ($this->inxobj) {
4331 // we are inside an XObject template
4332 $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
4333 }
4334 return $fontdata;
4335 }
4336 // get specified font directory (if any)
4337 $fontdir = false;
4338 if (!TCPDF_STATIC::empty_string($fontfile)) {
4339 $fontdir = dirname($fontfile);
4340 if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
4341 $fontdir = '';
4342 } else {
4343 $fontdir .= '/';
4344 }
4345 }
4346 // true when the font style variation is missing
4347 $missing_style = false;
4348 // search and include font file
4349 if (TCPDF_STATIC::empty_string($fontfile) OR (!@TCPDF_STATIC::file_exists($fontfile))) {
4350 // build a standard filenames for specified font
4351 $tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
4352 $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4353 if (TCPDF_STATIC::empty_string($fontfile)) {
4354 $missing_style = true;
4355 // try to remove the style part
4356 $tmp_fontfile = str_replace(' ', '', $family).'.php';
4357 $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4358 }
4359 }
4360 // include font file
4361 if (!TCPDF_STATIC::empty_string($fontfile) AND (@TCPDF_STATIC::file_exists($fontfile))) {
4362 $type=null;
4363 $name=null;
4364 $desc=null;
4365 $up=-null;
4366 $ut=null;
4367 $cw=null;
4368 $cbbox=null;
4369 $dw=null;
4370 $enc=null;
4371 $cidinfo=null;
4372 $file=null;
4373 $ctg=null;
4374 $diff=null;
4375 $originalsize=null;
4376 $size1=null;
4377 $size2=null;
4378 include($fontfile);
4379 } else {
4380 $this->Error('Could not include font definition file: '.$family.'');
4381 }
4382 // check font parameters
4383 if ((!isset($type)) OR (!isset($cw))) {
4384 $this->Error('The font definition file has a bad format: '.$fontfile.'');
4385 }
4386 // SET default parameters
4387 if (!isset($file) OR TCPDF_STATIC::empty_string($file)) {
4388 $file = '';
4389 }
4390 if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
4391 $enc = '';
4392 }
4393 if (!isset($cidinfo) OR TCPDF_STATIC::empty_string($cidinfo)) {
4394 $cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
4395 $cidinfo['uni2cid'] = array();
4396 }
4397 if (!isset($ctg) OR TCPDF_STATIC::empty_string($ctg)) {
4398 $ctg = '';
4399 }
4400 if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
4401 $desc = array();
4402 }
4403 if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
4404 $up = -100;
4405 }
4406 if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
4407 $ut = 50;
4408 }
4409 if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
4410 $cw = array();
4411 }
4412 if (!isset($dw) OR TCPDF_STATIC::empty_string($dw)) {
4413 // set default width
4414 if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4415 $dw = $desc['MissingWidth'];
4416 } elseif (isset($cw[32])) {
4417 $dw = $cw[32];
4418 } else {
4419 $dw = 600;
4420 }
4421 }
4422 ++$this->numfonts;
4423 if ($type == 'core') {
4424 $name = $this->CoreFonts[$fontkey];
4425 $subset = false;
4426 } elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4427 $subset = false;
4428 } elseif ($type == 'TrueTypeUnicode') {
4429 $enc = 'Identity-H';
4430 } elseif ($type == 'cidfont0') {
4431 if ($this->pdfa_mode) {
4432 $this->Error('All fonts must be embedded in PDF/A mode!');
4433 }
4434 } else {
4435 $this->Error('Unknown font type: '.$type.'');
4436 }
4437 // set name if unset
4438 if (empty($name)) {
4439 $name = $fontkey;
4440 }
4441 // create artificial font style variations if missing (only works with non-embedded fonts)
4442 if (($type != 'core') AND $missing_style) {
4443 // style variations
4444 $styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4445 $name .= $styles[$bistyle];
4446 // artificial bold
4447 if (strpos($bistyle, 'B') !== false) {
4448 if (isset($desc['StemV'])) {
4449 // from normal to bold
4450 $desc['StemV'] = round($desc['StemV'] * 1.75);
4451 } else {
4452 // bold
4453 $desc['StemV'] = 123;
4454 }
4455 }
4456 // artificial italic
4457 if (strpos($bistyle, 'I') !== false) {
4458 if (isset($desc['ItalicAngle'])) {
4459 $desc['ItalicAngle'] -= 11;
4460 } else {
4461 $desc['ItalicAngle'] = -11;
4462 }
4463 if (isset($desc['Flags'])) {
4464 $desc['Flags'] |= 64; //bit 7
4465 } else {
4466 $desc['Flags'] = 64;
4467 }
4468 }
4469 }
4470 // check if the array of characters bounding boxes is defined
4471 if (!isset($cbbox)) {
4472 $cbbox = array();
4473 }
4474 // initialize subsetchars
4475 $subsetchars = array_fill(0, 255, true);
4476 $this->setFontBuffer($fontkey, array('fontkey' => $fontkey, 'i' => $this->numfonts, 'type' => $type, 'name' => $name, 'desc' => $desc, 'up' => $up, 'ut' => $ut, 'cw' => $cw, 'cbbox' => $cbbox, 'dw' => $dw, 'enc' => $enc, 'cidinfo' => $cidinfo, 'file' => $file, 'ctg' => $ctg, 'subset' => $subset, 'subsetchars' => $subsetchars));
4477 if ($this->inxobj) {
4478 // we are inside an XObject template
4479 $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
4480 }
4481 if (!empty($diff)) {
4482 //Search existing encodings
4483 $d = 0;
4484 $nb = count($this->diffs);
4485 for ($i=1; $i <= $nb; ++$i) {
4486 if ($this->diffs[$i] == $diff) {
4487 $d = $i;
4488 break;
4489 }
4490 }
4491 if ($d == 0) {
4492 $d = $nb + 1;
4493 $this->diffs[$d] = $diff;
4494 }
4495 $this->setFontSubBuffer($fontkey, 'diff', $d);
4496 }
4497 if (!TCPDF_STATIC::empty_string($file)) {
4498 if (!isset($this->FontFiles[$file])) {
4499 if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4500 $this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4501 } elseif ($type != 'core') {
4502 $this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4503 }
4504 } else {
4505 // update fontkeys that are sharing this font file
4506 $this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
4507 if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
4508 $this->FontFiles[$file]['fontkeys'][] = $fontkey;
4509 }
4510 }
4511 }
4512 return $fontdata;
4513 }
4514
4515 /**
4516 * Sets the font used to print character strings.
4517 * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
4518 * The method can be called before the first page is created and the font is retained from page to page.
4519 * If you just wish to change the current font size, it is simpler to call SetFontSize().
4520 * Note: for the standard fonts, the font metric files must be accessible. There are three possibilities for this:<ul><li>They are in the current directory (the one where the running script lies)</li><li>They are in one of the directories defined by the include_path parameter</li><li>They are in the directory defined by the K_PATH_FONTS constant</li></ul><br />
4521 * @param string $family Family font. It can be either a name defined by AddFont() or one of the standard Type1 families (case insensitive):<ul><li>times (Times-Roman)</li><li>timesb (Times-Bold)</li><li>timesi (Times-Italic)</li><li>timesbi (Times-BoldItalic)</li><li>helvetica (Helvetica)</li><li>helveticab (Helvetica-Bold)</li><li>helveticai (Helvetica-Oblique)</li><li>helveticabi (Helvetica-BoldOblique)</li><li>courier (Courier)</li><li>courierb (Courier-Bold)</li><li>courieri (Courier-Oblique)</li><li>courierbi (Courier-BoldOblique)</li><li>symbol (Symbol)</li><li>zapfdingbats (ZapfDingbats)</li></ul> It is also possible to pass an empty string. In that case, the current family is retained.
4522 * @param string $style Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats basic fonts or other fonts when not defined.
4523 * @param float|null $size Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12
4524 * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4525 * @param mixed $subset if true embed only a subset of the font (stores only the information related to the used characters); if false embed full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
4526 * @param boolean $out if true output the font size command, otherwise only set the font properties.
4527 * @author Nicola Asuni
4528 * @public
4529 * @since 1.0
4530 * @see AddFont(), SetFontSize()
4531 */
4532 public function setFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
4533 //Select a font; size given in points
4534 if ($size === null) {
4535 $size = $this->FontSizePt;
4536 }
4537 if ($size < 0) {
4538 $size = 0;
4539 }
4540 // try to add font (if not already added)
4541 $fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4542 $this->FontFamily = $fontdata['family'];
4543 $this->FontStyle = $fontdata['style'];
4544 if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
4545 // save subset chars of the previous font
4546 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
4547 }
4548 $this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
4549 $this->setFontSize($size, $out);
4550 }
4551
4552 /**
4553 * Defines the size of the current font.
4554 * @param float $size The font size in points.
4555 * @param boolean $out if true output the font size command, otherwise only set the font properties.
4556 * @public
4557 * @since 1.0
4558 * @see SetFont()
4559 */
4560 public function setFontSize($size, $out=true) {
4561 $size = (float)$size;
4562 // font size in points
4563 $this->FontSizePt = $size;
4564 // font size in user units
4565 $this->FontSize = $size / $this->k;
4566 // calculate some font metrics
4567 if (isset($this->CurrentFont['desc']['FontBBox'])) {
4568 $bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4569 $font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4570 } else {
4571 $font_height = $size * 1.219;
4572 }
4573 if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
4574 $font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
4575 }
4576 if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
4577 $font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
4578 }
4579 if (!isset($font_ascent) AND !isset($font_descent)) {
4580 // core font
4581 $font_ascent = 0.76 * $font_height;
4582 $font_descent = $font_height - $font_ascent;
4583 } elseif (!isset($font_descent)) {
4584 $font_descent = $font_height - $font_ascent;
4585 } elseif (!isset($font_ascent)) {
4586 $font_ascent = $font_height - $font_descent;
4587 }
4588 $this->FontAscent = ($font_ascent / $this->k);
4589 $this->FontDescent = ($font_descent / $this->k);
4590 if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
4591 $this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4592 }
4593 }
4594
4595 /**
4596 * Returns the bounding box of the current font in user units.
4597 * @return array
4598 * @public
4599 * @since 5.9.152 (2012-03-23)
4600 */
4601 public function getFontBBox() {
4602 $fbbox = array();
4603 if (isset($this->CurrentFont['desc']['FontBBox'])) {
4604 $tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4605 $fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
4606 } else {
4607 // Find max width
4608 if (isset($this->CurrentFont['desc']['MaxWidth'])) {
4609 $maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
4610 } else {
4611 $maxw = 0;
4612 if (isset($this->CurrentFont['desc']['MissingWidth'])) {
4613 $maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
4614 }
4615 if (isset($this->CurrentFont['desc']['AvgWidth'])) {
4616 $maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
4617 }
4618 if (isset($this->CurrentFont['dw'])) {
4619 $maxw = max($maxw, $this->CurrentFont['dw']);
4620 }
4621 foreach ($this->CurrentFont['cw'] as $char => $w) {
4622 $maxw = max($maxw, $w);
4623 }
4624 if ($maxw == 0) {
4625 $maxw = 600;
4626 }
4627 $maxw = $this->getAbsFontMeasure($maxw);
4628 }
4629 $fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
4630 }
4631 return $fbbox;
4632 }
4633
4634 /**
4635 * Convert a relative font measure into absolute value.
4636 * @param int $s Font measure.
4637 * @return float Absolute measure.
4638 * @since 5.9.186 (2012-09-13)
4639 */
4640 public function getAbsFontMeasure($s) {
4641 return ($s * $this->FontSize / 1000);
4642 }
4643
4644 /**
4645 * Returns the glyph bounding box of the specified character in the current font in user units.
4646 * @param int $char Input character code.
4647 * @return false|array array(xMin, yMin, xMax, yMax) or FALSE if not defined.
4648 * @since 5.9.186 (2012-09-13)
4649 */
4650 public function getCharBBox($char) {
4651 $c = intval($char);
4652 if (isset($this->CurrentFont['cw'][$c])) {
4653 // glyph is defined ... use zero width & height for glyphs without outlines
4654 $result = array(0,0,0,0);
4655 if (isset($this->CurrentFont['cbbox'][$c])) {
4656 $result = $this->CurrentFont['cbbox'][$c];
4657 }
4658 return array_map(array($this,'getAbsFontMeasure'), $result);
4659 }
4660 return false;
4661 }
4662
4663 /**
4664 * Return the font descent value
4665 * @param string $font font name
4666 * @param string $style font style
4667 * @param float $size The size (in points)
4668 * @return int font descent
4669 * @public
4670 * @author Nicola Asuni
4671 * @since 4.9.003 (2010-03-30)
4672 */
4673 public function getFontDescent($font, $style='', $size=0) {
4674 $fontdata = $this->AddFont($font, $style);
4675 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4676 if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
4677 $descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
4678 } else {
4679 $descent = (1.219 * 0.24 * $size);
4680 }
4681 return ($descent / $this->k);
4682 }
4683
4684 /**
4685 * Return the font ascent value.
4686 * @param string $font font name
4687 * @param string $style font style
4688 * @param float $size The size (in points)
4689 * @return int font ascent
4690 * @public
4691 * @author Nicola Asuni
4692 * @since 4.9.003 (2010-03-30)
4693 */
4694 public function getFontAscent($font, $style='', $size=0) {
4695 $fontdata = $this->AddFont($font, $style);
4696 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4697 if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
4698 $ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
4699 } else {
4700 $ascent = 1.219 * 0.76 * $size;
4701 }
4702 return ($ascent / $this->k);
4703 }
4704
4705 /**
4706 * Return true in the character is present in the specified font.
4707 * @param mixed $char Character to check (integer value or string)
4708 * @param string $font Font name (family name).
4709 * @param string $style Font style.
4710 * @return bool true if the char is defined, false otherwise.
4711 * @public
4712 * @since 5.9.153 (2012-03-28)
4713 */
4714 public function isCharDefined($char, $font='', $style='') {
4715 if (is_string($char)) {
4716 // get character code
4717 $char = TCPDF_FONTS::UTF8StringToArray($char, $this->isunicode, $this->CurrentFont);
4718 $char = $char[0];
4719 }
4720 if (TCPDF_STATIC::empty_string($font)) {
4721 if (TCPDF_STATIC::empty_string($style)) {
4722 return (isset($this->CurrentFont['cw'][intval($char)]));
4723 }
4724 $font = $this->FontFamily;
4725 }
4726 $fontdata = $this->AddFont($font, $style);
4727 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4728 return (isset($fontinfo['cw'][intval($char)]));
4729 }
4730
4731 /**
4732 * Replace missing font characters on selected font with specified substitutions.
4733 * @param string $text Text to process.
4734 * @param string $font Font name (family name).
4735 * @param string $style Font style.
4736 * @param array $subs Array of possible character substitutions. The key is the character to check (integer value) and the value is a single intege value or an array of possible substitutes.
4737 * @return string Processed text.
4738 * @public
4739 * @since 5.9.153 (2012-03-28)
4740 */
4741 public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
4742 if (empty($subs)) {
4743 return $text;
4744 }
4745 if (TCPDF_STATIC::empty_string($font)) {
4746 $font = $this->FontFamily;
4747 }
4748 $fontdata = $this->AddFont($font, $style);
4749 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4750 $uniarr = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
4751 foreach ($uniarr as $k => $chr) {
4752 if (!isset($fontinfo['cw'][$chr])) {
4753 // this character is missing on the selected font
4754 if (isset($subs[$chr])) {
4755 // we have available substitutions
4756 if (is_array($subs[$chr])) {
4757 foreach($subs[$chr] as $s) {
4758 if (isset($fontinfo['cw'][$s])) {
4759 $uniarr[$k] = $s;
4760 break;
4761 }
4762 }
4763 } elseif (isset($fontinfo['cw'][$subs[$chr]])) {
4764 $uniarr[$k] = $subs[$chr];
4765 }
4766 }
4767 }
4768 }
4769 return TCPDF_FONTS::UniArrSubString(TCPDF_FONTS::UTF8ArrayToUniArray($uniarr, $this->isunicode));
4770 }
4771
4772 /**
4773 * Defines the default monospaced font.
4774 * @param string $font Font name.
4775 * @public
4776 * @since 4.5.025
4777 */
4778 public function setDefaultMonospacedFont($font) {
4779 $this->default_monospaced_font = $font;
4780 }
4781
4782 /**
4783 * Creates a new internal link and returns its identifier. An internal link is a clickable area which directs to another place within the document.<br />
4784 * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
4785 * @public
4786 * @since 1.5
4787 * @see Cell(), Write(), Image(), Link(), SetLink()
4788 */
4789 public function AddLink() {
4790 // create a new internal link
4791 $n = count($this->links) + 1;
4792 $this->links[$n] = array('p' => 0, 'y' => 0, 'f' => false);
4793 return $n;
4794 }
4795
4796 /**
4797 * Defines the page and position a link points to.
4798 * @param int $link The link identifier returned by AddLink()
4799 * @param float $y Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
4800 * @param int|string $page Number of target page; -1 indicates the current page (default value). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
4801 * @public
4802 * @since 1.5
4803 * @see AddLink()
4804 */
4805 public function setLink($link, $y=0, $page=-1) {
4806 $fixed = false;
4807 if (!empty($page) AND (substr($page, 0, 1) == '*')) {
4808 $page = intval(substr($page, 1));
4809 // this page number will not be changed when moving/add/deleting pages
4810 $fixed = true;
4811 }
4812 if ($page < 0) {
4813 $page = $this->page;
4814 }
4815 if ($y == -1) {
4816 $y = $this->y;
4817 }
4818 $this->links[$link] = array('p' => $page, 'y' => $y, 'f' => $fixed);
4819 }
4820
4821 /**
4822 * Puts a link on a rectangular area of the page.
4823 * Text or image links are generally put via Cell(), Write() or Image(), but this method can be useful for instance to define a clickable area inside an image.
4824 * @param float $x Abscissa of the upper-left corner of the rectangle
4825 * @param float $y Ordinate of the upper-left corner of the rectangle
4826 * @param float $w Width of the rectangle
4827 * @param float $h Height of the rectangle
4828 * @param mixed $link URL or identifier returned by AddLink()
4829 * @param int $spaces number of spaces on the text to link
4830 * @public
4831 * @since 1.5
4832 * @see AddLink(), Annotation(), Cell(), Write(), Image()
4833 */
4834 public function Link($x, $y, $w, $h, $link, $spaces=0) {
4835 $this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
4836 }
4837
4838 /**
4839 * Puts a markup annotation on a rectangular area of the page.
4840 * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
4841 * @param float $x Abscissa of the upper-left corner of the rectangle
4842 * @param float $y Ordinate of the upper-left corner of the rectangle
4843 * @param float $w Width of the rectangle
4844 * @param float $h Height of the rectangle
4845 * @param string $text annotation text or alternate content
4846 * @param array $opt array of options (see section 8.4 of PDF reference 1.7).
4847 * @param int $spaces number of spaces on the text to link
4848 * @public
4849 * @since 4.0.018 (2008-08-06)
4850 */
4851 public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
4852 if ($this->inxobj) {
4853 // store parameters for later use on template
4854 $this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
4855 return;
4856 }
4857 if ($x === '') {
4858 $x = $this->x;
4859 }
4860 if ($y === '') {
4861 $y = $this->y;
4862 }
4863 // check page for no-write regions and adapt page margins if necessary
4864 list($x, $y) = $this->checkPageRegions($h, $x, $y);
4865 // recalculate coordinates to account for graphic transformations
4866 if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
4867 for ($i=$this->transfmatrix_key; $i > 0; --$i) {
4868 $maxid = count($this->transfmatrix[$i]) - 1;
4869 for ($j=$maxid; $j >= 0; --$j) {
4870 $ctm = $this->transfmatrix[$i][$j];
4871 if (isset($ctm['a'])) {
4872 $x = $x * $this->k;
4873 $y = ($this->h - $y) * $this->k;
4874 $w = $w * $this->k;
4875 $h = $h * $this->k;
4876 // top left
4877 $xt = $x;
4878 $yt = $y;
4879 $x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4880 $y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4881 // top right
4882 $xt = $x + $w;
4883 $yt = $y;
4884 $x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4885 $y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4886 // bottom left
4887 $xt = $x;
4888 $yt = $y - $h;
4889 $x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4890 $y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4891 // bottom right
4892 $xt = $x + $w;
4893 $yt = $y - $h;
4894 $x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4895 $y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4896 // new coordinates (rectangle area)
4897 $x = min($x1, $x2, $x3, $x4);
4898 $y = max($y1, $y2, $y3, $y4);
4899 $w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
4900 $h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
4901 $x = $x / $this->k;
4902 $y = $this->h - ($y / $this->k);
4903 }
4904 }
4905 }
4906 }
4907 if ($this->page <= 0) {
4908 $page = 1;
4909 } else {
4910 $page = $this->page;
4911 }
4912 if (!isset($this->PageAnnots[$page])) {
4913 $this->PageAnnots[$page] = array();
4914 }
4915 $this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
4916 if (!$this->pdfa_mode || ($this->pdfa_mode && $this->pdfa_version == 3)) {
4917 if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
4918 AND (@TCPDF_STATIC::file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
4919 AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
4920 $this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
4921 }
4922 }
4923 // Add widgets annotation's icons
4924 if (isset($opt['mk']['i']) AND @TCPDF_STATIC::file_exists($opt['mk']['i'])) {
4925 $this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
4926 }
4927 if (isset($opt['mk']['ri']) AND @TCPDF_STATIC::file_exists($opt['mk']['ri'])) {
4928 $this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4929 }
4930 if (isset($opt['mk']['ix']) AND @TCPDF_STATIC::file_exists($opt['mk']['ix'])) {
4931 $this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4932 }
4933 }
4934
4935 /**
4936 * Embed the attached files.
4937 * @since 6.9.000 (2025-02-11)
4938 * @public
4939 */
4940 public function EmbedFile($opt) {
4941 if (!$this->pdfa_mode || ($this->pdfa_mode && $this->pdfa_version == 3)) {
4942 if ((($opt['Subtype'] == 'FileAttachment')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
4943 AND (@TCPDF_STATIC::file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
4944 AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
4945 $this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
4946 }
4947 }
4948 }
4949
4950 /**
4951 * Embed the attached files.
4952 * @since 6.9.000 (2025-02-11)
4953 * @public
4954 */
4955 public function EmbedFileFromString($filename, $content) {
4956 if (!$this->pdfa_mode || ($this->pdfa_mode && $this->pdfa_version == 3)) {
4957 $this->embeddedfiles[$filename] = array('f' => ++$this->n, 'n' => ++$this->n, 'content' => $content );
4958 }
4959 }
4960
4961 /**
4962 * Embedd the attached files.
4963 * @since 4.4.000 (2008-12-07)
4964 * @protected
4965 * @see Annotation()
4966 */
4967 protected function _putEmbeddedFiles() {
4968 if ($this->pdfa_mode && $this->pdfa_version != 3) {
4969 // embedded files are not allowed in PDF/A mode version 1 and 2
4970 return;
4971 }
4972 reset($this->embeddedfiles);
4973 foreach ($this->embeddedfiles as $filename => $filedata) {
4974 $data = false;
4975 if (isset($filedata['file']) && !empty($filedata['file'])) {
4976 $data = $this->getCachedFileContents($filedata['file']);
4977 } elseif ($filedata['content'] && !empty($filedata['content'])) {
4978 $data = $filedata['content'];
4979 }
4980 if ($data !== FALSE) {
4981 $rawsize = strlen($data);
4982 if ($rawsize > 0) {
4983 // update name tree
4984 $this->efnames[$filename] = $filedata['f'].' 0 R';
4985 // embedded file specification object
4986 $out = $this->_getobj($filedata['f'])."\n";
4987 $out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']);
4988 $out .= ' /UF '.$this->_datastring($filename, $filedata['f']);
4989 $out .= ' /AFRelationship /Source';
4990 $out .= ' /EF <</F '.$filedata['n'].' 0 R>> >>';
4991 $out .= "\n".'endobj';
4992 $this->_out($out);
4993 // embedded file object
4994 $filter = '';
4995 if ($this->compress) {
4996 $data = gzcompress($data);
4997 $filter .= ' /Filter /FlateDecode';
4998 }
4999 if ($this->pdfa_version == 3) {
5000 $filter .= ' /Subtype /text#2Fxml';
5001 }
5002
5003 $stream = $this->_getrawstream($data, $filedata['n']);
5004 $out = $this->_getobj($filedata['n'])."\n";
5005 $out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
5006 $out .= ' stream'."\n".$stream."\n".'endstream';
5007 $out .= "\n".'endobj';
5008 $this->_out($out);
5009 }
5010 }
5011 }
5012 }
5013
5014 /**
5015 * Prints a text cell at the specified position.
5016 * This method allows to place a string precisely on the page.
5017 * @param float $x Abscissa of the cell origin
5018 * @param float $y Ordinate of the cell origin
5019 * @param string $txt String to print
5020 * @param int $fstroke outline size in user units (0 = disable)
5021 * @param boolean $fclip if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
5022 * @param boolean $ffill if true fills the text
5023 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5024 * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5025 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5026 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5027 * @param mixed $link URL or identifier returned by AddLink().
5028 * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5029 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5030 * @param string $calign cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li><li>B : cell bottom</li></ul>
5031 * @param string $valign text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
5032 * @param boolean $rtloff if true uses the page top-left corner as origin of axis for $x and $y initial position.
5033 * @public
5034 * @since 1.0
5035 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
5036 */
5037 public function Text($x, $y, $txt, $fstroke=0, $fclip=false, $ffill=true, $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M', $rtloff=false) {
5038 $textrendermode = $this->textrendermode;
5039 $textstrokewidth = $this->textstrokewidth;
5040 $this->setTextRenderingMode($fstroke, $ffill, $fclip);
5041 $this->setXY($x, $y, $rtloff);
5042 $this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
5043 // restore previous rendering mode
5044 $this->textrendermode = $textrendermode;
5045 $this->textstrokewidth = $textstrokewidth;
5046 }
5047
5048 /**
5049 * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
5050 * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
5051 * This method is called automatically and should not be called directly by the application.
5052 * @return bool
5053 * @public
5054 * @since 1.4
5055 * @see SetAutoPageBreak()
5056 */
5057 public function AcceptPageBreak() {
5058 if ($this->num_columns > 1) {
5059 // multi column mode
5060 if ($this->current_column < ($this->num_columns - 1)) {
5061 // go to next column
5062 $this->selectColumn($this->current_column + 1);
5063 } elseif ($this->AutoPageBreak) {
5064 // add a new page
5065 $this->AddPage();
5066 // set first column
5067 $this->selectColumn(0);
5068 }
5069 // avoid page breaking from checkPageBreak()
5070 return false;
5071 }
5072 return $this->AutoPageBreak;
5073 }
5074
5075 /**
5076 * Add page if needed.
5077 * @param float $h Cell height. Default value: 0.
5078 * @param float|null $y starting y position, leave empty for current position.
5079 * @param bool $addpage if true add a page, otherwise only return the true/false state
5080 * @return bool true in case of page break, false otherwise.
5081 * @since 3.2.000 (2008-07-01)
5082 * @protected
5083 */
5084 protected function checkPageBreak($h=0, $y=null, $addpage=true) {
5085 if (TCPDF_STATIC::empty_string($y)) {
5086 $y = $this->y;
5087 }
5088 $current_page = $this->page;
5089 if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
5090 if ($addpage) {
5091 //Automatic page break
5092 $x = $this->x;
5093 $this->AddPage($this->CurOrientation);
5094 $this->y = $this->tMargin;
5095 $oldpage = $this->page - 1;
5096 if ($this->rtl) {
5097 if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
5098 $this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
5099 } else {
5100 $this->x = $x;
5101 }
5102 } else {
5103 if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
5104 $this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
5105 } else {
5106 $this->x = $x;
5107 }
5108 }
5109 }
5110 return true;
5111 }
5112 if ($current_page != $this->page) {
5113 // account for columns mode
5114 return true;
5115 }
5116 return false;
5117 }
5118
5119 /**
5120 * 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 />
5121 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5122 * @param float $w Cell width. If 0, the cell extends up to the right margin.
5123 * @param float $h Cell height. Default value: 0.
5124 * @param string $txt String to print. Default value: empty string.
5125 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5126 * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul> Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5127 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5128 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5129 * @param mixed $link URL or identifier returned by AddLink().
5130 * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5131 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5132 * @param string $calign cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5133 * @param string $valign text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
5134 * @public
5135 * @since 1.0
5136 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
5137 */
5138 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') {
5139 $prev_cell_margin = $this->cell_margin;
5140 $prev_cell_padding = $this->cell_padding;
5141 $this->adjustCellPadding($border);
5142 if (!$ignore_min_height) {
5143 $min_cell_height = $this->getCellHeight($this->FontSize);
5144 if ($h < $min_cell_height) {
5145 $h = $min_cell_height;
5146 }
5147 }
5148 $this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5149 // apply text shadow if enabled
5150 if ($this->txtshadow['enabled']) {
5151 // save data
5152 $x = $this->x;
5153 $y = $this->y;
5154 $bc = $this->bgcolor;
5155 $fc = $this->fgcolor;
5156 $sc = $this->strokecolor;
5157 $alpha = $this->alpha;
5158 // print shadow
5159 $this->x += $this->txtshadow['depth_w'];
5160 $this->y += $this->txtshadow['depth_h'];
5161 $this->setFillColorArray($this->txtshadow['color']);
5162 $this->setTextColorArray($this->txtshadow['color']);
5163 $this->setDrawColorArray($this->txtshadow['color']);
5164 if ($this->txtshadow['opacity'] != $alpha['CA']) {
5165 $this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
5166 }
5167 if ($this->state == 2) {
5168 $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5169 }
5170 //restore data
5171 $this->x = $x;
5172 $this->y = $y;
5173 $this->setFillColorArray($bc);
5174 $this->setTextColorArray($fc);
5175 $this->setDrawColorArray($sc);
5176 if ($this->txtshadow['opacity'] != $alpha['CA']) {
5177 $this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
5178 }
5179 }
5180 if ($this->state == 2) {
5181 $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5182 }
5183 $this->cell_padding = $prev_cell_padding;
5184 $this->cell_margin = $prev_cell_margin;
5185 }
5186
5187 /**
5188 * 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 />
5189 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5190 * @param float $w Cell width. If 0, the cell extends up to the right margin.
5191 * @param float $h Cell height. Default value: 0.
5192 * @param string $txt String to print. Default value: empty string.
5193 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5194 * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5195 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5196 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5197 * @param mixed $link URL or identifier returned by AddLink().
5198 * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5199 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5200 * @param string $calign cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5201 * @param string $valign text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>M : middle</li><li>B : bottom</li></ul>
5202 * @return string containing cell code
5203 * @protected
5204 * @since 1.0
5205 * @see Cell()
5206 */
5207 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') {
5208 // replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
5209 $txt = is_null($txt) ? '' : $txt;
5210 $txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
5211 $prev_cell_margin = $this->cell_margin;
5212 $prev_cell_padding = $this->cell_padding;
5213 $txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
5214 $rs = ''; //string to be returned
5215 $this->adjustCellPadding($border);
5216 if (!$ignore_min_height) {
5217 $min_cell_height = $this->getCellHeight($this->FontSize);
5218 if ($h < $min_cell_height) {
5219 $h = $min_cell_height;
5220 }
5221 }
5222 $k = $this->k;
5223 // check page for no-write regions and adapt page margins if necessary
5224 list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
5225 if ($this->rtl) {
5226 $x = $this->x - $this->cell_margin['R'];
5227 } else {
5228 $x = $this->x + $this->cell_margin['L'];
5229 }
5230 $y = $this->y + $this->cell_margin['T'];
5231 $prev_font_stretching = $this->font_stretching;
5232 $prev_font_spacing = $this->font_spacing;
5233 // cell vertical alignment
5234 switch ($calign) {
5235 case 'A': {
5236 // font top
5237 switch ($valign) {
5238 case 'T': {
5239 // top
5240 $y -= $this->cell_padding['T'];
5241 break;
5242 }
5243 case 'B': {
5244 // bottom
5245 $y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5246 break;
5247 }
5248 default:
5249 case 'C':
5250 case 'M': {
5251 // center
5252 $y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5253 break;
5254 }
5255 }
5256 break;
5257 }
5258 case 'L': {
5259 // font baseline
5260 switch ($valign) {
5261 case 'T': {
5262 // top
5263 $y -= ($this->cell_padding['T'] + $this->FontAscent);
5264 break;
5265 }
5266 case 'B': {
5267 // bottom
5268 $y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5269 break;
5270 }
5271 default:
5272 case 'C':
5273 case 'M': {
5274 // center
5275 $y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5276 break;
5277 }
5278 }
5279 break;
5280 }
5281 case 'D': {
5282 // font bottom
5283 switch ($valign) {
5284 case 'T': {
5285 // top
5286 $y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5287 break;
5288 }
5289 case 'B': {
5290 // bottom
5291 $y -= ($h - $this->cell_padding['B']);
5292 break;
5293 }
5294 default:
5295 case 'C':
5296 case 'M': {
5297 // center
5298 $y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5299 break;
5300 }
5301 }
5302 break;
5303 }
5304 case 'B': {
5305 // cell bottom
5306 $y -= $h;
5307 break;
5308 }
5309 case 'C':
5310 case 'M': {
5311 // cell center
5312 $y -= ($h / 2);
5313 break;
5314 }
5315 default:
5316 case 'T': {
5317 // cell top
5318 break;
5319 }
5320 }
5321 // text vertical alignment
5322 switch ($valign) {
5323 case 'T': {
5324 // top
5325 $yt = $y + $this->cell_padding['T'];
5326 break;
5327 }
5328 case 'B': {
5329 // bottom
5330 $yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5331 break;
5332 }
5333 default:
5334 case 'C':
5335 case 'M': {
5336 // center
5337 $yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5338 break;
5339 }
5340 }
5341 $basefonty = $yt + $this->FontAscent;
5342 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5343 if ($this->rtl) {
5344 $w = $x - $this->lMargin;
5345 } else {
5346 $w = $this->w - $this->rMargin - $x;
5347 }
5348 }
5349 $s = '';
5350 // fill and borders
5351 if (is_string($border) AND (strlen($border) == 4)) {
5352 // full border
5353 $border = 1;
5354 }
5355 if ($fill OR ($border == 1)) {
5356 if ($fill) {
5357 $op = ($border == 1) ? 'B' : 'f';
5358 } else {
5359 $op = 'S';
5360 }
5361 if ($this->rtl) {
5362 $xk = (($x - $w) * $k);
5363 } else {
5364 $xk = ($x * $k);
5365 }
5366 $s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5367 }
5368 // draw borders
5369 $s .= $this->getCellBorder($x, $y, $w, $h, $border);
5370 if ($txt != '') {
5371 $txt2 = $txt;
5372 if ($this->isunicode) {
5373 if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
5374 $txt2 = TCPDF_FONTS::UTF8ToLatin1($txt2, $this->isunicode, $this->CurrentFont);
5375 } else {
5376 $unicode = TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont); // array of UTF-8 unicode values
5377 $unicode = TCPDF_FONTS::utf8Bidi($unicode, '', $this->tmprtl, $this->isunicode, $this->CurrentFont);
5378 // replace thai chars (if any)
5379 if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
5380 // number of chars
5381 $numchars = count($unicode);
5382 // po pla, for far, for fan
5383 $longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
5384 // do chada, to patak
5385 $lowtail = array(0x0e0e, 0x0e0f);
5386 // mai hun arkad, sara i, sara ii, sara ue, sara uee
5387 $upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
5388 // mai ek, mai tho, mai tri, mai chattawa, karan
5389 $tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
5390 // sara u, sara uu, pinthu
5391 $lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
5392 $output = array();
5393 for ($i = 0; $i < $numchars; $i++) {
5394 if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
5395 $ch0 = $unicode[$i];
5396 $ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
5397 $ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
5398 $chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
5399 if (in_array($ch0, $tonemark)) {
5400 if ($chn == 0x0e33) {
5401 // sara um
5402 if (in_array($ch1, $longtail)) {
5403 // tonemark at upper left
5404 $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5405 } else {
5406 // tonemark at upper right (normal position)
5407 $output[] = $ch0;
5408 }
5409 } elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
5410 // tonemark at lower left
5411 $output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
5412 } elseif (in_array($ch1, $upvowel)) {
5413 if (in_array($ch2, $longtail)) {
5414 // tonemark at upper left
5415 $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5416 } else {
5417 // tonemark at upper right (normal position)
5418 $output[] = $ch0;
5419 }
5420 } else {
5421 // tonemark at lower right
5422 $output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
5423 }
5424 } elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
5425 // add lower left nikhahit and sara aa
5426 if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
5427 $output[] = 0xf711;
5428 $this->CurrentFont['subsetchars'][0xf711] = true;
5429 $output[] = 0x0e32;
5430 $this->CurrentFont['subsetchars'][0x0e32] = true;
5431 } else {
5432 $output[] = $ch0;
5433 }
5434 } elseif (in_array($ch1, $longtail)) {
5435 if ($ch0 == 0x0e31) {
5436 // lower left mai hun arkad
5437 $output[] = $this->replaceChar($ch0, 0xf710);
5438 } elseif (in_array($ch0, $upvowel)) {
5439 // lower left
5440 $output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
5441 } elseif ($ch0 == 0x0e47) {
5442 // lower left mai tai koo
5443 $output[] = $this->replaceChar($ch0, 0xf712);
5444 } else {
5445 // normal character
5446 $output[] = $ch0;
5447 }
5448 } elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
5449 // lower vowel
5450 $output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
5451 } elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
5452 // yo ying without lower part
5453 $output[] = $this->replaceChar($ch0, 0xf70f);
5454 } elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
5455 // tho santan without lower part
5456 $output[] = $this->replaceChar($ch0, 0xf700);
5457 } else {
5458 $output[] = $ch0;
5459 }
5460 } else {
5461 // non-thai character
5462 $output[] = $unicode[$i];
5463 }
5464 }
5465 $unicode = $output;
5466 // update font subsetchars
5467 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
5468 } // end of K_THAI_TOPCHARS
5469 $txt2 = TCPDF_FONTS::arrUTF8ToUTF16BE($unicode, false);
5470 }
5471 }
5472 $txt2 = TCPDF_STATIC::_escape($txt2);
5473 // get current text width (considering general font stretching and spacing)
5474 $txwidth = $this->GetStringWidth($txt);
5475 $width = $txwidth;
5476 // check for stretch mode
5477 if ($stretch > 0) {
5478 // calculate ratio between cell width and text width
5479 if ($width <= 0) {
5480 $ratio = 1;
5481 } else {
5482 $ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
5483 }
5484 // check if stretching is required
5485 if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
5486 // the text will be stretched to fit cell width
5487 if ($stretch > 2) {
5488 // set new character spacing
5489 $this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
5490 } else {
5491 // set new horizontal stretching
5492 $this->font_stretching *= $ratio;
5493 }
5494 // recalculate text width (the text fills the entire cell)
5495 $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5496 // reset alignment
5497 $align = '';
5498 }
5499 }
5500 if ($this->font_stretching != 100) {
5501 // apply font stretching
5502 $rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
5503 }
5504 if ($this->font_spacing != 0) {
5505 // increase/decrease font spacing
5506 $rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
5507 }
5508 if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5509 $s .= 'q '.$this->TextColor.' ';
5510 }
5511 // rendering mode
5512 $s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
5513 // count number of spaces
5514 $ns = substr_count($txt, chr(32));
5515 // Justification
5516 $spacewidth = 0;
5517 if (($align == 'J') AND ($ns > 0)) {
5518 if ($this->isUnicodeFont()) {
5519 // get string width without spaces
5520 $width = $this->GetStringWidth(str_replace(' ', '', $txt));
5521 // calculate average space width
5522 $spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / ($this->FontSize?$this->FontSize:1);
5523 if ($this->font_stretching != 100) {
5524 // word spacing is affected by stretching
5525 $spacewidth /= ($this->font_stretching / 100);
5526 }
5527 // set word position to be used with TJ operator
5528 $txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
5529 $unicode_justification = true;
5530 } else {
5531 // get string width
5532 $width = $txwidth;
5533 // new space width
5534 $spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
5535 if ($this->font_stretching != 100) {
5536 // word spacing (Tw) is affected by stretching
5537 $spacewidth /= ($this->font_stretching / 100);
5538 }
5539 // set word spacing
5540 $rs .= sprintf('BT %F Tw ET ', $spacewidth);
5541 }
5542 $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5543 }
5544 // replace carriage return characters
5545 $txt2 = str_replace("\r", ' ', $txt2);
5546 switch ($align) {
5547 case 'C': {
5548 $dx = ($w - $width) / 2;
5549 break;
5550 }
5551 case 'R': {
5552 if ($this->rtl) {
5553 $dx = $this->cell_padding['R'];
5554 } else {
5555 $dx = $w - $width - $this->cell_padding['R'];
5556 }
5557 break;
5558 }
5559 case 'L': {
5560 if ($this->rtl) {
5561 $dx = $w - $width - $this->cell_padding['L'];
5562 } else {
5563 $dx = $this->cell_padding['L'];
5564 }
5565 break;
5566 }
5567 case 'J':
5568 default: {
5569 if ($this->rtl) {
5570 $dx = $this->cell_padding['R'];
5571 } else {
5572 $dx = $this->cell_padding['L'];
5573 }
5574 break;
5575 }
5576 }
5577 if ($this->rtl) {
5578 $xdx = $x - $dx - $width;
5579 } else {
5580 $xdx = $x + $dx;
5581 }
5582 $xdk = $xdx * $k;
5583 // print text
5584 $s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
5585 if (isset($uniblock)) { // @phpstan-ignore-line
5586 // print overlapping characters as separate string
5587 $xshift = 0; // horizontal shift
5588 $ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
5589 $spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
5590 foreach ($uniblock as $uk => $uniarr) { // @phpstan-ignore-line
5591 if (($uk % 2) == 0) {
5592 // x space to skip
5593 if ($spacewidth != 0) {
5594 // justification shift
5595 $xshift += (count(array_keys($uniarr, 32)) * $spw);
5596 }
5597 $xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5598 } else {
5599 // character to print
5600 $topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
5601 $topchr = TCPDF_STATIC::_escape($topchr);
5602 $s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
5603 }
5604 }
5605 }
5606 if ($this->underline) {
5607 $s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5608 }
5609 if ($this->linethrough) {
5610 $s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5611 }
5612 if ($this->overline) {
5613 $s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5614 }
5615 if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5616 $s .= ' Q';
5617 }
5618 if ($link) {
5619 $this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5620 }
5621 }
5622 // output cell
5623 if ($s) {
5624 // output cell
5625 $rs .= $s;
5626 if ($this->font_spacing != 0) {
5627 // reset font spacing mode
5628 $rs .= ' BT 0 Tc ET';
5629 }
5630 if ($this->font_stretching != 100) {
5631 // reset font stretching mode
5632 $rs .= ' BT 100 Tz ET';
5633 }
5634 }
5635 // reset word spacing
5636 if (!$this->isUnicodeFont() AND ($align == 'J')) {
5637 $rs .= ' BT 0 Tw ET';
5638 }
5639 // reset stretching and spacing
5640 $this->font_stretching = $prev_font_stretching;
5641 $this->font_spacing = $prev_font_spacing;
5642 $this->lasth = $h;
5643 if ($ln > 0) {
5644 //Go to the beginning of the next line
5645 $this->y = $y + $h + $this->cell_margin['B'];
5646 if ($ln == 1) {
5647 if ($this->rtl) {
5648 $this->x = $this->w - $this->rMargin;
5649 } else {
5650 $this->x = $this->lMargin;
5651 }
5652 }
5653 } else {
5654 // go left or right by case
5655 if ($this->rtl) {
5656 $this->x = $x - $w - $this->cell_margin['L'];
5657 } else {
5658 $this->x = $x + $w + $this->cell_margin['R'];
5659 }
5660 }
5661 $gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5662 $rs = $gstyles.$rs;
5663 $this->cell_padding = $prev_cell_padding;
5664 $this->cell_margin = $prev_cell_margin;
5665 return $rs;
5666 }
5667
5668 /**
5669 * Replace a char if is defined on the current font.
5670 * @param int $oldchar Integer code (unicode) of the character to replace.
5671 * @param int $newchar Integer code (unicode) of the new character.
5672 * @return int the replaced char or the old char in case the new char i not defined
5673 * @protected
5674 * @since 5.9.167 (2012-06-22)
5675 */
5676 protected function replaceChar($oldchar, $newchar) {
5677 if ($this->isCharDefined($newchar)) {
5678 // add the new char on the subset list
5679 $this->CurrentFont['subsetchars'][$newchar] = true;
5680 // return the new character
5681 return $newchar;
5682 }
5683 // return the old char
5684 return $oldchar;
5685 }
5686
5687 /**
5688 * Returns the code to draw the cell border
5689 * @param float $x X coordinate.
5690 * @param float $y Y coordinate.
5691 * @param float $w Cell width.
5692 * @param float $h Cell height.
5693 * @param string|array|int $brd Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5694 * @return string containing cell border code
5695 * @protected
5696 * @see SetLineStyle()
5697 * @since 5.7.000 (2010-08-02)
5698 */
5699 protected function getCellBorder($x, $y, $w, $h, $brd) {
5700 $s = ''; // string to be returned
5701 if (empty($brd)) {
5702 return $s;
5703 }
5704 if ($brd == 1) {
5705 $brd = array('LRTB' => true);
5706 }
5707 // calculate coordinates for border
5708 $k = $this->k;
5709 if ($this->rtl) {
5710 $xeL = ($x - $w) * $k;
5711 $xeR = $x * $k;
5712 } else {
5713 $xeL = $x * $k;
5714 $xeR = ($x + $w) * $k;
5715 }
5716 $yeL = (($this->h - ($y + $h)) * $k);
5717 $yeT = (($this->h - $y) * $k);
5718 $xeT = $xeL;
5719 $xeB = $xeR;
5720 $yeR = $yeT;
5721 $yeB = $yeL;
5722 if (is_string($brd)) {
5723 // convert string to array
5724 $slen = strlen($brd);
5725 $newbrd = array();
5726 for ($i = 0; $i < $slen; ++$i) {
5727 $newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
5728 }
5729 $brd = $newbrd;
5730 }
5731 if (isset($brd['mode'])) {
5732 $mode = $brd['mode'];
5733 unset($brd['mode']);
5734 } else {
5735 $mode = 'normal';
5736 }
5737 foreach ($brd as $border => $style) {
5738 if (is_array($style) AND !empty($style)) {
5739 // apply border style
5740 $prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
5741 $s .= $this->setLineStyle($style, true)."\n";
5742 }
5743 switch ($mode) {
5744 case 'ext': {
5745 $off = (($this->LineWidth / 2) * $k);
5746 $xL = $xeL - $off;
5747 $xR = $xeR + $off;
5748 $yT = $yeT + $off;
5749 $yL = $yeL - $off;
5750 $xT = $xL;
5751 $xB = $xR;
5752 $yR = $yT;
5753 $yB = $yL;
5754 $w += $this->LineWidth;
5755 $h += $this->LineWidth;
5756 break;
5757 }
5758 case 'int': {
5759 $off = ($this->LineWidth / 2) * $k;
5760 $xL = $xeL + $off;
5761 $xR = $xeR - $off;
5762 $yT = $yeT - $off;
5763 $yL = $yeL + $off;
5764 $xT = $xL;
5765 $xB = $xR;
5766 $yR = $yT;
5767 $yB = $yL;
5768 $w -= $this->LineWidth;
5769 $h -= $this->LineWidth;
5770 break;
5771 }
5772 case 'normal':
5773 default: {
5774 $xL = $xeL;
5775 $xT = $xeT;
5776 $xB = $xeB;
5777 $xR = $xeR;
5778 $yL = $yeL;
5779 $yT = $yeT;
5780 $yB = $yeB;
5781 $yR = $yeR;
5782 break;
5783 }
5784 }
5785 // draw borders by case
5786 if (strlen($border) == 4) {
5787 $s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5788 } elseif (strlen($border) == 3) {
5789 if (strpos($border,'B') === false) { // LTR
5790 $s .= sprintf('%F %F m ', $xL, $yL);
5791 $s .= sprintf('%F %F l ', $xT, $yT);
5792 $s .= sprintf('%F %F l ', $xR, $yR);
5793 $s .= sprintf('%F %F l ', $xB, $yB);
5794 $s .= 'S ';
5795 } elseif (strpos($border,'L') === false) { // TRB
5796 $s .= sprintf('%F %F m ', $xT, $yT);
5797 $s .= sprintf('%F %F l ', $xR, $yR);
5798 $s .= sprintf('%F %F l ', $xB, $yB);
5799 $s .= sprintf('%F %F l ', $xL, $yL);
5800 $s .= 'S ';
5801 } elseif (strpos($border,'T') === false) { // RBL
5802 $s .= sprintf('%F %F m ', $xR, $yR);
5803 $s .= sprintf('%F %F l ', $xB, $yB);
5804 $s .= sprintf('%F %F l ', $xL, $yL);
5805 $s .= sprintf('%F %F l ', $xT, $yT);
5806 $s .= 'S ';
5807 } elseif (strpos($border,'R') === false) { // BLT
5808 $s .= sprintf('%F %F m ', $xB, $yB);
5809 $s .= sprintf('%F %F l ', $xL, $yL);
5810 $s .= sprintf('%F %F l ', $xT, $yT);
5811 $s .= sprintf('%F %F l ', $xR, $yR);
5812 $s .= 'S ';
5813 }
5814 } elseif (strlen($border) == 2) {
5815 if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5816 $s .= sprintf('%F %F m ', $xL, $yL);
5817 $s .= sprintf('%F %F l ', $xT, $yT);
5818 $s .= sprintf('%F %F l ', $xR, $yR);
5819 $s .= 'S ';
5820 } elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5821 $s .= sprintf('%F %F m ', $xT, $yT);
5822 $s .= sprintf('%F %F l ', $xR, $yR);
5823 $s .= sprintf('%F %F l ', $xB, $yB);
5824 $s .= 'S ';
5825 } elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5826 $s .= sprintf('%F %F m ', $xR, $yR);
5827 $s .= sprintf('%F %F l ', $xB, $yB);
5828 $s .= sprintf('%F %F l ', $xL, $yL);
5829 $s .= 'S ';
5830 } elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5831 $s .= sprintf('%F %F m ', $xB, $yB);
5832 $s .= sprintf('%F %F l ', $xL, $yL);
5833 $s .= sprintf('%F %F l ', $xT, $yT);
5834 $s .= 'S ';
5835 } elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5836 $s .= sprintf('%F %F m ', $xL, $yL);
5837 $s .= sprintf('%F %F l ', $xT, $yT);
5838 $s .= 'S ';
5839 $s .= sprintf('%F %F m ', $xR, $yR);
5840 $s .= sprintf('%F %F l ', $xB, $yB);
5841 $s .= 'S ';
5842 } elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5843 $s .= sprintf('%F %F m ', $xT, $yT);
5844 $s .= sprintf('%F %F l ', $xR, $yR);
5845 $s .= 'S ';
5846 $s .= sprintf('%F %F m ', $xB, $yB);
5847 $s .= sprintf('%F %F l ', $xL, $yL);
5848 $s .= 'S ';
5849 }
5850 } else { // strlen($border) == 1
5851 if (strpos($border,'L') !== false) { // L
5852 $s .= sprintf('%F %F m ', $xL, $yL);
5853 $s .= sprintf('%F %F l ', $xT, $yT);
5854 $s .= 'S ';
5855 } elseif (strpos($border,'T') !== false) { // T
5856 $s .= sprintf('%F %F m ', $xT, $yT);
5857 $s .= sprintf('%F %F l ', $xR, $yR);
5858 $s .= 'S ';
5859 } elseif (strpos($border,'R') !== false) { // R
5860 $s .= sprintf('%F %F m ', $xR, $yR);
5861 $s .= sprintf('%F %F l ', $xB, $yB);
5862 $s .= 'S ';
5863 } elseif (strpos($border,'B') !== false) { // B
5864 $s .= sprintf('%F %F m ', $xB, $yB);
5865 $s .= sprintf('%F %F l ', $xL, $yL);
5866 $s .= 'S ';
5867 }
5868 }
5869 if (is_array($style) AND !empty($style)) {
5870 // reset border style to previous value
5871 $s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
5872 }
5873 }
5874 return $s;
5875 }
5876
5877 /**
5878 * This method allows printing text with line breaks.
5879 * 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 />
5880 * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
5881 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
5882 * @param float $h Cell minimum height. The cell extends automatically if needed.
5883 * @param string $txt String to print
5884 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5885 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align</li><li>C: center</li><li>R: right align</li><li>J: justification (default value when $ishtml=false)</li></ul>
5886 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5887 * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line [DEFAULT]</li><li>2: below</li></ul>
5888 * @param float|null $x x position in user units
5889 * @param float|null $y y position in user units
5890 * @param boolean $reseth if true reset the last cell height (default true).
5891 * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5892 * @param boolean $ishtml INTERNAL USE ONLY -- set to true if $txt is HTML content (default = false). Never set this parameter to true, use instead writeHTMLCell() or writeHTML() methods.
5893 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width.
5894 * @param float $maxh maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. This feature works only when $ishtml=false.
5895 * @param string $valign Vertical alignment of text (requires $maxh = $h > 0). Possible values are:<ul><li>T: TOP</li><li>M: middle</li><li>B: bottom</li></ul>. This feature works only when $ishtml=false and the cell must fit in a single page.
5896 * @param boolean $fitcell if true attempt to fit all the text within the cell by reducing the font size (do not work in HTML mode). $maxh must be greater than 0 and equal to $h.
5897 * @return int Return the number of cells or 1 for html mode.
5898 * @public
5899 * @since 1.3
5900 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
5901 */
5902 public function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false, $ln=1, $x=null, $y=null, $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0, $valign='T', $fitcell=false) {
5903 $prev_cell_margin = $this->cell_margin;
5904 $prev_cell_padding = $this->cell_padding;
5905 // adjust internal padding
5906 $this->adjustCellPadding($border);
5907 $mc_padding = $this->cell_padding;
5908 $mc_margin = $this->cell_margin;
5909 $this->cell_padding['T'] = 0;
5910 $this->cell_padding['B'] = 0;
5911 $this->setCellMargins(0, 0, 0, 0);
5912 if (TCPDF_STATIC::empty_string($this->lasth) OR $reseth) {
5913 // reset row height
5914 $this->resetLastH();
5915 }
5916 if (!TCPDF_STATIC::empty_string($y)) {
5917 $this->setY($y); // set y in order to convert negative y values to positive ones
5918 }
5919 $y = $this->GetY();
5920 $resth = 0;
5921 if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
5922 // spit cell in more pages/columns
5923 $newh = ($this->PageBreakTrigger - $y);
5924 $resth = ($h - $newh); // cell to be printed on the next page/column
5925 $h = $newh;
5926 }
5927 // get current page number
5928 $startpage = $this->page;
5929 // get current column
5930 $startcolumn = $this->current_column;
5931 if (!TCPDF_STATIC::empty_string($x)) {
5932 $this->setX($x);
5933 } else {
5934 $x = $this->GetX();
5935 }
5936 // check page for no-write regions and adapt page margins if necessary
5937 list($x, $y) = $this->checkPageRegions(0, $x, $y);
5938 // apply margins
5939 $oy = $y + $mc_margin['T'];
5940 if ($this->rtl) {
5941 $ox = ($this->w - $x - $mc_margin['R']);
5942 } else {
5943 $ox = ($x + $mc_margin['L']);
5944 }
5945 $this->x = $ox;
5946 $this->y = $oy;
5947 // set width
5948 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5949 if ($this->rtl) {
5950 $w = ($this->x - $this->lMargin - $mc_margin['L']);
5951 } else {
5952 $w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
5953 }
5954 }
5955 // store original margin values
5956 $lMargin = $this->lMargin;
5957 $rMargin = $this->rMargin;
5958 if ($this->rtl) {
5959 $this->rMargin = ($this->w - $this->x);
5960 $this->lMargin = ($this->x - $w);
5961 } else {
5962 $this->lMargin = ($this->x);
5963 $this->rMargin = ($this->w - $this->x - $w);
5964 }
5965 $this->clMargin = $this->lMargin;
5966 $this->crMargin = $this->rMargin;
5967 if ($autopadding) {
5968 // add top padding
5969 $this->y += $mc_padding['T'];
5970 }
5971 if ($ishtml) { // ******* Write HTML text
5972 $this->writeHTML($txt, true, false, $reseth, true, $align);
5973 $nl = 1;
5974 } else { // ******* Write simple text
5975 $prev_FontSizePt = $this->FontSizePt;
5976 if ($fitcell) {
5977 // ajust height values
5978 $tobottom = ($this->h - $this->y - $this->bMargin - $this->cell_padding['T'] - $this->cell_padding['B']);
5979 $h = $maxh = max(min($h, $tobottom), min($maxh, $tobottom));
5980 }
5981 // vertical alignment
5982 if ($maxh > 0) {
5983 // get text height
5984 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5985 if ($fitcell AND ($text_height > $maxh) AND ($this->FontSizePt > 1)) {
5986 // try to reduce font size to fit text on cell (use a quick search algorithm)
5987 $fmin = 1;
5988 $fmax = $this->FontSizePt;
5989 $diff_epsilon = (1 / $this->k); // one point (min resolution)
5990 $maxit = (2 * min(100, max(10, intval($fmax)))); // max number of iterations
5991 while ($maxit >= 0) {
5992 $fmid = (($fmax + $fmin) / 2);
5993 $this->setFontSize($fmid, false);
5994 $this->resetLastH();
5995 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5996 $diff = ($maxh - $text_height);
5997 if ($diff >= 0) {
5998 if ($diff <= $diff_epsilon) {
5999 break;
6000 }
6001 $fmin = $fmid;
6002 } else {
6003 $fmax = $fmid;
6004 }
6005 --$maxit;
6006 }
6007 if ($maxit < 0) {
6008 // premature exit, we get the minimum font value to fit the cell
6009 $this->setFontSize($fmin);
6010 $this->resetLastH();
6011 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
6012 } else {
6013 $this->setFontSize($fmid);
6014 $this->resetLastH();
6015 }
6016 }
6017 if ($text_height < $maxh) {
6018 if ($valign == 'M') {
6019 // text vertically centered
6020 $this->y += (($maxh - $text_height) / 2);
6021 } elseif ($valign == 'B') {
6022 // text vertically aligned on bottom
6023 $this->y += ($maxh - $text_height);
6024 }
6025 }
6026 }
6027 $nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
6028 if ($fitcell) {
6029 // restore font size
6030 $this->setFontSize($prev_FontSizePt);
6031 }
6032 }
6033 if ($autopadding) {
6034 // add bottom padding
6035 $this->y += $mc_padding['B'];
6036 }
6037 // Get end-of-text Y position
6038 $currentY = $this->y;
6039 // get latest page number
6040 $endpage = $this->page;
6041 if ($resth > 0) {
6042 $skip = ($endpage - $startpage);
6043 $tmpresth = $resth;
6044 while ($tmpresth > 0) {
6045 if ($skip <= 0) {
6046 // add a page (or trig AcceptPageBreak() for multicolumn mode)
6047 $this->checkPageBreak($this->PageBreakTrigger + 1);
6048 }
6049 if ($this->num_columns > 1) {
6050 $tmpresth -= ($this->h - $this->y - $this->bMargin);
6051 } else {
6052 $tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
6053 }
6054 --$skip;
6055 }
6056 $currentY = $this->y;
6057 $endpage = $this->page;
6058 }
6059 // get latest column
6060 $endcolumn = $this->current_column;
6061 if ($this->num_columns == 0) {
6062 $this->num_columns = 1;
6063 }
6064 // disable page regions check
6065 $check_page_regions = $this->check_page_regions;
6066 $this->check_page_regions = false;
6067 // get border modes
6068 $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
6069 $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
6070 $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
6071 // design borders around HTML cells.
6072 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
6073 $ccode = '';
6074 $this->setPage($page);
6075 if ($this->num_columns < 2) {
6076 // single-column mode
6077 $this->setX($x);
6078 $this->y = $this->tMargin;
6079 }
6080 // account for margin changes
6081 if ($page > $startpage) {
6082 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
6083 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
6084 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
6085 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
6086 }
6087 }
6088 if ($startpage == $endpage) {
6089 // single page
6090 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
6091 if ($column != $this->current_column) {
6092 $this->selectColumn($column);
6093 }
6094 if ($this->rtl) {
6095 $this->x -= $mc_margin['R'];
6096 } else {
6097 $this->x += $mc_margin['L'];
6098 }
6099 if ($startcolumn == $endcolumn) { // single column
6100 $cborder = $border;
6101 $h = max($h, ($currentY - $oy));
6102 $this->y = $oy;
6103 } elseif ($column == $startcolumn) { // first column
6104 $cborder = $border_start;
6105 $this->y = $oy;
6106 $h = $this->h - $this->y - $this->bMargin;
6107 } elseif ($column == $endcolumn) { // end column
6108 $cborder = $border_end;
6109 $h = $currentY - $this->y;
6110 if ($resth > $h) {
6111 $h = $resth;
6112 }
6113 } else { // middle column
6114 $cborder = $border_middle;
6115 $h = $this->h - $this->y - $this->bMargin;
6116 $resth -= $h;
6117 }
6118 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6119 } // end for each column
6120 } elseif ($page == $startpage) { // first page
6121 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
6122 if ($column != $this->current_column) {
6123 $this->selectColumn($column);
6124 }
6125 if ($this->rtl) {
6126 $this->x -= $mc_margin['R'];
6127 } else {
6128 $this->x += $mc_margin['L'];
6129 }
6130 if ($column == $startcolumn) { // first column
6131 $cborder = $border_start;
6132 $this->y = $oy;
6133 $h = $this->h - $this->y - $this->bMargin;
6134 } else { // middle column
6135 $cborder = $border_middle;
6136 $h = $this->h - $this->y - $this->bMargin;
6137 $resth -= $h;
6138 }
6139 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6140 } // end for each column
6141 } elseif ($page == $endpage) { // last page
6142 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
6143 if ($column != $this->current_column) {
6144 $this->selectColumn($column);
6145 }
6146 if ($this->rtl) {
6147 $this->x -= $mc_margin['R'];
6148 } else {
6149 $this->x += $mc_margin['L'];
6150 }
6151 if ($column == $endcolumn) {
6152 // end column
6153 $cborder = $border_end;
6154 $h = $currentY - $this->y;
6155 if ($resth > $h) {
6156 $h = $resth;
6157 }
6158 } else {
6159 // middle column
6160 $cborder = $border_middle;
6161 $h = $this->h - $this->y - $this->bMargin;
6162 $resth -= $h;
6163 }
6164 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6165 } // end for each column
6166 } else { // middle page
6167 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6168 $this->selectColumn($column);
6169 if ($this->rtl) {
6170 $this->x -= $mc_margin['R'];
6171 } else {
6172 $this->x += $mc_margin['L'];
6173 }
6174 $cborder = $border_middle;
6175 $h = $this->h - $this->y - $this->bMargin;
6176 $resth -= $h;
6177 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6178 } // end for each column
6179 }
6180 if ($cborder OR $fill) {
6181 $offsetlen = strlen($ccode);
6182 // draw border and fill
6183 if ($this->inxobj) {
6184 // we are inside an XObject template
6185 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6186 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6187 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6188 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
6189 } else {
6190 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
6191 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
6192 }
6193 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6194 $pstart = substr($pagebuff, 0, $pagemark);
6195 $pend = substr($pagebuff, $pagemark);
6196 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6197 } else {
6198 if (end($this->transfmrk[$this->page]) !== false) {
6199 $pagemarkkey = key($this->transfmrk[$this->page]);
6200 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
6201 $this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
6202 } elseif ($this->InFooter) {
6203 $pagemark = $this->footerpos[$this->page];
6204 $this->footerpos[$this->page] += $offsetlen;
6205 } else {
6206 $pagemark = $this->intmrk[$this->page];
6207 $this->intmrk[$this->page] += $offsetlen;
6208 }
6209 $pagebuff = $this->getPageBuffer($this->page);
6210 $pstart = substr($pagebuff, 0, $pagemark);
6211 $pend = substr($pagebuff, $pagemark);
6212 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6213 }
6214 }
6215 } // end for each page
6216 // restore page regions check
6217 $this->check_page_regions = $check_page_regions;
6218 // Get end-of-cell Y position
6219 $currentY = $this->GetY();
6220 // restore previous values
6221 if ($this->num_columns > 1) {
6222 $this->selectColumn();
6223 } else {
6224 // restore original margins
6225 $this->lMargin = $lMargin;
6226 $this->rMargin = $rMargin;
6227 if ($this->page > $startpage) {
6228 // check for margin variations between pages (i.e. booklet mode)
6229 $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
6230 $dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
6231 if (($dl != 0) OR ($dr != 0)) {
6232 $this->lMargin += $dl;
6233 $this->rMargin += $dr;
6234 }
6235 }
6236 }
6237 if ($ln > 0) {
6238 //Go to the beginning of the next line
6239 $this->setY($currentY + $mc_margin['B']);
6240 if ($ln == 2) {
6241 $this->setX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6242 }
6243 } else {
6244 // go left or right by case
6245 $this->setPage($startpage);
6246 $this->y = $y;
6247 $this->setX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6248 }
6249 $this->setContentMark();
6250 $this->cell_padding = $prev_cell_padding;
6251 $this->cell_margin = $prev_cell_margin;
6252 $this->clMargin = $this->lMargin;
6253 $this->crMargin = $this->rMargin;
6254 return $nl;
6255 }
6256
6257 /**
6258 * This method return the estimated number of lines for print a simple text string using Multicell() method.
6259 * @param string $txt String for calculating his height
6260 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
6261 * @param boolean $reseth if true reset the last cell height (default false).
6262 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true).
6263 * @param array|null $cellpadding Internal cell padding, if empty uses default cell padding.
6264 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6265 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6266 * @author Alexander Escalona Fern\E1ndez,2026 Nicola Asuni
6267 * @public
6268 * @since 4.5.011
6269 */
6270 public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding=null, $border=0) {
6271 if ($txt === NULL) {
6272 return 0;
6273 }
6274 if ($txt === '') {
6275 // empty string
6276 return 1;
6277 }
6278 // adjust internal padding
6279 $prev_cell_padding = $this->cell_padding;
6280 $prev_lasth = $this->lasth;
6281 if (is_array($cellpadding)) {
6282 $this->cell_padding = $cellpadding;
6283 }
6284 $this->adjustCellPadding($border);
6285 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
6286 if ($this->rtl) {
6287 $w = $this->x - $this->lMargin;
6288 } else {
6289 $w = $this->w - $this->rMargin - $this->x;
6290 }
6291 }
6292 $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6293 if ($reseth) {
6294 // reset row height
6295 $this->resetLastH();
6296 }
6297 $lines = 1;
6298 $sum = 0;
6299 $chars = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont), $txt, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6300 $charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6301 $length = count($chars);
6302 $lastSeparator = -1;
6303 for ($i = 0; $i < $length; ++$i) {
6304 $c = $chars[$i];
6305 $charWidth = $charsWidth[$i];
6306 if (($c != 160)
6307 AND (($c == 173)
6308 OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6309 OR (($c == 45)
6310 AND ($i > 0) AND ($i < ($length - 1))
6311 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i - 1)], $this->isunicode))
6312 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6313 )
6314 )
6315 ) {
6316 $lastSeparator = $i;
6317 }
6318 if ((($sum + $charWidth) > $wmax) OR ($c == 10)) {
6319 ++$lines;
6320 if ($c == 10) {
6321 $lastSeparator = -1;
6322 $sum = 0;
6323 } elseif ($lastSeparator != -1) {
6324 $i = $lastSeparator;
6325 $lastSeparator = -1;
6326 $sum = 0;
6327 } else {
6328 $sum = $charWidth;
6329 }
6330 } else {
6331 $sum += $charWidth;
6332 }
6333 }
6334 if ($chars[($length - 1)] == 10) {
6335 --$lines;
6336 }
6337 $this->cell_padding = $prev_cell_padding;
6338 $this->lasth = $prev_lasth;
6339 return $lines;
6340 }
6341
6342 /**
6343 * This method return the estimated height needed for printing a simple text string using the Multicell() method.
6344 * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6345 * @pre
6346 * // store current object
6347 * $pdf->startTransaction();
6348 * // store starting values
6349 * $start_y = $pdf->GetY();
6350 * $start_page = $pdf->getPage();
6351 * // call your printing functions with your parameters
6352 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6353 * $pdf->MultiCell($w=0, $h=0, $txt, $border=1, $align='L', $fill=false, $ln=1, $x=null, $y=null, $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0);
6354 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6355 * // get the new Y
6356 * $end_y = $pdf->GetY();
6357 * $end_page = $pdf->getPage();
6358 * // calculate height
6359 * $height = 0;
6360 * if ($end_page == $start_page) {
6361 * $height = $end_y - $start_y;
6362 * } else {
6363 * for ($page=$start_page; $page <= $end_page; ++$page) {
6364 * $this->setPage($page);
6365 * if ($page == $start_page) {
6366 * // first page
6367 * $height += $this->h - $start_y - $this->bMargin;
6368 * } elseif ($page == $end_page) {
6369 * // last page
6370 * $height += $end_y - $this->tMargin;
6371 * } else {
6372 * $height += $this->h - $this->tMargin - $this->bMargin;
6373 * }
6374 * }
6375 * }
6376 * // restore previous object
6377 * $pdf = $pdf->rollbackTransaction();
6378 *
6379 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
6380 * @param string $txt String for calculating his height
6381 * @param boolean $reseth if true reset the last cell height (default false).
6382 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true).
6383 * @param array|null $cellpadding Internal cell padding, if empty uses default cell padding.
6384 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6385 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6386 * @author Nicola Asuni, Alexander Escalona Fern\E1ndez
6387 * @public
6388 */
6389 public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding=null, $border=0) {
6390 // adjust internal padding
6391 $prev_cell_padding = $this->cell_padding;
6392 $prev_lasth = $this->lasth;
6393 if (is_array($cellpadding)) {
6394 $this->cell_padding = $cellpadding;
6395 }
6396 $this->adjustCellPadding($border);
6397 $lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6398 $height = $this->getCellHeight(($lines * $this->FontSize), $autopadding);
6399 $this->cell_padding = $prev_cell_padding;
6400 $this->lasth = $prev_lasth;
6401 return $height;
6402 }
6403
6404 /**
6405 * This method prints text from the current position.<br />
6406 * @param float $h Line height
6407 * @param string $txt String to print
6408 * @param mixed $link URL or identifier returned by AddLink()
6409 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
6410 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
6411 * @param boolean $ln if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
6412 * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
6413 * @param boolean $firstline if true prints only the first line and return the remaining string.
6414 * @param boolean $firstblock if true the string is the starting of a line.
6415 * @param float $maxh maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature.
6416 * @param float $wadj first line width will be reduced by this amount (used in HTML mode).
6417 * @param array|null $margin margin array of the parent container
6418 * @return mixed Return the number of cells or the remaining string if $firstline = true.
6419 * @public
6420 * @since 1.5
6421 */
6422 public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin=null) {
6423 // check page for no-write regions and adapt page margins if necessary
6424 list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
6425 if (strlen($txt) == 0) {
6426 // fix empty text
6427 $txt = ' ';
6428 }
6429 if (!is_array($margin)) {
6430 // set default margins
6431 $margin = $this->cell_margin;
6432 }
6433 // remove carriage returns
6434 $s = str_replace("\r", '', $txt);
6435 // check if string contains arabic text
6436 if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $s)) {
6437 $arabic = true;
6438 } else {
6439 $arabic = false;
6440 }
6441 // check if string contains RTL text
6442 if ($arabic OR ($this->tmprtl == 'R') OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $s)) {
6443 $rtlmode = true;
6444 } else {
6445 $rtlmode = false;
6446 }
6447 // get a char width
6448 $chrwidth = $this->GetCharWidth(46); // dot character
6449 // get array of unicode values
6450 $chars = TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont);
6451 // calculate maximum width for a single character on string
6452 $chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
6453 array_walk($chrw, array($this, 'getRawCharWidth'));
6454 $maxchwidth = ((is_array($chrw) || $chrw instanceof Countable) && count($chrw) > 0) ? max($chrw) : 0;
6455 // get array of chars
6456 $uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
6457 // get the number of characters
6458 $nb = count($chars);
6459 // replacement for SHY character (minus symbol)
6460 $shy_replacement = 45;
6461 $shy_replacement_char = TCPDF_FONTS::unichr($shy_replacement, $this->isunicode);
6462 // width for SHY replacement
6463 $shy_replacement_width = $this->GetCharWidth($shy_replacement);
6464 // page width
6465 $pw = $w = $this->w - $this->lMargin - $this->rMargin;
6466 // calculate remaining line width ($w)
6467 if ($this->rtl) {
6468 $w = $this->x - $this->lMargin;
6469 } else {
6470 $w = $this->w - $this->rMargin - $this->x;
6471 }
6472 // max column width
6473 $wmax = ($w - $wadj);
6474 if (!$firstline) {
6475 $wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
6476 }
6477 if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
6478 // the maximum width character do not fit on column
6479 return '';
6480 }
6481 // minimum row height
6482 $row_height = max($h, $this->getCellHeight($this->FontSize));
6483 // max Y
6484 $maxy = $this->y + $maxh - max($row_height, $h);
6485 $start_page = $this->page;
6486 $i = 0; // character position
6487 $j = 0; // current starting position
6488 $sep = -1; // position of the last blank space
6489 $prevsep = $sep; // previous separator
6490 $shy = false; // true if the last blank is a soft hypen (SHY)
6491 $prevshy = $shy; // previous shy mode
6492 $l = 0; // current string length
6493 $nl = 0; //number of lines
6494 $linebreak = false;
6495 $pc = 0; // previous character
6496 // for each character
6497 while ($i < $nb) {
6498 if (($maxh > 0) AND ($this->y > $maxy) ) {
6499 break;
6500 }
6501 //Get the current character
6502 $c = $chars[$i];
6503 if ($c == 10) { // 10 = "\n" = new line
6504 //Explicit line break
6505 if ($align == 'J') {
6506 if ($this->rtl) {
6507 $talign = 'R';
6508 } else {
6509 $talign = 'L';
6510 }
6511 } else {
6512 $talign = $align;
6513 }
6514 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6515 if ($firstline) {
6516 $startx = $this->x;
6517 $tmparr = array_slice($chars, $j, ($i - $j));
6518 if ($rtlmode) {
6519 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6520 }
6521 $linew = $this->GetArrStringWidth($tmparr);
6522 unset($tmparr);
6523 if ($this->rtl) {
6524 $this->endlinex = $startx - $linew;
6525 } else {
6526 $this->endlinex = $startx + $linew;
6527 }
6528 $w = $linew;
6529 $tmpcellpadding = $this->cell_padding;
6530 if ($maxh == 0) {
6531 $this->setCellPadding(0);
6532 }
6533 }
6534 if ($firstblock AND $this->isRTLTextDir()) {
6535 $tmpstr = $this->stringRightTrim($tmpstr);
6536 }
6537 // Skip newlines at the beginning of a page or column
6538 if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
6539 $this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6540 }
6541 unset($tmpstr);
6542 if ($firstline) {
6543 $this->cell_padding = $tmpcellpadding;
6544 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6545 }
6546 ++$nl;
6547 $j = $i + 1;
6548 $l = 0;
6549 $sep = -1;
6550 $prevsep = $sep;
6551 $shy = false;
6552 // account for margin changes
6553 if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6554 if ($this->AcceptPageBreak())
6555 {
6556 if ($this->rtl) {
6557 $this->x -= $margin['R'];
6558 } else {
6559 $this->x += $margin['L'];
6560 }
6561 $this->lMargin += $margin['L'];
6562 $this->rMargin += $margin['R'];
6563 }
6564 }
6565 $w = $this->getRemainingWidth();
6566 $wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
6567 } else {
6568 // 160 is the non-breaking space.
6569 // 173 is SHY (Soft Hypen).
6570 // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6571 // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6572 // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6573 if (($c != 160)
6574 AND (($c == 173)
6575 OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6576 OR (($c == 45)
6577 AND ($i < ($nb - 1))
6578 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($pc, $this->isunicode))
6579 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6580 )
6581 )
6582 ) {
6583 // update last blank space position
6584 $prevsep = $sep;
6585 $sep = $i;
6586 // check if is a SHY
6587 if (($c == 173) OR ($c == 45)) {
6588 $prevshy = $shy;
6589 $shy = true;
6590 if ($pc == 45) {
6591 $tmp_shy_replacement_width = 0;
6592 $tmp_shy_replacement_char = '';
6593 } else {
6594 $tmp_shy_replacement_width = $shy_replacement_width;
6595 $tmp_shy_replacement_char = $shy_replacement_char;
6596 }
6597 } else {
6598 $shy = false;
6599 }
6600 }
6601 // update string length
6602 if ($this->isUnicodeFont() AND ($arabic)) {
6603 // with bidirectional algorithm some chars may be changed affecting the line length
6604 // *** very slow ***
6605 $l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont));
6606 } else {
6607 $l += $this->GetCharWidth($c, ($i+1 < $nb));
6608 }
6609 if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) >= $wmax))) {
6610 if (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) {
6611 $sep = $prevsep;
6612 $shy = $prevshy;
6613 }
6614 // we have reached the end of column
6615 if ($sep == -1) {
6616 // check if the line was already started
6617 if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
6618 OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
6619 // print a void cell and go to next line
6620 $this->Cell($w, $h, '', 0, 1);
6621 $linebreak = true;
6622 if ($firstline) {
6623 return (TCPDF_FONTS::UniArrSubString($uchars, $j));
6624 }
6625 } else {
6626 // truncate the word because do not fit on column
6627 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6628 if ($firstline) {
6629 $startx = $this->x;
6630 $tmparr = array_slice($chars, $j, ($i - $j));
6631 if ($rtlmode) {
6632 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6633 }
6634 $linew = $this->GetArrStringWidth($tmparr);
6635 unset($tmparr);
6636 if ($this->rtl) {
6637 $this->endlinex = $startx - $linew;
6638 } else {
6639 $this->endlinex = $startx + $linew;
6640 }
6641 $w = $linew;
6642 $tmpcellpadding = $this->cell_padding;
6643 if ($maxh == 0) {
6644 $this->setCellPadding(0);
6645 }
6646 }
6647 if ($firstblock AND $this->isRTLTextDir()) {
6648 $tmpstr = $this->stringRightTrim($tmpstr);
6649 }
6650 $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6651 unset($tmpstr);
6652 if ($firstline) {
6653 $this->cell_padding = $tmpcellpadding;
6654 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6655 }
6656 $j = $i;
6657 --$i;
6658 }
6659 } else {
6660 // word wrapping
6661 if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6662 $endspace = 1;
6663 } else {
6664 $endspace = 0;
6665 }
6666 // check the length of the next string
6667 $strrest = TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace));
6668 $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $this->stringTrim($strrest));
6669 if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
6670 // truncate the word because do not fit on a full page width
6671 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6672 if ($firstline) {
6673 $startx = $this->x;
6674 $tmparr = array_slice($chars, $j, ($i - $j));
6675 if ($rtlmode) {
6676 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6677 }
6678 $linew = $this->GetArrStringWidth($tmparr);
6679 unset($tmparr);
6680 if ($this->rtl) {
6681 $this->endlinex = ($startx - $linew);
6682 } else {
6683 $this->endlinex = ($startx + $linew);
6684 }
6685 $w = $linew;
6686 $tmpcellpadding = $this->cell_padding;
6687 if ($maxh == 0) {
6688 $this->setCellPadding(0);
6689 }
6690 }
6691 if ($firstblock AND $this->isRTLTextDir()) {
6692 $tmpstr = $this->stringRightTrim($tmpstr);
6693 }
6694 $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6695 unset($tmpstr);
6696 if ($firstline) {
6697 $this->cell_padding = $tmpcellpadding;
6698 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6699 }
6700 $j = $i;
6701 --$i;
6702 } else {
6703 // word wrapping
6704 if ($shy) {
6705 // add hypen (minus symbol) at the end of the line
6706 $shy_width = $tmp_shy_replacement_width;
6707 if ($this->rtl) {
6708 $shy_char_left = $tmp_shy_replacement_char;
6709 $shy_char_right = '';
6710 } else {
6711 $shy_char_left = '';
6712 $shy_char_right = $tmp_shy_replacement_char;
6713 }
6714 } else {
6715 $shy_width = 0;
6716 $shy_char_left = '';
6717 $shy_char_right = '';
6718 }
6719 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
6720 if ($firstline) {
6721 $startx = $this->x;
6722 $tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6723 if ($rtlmode) {
6724 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6725 }
6726 $linew = $this->GetArrStringWidth($tmparr);
6727 unset($tmparr);
6728 if ($this->rtl) {
6729 $this->endlinex = $startx - $linew - $shy_width;
6730 } else {
6731 $this->endlinex = $startx + $linew + $shy_width;
6732 }
6733 $w = $linew;
6734 $tmpcellpadding = $this->cell_padding;
6735 if ($maxh == 0) {
6736 $this->setCellPadding(0);
6737 }
6738 }
6739 // print the line
6740 if ($firstblock AND $this->isRTLTextDir()) {
6741 $tmpstr = $this->stringRightTrim($tmpstr);
6742 }
6743 $this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6744 unset($tmpstr);
6745 if ($firstline) {
6746 if ($chars[$sep] == 45) {
6747 $endspace += 1;
6748 }
6749 // return the remaining text
6750 $this->cell_padding = $tmpcellpadding;
6751 return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
6752 }
6753 $i = $sep;
6754 $sep = -1;
6755 $shy = false;
6756 $j = ($i + 1);
6757 }
6758 }
6759 // account for margin changes
6760 if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6761 if ($this->AcceptPageBreak())
6762 {
6763 if ($this->rtl) {
6764 $this->x -= $margin['R'];
6765 } else {
6766 $this->x += $margin['L'];
6767 }
6768 $this->lMargin += $margin['L'];
6769 $this->rMargin += $margin['R'];
6770 }
6771 }
6772 $w = $this->getRemainingWidth();
6773 $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6774 if ($linebreak) {
6775 $linebreak = false;
6776 } else {
6777 ++$nl;
6778 $l = 0;
6779 }
6780 }
6781 }
6782 // save last character
6783 $pc = $c;
6784 ++$i;
6785 } // end while i < nb
6786 // print last substring (if any)
6787 if ($l > 0) {
6788 switch ($align) {
6789 case 'J':
6790 case 'C': {
6791 break;
6792 }
6793 case 'L': {
6794 if (!$this->rtl) {
6795 $w = $l;
6796 }
6797 break;
6798 }
6799 case 'R': {
6800 if ($this->rtl) {
6801 $w = $l;
6802 }
6803 break;
6804 }
6805 default: {
6806 $w = $l;
6807 break;
6808 }
6809 }
6810 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
6811 if ($firstline) {
6812 $startx = $this->x;
6813 $tmparr = array_slice($chars, $j, ($nb - $j));
6814 if ($rtlmode) {
6815 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6816 }
6817 $linew = $this->GetArrStringWidth($tmparr);
6818 unset($tmparr);
6819 if ($this->rtl) {
6820 $this->endlinex = $startx - $linew;
6821 } else {
6822 $this->endlinex = $startx + $linew;
6823 }
6824 $w = $linew;
6825 $tmpcellpadding = $this->cell_padding;
6826 if ($maxh == 0) {
6827 $this->setCellPadding(0);
6828 }
6829 }
6830 if ($firstblock AND $this->isRTLTextDir()) {
6831 $tmpstr = $this->stringRightTrim($tmpstr);
6832 }
6833 $this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6834 unset($tmpstr);
6835 if ($firstline) {
6836 $this->cell_padding = $tmpcellpadding;
6837 return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
6838 }
6839 ++$nl;
6840 }
6841 if ($firstline) {
6842 return '';
6843 }
6844 return $nl;
6845 }
6846
6847 /**
6848 * Returns the remaining width between the current position and margins.
6849 * @return float Return the remaining width
6850 * @protected
6851 */
6852 protected function getRemainingWidth() {
6853 list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
6854 if ($this->rtl) {
6855 return ($this->x - $this->lMargin);
6856 } else {
6857 return ($this->w - $this->rMargin - $this->x);
6858 }
6859 }
6860
6861 /**
6862 * Set the block dimensions accounting for page breaks and page/column fitting
6863 * @param float $w width
6864 * @param float $h height
6865 * @param float $x X coordinate
6866 * @param float $y Y coodiante
6867 * @param boolean $fitonpage if true the block is resized to not exceed page dimensions.
6868 * @return array array($w, $h, $x, $y)
6869 * @protected
6870 * @since 5.5.009 (2010-07-05)
6871 */
6872 protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
6873 if ($w <= 0) {
6874 // set maximum width
6875 $w = ($this->w - $this->lMargin - $this->rMargin);
6876 if ($w <= 0) {
6877 $w = 1;
6878 }
6879 }
6880 if ($h <= 0) {
6881 // set maximum height
6882 $h = ($this->PageBreakTrigger - $this->tMargin);
6883 if ($h <= 0) {
6884 $h = 1;
6885 }
6886 }
6887 // resize the block to be vertically contained on a single page or single column
6888 if ($fitonpage OR $this->AutoPageBreak) {
6889 $ratio_wh = ($w / $h);
6890 if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
6891 $h = $this->PageBreakTrigger - $this->tMargin;
6892 $w = ($h * $ratio_wh);
6893 }
6894 // resize the block to be horizontally contained on a single page or single column
6895 if ($fitonpage) {
6896 $maxw = ($this->w - $this->lMargin - $this->rMargin);
6897 if ($w > $maxw) {
6898 $w = $maxw;
6899 $h = ($w / $ratio_wh);
6900 }
6901 }
6902 }
6903 // Check whether we need a new page or new column first as this does not fit
6904 $prev_x = $this->x;
6905 $prev_y = $this->y;
6906 if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
6907 $y = $this->y;
6908 if ($this->rtl) {
6909 $x += ($prev_x - $this->x);
6910 } else {
6911 $x += ($this->x - $prev_x);
6912 }
6913 $this->newline = true;
6914 }
6915 // resize the block to be contained on the remaining available page or column space
6916 if ($fitonpage) {
6917 // fallback to avoid division by zero
6918 $h = $h == 0 ? 1 : $h;
6919 $ratio_wh = ($w / $h);
6920 if (($y + $h) > $this->PageBreakTrigger + $this->bMargin) {
6921 $h = $this->PageBreakTrigger + $this->bMargin - $y;
6922 $w = ($h * $ratio_wh);
6923 }
6924 if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
6925 $w = $this->w - $this->rMargin - $x;
6926 $h = ($w / $ratio_wh);
6927 } elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
6928 $w = $x - $this->lMargin;
6929 $h = ($w / $ratio_wh);
6930 }
6931 }
6932 return array($w, $h, $x, $y);
6933 }
6934
6935 /**
6936 * Puts an image in the page.
6937 * The upper-left corner must be given.
6938 * The dimensions can be specified in different ways:<ul>
6939 * <li>explicit width and height (expressed in user unit)</li>
6940 * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
6941 * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
6942 * 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;
6943 * The format can be specified explicitly or inferred from the file extension.<br />
6944 * It is possible to put a link on the image.<br />
6945 * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
6946 * @param string $file Name of the file containing the image or a '@' character followed by the image data string. To link an image without embedding it on the document, set an asterisk character before the URL (i.e.: '*http://www.example.com/image.jpg').
6947 * @param float|null $x Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
6948 * @param float|null $y Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
6949 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
6950 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
6951 * @param string $type Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
6952 * @param mixed $link URL or identifier returned by AddLink().
6953 * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
6954 * @param mixed $resize If true resize (reduce) the image to fit $w and $h (requires GD or ImageMagick library); if false do not resize; if 2 force resize in all cases (upscaling and downscaling).
6955 * @param int $dpi dot-per-inch resolution used on resize
6956 * @param string $palign Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
6957 * @param boolean $ismask true if this image is a mask, false otherwise
6958 * @param mixed $imgmask image object returned by this function or false
6959 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6960 * @param mixed $fitbox If not false scale image dimensions proportionally to fit within the ($w, $h) box. $fitbox can be true or a 2 characters string indicating the image alignment inside the box. The first character indicate the horizontal alignment (L = left, C = center, R = right) the second character indicate the vertical algnment (T = top, M = middle, B = bottom).
6961 * @param boolean $hidden If true do not display the image.
6962 * @param boolean $fitonpage If true the image is resized to not exceed page dimensions.
6963 * @param boolean $alt If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
6964 * @param array $altimgs Array of alternate images IDs. Each alternative image must be an array with two values: an integer representing the image ID (the value returned by the Image method) and a boolean value to indicate if the image is the default for printing.
6965 * @return mixed|false image information
6966 * @public
6967 * @since 1.1
6968 */
6969 public function Image($file, $x=null, $y=null, $w=0, $h=0, $type='', $link='', $align='', $resize=false, $dpi=300, $palign='', $ismask=false, $imgmask=false, $border=0, $fitbox=false, $hidden=false, $fitonpage=false, $alt=false, $altimgs=array()) {
6970 if ($this->state != 2) {
6971 return false;
6972 }
6973 if (TCPDF_STATIC::empty_string($x)) {
6974 $x = $this->x;
6975 }
6976 if (TCPDF_STATIC::empty_string($y)) {
6977 $y = $this->y;
6978 }
6979 // check page for no-write regions and adapt page margins if necessary
6980 list($x, $y) = $this->checkPageRegions($h, $x, $y);
6981 $exurl = ''; // external streams
6982 $imsize = FALSE;
6983
6984 // Make sure the file variable is not empty or null because accessing $file[0] later
6985 // results in error when running PHP 7.4
6986 if (empty($file)) {
6987 return false;
6988 }
6989 // check if we are passing an image as file or string
6990 if ($file[0] === '@') {
6991 // image from string
6992 $imgdata = substr($file, 1);
6993 } else { // image file
6994 if ($file[0] === '*') {
6995 // image as external stream
6996 $file = substr($file, 1);
6997 $exurl = $file;
6998 }
6999 // check if file exist and it is valid
7000 if (!@$this->fileExists($file)) {
7001 return false;
7002 }
7003 if (false !== $info = $this->getImageBuffer($file)) {
7004 $imsize = array($info['w'], $info['h']);
7005 } elseif (($imsize = @getimagesize($file)) === FALSE && strpos($file, '__tcpdf_'.$this->file_id.'_img') === FALSE){
7006 $imgdata = $this->getCachedFileContents($file);
7007 }
7008 }
7009 if (!empty($imgdata)) {
7010 // copy image to cache
7011 $original_file = $file;
7012 $file = TCPDF_STATIC::getObjFilename('img', $this->file_id);
7013 $fp = TCPDF_STATIC::fopenLocal($file, 'w');
7014 if (!$fp) {
7015 $this->Error('Unable to write file: '.$file);
7016 }
7017 fwrite($fp, $imgdata);
7018 fclose($fp);
7019 unset($imgdata);
7020 $imsize = @getimagesize($file);
7021 if ($imsize === FALSE) {
7022 $this->_unlink($file);
7023 $file = $original_file;
7024 }
7025 }
7026 if ($imsize === FALSE) {
7027 if (($w > 0) AND ($h > 0)) {
7028 // get measures from specified data
7029 $pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
7030 $ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
7031 $imsize = array($pw, $ph);
7032 } else {
7033 $this->Error('[Image] Unable to get the size of the image: '.$file);
7034 }
7035 }
7036 // file hash
7037 $filehash = md5($file);
7038 // get original image width and height in pixels
7039 list($pixw, $pixh) = $imsize;
7040 // calculate image width and height on document
7041 if (($w <= 0) AND ($h <= 0)) {
7042 // convert image size to document unit
7043 $w = $this->pixelsToUnits($pixw);
7044 $h = $this->pixelsToUnits($pixh);
7045 } elseif ($w <= 0) {
7046 $w = $h * $pixw / $pixh;
7047 } elseif ($h <= 0) {
7048 $h = $w * $pixh / $pixw;
7049 } elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
7050 if (strlen($fitbox) !== 2) {
7051 // set default alignment
7052 $fitbox = '--';
7053 }
7054 // scale image dimensions proportionally to fit within the ($w, $h) box
7055 if ((($w * $pixh) / ($h * $pixw)) < 1) {
7056 // store current height
7057 $oldh = $h;
7058 // calculate new height
7059 $h = $w * $pixh / $pixw;
7060 // height difference
7061 $hdiff = ($oldh - $h);
7062 // vertical alignment
7063 switch (strtoupper($fitbox[1])) {
7064 case 'T': {
7065 break;
7066 }
7067 case 'M': {
7068 $y += ($hdiff / 2);
7069 break;
7070 }
7071 case 'B': {
7072 $y += $hdiff;
7073 break;
7074 }
7075 }
7076 } else {
7077 // store current width
7078 $oldw = $w;
7079 // calculate new width
7080 $w = $h * $pixw / $pixh;
7081 // width difference
7082 $wdiff = ($oldw - $w);
7083 // horizontal alignment
7084 switch (strtoupper($fitbox[0])) {
7085 case 'L': {
7086 if ($this->rtl) {
7087 $x -= $wdiff;
7088 }
7089 break;
7090 }
7091 case 'C': {
7092 if ($this->rtl) {
7093 $x -= ($wdiff / 2);
7094 } else {
7095 $x += ($wdiff / 2);
7096 }
7097 break;
7098 }
7099 case 'R': {
7100 if (!$this->rtl) {
7101 $x += $wdiff;
7102 }
7103 break;
7104 }
7105 }
7106 }
7107 }
7108 // fit the image on available space
7109 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
7110 // calculate new minimum dimensions in pixels
7111 $neww = round($w * $this->k * $dpi / $this->dpi);
7112 $newh = round($h * $this->k * $dpi / $this->dpi);
7113 // check if resize is necessary (resize is used only to reduce the image)
7114 $newsize = ($neww * $newh);
7115 $pixsize = ($pixw * $pixh);
7116 if (intval($resize) == 2) {
7117 $resize = true;
7118 } elseif ($newsize >= $pixsize) {
7119 $resize = false;
7120 }
7121 // check if image has been already added on document
7122 $newimage = true;
7123 if (in_array($file, $this->imagekeys)) {
7124 $newimage = false;
7125 // get existing image data
7126 $info = $this->getImageBuffer($file);
7127 if (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE) {
7128 // check if the newer image is larger
7129 $oldsize = ($info['w'] * $info['h']);
7130 if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7131 $newimage = true;
7132 }
7133 }
7134 } elseif (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)) {
7135 // create temp image file (without alpha channel)
7136 $tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7137 // create temp alpha file
7138 $tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7139 // check for cached images
7140 if (in_array($tempfile_plain, $this->imagekeys)) {
7141 // get existing image data
7142 $info = $this->getImageBuffer($tempfile_plain);
7143 // check if the newer image is larger
7144 $oldsize = ($info['w'] * $info['h']);
7145 if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7146 $newimage = true;
7147 } else {
7148 $newimage = false;
7149 // embed mask image
7150 $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7151 // embed image, masked with previously embedded mask
7152 return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7153 }
7154 }
7155 }
7156 if ($newimage) {
7157 //First use of image, get info
7158 $type = strtolower($type);
7159 if ($type == '') {
7160 $type = TCPDF_IMAGES::getImageFileType($file, $imsize);
7161 } elseif ($type == 'jpg') {
7162 $type = 'jpeg';
7163 }
7164 // Specific image handlers (defined on TCPDF_IMAGES CLASS)
7165 $mtd = '_parse'.$type;
7166 // GD image handler function
7167 $gdfunction = 'imagecreatefrom'.$type;
7168 $info = false;
7169 if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
7170 // TCPDF image functions
7171 $info = TCPDF_IMAGES::$mtd($file);
7172 if (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)
7173 AND (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns'])))) {
7174 return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
7175 }
7176 }
7177 if (($info === false) AND function_exists($gdfunction)) {
7178 try {
7179 // GD library
7180 $img = $gdfunction($file);
7181 if ($img !== false) {
7182 if ($resize) {
7183 $imgr = imagecreatetruecolor($neww, $newh);
7184 if (($type == 'gif') OR ($type == 'png')) {
7185 $imgr = TCPDF_IMAGES::setGDImageTransparency($imgr, $img);
7186 }
7187 imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7188 $img = $imgr;
7189 }
7190 if (($type == 'gif') OR ($type == 'png')) {
7191 $info = TCPDF_IMAGES::_toPNG($img, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7192 } else {
7193 $info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7194 }
7195 }
7196 } catch(Exception $e) {
7197 $info = false;
7198 }
7199 }
7200 if (($info === false) AND extension_loaded('imagick')) {
7201 try {
7202 // ImageMagick library
7203 $img = new Imagick();
7204 if ($type == 'svg') {
7205 if ($file[0] === '@') {
7206 // image from string
7207 $svgimg = substr($file, 1);
7208 } else {
7209 // get SVG file content
7210 $svgimg = $this->getCachedFileContents($file);
7211 }
7212 if ($svgimg !== FALSE) {
7213 // get width and height
7214 $regs = array();
7215 if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7216 $svgtag = $regs[1];
7217 $tmp = array();
7218 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7219 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7220 $owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
7221 $svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7222 } else {
7223 $ow = $w;
7224 }
7225 $tmp = array();
7226 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7227 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7228 $ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
7229 $svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7230 } else {
7231 $oh = $h;
7232 }
7233 $tmp = array();
7234 if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7235 $vbw = ($ow * $this->imgscale * $this->k);
7236 $vbh = ($oh * $this->imgscale * $this->k);
7237 $vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
7238 $svgtag = $vbox.$svgtag;
7239 }
7240 $svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7241 }
7242 $img->readImageBlob($svgimg);
7243 }
7244 } else {
7245 $img->readImage($file);
7246 }
7247 if ($resize) {
7248 $img->resizeImage($neww, $newh, 10, 1, false);
7249 }
7250 $img->setCompressionQuality($this->jpeg_quality);
7251 $img->setImageFormat('jpeg');
7252 $tempname = TCPDF_STATIC::getObjFilename('img', $this->file_id);
7253 $img->writeImage($tempname);
7254 $info = TCPDF_IMAGES::_parsejpeg($tempname);
7255 $this->_unlink($tempname);
7256 $img->destroy();
7257 } catch(Exception $e) {
7258 $info = false;
7259 }
7260 }
7261 if ($info === false) {
7262 // unable to process image
7263 return false;
7264 }
7265 if ($ismask) {
7266 // force grayscale
7267 $info['cs'] = 'DeviceGray';
7268 }
7269 if ($imgmask !== false) {
7270 $info['masked'] = $imgmask;
7271 }
7272 if (!empty($exurl)) {
7273 $info['exurl'] = $exurl;
7274 }
7275 // array of alternative images
7276 $info['altimgs'] = $altimgs;
7277 // add image to document
7278 $info['i'] = $this->setImageBuffer($file, $info);
7279 }
7280 // set alignment
7281 $this->img_rb_x = $x + $w;
7282 $this->img_rb_y = $y + $h;
7283
7284 // set alignment
7285 if ($palign == 'L') {
7286 $ximg = $this->lMargin;
7287 } elseif ($palign == 'C') {
7288 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7289 } elseif ($palign == 'R') {
7290 $ximg = $this->w - $this->rMargin - $w;
7291 } else {
7292 $ximg = $this->rtl ? $x - $w : $x;
7293 }
7294
7295 if ($ismask OR $hidden) {
7296 // image is not displayed
7297 return $info['i'];
7298 }
7299 $xkimg = $ximg * $this->k;
7300 if (!$alt) {
7301 // only non-alternative immages will be set
7302 $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']));
7303 }
7304 if (!empty($border)) {
7305 $bx = $this->x;
7306 $by = $this->y;
7307 $this->x = $ximg;
7308 if ($this->rtl) {
7309 $this->x += $w;
7310 }
7311 $this->y = $y;
7312 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7313 $this->x = $bx;
7314 $this->y = $by;
7315 }
7316 if ($link) {
7317 $this->Link($ximg, $y, $w, $h, $link, 0);
7318 }
7319 // set pointer to align the next text/objects
7320 switch($align) {
7321 case 'T': {
7322 $this->y = $y;
7323 $this->x = $this->img_rb_x;
7324 break;
7325 }
7326 case 'M': {
7327 $this->y = $y + round($h/2);
7328 $this->x = $this->img_rb_x;
7329 break;
7330 }
7331 case 'B': {
7332 $this->y = $this->img_rb_y;
7333 $this->x = $this->img_rb_x;
7334 break;
7335 }
7336 case 'N': {
7337 $this->setY($this->img_rb_y);
7338 break;
7339 }
7340 default:{
7341 break;
7342 }
7343 }
7344 $this->endlinex = $this->img_rb_x;
7345 if ($this->inxobj) {
7346 // we are inside an XObject template
7347 $this->xobjects[$this->xobjid]['images'][] = $info['i'];
7348 }
7349 return $info['i'];
7350 }
7351
7352 /**
7353 * Extract info from a PNG image with alpha channel using the Imagick or GD library.
7354 * @param string $file Name of the file containing the image.
7355 * @param float $x Abscissa of the upper-left corner.
7356 * @param float $y Ordinate of the upper-left corner.
7357 * @param float $wpx Original width of the image in pixels.
7358 * @param float $hpx original height of the image in pixels.
7359 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7360 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7361 * @param string $type Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
7362 * @param mixed $link URL or identifier returned by AddLink().
7363 * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
7364 * @param boolean $resize If true resize (reduce) the image to fit $w and $h (requires GD library).
7365 * @param int $dpi dot-per-inch resolution used on resize
7366 * @param string $palign Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
7367 * @param string $filehash File hash used to build unique file names.
7368 * @author Nicola Asuni
7369 * @protected
7370 * @since 4.3.007 (2008-12-04)
7371 * @see Image()
7372 */
7373 protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
7374 // create temp images
7375 if (empty($filehash)) {
7376 $filehash = md5($file);
7377 }
7378 // create temp image file (without alpha channel)
7379 $tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7380 // create temp alpha file
7381 $tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7382 $parsed = false;
7383 $parse_error = '';
7384 // ImageMagick extension
7385 if (($parsed === false) AND extension_loaded('imagick')) {
7386 try {
7387 // ImageMagick library
7388 $img = new Imagick();
7389 $img->readImage($file);
7390 // clone image object
7391 $imga = TCPDF_STATIC::objclone($img);
7392 // extract alpha channel
7393 if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
7394 $img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
7395 } else {
7396 $img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7397 $img->negateImage(true);
7398 }
7399 $img->setImageFormat('png');
7400 $img->writeImage($tempfile_alpha);
7401 // remove alpha channel
7402 if (method_exists($imga, 'setImageMatte')) {
7403 $imga->setImageMatte(false);
7404 } else {
7405 $imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7406 }
7407 $imga->setImageFormat('png');
7408 $imga->writeImage($tempfile_plain);
7409 $parsed = true;
7410 } catch (Exception $e) {
7411 // Imagemagick fails, try with GD
7412 $parse_error = 'Imagick library error: '.$e->getMessage();
7413 }
7414 }
7415 // GD extension
7416 if (($parsed === false) AND function_exists('imagecreatefrompng')) {
7417 try {
7418 // generate images
7419 $img = imagecreatefrompng($file);
7420 $imgalpha = imagecreate($wpx, $hpx);
7421 // generate gray scale palette (0 -> 255)
7422 for ($c = 0; $c < 256; ++$c) {
7423 ImageColorAllocate($imgalpha, $c, $c, $c);
7424 }
7425 // extract alpha channel
7426 for ($xpx = 0; $xpx < $wpx; ++$xpx) {
7427 for ($ypx = 0; $ypx < $hpx; ++$ypx) {
7428 $color = imagecolorat($img, $xpx, $ypx);
7429 // get and correct gamma color
7430 $alpha = $this->getGDgamma($img, $color);
7431 imagesetpixel($imgalpha, (int) $xpx, (int) $ypx, (int) $alpha);
7432 }
7433 }
7434 imagepng($imgalpha, $tempfile_alpha);
7435 if (PHP_VERSION_ID < 80000) {
7436 imagedestroy($imgalpha);
7437 }
7438 // extract image without alpha channel
7439 $imgplain = imagecreatetruecolor($wpx, $hpx);
7440 imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7441 imagepng($imgplain, $tempfile_plain);
7442 if (PHP_VERSION_ID < 80000) {
7443 imagedestroy($imgplain);
7444 }
7445 $parsed = true;
7446 } catch (Exception $e) {
7447 // GD fails
7448 $parse_error = 'GD library error: '.$e->getMessage();
7449 }
7450 }
7451 if ($parsed === false) {
7452 if (empty($parse_error)) {
7453 $this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
7454 } else {
7455 $this->Error($parse_error);
7456 }
7457 }
7458 // embed mask image
7459 $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7460 // embed image, masked with previously embedded mask
7461 $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7462 }
7463
7464 /**
7465 * Get the GD-corrected PNG gamma value from alpha color
7466 * @param resource $img GD image Resource ID.
7467 * @param int $c alpha color
7468 * @protected
7469 * @since 4.3.007 (2008-12-04)
7470 */
7471 protected function getGDgamma($img, $c) {
7472 if (!isset($this->gdgammacache['#'.$c])) {
7473 $colors = imagecolorsforindex($img, $c);
7474 // GD alpha is only 7 bit (0 -> 127)
7475 $this->gdgammacache['#'.$c] = (int) (((127 - $colors['alpha']) / 127) * 255);
7476 // correct gamma
7477 $this->gdgammacache['#'.$c] = (int) (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255);
7478 // store the latest values on cache to improve performances
7479 if (count($this->gdgammacache) > 8) {
7480 // remove one element from the cache array
7481 array_shift($this->gdgammacache);
7482 }
7483 }
7484 return $this->gdgammacache['#'.$c];
7485 }
7486
7487 /**
7488 * Performs a line break.
7489 * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
7490 * @param float|null $h The height of the break. By default, the value equals the height of the last printed cell.
7491 * @param boolean $cell if true add the current left (or right o for RTL) padding to the X coordinate
7492 * @public
7493 * @since 1.0
7494 * @see Cell()
7495 */
7496 public function Ln($h=null, $cell=false) {
7497 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'])) {
7498 // revove vertical space from the top of the column
7499 return;
7500 }
7501 if ($cell) {
7502 if ($this->rtl) {
7503 $cellpadding = $this->cell_padding['R'];
7504 } else {
7505 $cellpadding = $this->cell_padding['L'];
7506 }
7507 } else {
7508 $cellpadding = 0;
7509 }
7510 if ($this->rtl) {
7511 $this->x = $this->w - $this->rMargin - $cellpadding;
7512 } else {
7513 $this->x = $this->lMargin + $cellpadding;
7514 }
7515 if (TCPDF_STATIC::empty_string($h)) {
7516 $h = $this->lasth;
7517 }
7518 $this->y += $h;
7519 $this->newline = true;
7520 }
7521
7522 /**
7523 * Returns the relative X value of current position.
7524 * The value is relative to the left border for LTR languages and to the right border for RTL languages.
7525 * @return float
7526 * @public
7527 * @since 1.2
7528 * @see SetX(), GetY(), SetY()
7529 */
7530 public function GetX() {
7531 //Get x position
7532 if ($this->rtl) {
7533 return ($this->w - $this->x);
7534 } else {
7535 return $this->x;
7536 }
7537 }
7538
7539 /**
7540 * Returns the absolute X value of current position.
7541 * @return float
7542 * @public
7543 * @since 1.2
7544 * @see SetX(), GetY(), SetY()
7545 */
7546 public function GetAbsX() {
7547 return $this->x;
7548 }
7549
7550 /**
7551 * Returns the ordinate of the current position.
7552 * @return float
7553 * @public
7554 * @since 1.0
7555 * @see SetY(), GetX(), SetX()
7556 */
7557 public function GetY() {
7558 return $this->y;
7559 }
7560
7561 /**
7562 * Defines the abscissa of the current position.
7563 * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
7564 * @param float $x The value of the abscissa in user units.
7565 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7566 * @public
7567 * @since 1.2
7568 * @see GetX(), GetY(), SetY(), SetXY()
7569 */
7570 public function setX($x, $rtloff=false) {
7571 $x = floatval($x);
7572 if (!$rtloff AND $this->rtl) {
7573 if ($x >= 0) {
7574 $this->x = $this->w - $x;
7575 } else {
7576 $this->x = abs($x);
7577 }
7578 } else {
7579 if ($x >= 0) {
7580 $this->x = $x;
7581 } else {
7582 $this->x = $this->w + $x;
7583 }
7584 }
7585 if ($this->x < 0) {
7586 $this->x = 0;
7587 }
7588 if ($this->x > $this->w) {
7589 $this->x = $this->w;
7590 }
7591 }
7592
7593 /**
7594 * Moves the current abscissa back to the left margin and sets the ordinate.
7595 * If the passed value is negative, it is relative to the bottom of the page.
7596 * @param float $y The value of the ordinate in user units.
7597 * @param bool $resetx if true (default) reset the X position.
7598 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7599 * @public
7600 * @since 1.0
7601 * @see GetX(), GetY(), SetY(), SetXY()
7602 */
7603 public function setY($y, $resetx=true, $rtloff=false) {
7604 $y = floatval($y);
7605 if ($resetx) {
7606 //reset x
7607 if (!$rtloff AND $this->rtl) {
7608 $this->x = $this->w - $this->rMargin;
7609 } else {
7610 $this->x = $this->lMargin;
7611 }
7612 }
7613 if ($y >= 0) {
7614 $this->y = $y;
7615 } else {
7616 $this->y = $this->h + $y;
7617 }
7618 if ($this->y < 0) {
7619 $this->y = 0;
7620 }
7621 if ($this->y > $this->h) {
7622 $this->y = $this->h;
7623 }
7624 }
7625
7626 /**
7627 * Defines the abscissa and ordinate of the current position.
7628 * If the passed values are negative, they are relative respectively to the right and bottom of the page.
7629 * @param float $x The value of the abscissa.
7630 * @param float $y The value of the ordinate.
7631 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7632 * @public
7633 * @since 1.2
7634 * @see SetX(), SetY()
7635 */
7636 public function setXY($x, $y, $rtloff=false) {
7637 $this->setY($y, false, $rtloff);
7638 $this->setX($x, $rtloff);
7639 }
7640
7641 /**
7642 * Set the absolute X coordinate of the current pointer.
7643 * @param float $x The value of the abscissa in user units.
7644 * @public
7645 * @since 5.9.186 (2012-09-13)
7646 * @see setAbsX(), setAbsY(), SetAbsXY()
7647 */
7648 public function setAbsX($x) {
7649 $this->x = floatval($x);
7650 }
7651
7652 /**
7653 * Set the absolute Y coordinate of the current pointer.
7654 * @param float $y (float) The value of the ordinate in user units.
7655 * @public
7656 * @since 5.9.186 (2012-09-13)
7657 * @see setAbsX(), setAbsY(), SetAbsXY()
7658 */
7659 public function setAbsY($y) {
7660 $this->y = floatval($y);
7661 }
7662
7663 /**
7664 * Set the absolute X and Y coordinates of the current pointer.
7665 * @param float $x The value of the abscissa in user units.
7666 * @param float $y (float) The value of the ordinate in user units.
7667 * @public
7668 * @since 5.9.186 (2012-09-13)
7669 * @see setAbsX(), setAbsY(), SetAbsXY()
7670 */
7671 public function setAbsXY($x, $y) {
7672 $this->setAbsX($x);
7673 $this->setAbsY($y);
7674 }
7675
7676 /**
7677 * Send the document to a given destination: string, local file or browser.
7678 * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
7679 * The method first calls Close() if necessary to terminate the document.
7680 * @param string $name The name of the file when saved
7681 * @param string $dest Destination where to send the document. It can take one of the following values:<ul><li>I: send the file inline to the browser (default). The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.</li><li>D: send to the browser and force a file download with the name given by name.</li><li>F: save to a local server file with the name given by name.</li><li>S: return the document as a string (name is ignored).</li><li>FI: equivalent to F + I option</li><li>FD: equivalent to F + D option</li><li>E: return the document as base64 mime multi-part email attachment (RFC 2045)</li></ul>
7682 * @return string
7683 * @public
7684 * @since 1.0
7685 * @see Close()
7686 */
7687 public function Output($name='doc.pdf', $dest='I') {
7688 //Output PDF to some destination
7689 //Finish document if necessary
7690 if ($this->state < 3) {
7691 $this->Close();
7692 }
7693 //Normalize parameters
7694 if (is_bool($dest)) {
7695 $dest = $dest ? 'D' : 'F';
7696 }
7697 $dest = strtoupper($dest);
7698
7699 if ($this->sign) {
7700 // *** apply digital signature to the document ***
7701 // get the document content
7702 $pdfdoc = $this->getBuffer();
7703 // remove last newline
7704 $pdfdoc = substr($pdfdoc, 0, -1);
7705 // remove filler space
7706 $byterange_string_len = strlen(TCPDF_STATIC::$byterange_string);
7707 // define the ByteRange
7708 $byte_range = array();
7709 $byte_range[0] = 0;
7710 $byte_range[1] = strpos($pdfdoc, TCPDF_STATIC::$byterange_string) + $byterange_string_len + 10;
7711 $byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
7712 $byte_range[3] = strlen($pdfdoc) - $byte_range[2];
7713 $pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
7714 // replace the ByteRange
7715 $byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
7716 $byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
7717 $pdfdoc = str_replace(TCPDF_STATIC::$byterange_string, $byterange, $pdfdoc);
7718 // write the document to a temporary folder
7719 $tempdoc = TCPDF_STATIC::getObjFilename('doc', $this->file_id);
7720 $f = TCPDF_STATIC::fopenLocal($tempdoc, 'wb');
7721 if (!$f) {
7722 $this->Error('Unable to create temporary file: '.$tempdoc);
7723 }
7724 $pdfdoc_length = strlen($pdfdoc);
7725 fwrite($f, $pdfdoc, $pdfdoc_length);
7726 fclose($f);
7727 // get digital signature via openssl library
7728 $tempsign = TCPDF_STATIC::getObjFilename('sig', $this->file_id);
7729 if (empty($this->signature_data['extracerts'])) {
7730 openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
7731 } else {
7732 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']);
7733 }
7734 // read signature
7735 $signature = file_get_contents($tempsign);
7736 // extract signature
7737 $signature = substr($signature, $pdfdoc_length);
7738 $signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
7739 $tmparr = explode("\n\n", $signature);
7740 $signature = $tmparr[1];
7741 // decode signature
7742 $signature = base64_decode(trim($signature));
7743 // add TSA timestamp to signature
7744 $signature = $this->applyTSA($signature);
7745 // convert signature to hex
7746 $signature = current(unpack('H*', $signature));
7747 $signature = str_pad($signature, $this->signature_max_length, '0');
7748 // Add signature to the document
7749 $this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
7750 $this->bufferlen = strlen($this->buffer);
7751 }
7752 switch($dest) {
7753 case 'I': {
7754 // Send PDF to the standard output
7755 if (ob_get_contents()) {
7756 $this->Error('Some data has already been output, can\'t send PDF file');
7757 }
7758 if (php_sapi_name() != 'cli') {
7759 // send output to a browser
7760 header('Content-Type: application/pdf');
7761 if (headers_sent()) {
7762 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7763 }
7764 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7765 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7766 header('Pragma: public');
7767 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7768 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7769 header('Content-Disposition: inline; filename="' . rawurlencode(basename($name)) . '"; ' .
7770 'filename*=UTF-8\'\'' . rawurlencode(basename($name)));
7771 TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7772 } else {
7773 echo $this->getBuffer();
7774 }
7775 break;
7776 }
7777 case 'D': {
7778 // download PDF as file
7779 if (ob_get_contents()) {
7780 $this->Error('Some data has already been output, can\'t send PDF file');
7781 }
7782 header('Content-Description: File Transfer');
7783 if (headers_sent()) {
7784 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7785 }
7786 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7787 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7788 header('Pragma: public');
7789 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7790 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7791 // force download dialog
7792 if (strpos(php_sapi_name(), 'cgi') === false) {
7793 header('Content-Type: application/force-download');
7794 header('Content-Type: application/octet-stream', false);
7795 header('Content-Type: application/download', false);
7796 header('Content-Type: application/pdf', false);
7797 } else {
7798 header('Content-Type: application/pdf');
7799 }
7800 // use the Content-Disposition header to supply a recommended filename
7801 header('Content-Disposition: attachment; filename="' . rawurlencode(basename($name)) . '"; ' .
7802 'filename*=UTF-8\'\'' . rawurlencode(basename($name)));
7803 header('Content-Transfer-Encoding: binary');
7804 TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7805 break;
7806 }
7807 case 'F':
7808 case 'FI':
7809 case 'FD': {
7810 // save PDF to a local file
7811 $f = TCPDF_STATIC::fopenLocal($name, 'wb');
7812 if (!$f) {
7813 $this->Error('Unable to create output file: '.$name);
7814 }
7815 fwrite($f, $this->getBuffer(), $this->bufferlen);
7816 fclose($f);
7817 if ($dest == 'FI') {
7818 // send headers to browser
7819 header('Content-Type: application/pdf');
7820 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7821 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7822 header('Pragma: public');
7823 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7824 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7825 header('Content-Disposition: inline; filename="'.basename($name).'"');
7826 TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7827 } elseif ($dest == 'FD') {
7828 // send headers to browser
7829 if (ob_get_contents()) {
7830 $this->Error('Some data has already been output, can\'t send PDF file');
7831 }
7832 header('Content-Description: File Transfer');
7833 if (headers_sent()) {
7834 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7835 }
7836 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7837 header('Pragma: public');
7838 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7839 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7840 // force download dialog
7841 if (strpos(php_sapi_name(), 'cgi') === false) {
7842 header('Content-Type: application/force-download');
7843 header('Content-Type: application/octet-stream', false);
7844 header('Content-Type: application/download', false);
7845 header('Content-Type: application/pdf', false);
7846 } else {
7847 header('Content-Type: application/pdf');
7848 }
7849 // use the Content-Disposition header to supply a recommended filename
7850 header('Content-Disposition: attachment; filename="'.basename($name).'"');
7851 header('Content-Transfer-Encoding: binary');
7852 TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7853 }
7854 break;
7855 }
7856 case 'E': {
7857 // return PDF as base64 mime multi-part email attachment (RFC 2045)
7858 $retval = 'Content-Type: application/pdf;'."\r\n";
7859 $retval .= ' name="'.$name.'"'."\r\n";
7860 $retval .= 'Content-Transfer-Encoding: base64'."\r\n";
7861 $retval .= 'Content-Disposition: attachment;'."\r\n";
7862 $retval .= ' filename="'.$name.'"'."\r\n\r\n";
7863 $retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
7864 return $retval;
7865 }
7866 case 'S': {
7867 // returns PDF as a string
7868 return $this->getBuffer();
7869 }
7870 default: {
7871 $this->Error('Incorrect output destination: '.$dest);
7872 }
7873 }
7874 return '';
7875 }
7876
7877 protected static $cleaned_ids = array();
7878 /**
7879 * Unset all class variables except the following critical variables.
7880 * @param boolean $destroyall if true destroys all class variables, otherwise preserves critical variables.
7881 * @param boolean $preserve_objcopy if true preserves the objcopy variable
7882 * @public
7883 * @since 4.5.016 (2009-02-24)
7884 */
7885 public function _destroy($destroyall=false, $preserve_objcopy=false) {
7886 if (isset($this->file_id) && isset(self::$cleaned_ids[$this->file_id])) {
7887 $destroyall = false;
7888 }
7889 if ($destroyall AND !$preserve_objcopy && isset($this->file_id)) {
7890 self::$cleaned_ids[$this->file_id] = true;
7891 // remove all temporary files
7892 if ($handle = @opendir(K_PATH_CACHE)) {
7893 while ( false !== ( $file_name = readdir( $handle ) ) ) {
7894 if (strpos($file_name, '__tcpdf_'.$this->file_id.'_') === 0) {
7895 $this->_unlink(K_PATH_CACHE.$file_name);
7896 }
7897 }
7898 closedir($handle);
7899 }
7900 if (isset($this->imagekeys)) {
7901 foreach($this->imagekeys as $file) {
7902 if ((strpos($file, K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_') === 0)
7903 && TCPDF_STATIC::file_exists($file)) {
7904 $this->_unlink($file);
7905 }
7906 }
7907 }
7908 }
7909 $preserve = array(
7910 'file_id',
7911 'state',
7912 'bufferlen',
7913 'buffer',
7914 'cached_files',
7915 'imagekeys',
7916 'sign',
7917 'signature_data',
7918 'signature_max_length',
7919 'byterange_string',
7920 'tsa_timestamp',
7921 'tsa_data'
7922 );
7923 foreach (array_keys(get_object_vars($this)) as $val) {
7924 if ($destroyall OR !in_array($val, $preserve)) {
7925 if ((!$preserve_objcopy OR ($val != 'objcopy')) AND ($val != 'file_id') AND isset($this->$val)) {
7926 unset($this->$val);
7927 }
7928 }
7929 }
7930 }
7931
7932 /**
7933 * Check for locale-related bug
7934 * @protected
7935 */
7936 protected function _dochecks() {
7937 //Check for locale-related bug
7938 if (1.1 == 1) {
7939 $this->Error('Don\'t alter the locale before including class file');
7940 }
7941 //Check for decimal separator
7942 if (sprintf('%.1F', 1.0) != '1.0') {
7943 setlocale(LC_NUMERIC, 'C');
7944 }
7945 }
7946
7947 /**
7948 * Return an array containing variations for the basic page number alias.
7949 * @param string $a Base alias.
7950 * @return array of page number aliases
7951 * @protected
7952 */
7953 protected function getInternalPageNumberAliases($a= '') {
7954 $alias = array();
7955 // build array of Unicode + ASCII variants (the order is important)
7956 $alias = array('u' => array(), 'a' => array());
7957 $u = '{'.$a.'}';
7958 $alias['u'][] = TCPDF_STATIC::_escape($u);
7959 if ($this->isunicode) {
7960 $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($u, $this->isunicode, $this->CurrentFont));
7961 $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($u, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7962 $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($a, $this->isunicode, $this->CurrentFont));
7963 $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($a, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7964 }
7965 $alias['a'][] = TCPDF_STATIC::_escape($a);
7966 return $alias;
7967 }
7968
7969 /**
7970 * Return an array containing all internal page aliases.
7971 * @return array of page number aliases
7972 * @protected
7973 */
7974 protected function getAllInternalPageNumberAliases() {
7975 $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);
7976 $pnalias = array();
7977 foreach($basic_alias as $k => $a) {
7978 $pnalias[$k] = $this->getInternalPageNumberAliases($a);
7979 }
7980 return $pnalias;
7981 }
7982
7983 /**
7984 * Replace right shift page number aliases with spaces to correct right alignment.
7985 * This works perfectly only when using monospaced fonts.
7986 * @param string $page Page content.
7987 * @param array $aliases Array of page aliases.
7988 * @param int $diff initial difference to add.
7989 * @return string replaced page content.
7990 * @protected
7991 */
7992 protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
7993 foreach ($aliases as $type => $alias) {
7994 foreach ($alias as $a) {
7995 // find position of compensation factor
7996 $startnum = (strpos($a, ':') + 1);
7997 $a = substr($a, 0, $startnum);
7998 if (($pos = strpos($page, $a)) !== false) {
7999 // end of alias
8000 $endnum = strpos($page, '}', $pos);
8001 // string to be replaced
8002 $aa = substr($page, $pos, ($endnum - $pos + 1));
8003 // get compensation factor
8004 $ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
8005 $ratio = preg_replace('/[^0-9\.]/', '', $ratio);
8006 $ratio = floatval($ratio);
8007 if ($type == 'u') {
8008 $chrdiff = floor(($diff + 12) * $ratio);
8009 $shift = str_repeat(' ', $chrdiff);
8010 $shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
8011 } else {
8012 $chrdiff = floor(($diff + 11) * $ratio);
8013 $shift = str_repeat(' ', $chrdiff);
8014 }
8015 $page = str_replace($aa, $shift, $page);
8016 }
8017 }
8018 }
8019 return $page;
8020 }
8021
8022 /**
8023 * Set page boxes to be included on page descriptions.
8024 * @param array $boxes Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
8025 * @protected
8026 */
8027 protected function setPageBoxTypes($boxes) {
8028 $this->page_boxes = array();
8029 foreach ($boxes as $box) {
8030 if (in_array($box, TCPDF_STATIC::$pageboxes)) {
8031 $this->page_boxes[] = $box;
8032 }
8033 }
8034 }
8035
8036 /**
8037 * Output pages (and replace page number aliases).
8038 * @protected
8039 */
8040 protected function _putpages() {
8041 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
8042 // get internal aliases for page numbers
8043 $pnalias = $this->getAllInternalPageNumberAliases();
8044 $num_pages = $this->numpages;
8045 $ptpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $num_pages - 1));
8046 $ptpu = TCPDF_FONTS::UTF8ToUTF16BE($ptpa, false, $this->isunicode, $this->CurrentFont);
8047 $ptp_num_chars = $this->GetNumChars($ptpa);
8048 $pagegroupnum = 0;
8049 $groupnum = 0;
8050 $ptgu = 1;
8051 $ptga = 1;
8052 $ptg_num_chars = 1;
8053 for ($n = 1; $n <= $num_pages; ++$n) {
8054 // get current page
8055 $temppage = $this->getPageBuffer($n);
8056 $pagelen = strlen($temppage);
8057 // set replacements for total pages number
8058 $pnpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $n - 1));
8059 $pnpu = TCPDF_FONTS::UTF8ToUTF16BE($pnpa, false, $this->isunicode, $this->CurrentFont);
8060 $pnp_num_chars = $this->GetNumChars($pnpa);
8061 $pdiff = 0; // difference used for right shift alignment of page numbers
8062 $gdiff = 0; // difference used for right shift alignment of page group numbers
8063 if (!empty($this->pagegroups)) {
8064 if (isset($this->newpagegroup[$n])) {
8065 $pagegroupnum = 0;
8066 ++$groupnum;
8067 $ptga = TCPDF_STATIC::formatPageNumber($this->pagegroups[$groupnum]);
8068 $ptgu = TCPDF_FONTS::UTF8ToUTF16BE($ptga, false, $this->isunicode, $this->CurrentFont);
8069 $ptg_num_chars = $this->GetNumChars($ptga);
8070 }
8071 ++$pagegroupnum;
8072 $pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
8073 $pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
8074 $png_num_chars = $this->GetNumChars($pnga);
8075 // replace page numbers
8076 $replace = array();
8077 $replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
8078 $replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
8079 $replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
8080 $replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
8081 list($temppage, $gdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $gdiff);
8082 }
8083 // replace page numbers
8084 $replace = array();
8085 $replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
8086 $replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
8087 $replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
8088 $replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
8089 list($temppage, $pdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $pdiff);
8090 // replace right shift alias
8091 $temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
8092 // replace EPS marker
8093 $temppage = str_replace($this->epsmarker, '', $temppage);
8094 //Page
8095 $this->page_obj_id[$n] = $this->_newobj();
8096 $out = '<<';
8097 $out .= ' /Type /Page';
8098 $out .= ' /Parent 1 0 R';
8099 if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
8100 $out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
8101 }
8102 $out .= ' /Resources 2 0 R';
8103 foreach ($this->page_boxes as $box) {
8104 $out .= ' /'.$box;
8105 $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']);
8106 }
8107 if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
8108 $out .= ' /BoxColorInfo <<';
8109 foreach ($this->page_boxes as $box) {
8110 if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
8111 $out .= ' /'.$box.' <<';
8112 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
8113 $color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
8114 $out .= ' /C [';
8115 $out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
8116 $out .= ' ]';
8117 }
8118 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
8119 $out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
8120 }
8121 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
8122 $out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
8123 }
8124 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
8125 $dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
8126 $out .= ' /D [';
8127 foreach ($dashes as $dash) {
8128 $out .= sprintf(' %F', ($dash * $this->k));
8129 }
8130 $out .= ' ]';
8131 }
8132 $out .= ' >>';
8133 }
8134 }
8135 $out .= ' >>';
8136 }
8137 $out .= ' /Contents '.($this->n + 1).' 0 R';
8138 $out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
8139 if (!$this->pdfa_mode || $this->pdfa_version >= 2) {
8140 $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
8141 }
8142 if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
8143 // page transitions
8144 if (isset($this->pagedim[$n]['trans']['Dur'])) {
8145 $out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
8146 }
8147 $out .= ' /Trans <<';
8148 $out .= ' /Type /Trans';
8149 if (isset($this->pagedim[$n]['trans']['S'])) {
8150 $out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
8151 }
8152 if (isset($this->pagedim[$n]['trans']['D'])) {
8153 $out .= ' /D '.$this->pagedim[$n]['trans']['D'];
8154 }
8155 if (isset($this->pagedim[$n]['trans']['Dm'])) {
8156 $out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
8157 }
8158 if (isset($this->pagedim[$n]['trans']['M'])) {
8159 $out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
8160 }
8161 if (isset($this->pagedim[$n]['trans']['Di'])) {
8162 $out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
8163 }
8164 if (isset($this->pagedim[$n]['trans']['SS'])) {
8165 $out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
8166 }
8167 if (isset($this->pagedim[$n]['trans']['B'])) {
8168 $out .= ' /B '.$this->pagedim[$n]['trans']['B'];
8169 }
8170 $out .= ' >>';
8171 }
8172 $out .= $this->_getannotsrefs($n);
8173 $out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8174 $out .= ' >>';
8175 $out .= "\n".'endobj';
8176 $this->_out($out);
8177 //Page content
8178 $p = ($this->compress) ? gzcompress($temppage) : $temppage;
8179 $this->_newobj();
8180 $p = $this->_getrawstream($p);
8181 $this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8182 }
8183 //Pages root
8184 $out = $this->_getobj(1)."\n";
8185 $out .= '<< /Type /Pages /Kids [';
8186 foreach($this->page_obj_id as $page_obj) {
8187 $out .= ' '.$page_obj.' 0 R';
8188 }
8189 $out .= ' ] /Count '.$num_pages.' >>';
8190 $out .= "\n".'endobj';
8191 $this->_out($out);
8192 }
8193
8194 /**
8195 * Get references to page annotations.
8196 * @param int $n page number
8197 * @return string
8198 * @protected
8199 * @author Nicola Asuni
8200 * @since 5.0.010 (2010-05-17)
8201 */
8202 protected function _getannotsrefs($n) {
8203 if (!(isset($this->PageAnnots[$n]) OR count($this->empty_signature_appearance)>0 OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
8204 return '';
8205 }
8206 $out = ' /Annots [';
8207 if (isset($this->PageAnnots[$n])) {
8208 foreach ($this->PageAnnots[$n] as $key => $val) {
8209 if (!in_array($val['n'], $this->radio_groups)) {
8210 $out .= ' '.$val['n'].' 0 R';
8211 }
8212 }
8213 // add radiobutton groups
8214 if (isset($this->radiobutton_groups[$n])) {
8215 foreach ($this->radiobutton_groups[$n] as $key => $data) {
8216 if (isset($data['n'])) {
8217 $out .= ' '.$data['n'].' 0 R';
8218 }
8219 }
8220 }
8221 }
8222 if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
8223 // set reference for signature object
8224 $out .= ' '.$this->sig_obj_id.' 0 R';
8225 }
8226 if (!empty($this->empty_signature_appearance)) {
8227 foreach ($this->empty_signature_appearance as $esa) {
8228 if ($esa['page'] == $n) {
8229 // set reference for empty signature objects
8230 $out .= ' '.$esa['objid'].' 0 R';
8231 }
8232 }
8233 }
8234 $out .= ' ]';
8235 return $out;
8236 }
8237
8238 /**
8239 * Output annotations objects for all pages.
8240 * !!! THIS METHOD IS NOT YET COMPLETED !!!
8241 * See section 12.5 of PDF 32000_2008 reference.
8242 * @protected
8243 * @author Nicola Asuni
8244 * @since 4.0.018 (2008-08-06)
8245 */
8246 protected function _putannotsobjs() {
8247 // reset object counter
8248 for ($n=1; $n <= $this->numpages; ++$n) {
8249 if (isset($this->PageAnnots[$n])) {
8250 // set page annotations
8251 foreach ($this->PageAnnots[$n] as $key => $pl) {
8252 $annot_obj_id = $this->PageAnnots[$n][$key]['n'];
8253 // create annotation object for grouping radiobuttons
8254 if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
8255 $radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
8256 $annots = '<<';
8257 $annots .= ' /Type /Annot';
8258 $annots .= ' /Subtype /Widget';
8259 $annots .= ' /Rect [0 0 0 0]';
8260 if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
8261 // read only
8262 $annots .= ' /F 68';
8263 $annots .= ' /Ff 49153';
8264 } else {
8265 $annots .= ' /F 4'; // default print for PDF/A
8266 $annots .= ' /Ff 49152';
8267 }
8268 $annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8269 if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8270 $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
8271 }
8272 $annots .= ' /FT /Btn';
8273 $annots .= ' /Kids [';
8274 $defval = '';
8275 foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
8276 if (isset($data['kid'])) {
8277 $annots .= ' '.$data['kid'].' 0 R';
8278 if ($data['def'] !== 'Off') {
8279 $defval = $data['def'];
8280 }
8281 }
8282 }
8283 $annots .= ' ]';
8284 if (!empty($defval)) {
8285 $annots .= ' /V /'.$defval;
8286 }
8287 $annots .= ' >>';
8288 $this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8289 $this->form_obj_id[] = $radio_button_obj_id;
8290 // store object id to be used on Parent entry of Kids
8291 $this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
8292 }
8293 $formfield = false;
8294 $pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
8295 $a = $pl['x'] * $this->k;
8296 $b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
8297 $c = $pl['w'] * $this->k;
8298 $d = $pl['h'] * $this->k;
8299 $rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
8300 // create new annotation object
8301 $annots = '<</Type /Annot';
8302 $annots .= ' /Subtype /'.$pl['opt']['subtype'];
8303 $annots .= ' /Rect ['.$rect.']';
8304 $ft = array('Btn', 'Tx', 'Ch', 'Sig');
8305 if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8306 $annots .= ' /FT /'.$pl['opt']['ft'];
8307 $formfield = true;
8308 }
8309 if ($pl['opt']['subtype'] !== 'Link') {
8310 $annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8311 }
8312 $annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
8313 $annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8314 $annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
8315 if (isset($pl['opt']['f'])) {
8316 $fval = 0;
8317 if (is_array($pl['opt']['f'])) {
8318 foreach ($pl['opt']['f'] as $f) {
8319 switch (strtolower($f)) {
8320 case 'invisible': {
8321 $fval += 1 << 0;
8322 break;
8323 }
8324 case 'hidden': {
8325 $fval += 1 << 1;
8326 break;
8327 }
8328 case 'print': {
8329 $fval += 1 << 2;
8330 break;
8331 }
8332 case 'nozoom': {
8333 $fval += 1 << 3;
8334 break;
8335 }
8336 case 'norotate': {
8337 $fval += 1 << 4;
8338 break;
8339 }
8340 case 'noview': {
8341 $fval += 1 << 5;
8342 break;
8343 }
8344 case 'readonly': {
8345 $fval += 1 << 6;
8346 break;
8347 }
8348 case 'locked': {
8349 $fval += 1 << 7;
8350 break;
8351 }
8352 case 'togglenoview': {
8353 $fval += 1 << 8;
8354 break;
8355 }
8356 case 'lockedcontents': {
8357 $fval += 1 << 9;
8358 break;
8359 }
8360 default: {
8361 break;
8362 }
8363 }
8364 }
8365 } else {
8366 $fval = intval($pl['opt']['f']);
8367 }
8368 } else {
8369 $fval = 4;
8370 }
8371 if ($this->pdfa_mode) {
8372 // force print flag for PDF/A mode
8373 $fval |= 4;
8374 }
8375 $annots .= ' /F '.intval($fval);
8376 if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8377 $annots .= ' /AS /'.$pl['opt']['as'];
8378 }
8379 if (isset($pl['opt']['ap'])) {
8380 // appearance stream
8381 $annots .= ' /AP <<';
8382 if (is_array($pl['opt']['ap'])) {
8383 foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8384 // $apmode can be: n = normal; r = rollover; d = down;
8385 $annots .= ' /'.strtoupper($apmode);
8386 if (is_array($apdef)) {
8387 $annots .= ' <<';
8388 foreach ($apdef as $apstate => $stream) {
8389 // reference to XObject that define the appearance for this mode-state
8390 $apsobjid = $this->_putAPXObject($c, $d, $stream);
8391 $annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8392 }
8393 $annots .= ' >>';
8394 } else {
8395 // reference to XObject that define the appearance for this mode
8396 $apsobjid = $this->_putAPXObject($c, $d, $apdef);
8397 $annots .= ' '.$apsobjid.' 0 R';
8398 }
8399 }
8400 } else {
8401 $annots .= $pl['opt']['ap'];
8402 }
8403 $annots .= ' >>';
8404 }
8405 if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8406 $annots .= ' /BS <<';
8407 $annots .= ' /Type /Border';
8408 if (isset($pl['opt']['bs']['w'])) {
8409 $annots .= ' /W '.intval($pl['opt']['bs']['w']);
8410 }
8411 $bstyles = array('S', 'D', 'B', 'I', 'U');
8412 if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8413 $annots .= ' /S /'.$pl['opt']['bs']['s'];
8414 }
8415 if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8416 $annots .= ' /D [';
8417 foreach ($pl['opt']['bs']['d'] as $cord) {
8418 $annots .= ' '.intval($cord);
8419 }
8420 $annots .= ']';
8421 }
8422 $annots .= ' >>';
8423 } else {
8424 $annots .= ' /Border [';
8425 if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8426 $annots .= intval($pl['opt']['border'][0]).' ';
8427 $annots .= intval($pl['opt']['border'][1]).' ';
8428 $annots .= intval($pl['opt']['border'][2]);
8429 if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8430 $annots .= ' [';
8431 foreach ($pl['opt']['border'][3] as $dash) {
8432 $annots .= intval($dash).' ';
8433 }
8434 $annots .= ']';
8435 }
8436 } else {
8437 $annots .= '0 0 0';
8438 }
8439 $annots .= ']';
8440 }
8441 if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8442 $annots .= ' /BE <<';
8443 $bstyles = array('S', 'C');
8444 if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
8445 $annots .= ' /S /'.$pl['opt']['bs']['s'];
8446 } else {
8447 $annots .= ' /S /S';
8448 }
8449 if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8450 $annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
8451 }
8452 $annots .= '>>';
8453 }
8454 if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8455 $annots .= ' /C '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['c']);
8456 }
8457 //$annots .= ' /StructParent ';
8458 //$annots .= ' /OC ';
8459 $markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8460 if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8461 // this is a markup type
8462 if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8463 $annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8464 }
8465 //$annots .= ' /Popup ';
8466 if (isset($pl['opt']['ca'])) {
8467 $annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
8468 }
8469 if (isset($pl['opt']['rc'])) {
8470 $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8471 }
8472 $annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
8473 //$annots .= ' /IRT ';
8474 if (isset($pl['opt']['subj'])) {
8475 $annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8476 }
8477 //$annots .= ' /RT ';
8478 //$annots .= ' /IT ';
8479 //$annots .= ' /ExData ';
8480 }
8481 $lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8482 // Annotation types
8483 switch (strtolower($pl['opt']['subtype'])) {
8484 case 'text': {
8485 if (isset($pl['opt']['open'])) {
8486 $annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
8487 }
8488 $iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8489 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8490 $annots .= ' /Name /'.$pl['opt']['name'];
8491 } else {
8492 $annots .= ' /Name /Note';
8493 }
8494 $hasStateModel = isset($pl['opt']['statemodel']);
8495 $hasState = isset($pl['opt']['state']);
8496 $statemodels = array('Marked', 'Review');
8497 if (!$hasStateModel && !$hasState) {
8498 break;
8499 }
8500 if ($hasStateModel AND in_array($pl['opt']['statemodel'], $statemodels)) {
8501 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8502 } else {
8503 $pl['opt']['statemodel'] = 'Marked';
8504 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8505 }
8506 if ($pl['opt']['statemodel'] == 'Marked') {
8507 $states = array('Accepted', 'Unmarked');
8508 } else {
8509 $states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8510 }
8511 if ($hasState AND in_array($pl['opt']['state'], $states)) {
8512 $annots .= ' /State /'.$pl['opt']['state'];
8513 } else {
8514 if ($pl['opt']['statemodel'] == 'Marked') {
8515 $annots .= ' /State /Unmarked';
8516 } else {
8517 $annots .= ' /State /None';
8518 }
8519 }
8520 break;
8521 }
8522 case 'link': {
8523 if (is_string($pl['txt']) && !empty($pl['txt'])) {
8524 if ($pl['txt'][0] == '#') {
8525 // internal destination
8526 $annots .= ' /A <</S /GoTo /D /'.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1)).'>>';
8527 } elseif ($pl['txt'][0] == '%') {
8528 // embedded PDF file
8529 $filename = basename(substr($pl['txt'], 1));
8530 $annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
8531 } elseif ($pl['txt'][0] == '*') {
8532 // embedded generic file
8533 $filename = basename(substr($pl['txt'], 1));
8534 $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});';
8535 $annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
8536 } else {
8537 $parsedUrl = parse_url($pl['txt']);
8538 if (empty($parsedUrl['scheme']) AND (!empty($parsedUrl['path']) && strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
8539 // relative link to a PDF file
8540 $dest = '[0 /Fit]'; // default page 0
8541 if (!empty($parsedUrl['fragment'])) {
8542 // check for named destination
8543 $tmp = explode('=', $parsedUrl['fragment']);
8544 $dest = '('.((count($tmp) == 2) ? $tmp[1] : $tmp[0]).')';
8545 }
8546 $annots .= ' /A <</S /GoToR /D '.$dest.' /F '.$this->_datastring($this->unhtmlentities($parsedUrl['path']), $annot_obj_id).' /NewWindow true>>';
8547 } else {
8548 // external URI link
8549 $annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8550 }
8551 }
8552 } elseif (isset($this->links[$pl['txt']])) {
8553 // internal link ID
8554 $l = $this->links[$pl['txt']];
8555 if (isset($this->page_obj_id[($l['p'])])) {
8556 $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)));
8557 }
8558 }
8559 $hmodes = array('N', 'I', 'O', 'P');
8560 if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8561 $annots .= ' /H /'.$pl['opt']['h'];
8562 } else {
8563 $annots .= ' /H /I';
8564 }
8565 //$annots .= ' /PA ';
8566 //$annots .= ' /Quadpoints ';
8567 break;
8568 }
8569 case 'freetext': {
8570 if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8571 $annots .= ' /DA '.$this->_datastring($pl['opt']['da']);
8572 }
8573 if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8574 $annots .= ' /Q '.intval($pl['opt']['q']);
8575 }
8576 if (isset($pl['opt']['rc'])) {
8577 $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8578 }
8579 if (isset($pl['opt']['ds'])) {
8580 $annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8581 }
8582 if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8583 $annots .= ' /CL [';
8584 foreach ($pl['opt']['cl'] as $cl) {
8585 $annots .= sprintf('%F ', $cl * $this->k);
8586 }
8587 $annots .= ']';
8588 }
8589 $tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8590 if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8591 $annots .= ' /IT /'.$pl['opt']['it'];
8592 }
8593 if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8594 $l = $pl['opt']['rd'][0] * $this->k;
8595 $r = $pl['opt']['rd'][1] * $this->k;
8596 $t = $pl['opt']['rd'][2] * $this->k;
8597 $b = $pl['opt']['rd'][3] * $this->k;
8598 $annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
8599 }
8600 if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8601 $annots .= ' /LE /'.$pl['opt']['le'];
8602 }
8603 break;
8604 }
8605 case 'line': {
8606 break;
8607 }
8608 case 'square': {
8609 break;
8610 }
8611 case 'circle': {
8612 break;
8613 }
8614 case 'polygon': {
8615 break;
8616 }
8617 case 'polyline': {
8618 break;
8619 }
8620 case 'highlight': {
8621 break;
8622 }
8623 case 'underline': {
8624 break;
8625 }
8626 case 'squiggly': {
8627 break;
8628 }
8629 case 'strikeout': {
8630 break;
8631 }
8632 case 'stamp': {
8633 break;
8634 }
8635 case 'caret': {
8636 break;
8637 }
8638 case 'ink': {
8639 break;
8640 }
8641 case 'popup': {
8642 break;
8643 }
8644 case 'fileattachment': {
8645 if ($this->pdfa_mode && $this->pdfa_version != 3) {
8646 // embedded files are not allowed in PDF/A mode version 1 and 2
8647 break;
8648 }
8649 if (!isset($pl['opt']['fs'])) {
8650 break;
8651 }
8652 $filename = basename($pl['opt']['fs']);
8653 if (isset($this->embeddedfiles[$filename]['f'])) {
8654 $annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
8655 $iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8656 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8657 $annots .= ' /Name /'.$pl['opt']['name'];
8658 } else {
8659 $annots .= ' /Name /PushPin';
8660 }
8661 // index (zero-based) of the annotation in the Annots array of this page
8662 $this->embeddedfiles[$filename]['a'] = $key;
8663 }
8664 break;
8665 }
8666 case 'sound': {
8667 if (!isset($pl['opt']['fs'])) {
8668 break;
8669 }
8670 $filename = basename($pl['opt']['fs']);
8671 if (isset($this->embeddedfiles[$filename]['f'])) {
8672 // ... TO BE COMPLETED ...
8673 // /R /C /B /E /CO /CP
8674 $annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
8675 $iconsapp = array('Speaker', 'Mic');
8676 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8677 $annots .= ' /Name /'.$pl['opt']['name'];
8678 } else {
8679 $annots .= ' /Name /Speaker';
8680 }
8681 }
8682 break;
8683 }
8684 case 'movie': {
8685 break;
8686 }
8687 case 'widget': {
8688 $hmode = array('N', 'I', 'O', 'P', 'T');
8689 if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8690 $annots .= ' /H /'.$pl['opt']['h'];
8691 }
8692 if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8693 $annots .= ' /MK <<';
8694 if (isset($pl['opt']['mk']['r'])) {
8695 $annots .= ' /R '.$pl['opt']['mk']['r'];
8696 }
8697 if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8698 $annots .= ' /BC '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bc']);
8699 }
8700 if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8701 $annots .= ' /BG '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bg']);
8702 }
8703 if (isset($pl['opt']['mk']['ca'])) {
8704 $annots .= ' /CA '.$pl['opt']['mk']['ca'];
8705 }
8706 if (isset($pl['opt']['mk']['rc'])) {
8707 $annots .= ' /RC '.$pl['opt']['mk']['rc'];
8708 }
8709 if (isset($pl['opt']['mk']['ac'])) {
8710 $annots .= ' /AC '.$pl['opt']['mk']['ac'];
8711 }
8712 if (isset($pl['opt']['mk']['i'])) {
8713 $info = $this->getImageBuffer($pl['opt']['mk']['i']);
8714 if ($info !== false) {
8715 $annots .= ' /I '.$info['n'].' 0 R';
8716 }
8717 }
8718 if (isset($pl['opt']['mk']['ri'])) {
8719 $info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8720 if ($info !== false) {
8721 $annots .= ' /RI '.$info['n'].' 0 R';
8722 }
8723 }
8724 if (isset($pl['opt']['mk']['ix'])) {
8725 $info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8726 if ($info !== false) {
8727 $annots .= ' /IX '.$info['n'].' 0 R';
8728 }
8729 }
8730 if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8731 $annots .= ' /IF <<';
8732 $if_sw = array('A', 'B', 'S', 'N');
8733 if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8734 $annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8735 }
8736 $if_s = array('A', 'P');
8737 if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8738 $annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8739 }
8740 if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8741 $annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8742 }
8743 if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8744 $annots .= ' /FB true';
8745 }
8746 $annots .= '>>';
8747 }
8748 if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8749 $annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8750 }
8751 $annots .= '>>';
8752 } // end MK
8753 // --- Entries for field dictionaries ---
8754 if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8755 // set parent
8756 $annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
8757 }
8758 if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8759 $annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8760 }
8761 if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8762 $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8763 }
8764 if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8765 $annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8766 }
8767 if (isset($pl['opt']['ff'])) {
8768 if (is_array($pl['opt']['ff'])) {
8769 // array of bit settings
8770 $flag = 0;
8771 foreach($pl['opt']['ff'] as $val) {
8772 $flag += 1 << ($val - 1);
8773 }
8774 } else {
8775 $flag = intval($pl['opt']['ff']);
8776 }
8777 $annots .= ' /Ff '.$flag;
8778 }
8779 if (isset($pl['opt']['maxlen'])) {
8780 $annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
8781 }
8782 if (isset($pl['opt']['v'])) {
8783 $annots .= ' /V';
8784 if (is_array($pl['opt']['v'])) {
8785 foreach ($pl['opt']['v'] AS $optval) {
8786 if (is_float($optval)) {
8787 $optval = sprintf('%F', $optval);
8788 }
8789 $annots .= ' '.$optval;
8790 }
8791 } else {
8792 $annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8793 }
8794 }
8795 if (isset($pl['opt']['dv'])) {
8796 $annots .= ' /DV';
8797 if (is_array($pl['opt']['dv'])) {
8798 foreach ($pl['opt']['dv'] AS $optval) {
8799 if (is_float($optval)) {
8800 $optval = sprintf('%F', $optval);
8801 }
8802 $annots .= ' '.$optval;
8803 }
8804 } else {
8805 $annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8806 }
8807 }
8808 if (isset($pl['opt']['rv'])) {
8809 $annots .= ' /RV';
8810 if (is_array($pl['opt']['rv'])) {
8811 foreach ($pl['opt']['rv'] AS $optval) {
8812 if (is_float($optval)) {
8813 $optval = sprintf('%F', $optval);
8814 }
8815 $annots .= ' '.$optval;
8816 }
8817 } else {
8818 $annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
8819 }
8820 }
8821 if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
8822 $annots .= ' /A << '.$pl['opt']['a'].' >>';
8823 }
8824 if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
8825 $annots .= ' /AA << '.$pl['opt']['aa'].' >>';
8826 }
8827 if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8828 $annots .= ' /DA '.$this->_datastring($pl['opt']['da']);
8829 }
8830 if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8831 $annots .= ' /Q '.intval($pl['opt']['q']);
8832 }
8833 if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
8834 $annots .= ' /Opt [';
8835 foreach($pl['opt']['opt'] AS $copt) {
8836 if (is_array($copt)) {
8837 $annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
8838 } else {
8839 $annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8840 }
8841 }
8842 $annots .= ']';
8843 }
8844 if (isset($pl['opt']['ti'])) {
8845 $annots .= ' /TI '.intval($pl['opt']['ti']);
8846 }
8847 if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
8848 $annots .= ' /I [';
8849 foreach($pl['opt']['i'] AS $copt) {
8850 $annots .= intval($copt).' ';
8851 }
8852 $annots .= ']';
8853 }
8854 break;
8855 }
8856 case 'screen': {
8857 break;
8858 }
8859 case 'printermark': {
8860 break;
8861 }
8862 case 'trapnet': {
8863 break;
8864 }
8865 case 'watermark': {
8866 break;
8867 }
8868 case '3d': {
8869 break;
8870 }
8871 default: {
8872 break;
8873 }
8874 }
8875 $annots .= '>>';
8876 // create new annotation object
8877 $this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
8878 if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
8879 // store reference of form object
8880 $this->form_obj_id[] = $annot_obj_id;
8881 }
8882 }
8883 }
8884 } // end for each page
8885 }
8886
8887 /**
8888 * Put appearance streams XObject used to define annotation's appearance states.
8889 * @param int $w annotation width
8890 * @param int $h annotation height
8891 * @param string $stream appearance stream
8892 * @return int object ID
8893 * @protected
8894 * @since 4.8.001 (2009-09-09)
8895 */
8896 protected function _putAPXObject($w=0, $h=0, $stream='') {
8897 $stream = trim($stream);
8898 $out = $this->_getobj()."\n";
8899 $this->xobjects['AX'.$this->n] = array('n' => $this->n);
8900 $out .= '<<';
8901 $out .= ' /Type /XObject';
8902 $out .= ' /Subtype /Form';
8903 $out .= ' /FormType 1';
8904 if ($this->compress) {
8905 $stream = gzcompress($stream);
8906 $out .= ' /Filter /FlateDecode';
8907 }
8908 $rect = sprintf('%F %F', $w, $h);
8909 $out .= ' /BBox [0 0 '.$rect.']';
8910 $out .= ' /Matrix [1 0 0 1 0 0]';
8911 $out .= ' /Resources 2 0 R';
8912 $stream = $this->_getrawstream($stream);
8913 $out .= ' /Length '.strlen($stream);
8914 $out .= ' >>';
8915 $out .= ' stream'."\n".$stream."\n".'endstream';
8916 $out .= "\n".'endobj';
8917 $this->_out($out);
8918 return $this->n;
8919 }
8920
8921 /**
8922 * Output fonts.
8923 * @author Nicola Asuni
8924 * @protected
8925 */
8926 protected function _putfonts() {
8927 $nf = $this->n;
8928 foreach ($this->diffs as $diff) {
8929 //Encodings
8930 $this->_newobj();
8931 $this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
8932 }
8933 foreach ($this->FontFiles as $file => $info) {
8934 // search and get font file to embed
8935 $fontfile = TCPDF_FONTS::getFontFullPath($file, $info['fontdir']);
8936 if (!TCPDF_STATIC::empty_string($fontfile)) {
8937 $font = file_get_contents($fontfile);
8938 $compressed = (substr($file, -2) == '.z');
8939 if ((!$compressed) AND (isset($info['length2']))) {
8940 $header = (ord($font[0]) == 128);
8941 if ($header) {
8942 // strip first binary header
8943 $font = substr($font, 6);
8944 }
8945 if ($header AND (ord($font[$info['length1']]) == 128)) {
8946 // strip second binary header
8947 $font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
8948 }
8949 } elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
8950 if ($compressed) {
8951 // uncompress font
8952 $font = gzuncompress($font);
8953 }
8954 // merge subset characters
8955 $subsetchars = array(); // used chars
8956 foreach ($info['fontkeys'] as $fontkey) {
8957 $fontinfo = $this->getFontBuffer($fontkey);
8958 $subsetchars += $fontinfo['subsetchars'];
8959 }
8960 // rebuild a font subset
8961 $font = TCPDF_FONTS::_getTrueTypeFontSubset($font, $subsetchars);
8962 // calculate new font length
8963 $info['length1'] = strlen($font);
8964 if ($compressed) {
8965 // recompress font
8966 $font = gzcompress($font);
8967 }
8968 }
8969 $this->_newobj();
8970 $this->FontFiles[$file]['n'] = $this->n;
8971 $stream = $this->_getrawstream($font);
8972 $out = '<< /Length '.strlen($stream);
8973 if ($compressed) {
8974 $out .= ' /Filter /FlateDecode';
8975 }
8976 $out .= ' /Length1 '.$info['length1'];
8977 if (isset($info['length2'])) {
8978 $out .= ' /Length2 '.$info['length2'].' /Length3 0';
8979 }
8980 $out .= ' >>';
8981 $out .= ' stream'."\n".$stream."\n".'endstream';
8982 $out .= "\n".'endobj';
8983 $this->_out($out);
8984 }
8985 }
8986 foreach ($this->fontkeys as $k) {
8987 //Font objects
8988 $font = $this->getFontBuffer($k);
8989 $type = $font['type'];
8990 $name = $font['name'];
8991 if ($type == 'core') {
8992 // standard core font
8993 $out = $this->_getobj($this->font_obj_ids[$k])."\n";
8994 $out .= '<</Type /Font';
8995 $out .= ' /Subtype /Type1';
8996 $out .= ' /BaseFont /'.$name;
8997 $out .= ' /Name /F'.$font['i'];
8998 if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
8999 $out .= ' /Encoding /WinAnsiEncoding';
9000 }
9001 if ($k == 'helvetica') {
9002 // add default font for annotations
9003 $this->annotation_fonts[$k] = $font['i'];
9004 }
9005 $out .= ' >>';
9006 $out .= "\n".'endobj';
9007 $this->_out($out);
9008 } elseif (($type == 'Type1') OR ($type == 'TrueType')) {
9009 // additional Type1 or TrueType font
9010 $out = $this->_getobj($this->font_obj_ids[$k])."\n";
9011 $out .= '<</Type /Font';
9012 $out .= ' /Subtype /'.$type;
9013 $out .= ' /BaseFont /'.$name;
9014 $out .= ' /Name /F'.$font['i'];
9015 $out .= ' /FirstChar 32 /LastChar 255';
9016 $out .= ' /Widths '.($this->n + 1).' 0 R';
9017 $out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
9018 if ($font['enc']) {
9019 if (isset($font['diff'])) {
9020 $out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
9021 } else {
9022 $out .= ' /Encoding /WinAnsiEncoding';
9023 }
9024 }
9025 $out .= ' >>';
9026 $out .= "\n".'endobj';
9027 $this->_out($out);
9028 // Widths
9029 $this->_newobj();
9030 $s = '[';
9031 for ($i = 32; $i < 256; ++$i) {
9032 if (isset($font['cw'][$i])) {
9033 $s .= $font['cw'][$i].' ';
9034 } else {
9035 $s .= $font['dw'].' ';
9036 }
9037 }
9038 $s .= ']';
9039 $s .= "\n".'endobj';
9040 $this->_out($s);
9041 //Descriptor
9042 $this->_newobj();
9043 $s = '<</Type /FontDescriptor /FontName /'.$name;
9044 foreach ($font['desc'] as $fdk => $fdv) {
9045 if (is_float($fdv)) {
9046 $fdv = sprintf('%F', $fdv);
9047 }
9048 $s .= ' /'.$fdk.' '.$fdv.'';
9049 }
9050 if (!TCPDF_STATIC::empty_string($font['file'])) {
9051 $s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
9052 }
9053 $s .= '>>';
9054 $s .= "\n".'endobj';
9055 $this->_out($s);
9056 } else {
9057 // additional types
9058 $mtd = '_put'.strtolower($type);
9059 if (!method_exists($this, $mtd)) {
9060 $this->Error('Unsupported font type: '.$type);
9061 }
9062 $this->$mtd($font);
9063 }
9064 }
9065 }
9066
9067 /**
9068 * Adds unicode fonts.<br>
9069 * Based on PDF Reference 1.3 (section 5)
9070 * @param array $font font data
9071 * @protected
9072 * @author Nicola Asuni
9073 * @since 1.52.0.TC005 (2005-01-05)
9074 */
9075 protected function _puttruetypeunicode($font) {
9076 $fontname = '';
9077 if ($font['subset']) {
9078 // change name for font subsetting
9079 $subtag = sprintf('%06u', $font['i']);
9080 $subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
9081 $fontname .= $subtag.'+';
9082 }
9083 $fontname .= $font['name'];
9084 // Type0 Font
9085 // A composite font composed of other fonts, organized hierarchically
9086 $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9087 $out .= '<< /Type /Font';
9088 $out .= ' /Subtype /Type0';
9089 $out .= ' /BaseFont /'.$fontname;
9090 $out .= ' /Name /F'.$font['i'];
9091 $out .= ' /Encoding /'.$font['enc'];
9092 $out .= ' /ToUnicode '.($this->n + 1).' 0 R';
9093 $out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
9094 $out .= ' >>';
9095 $out .= "\n".'endobj';
9096 $this->_out($out);
9097 // ToUnicode map for Identity-H
9098 $stream = TCPDF_FONT_DATA::$uni_identity_h;
9099 // ToUnicode Object
9100 $this->_newobj();
9101 $stream = ($this->compress) ? gzcompress($stream) : $stream;
9102 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9103 $stream = $this->_getrawstream($stream);
9104 $this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
9105 // CIDFontType2
9106 // A CIDFont whose glyph descriptions are based on TrueType font technology
9107 $oid = $this->_newobj();
9108 $out = '<< /Type /Font';
9109 $out .= ' /Subtype /CIDFontType2';
9110 $out .= ' /BaseFont /'.$fontname;
9111 // A dictionary containing entries that define the character collection of the CIDFont.
9112 $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9113 $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9114 $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9115 $out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
9116 $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9117 $out .= ' /DW '.$font['dw']; // default width
9118 $out .= "\n".TCPDF_FONTS::_putfontwidths($font, 0);
9119 if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9120 $out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
9121 }
9122 $out .= ' >>';
9123 $out .= "\n".'endobj';
9124 $this->_out($out);
9125 // Font descriptor
9126 // A font descriptor describing the CIDFont default metrics other than its glyph widths
9127 $this->_newobj();
9128 $out = '<< /Type /FontDescriptor';
9129 $out .= ' /FontName /'.$fontname;
9130 foreach ($font['desc'] as $key => $value) {
9131 if (is_float($value)) {
9132 $value = sprintf('%F', $value);
9133 }
9134 $out .= ' /'.$key.' '.$value;
9135 }
9136 $fontdir = false;
9137 if (!TCPDF_STATIC::empty_string($font['file'])) {
9138 // A stream containing a TrueType font
9139 $out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
9140 $fontdir = $this->FontFiles[$font['file']]['fontdir'];
9141 }
9142 $out .= ' >>';
9143 $out .= "\n".'endobj';
9144 $this->_out($out);
9145 if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9146 $this->_newobj();
9147 // Embed CIDToGIDMap
9148 // A specification of the mapping from CIDs to glyph indices
9149 // search and get CTG font file to embed
9150 $ctgfile = strtolower($font['ctg']);
9151 // search and get ctg font file to embed
9152 $fontfile = TCPDF_FONTS::getFontFullPath($ctgfile, $fontdir);
9153 if (TCPDF_STATIC::empty_string($fontfile)) {
9154 $this->Error('Font file not found: '.$ctgfile);
9155 }
9156 $stream = $this->_getrawstream(file_get_contents($fontfile));
9157 $out = '<< /Length '.strlen($stream).'';
9158 if (substr($fontfile, -2) == '.z') { // check file extension
9159 // Decompresses data encoded using the public-domain
9160 // zlib/deflate compression method, reproducing the
9161 // original text or binary data
9162 $out .= ' /Filter /FlateDecode';
9163 }
9164 $out .= ' >>';
9165 $out .= ' stream'."\n".$stream."\n".'endstream';
9166 $out .= "\n".'endobj';
9167 $this->_out($out);
9168 }
9169 }
9170
9171 /**
9172 * Output CID-0 fonts.
9173 * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
9174 * @param array $font font data
9175 * @protected
9176 * @author Andrew Whitehead,2026 Nicola Asuni, Yukihiro Nakadaira
9177 * @since 3.2.000 (2008-06-23)
9178 */
9179 protected function _putcidfont0($font) {
9180 $cidoffset = 0;
9181 if (!isset($font['cw'][1])) {
9182 $cidoffset = 31;
9183 }
9184 if (isset($font['cidinfo']['uni2cid'])) {
9185 // convert unicode to cid.
9186 $uni2cid = $font['cidinfo']['uni2cid'];
9187 $cw = array();
9188 foreach ($font['cw'] as $uni => $width) {
9189 if (isset($uni2cid[$uni])) {
9190 $cw[($uni2cid[$uni] + $cidoffset)] = $width;
9191 } elseif ($uni < 256) {
9192 $cw[$uni] = $width;
9193 } // else unknown character
9194 }
9195 $font = array_merge($font, array('cw' => $cw));
9196 }
9197 $name = $font['name'];
9198 $enc = $font['enc'];
9199 if ($enc) {
9200 $longname = $name.'-'.$enc;
9201 } else {
9202 $longname = $name;
9203 }
9204 $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9205 $out .= '<</Type /Font';
9206 $out .= ' /Subtype /Type0';
9207 $out .= ' /BaseFont /'.$longname;
9208 $out .= ' /Name /F'.$font['i'];
9209 if ($enc) {
9210 $out .= ' /Encoding /'.$enc;
9211 }
9212 $out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
9213 $out .= ' >>';
9214 $out .= "\n".'endobj';
9215 $this->_out($out);
9216 $oid = $this->_newobj();
9217 $out = '<</Type /Font';
9218 $out .= ' /Subtype /CIDFontType0';
9219 $out .= ' /BaseFont /'.$name;
9220 $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9221 $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9222 $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9223 $out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
9224 $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9225 $out .= ' /DW '.$font['dw'];
9226 $out .= "\n".TCPDF_FONTS::_putfontwidths($font, $cidoffset);
9227 $out .= ' >>';
9228 $out .= "\n".'endobj';
9229 $this->_out($out);
9230 $this->_newobj();
9231 $s = '<</Type /FontDescriptor /FontName /'.$name;
9232 foreach ($font['desc'] as $k => $v) {
9233 if ($k != 'Style') {
9234 if (is_float($v)) {
9235 $v = sprintf('%F', $v);
9236 }
9237 $s .= ' /'.$k.' '.$v.'';
9238 }
9239 }
9240 $s .= '>>';
9241 $s .= "\n".'endobj';
9242 $this->_out($s);
9243 }
9244
9245 /**
9246 * Output images.
9247 * @protected
9248 */
9249 protected function _putimages() {
9250 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9251 foreach ($this->imagekeys as $file) {
9252 $info = $this->getImageBuffer($file);
9253 // set object for alternate images array
9254 $altoid = null;
9255 if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
9256 $altoid = $this->_newobj();
9257 $out = '[';
9258 foreach ($info['altimgs'] as $altimage) {
9259 if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
9260 $out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
9261 $out .= ' /DefaultForPrinting';
9262 if ($altimage[1] === true) {
9263 $out .= ' true';
9264 } else {
9265 $out .= ' false';
9266 }
9267 $out .= ' >>';
9268 }
9269 }
9270 $out .= ' ]';
9271 $out .= "\n".'endobj';
9272 $this->_out($out);
9273 }
9274 // set image object
9275 $oid = $this->_newobj();
9276 $this->xobjects['I'.$info['i']] = array('n' => $oid);
9277 $this->setImageSubBuffer($file, 'n', $this->n);
9278 $out = '<</Type /XObject';
9279 $out .= ' /Subtype /Image';
9280 $out .= ' /Width '.$info['w'];
9281 $out .= ' /Height '.$info['h'];
9282 if (array_key_exists('masked', $info)) {
9283 $out .= ' /SMask '.($this->n - 1).' 0 R';
9284 }
9285 // set color space
9286 $icc = false;
9287 if (isset($info['icc']) AND ($info['icc'] !== false)) {
9288 // ICC Colour Space
9289 $icc = true;
9290 $out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
9291 } elseif ($info['cs'] == 'Indexed') {
9292 // Indexed Colour Space
9293 $out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
9294 } else {
9295 // Device Colour Space
9296 $out .= ' /ColorSpace /'.$info['cs'];
9297 }
9298 if ($info['cs'] == 'DeviceCMYK') {
9299 $out .= ' /Decode [1 0 1 0 1 0 1 0]';
9300 }
9301 $out .= ' /BitsPerComponent '.$info['bpc'];
9302 if ($altoid > 0) {
9303 // reference to alternate images dictionary
9304 $out .= ' /Alternates '.$altoid.' 0 R';
9305 }
9306 if (isset($info['exurl']) AND !empty($info['exurl'])) {
9307 // external stream
9308 $out .= ' /Length 0';
9309 $out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
9310 if (isset($info['f'])) {
9311 $out .= ' /FFilter /'.$info['f'];
9312 }
9313 $out .= ' >>';
9314 $out .= ' stream'."\n".'endstream';
9315 } else {
9316 if (isset($info['f'])) {
9317 $out .= ' /Filter /'.$info['f'];
9318 }
9319 if (isset($info['parms'])) {
9320 $out .= ' '.$info['parms'];
9321 }
9322 if (isset($info['trns']) AND is_array($info['trns'])) {
9323 $trns = '';
9324 $count_info = count($info['trns']);
9325 if ($info['cs'] == 'Indexed') {
9326 $maxval =(pow(2, $info['bpc']) - 1);
9327 for ($i = 0; $i < $count_info; ++$i) {
9328 if (($info['trns'][$i] != 0) AND ($info['trns'][$i] != $maxval)) {
9329 // this is not a binary type mask @TODO: create a SMask
9330 $trns = '';
9331 break;
9332 } elseif (empty($trns) AND ($info['trns'][$i] == 0)) {
9333 // store the first fully transparent value
9334 $trns .= $i.' '.$i.' ';
9335 }
9336 }
9337 } else {
9338 // grayscale or RGB
9339 for ($i = 0; $i < $count_info; ++$i) {
9340 if ($info['trns'][$i] == 0) {
9341 $trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
9342 }
9343 }
9344 }
9345 // Colour Key Masking
9346 if (!empty($trns)) {
9347 $out .= ' /Mask ['.$trns.']';
9348 }
9349 }
9350 $stream = $this->_getrawstream($info['data']);
9351 $out .= ' /Length '.strlen($stream).' >>';
9352 $out .= ' stream'."\n".$stream."\n".'endstream';
9353 }
9354 $out .= "\n".'endobj';
9355 $this->_out($out);
9356 if ($icc) {
9357 // ICC colour profile
9358 $this->_newobj();
9359 $icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
9360 $icc = $this->_getrawstream($icc);
9361 $this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9362 } elseif ($info['cs'] == 'Indexed') {
9363 // colour palette
9364 $this->_newobj();
9365 $pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
9366 $pal = $this->_getrawstream($pal);
9367 $this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
9368 }
9369 }
9370 }
9371
9372 /**
9373 * Output Form XObjects Templates.
9374 * @author Nicola Asuni
9375 * @since 5.8.017 (2010-08-24)
9376 * @protected
9377 * @see startTemplate(), endTemplate(), printTemplate()
9378 */
9379 protected function _putxobjects() {
9380 foreach ($this->xobjects as $key => $data) {
9381 if (isset($data['outdata'])) {
9382 $stream = str_replace($this->epsmarker, '', trim($data['outdata']));
9383 $out = $this->_getobj($data['n'])."\n";
9384 $out .= '<<';
9385 $out .= ' /Type /XObject';
9386 $out .= ' /Subtype /Form';
9387 $out .= ' /FormType 1';
9388 if ($this->compress) {
9389 $stream = gzcompress($stream);
9390 $out .= ' /Filter /FlateDecode';
9391 }
9392 $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));
9393 $out .= ' /Matrix [1 0 0 1 0 0]';
9394 $out .= ' /Resources <<';
9395 $out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9396 if (!$this->pdfa_mode || $this->pdfa_version >= 2) {
9397 // transparency
9398 if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
9399 $out .= ' /ExtGState <<';
9400 foreach ($data['extgstates'] as $k => $extgstate) {
9401 if (isset($this->extgstates[$k]['name'])) {
9402 $out .= ' /'.$this->extgstates[$k]['name'];
9403 } else {
9404 $out .= ' /GS'.$k;
9405 }
9406 $out .= ' '.$this->extgstates[$k]['n'].' 0 R';
9407 }
9408 $out .= ' >>';
9409 }
9410 if (isset($data['gradients']) AND !empty($data['gradients'])) {
9411 $gp = '';
9412 $gs = '';
9413 foreach ($data['gradients'] as $id => $grad) {
9414 // gradient patterns
9415 $gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
9416 // gradient shadings
9417 $gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
9418 }
9419 $out .= ' /Pattern <<'.$gp.' >>';
9420 $out .= ' /Shading <<'.$gs.' >>';
9421 }
9422 }
9423 // spot colors
9424 if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
9425 $out .= ' /ColorSpace <<';
9426 foreach ($data['spot_colors'] as $name => $color) {
9427 $out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
9428 }
9429 $out .= ' >>';
9430 }
9431 // fonts
9432 if (!empty($data['fonts'])) {
9433 $out .= ' /Font <<';
9434 foreach ($data['fonts'] as $fontkey => $fontid) {
9435 $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9436 }
9437 $out .= ' >>';
9438 }
9439 // images or nested xobjects
9440 if (!empty($data['images']) OR !empty($data['xobjects'])) {
9441 $out .= ' /XObject <<';
9442 foreach ($data['images'] as $imgid) {
9443 $out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
9444 }
9445 foreach ($data['xobjects'] as $sub_id => $sub_objid) {
9446 $out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
9447 }
9448 $out .= ' >>';
9449 }
9450 $out .= ' >>'; //end resources
9451 if (isset($data['group']) AND ($data['group'] !== false)) {
9452 // set transparency group
9453 $out .= ' /Group << /Type /Group /S /Transparency';
9454 if (is_array($data['group'])) {
9455 if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
9456 $out .= ' /CS /'.$data['group']['CS'];
9457 }
9458 if (isset($data['group']['I'])) {
9459 $out .= ' /I /'.($data['group']['I']===true?'true':'false');
9460 }
9461 if (isset($data['group']['K'])) {
9462 $out .= ' /K /'.($data['group']['K']===true?'true':'false');
9463 }
9464 }
9465 $out .= ' >>';
9466 }
9467 $stream = $this->_getrawstream($stream, $data['n']);
9468 $out .= ' /Length '.strlen($stream);
9469 $out .= ' >>';
9470 $out .= ' stream'."\n".$stream."\n".'endstream';
9471 $out .= "\n".'endobj';
9472 $this->_out($out);
9473 }
9474 }
9475 }
9476
9477 /**
9478 * Output Spot Colors Resources.
9479 * @protected
9480 * @since 4.0.024 (2008-09-12)
9481 */
9482 protected function _putspotcolors() {
9483 foreach ($this->spot_colors as $name => $color) {
9484 $this->_newobj();
9485 $this->spot_colors[$name]['n'] = $this->n;
9486 $out = '[/Separation /'.str_replace(' ', '#20', $name);
9487 $out .= ' /DeviceCMYK <<';
9488 $out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
9489 $out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
9490 $out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
9491 $out .= "\n".'endobj';
9492 $this->_out($out);
9493 }
9494 }
9495
9496 /**
9497 * Return XObjects Dictionary.
9498 * @return string XObjects dictionary
9499 * @protected
9500 * @since 5.8.014 (2010-08-23)
9501 */
9502 protected function _getxobjectdict() {
9503 $out = '';
9504 foreach ($this->xobjects as $id => $objid) {
9505 $out .= ' /'.$id.' '.$objid['n'].' 0 R';
9506 }
9507 return $out;
9508 }
9509
9510 /**
9511 * Output Resources Dictionary.
9512 * @protected
9513 */
9514 protected function _putresourcedict() {
9515 $out = $this->_getobj(2)."\n";
9516 $out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9517 $out .= ' /Font <<';
9518 foreach ($this->fontkeys as $fontkey) {
9519 $font = $this->getFontBuffer($fontkey);
9520 $out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
9521 }
9522 $out .= ' >>';
9523 $out .= ' /XObject <<';
9524 $out .= $this->_getxobjectdict();
9525 $out .= ' >>';
9526 // layers
9527 if (!empty($this->pdflayers)) {
9528 $out .= ' /Properties <<';
9529 foreach ($this->pdflayers as $layer) {
9530 $out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
9531 }
9532 $out .= ' >>';
9533 }
9534 if (!$this->pdfa_mode || $this->pdfa_version >= 2) {
9535 // transparency
9536 if (isset($this->extgstates) AND !empty($this->extgstates)) {
9537 $out .= ' /ExtGState <<';
9538 foreach ($this->extgstates as $k => $extgstate) {
9539 if (isset($extgstate['name'])) {
9540 $out .= ' /'.$extgstate['name'];
9541 } else {
9542 $out .= ' /GS'.$k;
9543 }
9544 $out .= ' '.$extgstate['n'].' 0 R';
9545 }
9546 $out .= ' >>';
9547 }
9548 if (isset($this->gradients) AND !empty($this->gradients)) {
9549 $gp = '';
9550 $gs = '';
9551 foreach ($this->gradients as $id => $grad) {
9552 // gradient patterns
9553 $gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
9554 // gradient shadings
9555 $gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
9556 }
9557 $out .= ' /Pattern <<'.$gp.' >>';
9558 $out .= ' /Shading <<'.$gs.' >>';
9559 }
9560 }
9561 // spot colors
9562 if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
9563 $out .= ' /ColorSpace <<';
9564 foreach ($this->spot_colors as $color) {
9565 $out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
9566 }
9567 $out .= ' >>';
9568 }
9569 $out .= ' >>';
9570 $out .= "\n".'endobj';
9571 $this->_out($out);
9572 }
9573
9574 /**
9575 * Output Resources.
9576 * @protected
9577 */
9578 protected function _putresources() {
9579 $this->_putextgstates();
9580 $this->_putocg();
9581 $this->_putfonts();
9582 $this->_putimages();
9583 $this->_putspotcolors();
9584 $this->_putshaders();
9585 $this->_putxobjects();
9586 $this->_putresourcedict();
9587 $this->_putdests();
9588 $this->_putEmbeddedFiles();
9589 $this->_putannotsobjs();
9590 $this->_putjavascript();
9591 $this->_putbookmarks();
9592 $this->_putencryption();
9593 }
9594
9595 /**
9596 * Adds some Metadata information (Document Information Dictionary)
9597 * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
9598 * @return int object id
9599 * @protected
9600 */
9601 protected function _putinfo() {
9602 $oid = $this->_newobj();
9603 $out = '<<';
9604 // store current isunicode value
9605 $prev_isunicode = $this->isunicode;
9606 if ($this->docinfounicode) {
9607 $this->isunicode = true;
9608 }
9609 if (!TCPDF_STATIC::empty_string($this->title)) {
9610 // The document's title.
9611 $out .= ' /Title '.$this->_textstring($this->title, $oid);
9612 }
9613 if (!TCPDF_STATIC::empty_string($this->author)) {
9614 // The name of the person who created the document.
9615 $out .= ' /Author '.$this->_textstring($this->author, $oid);
9616 }
9617 if (!TCPDF_STATIC::empty_string($this->subject)) {
9618 // The subject of the document.
9619 $out .= ' /Subject '.$this->_textstring($this->subject, $oid);
9620 }
9621 if (!TCPDF_STATIC::empty_string($this->keywords)) {
9622 // Keywords associated with the document.
9623 $out .= ' /Keywords '.$this->_textstring($this->keywords, $oid);
9624 }
9625 if (!TCPDF_STATIC::empty_string($this->creator)) {
9626 // 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.
9627 $out .= ' /Creator '.$this->_textstring($this->creator, $oid);
9628 }
9629 // restore previous isunicode value
9630 $this->isunicode = $prev_isunicode;
9631 // default producer
9632 $out .= ' /Producer '.$this->_textstring(TCPDF_STATIC::getTCPDFProducer(), $oid);
9633 // The date and time the document was created, in human-readable form
9634 $out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
9635 // The date and time the document was most recently modified, in human-readable form
9636 $out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
9637 // A name object indicating whether the document has been modified to include trapping information
9638 $out .= ' /Trapped /False';
9639 $out .= ' >>';
9640 $out .= "\n".'endobj';
9641 $this->_out($out);
9642 return $oid;
9643 }
9644
9645 /**
9646 * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
9647 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9648 * @param string $xmp Custom XMP data.
9649 * @since 5.9.128 (2011-10-06)
9650 * @public
9651 */
9652 public function setExtraXMP($xmp) {
9653 $this->custom_xmp = $xmp;
9654 }
9655
9656 /**
9657 * Set additional XMP data to be added on the default XMP data just before the end of "rdf:RDF" tag.
9658 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9659 * @param string $xmp Custom XMP RDF data.
9660 * @since 6.3.0 (2019-09-19)
9661 * @public
9662 */
9663 public function setExtraXMPRDF($xmp) {
9664 $this->custom_xmp_rdf = $xmp;
9665 }
9666
9667 /**
9668 * Set additional XMP data to be added to the default XMP data for PDF/A extensions.
9669 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9670 * @param string $xmp Custom XMP RDF data.
9671 * @since 6.9.0 (2025-02-14)
9672 * @public
9673 */
9674 public function setExtraXMPPdfaextension($xmp) {
9675 $this->custom_xmp_rdf_pdfaExtension = $xmp;
9676 }
9677
9678 /**
9679 * Put XMP data object and return ID.
9680 * @return int The object ID.
9681 * @since 5.9.121 (2011-09-28)
9682 * @protected
9683 */
9684 protected function _putXMP() {
9685 $oid = $this->_newobj();
9686 // store current isunicode value
9687 $prev_isunicode = $this->isunicode;
9688 $this->isunicode = true;
9689 $prev_encrypted = $this->encrypted;
9690 $this->encrypted = false;
9691 // set XMP data
9692 $xmp = '<?xpacket begin="'.TCPDF_FONTS::unichr(0xfeff, $this->isunicode).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
9693 $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";
9694 $xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
9695 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
9696 $xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
9697 $xmp .= "\t\t\t".'<dc:title>'."\n";
9698 $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9699 $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->title).'</rdf:li>'."\n";
9700 $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9701 $xmp .= "\t\t\t".'</dc:title>'."\n";
9702 $xmp .= "\t\t\t".'<dc:creator>'."\n";
9703 $xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
9704 $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->author).'</rdf:li>'."\n";
9705 $xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
9706 $xmp .= "\t\t\t".'</dc:creator>'."\n";
9707 $xmp .= "\t\t\t".'<dc:description>'."\n";
9708 $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9709 $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->subject).'</rdf:li>'."\n";
9710 $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9711 $xmp .= "\t\t\t".'</dc:description>'."\n";
9712 $xmp .= "\t\t\t".'<dc:subject>'."\n";
9713 $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9714 $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->keywords).'</rdf:li>'."\n";
9715 $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9716 $xmp .= "\t\t\t".'</dc:subject>'."\n";
9717 $xmp .= "\t\t".'</rdf:Description>'."\n";
9718 // convert doc creation date format
9719 $dcdate = TCPDF_STATIC::getFormattedDate($this->doc_creation_timestamp);
9720 $doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
9721 $doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
9722 $doccreationdate .= substr($dcdate, 14, 3).':'.substr($dcdate, 18, 2);
9723 $doccreationdate = TCPDF_STATIC::_escapeXML($doccreationdate);
9724 // convert doc modification date format
9725 $dmdate = TCPDF_STATIC::getFormattedDate($this->doc_modification_timestamp);
9726 $docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
9727 $docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
9728 $docmoddate .= substr($dmdate, 14, 3).':'.substr($dmdate, 18, 2);
9729 $docmoddate = TCPDF_STATIC::_escapeXML($docmoddate);
9730 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
9731 $xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
9732 $xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
9733 $xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
9734 $xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
9735 $xmp .= "\t\t".'</rdf:Description>'."\n";
9736 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
9737 $xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC::_escapeXML($this->keywords).'</pdf:Keywords>'."\n";
9738 $xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC::_escapeXML(TCPDF_STATIC::getTCPDFProducer()).'</pdf:Producer>'."\n";
9739 $xmp .= "\t\t".'</rdf:Description>'."\n";
9740 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
9741 $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);
9742 $xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
9743 $xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
9744 $xmp .= "\t\t".'</rdf:Description>'."\n";
9745 if ($this->pdfa_mode) {
9746 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
9747 $xmp .= "\t\t\t".'<pdfaid:part>'.$this->pdfa_version.'</pdfaid:part>'."\n";
9748 $xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
9749 $xmp .= "\t\t".'</rdf:Description>'."\n";
9750 }
9751 // XMP extension schemas
9752 $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";
9753 $xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
9754 $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9755 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9756 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
9757 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
9758 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
9759 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9760 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9761 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9762 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9763 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Adobe PDF Schema</pdfaProperty:description>'."\n";
9764 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9765 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9766 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9767 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9768 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9769 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9770 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9771 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
9772 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
9773 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
9774 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9775 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9776 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9777 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9778 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
9779 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9780 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9781 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9782 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9783 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9784 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9785 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9786 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
9787 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
9788 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
9789 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9790 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9791 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9792 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9793 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
9794 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
9795 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
9796 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9797 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9798 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9799 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
9800 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
9801 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9802 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9803 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9804 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9805 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
9806 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
9807 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9808 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9809 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9810 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9811 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9812 $xmp .= $this->custom_xmp_rdf_pdfaExtension;
9813 $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9814 $xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
9815 $xmp .= "\t\t".'</rdf:Description>'."\n";
9816 $xmp .= $this->custom_xmp_rdf;
9817 $xmp .= "\t".'</rdf:RDF>'."\n";
9818 $xmp .= $this->custom_xmp;
9819 $xmp .= '</x:xmpmeta>'."\n";
9820 $xmp .= '<?xpacket end="w"?>';
9821 $out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
9822 // restore previous isunicode value
9823 $this->isunicode = $prev_isunicode;
9824 $this->encrypted = $prev_encrypted;
9825 $this->_out($out);
9826 return $oid;
9827 }
9828
9829 /**
9830 * Output Catalog.
9831 * @return int object id
9832 * @protected
9833 */
9834 protected function _putcatalog() {
9835 // put XMP
9836 $xmpobj = $this->_putXMP();
9837 // if required, add standard sRGB ICC colour profile
9838 if ($this->pdfa_mode OR $this->force_srgb) {
9839 $iccobj = $this->_newobj();
9840 $icc = file_get_contents(dirname(__FILE__).'/include/sRGB.icc');
9841 $filter = '';
9842 if ($this->compress) {
9843 $filter = ' /Filter /FlateDecode';
9844 $icc = gzcompress($icc);
9845 }
9846 $icc = $this->_getrawstream($icc);
9847 $this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9848 }
9849 // start catalog
9850 $oid = $this->_newobj();
9851 $out = '<< ';
9852 if (!empty($this->efnames)) {
9853 $out .= ' /AF [ '. implode(' ', $this->efnames) .' ]';
9854 }
9855 $out .= ' /Type /Catalog';
9856 $out .= ' /Version /'.$this->PDFVersion;
9857 //$out .= ' /Extensions <<>>';
9858 $out .= ' /Pages 1 0 R';
9859 //$out .= ' /PageLabels ' //...;
9860 $out .= ' /Names <<';
9861 if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
9862 $out .= ' /JavaScript '.$this->n_js;
9863 }
9864 if (!empty($this->efnames)) {
9865 $out .= ' /EmbeddedFiles <</Names [';
9866 foreach ($this->efnames AS $fn => $fref) {
9867 $out .= ' '.$this->_datastring($fn).' '.$fref;
9868 }
9869 $out .= ' ]>>';
9870 }
9871 $out .= ' >>';
9872 if (!empty($this->dests)) {
9873 $out .= ' /Dests '.($this->n_dests).' 0 R';
9874 }
9875 $out .= $this->_putviewerpreferences();
9876 if (isset($this->LayoutMode) AND (!TCPDF_STATIC::empty_string($this->LayoutMode))) {
9877 $out .= ' /PageLayout /'.$this->LayoutMode;
9878 }
9879 if (isset($this->PageMode) AND (!TCPDF_STATIC::empty_string($this->PageMode))) {
9880 $out .= ' /PageMode /'.$this->PageMode;
9881 }
9882 if (count($this->outlines) > 0) {
9883 $out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
9884 $out .= ' /PageMode /UseOutlines';
9885 }
9886 //$out .= ' /Threads []';
9887 if ($this->ZoomMode == 'fullpage') {
9888 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
9889 } elseif ($this->ZoomMode == 'fullwidth') {
9890 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
9891 } elseif ($this->ZoomMode == 'real') {
9892 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
9893 } elseif (!is_string($this->ZoomMode)) {
9894 $out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
9895 }
9896 //$out .= ' /AA <<>>';
9897 //$out .= ' /URI <<>>';
9898 $out .= ' /Metadata '.$xmpobj.' 0 R';
9899 //$out .= ' /StructTreeRoot <<>>';
9900 //$out .= ' /MarkInfo <<>>';
9901 if (isset($this->l['a_meta_language'])) {
9902 $out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
9903 }
9904 //$out .= ' /SpiderInfo <<>>';
9905 // set OutputIntent to sRGB IEC61966-2.1 if required
9906 if ($this->pdfa_mode OR $this->force_srgb) {
9907 $out .= ' /OutputIntents [<<';
9908 $out .= ' /Type /OutputIntent';
9909 $out .= ' /S /GTS_PDFA1';
9910 $out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9911 $out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9912 $out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
9913 $out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9914 $out .= ' /DestOutputProfile '.$iccobj.' 0 R';
9915 $out .= ' >>]';
9916 }
9917 //$out .= ' /PieceInfo <<>>';
9918 if (!empty($this->pdflayers)) {
9919 $lyrobjs = '';
9920 $lyrobjs_off = '';
9921 $lyrobjs_lock = '';
9922 foreach ($this->pdflayers as $layer) {
9923 $layer_obj_ref = ' '.$layer['objid'].' 0 R';
9924 $lyrobjs .= $layer_obj_ref;
9925 if ($layer['view'] === false) {
9926 $lyrobjs_off .= $layer_obj_ref;
9927 }
9928 if ($layer['lock']) {
9929 $lyrobjs_lock .= $layer_obj_ref;
9930 }
9931 }
9932 $out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
9933 $out .= ' /D <<';
9934 $out .= ' /Name '.$this->_textstring('Layers', $oid);
9935 $out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
9936 $out .= ' /BaseState /ON';
9937 $out .= ' /OFF ['.$lyrobjs_off.']';
9938 $out .= ' /Locked ['.$lyrobjs_lock.']';
9939 $out .= ' /Intent /View';
9940 $out .= ' /AS [';
9941 $out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
9942 $out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
9943 $out .= ' ]';
9944 $out .= ' /Order ['.$lyrobjs.']';
9945 $out .= ' /ListMode /AllPages';
9946 //$out .= ' /RBGroups ['..']';
9947 //$out .= ' /Locked ['..']';
9948 $out .= ' >>';
9949 $out .= ' >>';
9950 }
9951 // AcroForm
9952 if (!empty($this->form_obj_id)
9953 OR ($this->sign AND isset($this->signature_data['cert_type']))
9954 OR !empty($this->empty_signature_appearance)) {
9955 $out .= ' /AcroForm <<';
9956 $objrefs = '';
9957 if ($this->sign AND isset($this->signature_data['cert_type'])) {
9958 // set reference for signature object
9959 $objrefs .= $this->sig_obj_id.' 0 R';
9960 }
9961 if (!empty($this->empty_signature_appearance)) {
9962 foreach ($this->empty_signature_appearance as $esa) {
9963 // set reference for empty signature objects
9964 $objrefs .= ' '.$esa['objid'].' 0 R';
9965 }
9966 }
9967 if (!empty($this->form_obj_id)) {
9968 foreach($this->form_obj_id as $objid) {
9969 $objrefs .= ' '.$objid.' 0 R';
9970 }
9971 }
9972 $out .= ' /Fields ['.$objrefs.']';
9973 // It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
9974 if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
9975 $out .= ' /NeedAppearances false';
9976 }
9977 if ($this->sign AND isset($this->signature_data['cert_type'])) {
9978 if ($this->signature_data['cert_type'] > 0) {
9979 $out .= ' /SigFlags 3';
9980 } else {
9981 $out .= ' /SigFlags 1';
9982 }
9983 }
9984 //$out .= ' /CO ';
9985 if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
9986 $out .= ' /DR <<';
9987 $out .= ' /Font <<';
9988 foreach ($this->annotation_fonts as $fontkey => $fontid) {
9989 $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9990 }
9991 $out .= ' >> >>';
9992 }
9993 $font = $this->getFontBuffer((($this->pdfa_mode) ? 'pdfa' : '') .'helvetica');
9994 $out .= ' /DA ' . $this->_datastring('/F'.$font['i'].' 0 Tf 0 g');
9995 $out .= ' /Q '.(($this->rtl)?'2':'0');
9996 //$out .= ' /XFA ';
9997 $out .= ' >>';
9998 // signatures
9999 if ($this->sign AND isset($this->signature_data['cert_type'])
10000 AND (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A'))) {
10001 if ($this->signature_data['cert_type'] > 0) {
10002 $out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
10003 } else {
10004 $out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
10005 }
10006 }
10007 }
10008 //$out .= ' /Legal <<>>';
10009 //$out .= ' /Requirements []';
10010 //$out .= ' /Collection <<>>';
10011 //$out .= ' /NeedsRendering true';
10012 $out .= ' >>';
10013 $out .= "\n".'endobj';
10014 $this->_out($out);
10015 return $oid;
10016 }
10017
10018 /**
10019 * Output viewer preferences.
10020 * @return string for viewer preferences
10021 * @author Nicola asuni
10022 * @since 3.1.000 (2008-06-09)
10023 * @protected
10024 */
10025 protected function _putviewerpreferences() {
10026 $vp = $this->viewer_preferences;
10027 $out = ' /ViewerPreferences <<';
10028 if ($this->rtl) {
10029 $out .= ' /Direction /R2L';
10030 } else {
10031 $out .= ' /Direction /L2R';
10032 }
10033 if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
10034 $out .= ' /HideToolbar true';
10035 }
10036 if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
10037 $out .= ' /HideMenubar true';
10038 }
10039 if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
10040 $out .= ' /HideWindowUI true';
10041 }
10042 if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
10043 $out .= ' /FitWindow true';
10044 }
10045 if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
10046 $out .= ' /CenterWindow true';
10047 }
10048 if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
10049 $out .= ' /DisplayDocTitle true';
10050 }
10051 if (isset($vp['NonFullScreenPageMode'])) {
10052 $out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
10053 }
10054 if (isset($vp['ViewArea'])) {
10055 $out .= ' /ViewArea /'.$vp['ViewArea'];
10056 }
10057 if (isset($vp['ViewClip'])) {
10058 $out .= ' /ViewClip /'.$vp['ViewClip'];
10059 }
10060 if (isset($vp['PrintArea'])) {
10061 $out .= ' /PrintArea /'.$vp['PrintArea'];
10062 }
10063 if (isset($vp['PrintClip'])) {
10064 $out .= ' /PrintClip /'.$vp['PrintClip'];
10065 }
10066 if (isset($vp['PrintScaling'])) {
10067 $out .= ' /PrintScaling /'.$vp['PrintScaling'];
10068 }
10069 if (isset($vp['Duplex']) AND (!TCPDF_STATIC::empty_string($vp['Duplex']))) {
10070 $out .= ' /Duplex /'.$vp['Duplex'];
10071 }
10072 if (isset($vp['PickTrayByPDFSize'])) {
10073 if ($vp['PickTrayByPDFSize']) {
10074 $out .= ' /PickTrayByPDFSize true';
10075 } else {
10076 $out .= ' /PickTrayByPDFSize false';
10077 }
10078 }
10079 if (isset($vp['PrintPageRange'])) {
10080 $PrintPageRangeNum = '';
10081 foreach ($vp['PrintPageRange'] as $k => $v) {
10082 $PrintPageRangeNum .= ' '.($v - 1).'';
10083 }
10084 $out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
10085 }
10086 if (isset($vp['NumCopies'])) {
10087 $out .= ' /NumCopies '.intval($vp['NumCopies']);
10088 }
10089 $out .= ' >>';
10090 return $out;
10091 }
10092
10093 /**
10094 * Output PDF File Header (7.5.2).
10095 * @protected
10096 */
10097 protected function _putheader() {
10098 $this->_out('%PDF-'.$this->PDFVersion);
10099 $this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
10100 }
10101
10102 /**
10103 * Output end of document (EOF).
10104 * @protected
10105 */
10106 protected function _enddoc() {
10107 if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
10108 // save subset chars of the previous font
10109 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
10110 }
10111 $this->state = 1;
10112 $this->_putheader();
10113 $this->_putpages();
10114 $this->_putresources();
10115 // empty signature fields
10116 if (!empty($this->empty_signature_appearance)) {
10117 foreach ($this->empty_signature_appearance as $key => $esa) {
10118 // widget annotation for empty signature
10119 $out = $this->_getobj($esa['objid'])."\n";
10120 $out .= '<< /Type /Annot';
10121 $out .= ' /Subtype /Widget';
10122 $out .= ' /Rect ['.$esa['rect'].']';
10123 $out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
10124 $out .= ' /F 4';
10125 $out .= ' /FT /Sig';
10126 $signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
10127 $out .= ' /T '.$this->_textstring($signame, $esa['objid']);
10128 $out .= ' /Ff 0';
10129 $out .= ' >>';
10130 $out .= "\n".'endobj';
10131 $this->_out($out);
10132 }
10133 }
10134 // Signature
10135 if ($this->sign AND isset($this->signature_data['cert_type'])) {
10136 // widget annotation for signature
10137 $out = $this->_getobj($this->sig_obj_id)."\n";
10138 $out .= '<< /Type /Annot';
10139 $out .= ' /Subtype /Widget';
10140 $out .= ' /Rect ['.$this->signature_appearance['rect'].']';
10141 $out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
10142 $out .= ' /F 4';
10143 $out .= ' /FT /Sig';
10144 $out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
10145 $out .= ' /Ff 0';
10146 $out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
10147 $out .= ' >>';
10148 $out .= "\n".'endobj';
10149 $this->_out($out);
10150 // signature
10151 $this->_putsignature();
10152 }
10153 // Info
10154 $objid_info = $this->_putinfo();
10155 // Catalog
10156 $objid_catalog = $this->_putcatalog();
10157 // Cross-ref
10158 $o = $this->bufferlen;
10159 // XREF section
10160 $this->_out('xref');
10161 $this->_out('0 '.($this->n + 1));
10162 $this->_out('0000000000 65535 f ');
10163 $freegen = ($this->n + 2);
10164 for ($i=1; $i <= $this->n; ++$i) {
10165 if (!isset($this->offsets[$i]) AND ($i > 1)) {
10166 $this->_out(sprintf('0000000000 %05d f ', $freegen));
10167 ++$freegen;
10168 } else {
10169 $this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
10170 }
10171 }
10172 // TRAILER
10173 $out = 'trailer'."\n";
10174 $out .= '<<';
10175 $out .= ' /Size '.($this->n + 1);
10176 $out .= ' /Root '.$objid_catalog.' 0 R';
10177 $out .= ' /Info '.$objid_info.' 0 R';
10178 if ($this->encrypted) {
10179 $out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
10180 }
10181 $out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
10182 $out .= ' >>';
10183 $this->_out($out);
10184 $this->_out('startxref');
10185 $this->_out($o);
10186 $this->_out('%%EOF');
10187 $this->state = 3; // end-of-doc
10188 }
10189
10190 /**
10191 * Initialize a new page.
10192 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
10193 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
10194 * @protected
10195 * @see getPageSizeFromFormat(), setPageFormat()
10196 */
10197 protected function _beginpage($orientation='', $format='') {
10198 ++$this->page;
10199 $this->pageobjects[$this->page] = array();
10200 $this->setPageBuffer($this->page, '');
10201 // initialize array for graphics tranformation positions inside a page buffer
10202 $this->transfmrk[$this->page] = array();
10203 $this->state = 2;
10204 if (TCPDF_STATIC::empty_string($orientation)) {
10205 if (isset($this->CurOrientation)) {
10206 $orientation = $this->CurOrientation;
10207 } elseif ($this->fwPt > $this->fhPt) {
10208 // landscape
10209 $orientation = 'L';
10210 } else {
10211 // portrait
10212 $orientation = 'P';
10213 }
10214 }
10215 if (TCPDF_STATIC::empty_string($format)) {
10216 $this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10217 $this->setPageOrientation($orientation);
10218 } else {
10219 $this->setPageFormat($format, $orientation);
10220 }
10221 if ($this->rtl) {
10222 $this->x = $this->w - $this->rMargin;
10223 } else {
10224 $this->x = $this->lMargin;
10225 }
10226 $this->y = $this->tMargin;
10227 if (isset($this->newpagegroup[$this->page])) {
10228 // start a new group
10229 $this->currpagegroup = $this->newpagegroup[$this->page];
10230 $this->pagegroups[$this->currpagegroup] = 1;
10231 } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
10232 ++$this->pagegroups[$this->currpagegroup];
10233 }
10234 }
10235
10236 /**
10237 * Mark end of page.
10238 * @protected
10239 */
10240 protected function _endpage() {
10241 $this->setVisibility('all');
10242 $this->state = 1;
10243 }
10244
10245 /**
10246 * Begin a new object and return the object number.
10247 * @return int object number
10248 * @protected
10249 */
10250 protected function _newobj() {
10251 $this->_out($this->_getobj());
10252 return $this->n;
10253 }
10254
10255 /**
10256 * Return the starting object string for the selected object ID.
10257 * @param int|null $objid Object ID (leave empty to get a new ID).
10258 * @return string the starting object string
10259 * @protected
10260 * @since 5.8.009 (2010-08-20)
10261 */
10262 protected function _getobj($objid=null) {
10263 if (TCPDF_STATIC::empty_string($objid)) {
10264 ++$this->n;
10265 $objid = $this->n;
10266 }
10267 $this->offsets[$objid] = $this->bufferlen;
10268 $this->pageobjects[$this->page][] = $objid;
10269 return $objid.' 0 obj';
10270 }
10271
10272 /**
10273 * Underline text.
10274 * @param int $x X coordinate
10275 * @param int $y Y coordinate
10276 * @param string $txt text to underline
10277 * @protected
10278 */
10279 protected function _dounderline($x, $y, $txt) {
10280 $w = $this->GetStringWidth($txt);
10281 return $this->_dounderlinew($x, $y, $w);
10282 }
10283
10284 /**
10285 * Underline for rectangular text area.
10286 * @param int $x X coordinate
10287 * @param int $y Y coordinate
10288 * @param int $w width to underline
10289 * @protected
10290 * @since 4.8.008 (2009-09-29)
10291 */
10292 protected function _dounderlinew($x, $y, $w) {
10293 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10294 return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
10295 }
10296
10297 /**
10298 * Line through text.
10299 * @param int $x X coordinate
10300 * @param int $y Y coordinate
10301 * @param string $txt text to linethrough
10302 * @protected
10303 */
10304 protected function _dolinethrough($x, $y, $txt) {
10305 $w = $this->GetStringWidth($txt);
10306 return $this->_dolinethroughw($x, $y, $w);
10307 }
10308
10309 /**
10310 * Line through for rectangular text area.
10311 * @param int $x X coordinate
10312 * @param int $y Y coordinate
10313 * @param int $w line length (width)
10314 * @protected
10315 * @since 4.9.008 (2009-09-29)
10316 */
10317 protected function _dolinethroughw($x, $y, $w) {
10318 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10319 return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
10320 }
10321
10322 /**
10323 * Overline text.
10324 * @param int $x X coordinate
10325 * @param int $y Y coordinate
10326 * @param string $txt text to overline
10327 * @protected
10328 * @since 4.9.015 (2010-04-19)
10329 */
10330 protected function _dooverline($x, $y, $txt) {
10331 $w = $this->GetStringWidth($txt);
10332 return $this->_dooverlinew($x, $y, $w);
10333 }
10334
10335 /**
10336 * Overline for rectangular text area.
10337 * @param int $x X coordinate
10338 * @param int $y Y coordinate
10339 * @param int $w width to overline
10340 * @protected
10341 * @since 4.9.015 (2010-04-19)
10342 */
10343 protected function _dooverlinew($x, $y, $w) {
10344 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10345 return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
10346
10347 }
10348
10349 /**
10350 * Format a data string for meta information
10351 * @param string $s data string to escape.
10352 * @param int $n object ID
10353 * @return string escaped string.
10354 * @protected
10355 */
10356 protected function _datastring($s, $n=0) {
10357 if ($n == 0) {
10358 $n = $this->n;
10359 }
10360 $s = $this->_encrypt_data($n, $s);
10361 return '('. TCPDF_STATIC::_escape($s).')';
10362 }
10363
10364 /**
10365 * Set the document creation timestamp
10366 * @param mixed $time Document creation timestamp in seconds or date-time string.
10367 * @public
10368 * @since 5.9.152 (2012-03-23)
10369 */
10370 public function setDocCreationTimestamp($time) {
10371 if (is_string($time)) {
10372 $time = TCPDF_STATIC::getTimestamp($time);
10373 }
10374 $this->doc_creation_timestamp = intval($time);
10375 }
10376
10377 /**
10378 * Set the document modification timestamp
10379 * @param mixed $time Document modification timestamp in seconds or date-time string.
10380 * @public
10381 * @since 5.9.152 (2012-03-23)
10382 */
10383 public function setDocModificationTimestamp($time) {
10384 if (is_string($time)) {
10385 $time = TCPDF_STATIC::getTimestamp($time);
10386 }
10387 $this->doc_modification_timestamp = intval($time);
10388 }
10389
10390 /**
10391 * Returns document creation timestamp in seconds.
10392 * @return int Creation timestamp in seconds.
10393 * @public
10394 * @since 5.9.152 (2012-03-23)
10395 */
10396 public function getDocCreationTimestamp() {
10397 return $this->doc_creation_timestamp;
10398 }
10399
10400 /**
10401 * Returns document modification timestamp in seconds.
10402 * @return int Modfication timestamp in seconds.
10403 * @public
10404 * @since 5.9.152 (2012-03-23)
10405 */
10406 public function getDocModificationTimestamp() {
10407 return $this->doc_modification_timestamp;
10408 }
10409
10410 /**
10411 * Returns a formatted date for meta information
10412 * @param int $n Object ID.
10413 * @param int $timestamp Timestamp to convert.
10414 * @return string escaped date string.
10415 * @protected
10416 * @since 4.6.028 (2009-08-25)
10417 */
10418 protected function _datestring($n=0, $timestamp=0) {
10419 if ((empty($timestamp)) OR ($timestamp < 0)) {
10420 $timestamp = $this->doc_creation_timestamp;
10421 }
10422 return $this->_datastring('D:'.TCPDF_STATIC::getFormattedDate($timestamp), $n);
10423 }
10424
10425 /**
10426 * Format a text string for meta information
10427 * @param string $s string to escape.
10428 * @param int $n object ID
10429 * @return string escaped string.
10430 * @protected
10431 */
10432 protected function _textstring($s, $n=0) {
10433 if ($this->isunicode) {
10434 //Convert string to UTF-16BE
10435 $s = TCPDF_FONTS::UTF8ToUTF16BE($s, true, $this->isunicode, $this->CurrentFont);
10436 }
10437 return $this->_datastring($s, $n);
10438 }
10439
10440 /**
10441 * get raw output stream.
10442 * @param string $s string to output.
10443 * @param int $n object reference for encryption mode
10444 * @protected
10445 * @author Nicola Asuni
10446 * @since 5.5.000 (2010-06-22)
10447 */
10448 protected function _getrawstream($s, $n=0) {
10449 if ($n <= 0) {
10450 // default to current object
10451 $n = $this->n;
10452 }
10453 return $this->_encrypt_data($n, $s);
10454 }
10455
10456 /**
10457 * Output a string to the document.
10458 * @param string $s string to output.
10459 * @protected
10460 */
10461 protected function _out($s) {
10462 if ($this->state == 2) {
10463 if ($this->inxobj) {
10464 // we are inside an XObject template
10465 $this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
10466 } elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
10467 // puts data before page footer
10468 $pagebuff = $this->getPageBuffer($this->page);
10469 $page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
10470 $footer = substr($pagebuff, -$this->footerlen[$this->page]);
10471 $this->setPageBuffer($this->page, $page.$s."\n".$footer);
10472 // update footer position
10473 $this->footerpos[$this->page] += strlen($s."\n");
10474 } else {
10475 // set page data
10476 $this->setPageBuffer($this->page, $s."\n", true);
10477 }
10478 } elseif ($this->state > 0) {
10479 // set general data
10480 $this->setBuffer($s."\n");
10481 }
10482 }
10483
10484 /**
10485 * Set header font.
10486 * @param array<int,string|float|null> $font Array describing the basic font parameters: (family, style, size).
10487 * @phpstan-param array{0: string, 1: string, 2: float|null} $font
10488 * @public
10489 * @since 1.1
10490 */
10491 public function setHeaderFont($font) {
10492 $this->header_font = $font;
10493 }
10494
10495 /**
10496 * Get header font.
10497 * @return array<int,string|float|null> Array describing the basic font parameters: (family, style, size).
10498 * @phpstan-return array{0: string, 1: string, 2: float|null}
10499 * @public
10500 * @since 4.0.012 (2008-07-24)
10501 */
10502 public function getHeaderFont() {
10503 return $this->header_font;
10504 }
10505
10506 /**
10507 * Set footer font.
10508 * @param array<int,string|float|null> $font Array describing the basic font parameters: (family, style, size).
10509 * @phpstan-param array{0: string, 1: string, 2: float|null} $font
10510 * @public
10511 * @since 1.1
10512 */
10513 public function setFooterFont($font) {
10514 $this->footer_font = $font;
10515 }
10516
10517 /**
10518 * Get Footer font.
10519 * @return array<int,string|float|null> Array describing the basic font parameters: (family, style, size).
10520 * @phpstan-return array{0: string, 1: string, 2: float|null} $font
10521 * @public
10522 * @since 4.0.012 (2008-07-24)
10523 */
10524 public function getFooterFont() {
10525 return $this->footer_font;
10526 }
10527
10528 /**
10529 * Set language array.
10530 * @param array $language
10531 * @public
10532 * @since 1.1
10533 */
10534 public function setLanguageArray($language) {
10535 $this->l = $language;
10536 if (isset($this->l['a_meta_dir'])) {
10537 $this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
10538 } else {
10539 $this->rtl = false;
10540 }
10541 }
10542
10543 /**
10544 * Returns the PDF data.
10545 * @public
10546 */
10547 public function getPDFData() {
10548 if ($this->state < 3) {
10549 $this->Close();
10550 }
10551 return $this->buffer;
10552 }
10553
10554 /**
10555 * Output anchor link.
10556 * @param string $url link URL or internal link (i.e.: &lt;a href="#23,4.5"&gt;link to page 23 at 4.5 Y position&lt;/a&gt;)
10557 * @param string $name link name
10558 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
10559 * @param boolean $firstline if true prints only the first line and return the remaining string.
10560 * @param array|null $color array of RGB text color
10561 * @param string $style font style (U, D, B, I)
10562 * @param boolean $firstblock if true the string is the starting of a line.
10563 * @return int the number of cells used or the remaining text if $firstline = true;
10564 * @public
10565 */
10566 public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color=null, $style=-1, $firstblock=false) {
10567 if (isset($url[1]) AND ($url[0] == '#') AND is_numeric($url[1])) {
10568 // convert url to internal link
10569 $lnkdata = explode(',', $url);
10570 if (isset($lnkdata[0]) ) {
10571 $page = substr($lnkdata[0], 1);
10572 if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
10573 $lnky = floatval($lnkdata[1]);
10574 } else {
10575 $lnky = 0;
10576 }
10577 $url = $this->AddLink();
10578 $this->setLink($url, $lnky, $page);
10579 }
10580 }
10581 // store current settings
10582 $prevcolor = $this->fgcolor;
10583 $prevstyle = $this->FontStyle;
10584 if (empty($color)) {
10585 $this->setTextColorArray($this->htmlLinkColorArray);
10586 } else {
10587 $this->setTextColorArray($color);
10588 }
10589 if ($style == -1) {
10590 $this->setFont('', $this->FontStyle.$this->htmlLinkFontStyle);
10591 } else {
10592 $this->setFont('', $this->FontStyle.$style);
10593 }
10594 $ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
10595 // restore settings
10596 $this->setFont('', $prevstyle);
10597 $this->setTextColorArray($prevcolor);
10598 return $ret;
10599 }
10600
10601 /**
10602 * Converts pixels to User's Units.
10603 * @param int $px pixels
10604 * @return float value in user's unit
10605 * @public
10606 * @see setImageScale(), getImageScale()
10607 */
10608 public function pixelsToUnits($px) {
10609 return ($px / ($this->imgscale * $this->k));
10610 }
10611
10612 /**
10613 * Reverse function for htmlentities.
10614 * Convert entities in UTF-8.
10615 * @param string $text_to_convert Text to convert.
10616 * @return string converted text string
10617 * @public
10618 */
10619 public function unhtmlentities($text_to_convert) {
10620 return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
10621 }
10622
10623 // ENCRYPTION METHODS ----------------------------------
10624
10625 /**
10626 * Compute encryption key depending on object number where the encrypted data is stored.
10627 * This is used for all strings and streams without crypt filter specifier.
10628 * @param int $n object number
10629 * @return int object key
10630 * @protected
10631 * @author Nicola Asuni
10632 * @since 2.0.000 (2008-01-02)
10633 */
10634 protected function _objectkey($n) {
10635 $objkey = $this->encryptdata['key'].pack('VXxx', $n);
10636 if ($this->encryptdata['mode'] == 2) { // AES-128
10637 // AES padding
10638 $objkey .= "\x73\x41\x6C\x54"; // sAlT
10639 }
10640 $objkey = substr(TCPDF_STATIC::_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
10641 $objkey = substr($objkey, 0, 16);
10642 return $objkey;
10643 }
10644
10645 /**
10646 * Encrypt the input string.
10647 * @param int $n object number
10648 * @param string $s data string to encrypt
10649 * @return string encrypted string
10650 * @protected
10651 * @author Nicola Asuni
10652 * @since 5.0.005 (2010-05-11)
10653 */
10654 protected function _encrypt_data($n, $s) {
10655 if (!$this->encrypted) {
10656 return $s;
10657 }
10658 switch ($this->encryptdata['mode']) {
10659 case 0: // RC4-40
10660 case 1: { // RC4-128
10661 $s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
10662 break;
10663 }
10664 case 2: { // AES-128
10665 $s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
10666 break;
10667 }
10668 case 3: { // AES-256
10669 $s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
10670 break;
10671 }
10672 }
10673 return $s;
10674 }
10675
10676 /**
10677 * Put encryption on PDF document.
10678 * @protected
10679 * @author Nicola Asuni
10680 * @since 2.0.000 (2008-01-02)
10681 */
10682 protected function _putencryption() {
10683 if (!$this->encrypted) {
10684 return;
10685 }
10686 $this->encryptdata['objid'] = $this->_newobj();
10687 $out = '<<';
10688 if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
10689 $this->encryptdata['Filter'] = 'Standard';
10690 }
10691 $out .= ' /Filter /'.$this->encryptdata['Filter'];
10692 if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
10693 $out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
10694 }
10695 if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
10696 $this->encryptdata['V'] = 1;
10697 }
10698 // V is a code specifying the algorithm to be used in encrypting and decrypting the document
10699 $out .= ' /V '.$this->encryptdata['V'];
10700 if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
10701 // The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
10702 $out .= ' /Length '.$this->encryptdata['Length'];
10703 } else {
10704 $out .= ' /Length 40';
10705 }
10706 if ($this->encryptdata['V'] >= 4) {
10707 if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
10708 $this->encryptdata['StmF'] = 'Identity';
10709 }
10710 if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
10711 // The name of the crypt filter that shall be used when decrypting all strings in the document.
10712 $this->encryptdata['StrF'] = 'Identity';
10713 }
10714 // A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
10715 if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
10716 $out .= ' /CF <<';
10717 $out .= ' /'.$this->encryptdata['StmF'].' <<';
10718 $out .= ' /Type /CryptFilter';
10719 if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
10720 // The method used
10721 $out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
10722 if ($this->encryptdata['pubkey']) {
10723 $out .= ' /Recipients [';
10724 foreach ($this->encryptdata['Recipients'] as $rec) {
10725 $out .= ' <'.$rec.'>';
10726 }
10727 $out .= ' ]';
10728 if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
10729 $out .= ' /EncryptMetadata false';
10730 } else {
10731 $out .= ' /EncryptMetadata true';
10732 }
10733 }
10734 } else {
10735 $out .= ' /CFM /None';
10736 }
10737 if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
10738 // The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
10739 $out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
10740 } else {
10741 $out .= ' /AuthEvent /DocOpen';
10742 }
10743 if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
10744 // The bit length of the encryption key.
10745 $out .= ' /Length '.$this->encryptdata['CF']['Length'];
10746 }
10747 $out .= ' >> >>';
10748 }
10749 // The name of the crypt filter that shall be used by default when decrypting streams.
10750 $out .= ' /StmF /'.$this->encryptdata['StmF'];
10751 // The name of the crypt filter that shall be used when decrypting all strings in the document.
10752 $out .= ' /StrF /'.$this->encryptdata['StrF'];
10753 if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
10754 // The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
10755 $out .= ' /EFF /'.$this->encryptdata[''];
10756 }
10757 }
10758 // Additional encryption dictionary entries for the standard security handler
10759 if ($this->encryptdata['pubkey']) {
10760 if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
10761 $out .= ' /Recipients [';
10762 foreach ($this->encryptdata['Recipients'] as $rec) {
10763 $out .= ' <'.$rec.'>';
10764 }
10765 $out .= ' ]';
10766 }
10767 } else {
10768 $out .= ' /R';
10769 if ($this->encryptdata['V'] == 5) { // AES-256
10770 $out .= ' 5';
10771 $out .= ' /OE ('.TCPDF_STATIC::_escape($this->encryptdata['OE']).')';
10772 $out .= ' /UE ('.TCPDF_STATIC::_escape($this->encryptdata['UE']).')';
10773 $out .= ' /Perms ('.TCPDF_STATIC::_escape($this->encryptdata['perms']).')';
10774 } elseif ($this->encryptdata['V'] == 4) { // AES-128
10775 $out .= ' 4';
10776 } elseif ($this->encryptdata['V'] < 2) { // RC-40
10777 $out .= ' 2';
10778 } else { // RC-128
10779 $out .= ' 3';
10780 }
10781 $out .= ' /O ('.TCPDF_STATIC::_escape($this->encryptdata['O']).')';
10782 $out .= ' /U ('.TCPDF_STATIC::_escape($this->encryptdata['U']).')';
10783 $out .= ' /P '.$this->encryptdata['P'];
10784 if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
10785 $out .= ' /EncryptMetadata false';
10786 } else {
10787 $out .= ' /EncryptMetadata true';
10788 }
10789 }
10790 $out .= ' >>';
10791 $out .= "\n".'endobj';
10792 $this->_out($out);
10793 }
10794
10795 /**
10796 * Compute U value (used for encryption)
10797 * @return string U value
10798 * @protected
10799 * @since 2.0.000 (2008-01-02)
10800 * @author Nicola Asuni
10801 */
10802 protected function _Uvalue() {
10803 if ($this->encryptdata['mode'] == 0) { // RC4-40
10804 return TCPDF_STATIC::_RC4($this->encryptdata['key'], TCPDF_STATIC::$enc_padding, $this->last_enc_key, $this->last_enc_key_c);
10805 } elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
10806 $tmp = TCPDF_STATIC::_md5_16(TCPDF_STATIC::$enc_padding.$this->encryptdata['fileid']);
10807 $enc = TCPDF_STATIC::_RC4($this->encryptdata['key'], $tmp, $this->last_enc_key, $this->last_enc_key_c);
10808 $len = strlen($tmp);
10809 for ($i = 1; $i <= 19; ++$i) {
10810 $ek = '';
10811 for ($j = 0; $j < $len; ++$j) {
10812 $ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
10813 }
10814 $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10815 }
10816 $enc .= str_repeat("\x00", 16);
10817 return substr($enc, 0, 32);
10818 } elseif ($this->encryptdata['mode'] == 3) { // AES-256
10819 $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10820 // User Validation Salt
10821 $this->encryptdata['UVS'] = substr($seed, 0, 8);
10822 // User Key Salt
10823 $this->encryptdata['UKS'] = substr($seed, 8, 16);
10824 return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
10825 }
10826 }
10827
10828 /**
10829 * Compute UE value (used for encryption)
10830 * @return string UE value
10831 * @protected
10832 * @since 5.9.006 (2010-10-19)
10833 * @author Nicola Asuni
10834 */
10835 protected function _UEvalue() {
10836 $hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
10837 return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10838 }
10839
10840 /**
10841 * Compute O value (used for encryption)
10842 * @return string O value
10843 * @protected
10844 * @since 2.0.000 (2008-01-02)
10845 * @author Nicola Asuni
10846 */
10847 protected function _Ovalue() {
10848 if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
10849 $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['owner_password']);
10850 if ($this->encryptdata['mode'] > 0) {
10851 for ($i = 0; $i < 50; ++$i) {
10852 $tmp = TCPDF_STATIC::_md5_16($tmp);
10853 }
10854 }
10855 $owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
10856 $enc = TCPDF_STATIC::_RC4($owner_key, $this->encryptdata['user_password'], $this->last_enc_key, $this->last_enc_key_c);
10857 if ($this->encryptdata['mode'] > 0) {
10858 $len = strlen($owner_key);
10859 for ($i = 1; $i <= 19; ++$i) {
10860 $ek = '';
10861 for ($j = 0; $j < $len; ++$j) {
10862 $ek .= chr(ord($owner_key[$j]) ^ $i);
10863 }
10864 $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10865 }
10866 }
10867 return $enc;
10868 } elseif ($this->encryptdata['mode'] == 3) { // AES-256
10869 $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10870 // Owner Validation Salt
10871 $this->encryptdata['OVS'] = substr($seed, 0, 8);
10872 // Owner Key Salt
10873 $this->encryptdata['OKS'] = substr($seed, 8, 16);
10874 return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
10875 }
10876 }
10877
10878 /**
10879 * Compute OE value (used for encryption)
10880 * @return string OE value
10881 * @protected
10882 * @since 5.9.006 (2010-10-19)
10883 * @author Nicola Asuni
10884 */
10885 protected function _OEvalue() {
10886 $hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
10887 return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10888 }
10889
10890 /**
10891 * Convert password for AES-256 encryption mode
10892 * @param string $password password
10893 * @return string password
10894 * @protected
10895 * @since 5.9.006 (2010-10-19)
10896 * @author Nicola Asuni
10897 */
10898 protected function _fixAES256Password($password) {
10899 $psw = ''; // password to be returned
10900 $psw_array = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($password, $this->isunicode, $this->CurrentFont), $password, $this->rtl, $this->isunicode, $this->CurrentFont);
10901 foreach ($psw_array as $c) {
10902 $psw .= TCPDF_FONTS::unichr($c, $this->isunicode);
10903 }
10904 return substr($psw, 0, 127);
10905 }
10906
10907 /**
10908 * Compute encryption key
10909 * @protected
10910 * @since 2.0.000 (2008-01-02)
10911 * @author Nicola Asuni
10912 */
10913 protected function _generateencryptionkey() {
10914 $keybytelen = ($this->encryptdata['Length'] / 8);
10915 if (!$this->encryptdata['pubkey']) { // standard mode
10916 if ($this->encryptdata['mode'] == 3) { // AES-256
10917 // generate 256 bit random key
10918 $this->encryptdata['key'] = substr(hash('sha256', TCPDF_STATIC::getRandomSeed(), true), 0, $keybytelen);
10919 // truncate passwords
10920 $this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
10921 $this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
10922 // Compute U value
10923 $this->encryptdata['U'] = $this->_Uvalue();
10924 // Compute UE value
10925 $this->encryptdata['UE'] = $this->_UEvalue();
10926 // Compute O value
10927 $this->encryptdata['O'] = $this->_Ovalue();
10928 // Compute OE value
10929 $this->encryptdata['OE'] = $this->_OEvalue();
10930 // Compute P value
10931 $this->encryptdata['P'] = $this->encryptdata['protection'];
10932 // Computing the encryption dictionary's Perms (permissions) value
10933 $perms = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
10934 $perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
10935 if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
10936 $perms .= 'F';
10937 } else {
10938 $perms .= 'T';
10939 }
10940 $perms .= 'adb'; // bytes 9-11
10941 $perms .= 'nick'; // bytes 12-15
10942 $this->encryptdata['perms'] = TCPDF_STATIC::_AESnopad($this->encryptdata['key'], $perms);
10943 } else { // RC4-40, RC4-128, AES-128
10944 // Pad passwords
10945 $this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10946 $this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10947 // Compute O value
10948 $this->encryptdata['O'] = $this->_Ovalue();
10949 // get default permissions (reverse byte order)
10950 $permissions = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']);
10951 // Compute encryption key
10952 $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
10953 if ($this->encryptdata['mode'] > 0) {
10954 for ($i = 0; $i < 50; ++$i) {
10955 $tmp = TCPDF_STATIC::_md5_16(substr($tmp, 0, $keybytelen));
10956 }
10957 }
10958 $this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
10959 // Compute U value
10960 $this->encryptdata['U'] = $this->_Uvalue();
10961 // Compute P value
10962 $this->encryptdata['P'] = $this->encryptdata['protection'];
10963 }
10964 } else { // Public-Key mode
10965 // random 20-byte seed
10966 $seed = sha1(TCPDF_STATIC::getRandomSeed(), true);
10967 $recipient_bytes = '';
10968 foreach ($this->encryptdata['pubkeys'] as $pubkey) {
10969 // for each public certificate
10970 if (isset($pubkey['p'])) {
10971 $pkprotection = TCPDF_STATIC::getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
10972 } else {
10973 $pkprotection = $this->encryptdata['protection'];
10974 }
10975 // get default permissions (reverse byte order)
10976 $pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
10977 // envelope data
10978 $envelope = $seed.$pkpermissions;
10979 // write the envelope data to a temporary file
10980 $tempkeyfile = TCPDF_STATIC::getObjFilename('key', $this->file_id);
10981 $f = TCPDF_STATIC::fopenLocal($tempkeyfile, 'wb');
10982 if (!$f) {
10983 $this->Error('Unable to create temporary key file: '.$tempkeyfile);
10984 }
10985 $envelope_length = strlen($envelope);
10986 fwrite($f, $envelope, $envelope_length);
10987 fclose($f);
10988 $tempencfile = TCPDF_STATIC::getObjFilename('enc', $this->file_id);
10989 if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
10990 $this->Error('Unable to encrypt the file: '.$tempkeyfile);
10991 }
10992 // read encryption signature
10993 $signature = file_get_contents($tempencfile, false, null, $envelope_length);
10994 // extract signature
10995 $signature = substr($signature, strpos($signature, 'Content-Disposition'));
10996 $tmparr = explode("\n\n", $signature);
10997 $signature = trim($tmparr[1]);
10998 unset($tmparr);
10999 // decode signature
11000 $signature = base64_decode($signature);
11001 // convert signature to hex
11002 $hexsignature = current(unpack('H*', $signature));
11003 // store signature on recipients array
11004 $this->encryptdata['Recipients'][] = $hexsignature;
11005 // The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
11006 $recipient_bytes .= $signature;
11007 }
11008 // calculate encryption key
11009 if ($this->encryptdata['mode'] == 3) { // AES-256
11010 $this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
11011 } else { // RC4-40, RC4-128, AES-128
11012 $this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
11013 }
11014 }
11015 }
11016
11017 /**
11018 * Set document protection
11019 * Remark: the protection against modification is for people who have the full Acrobat product.
11020 * 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.
11021 * 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.
11022 * @param array $permissions the set of permissions (specify the ones you want to block):<ul><li>print : Print the document;</li><li>modify : Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';</li><li>copy : Copy or otherwise extract text and graphics from the document;</li><li>annot-forms : Add or modify text annotations, fill in interactive form fields, and, if 'modify' is also set, create or modify interactive form fields (including signature fields);</li><li>fill-forms : Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified;</li><li>extract : Extract text and graphics (in support of accessibility to users with disabilities or for other purposes);</li><li>assemble : Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set;</li><li>print-high : Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.</li><li>owner : (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.</li></ul>
11023 * @param string $user_pass user password. Empty by default.
11024 * @param string|null $owner_pass owner password. If not specified, a random value is used.
11025 * @param int $mode encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
11026 * @param array|null $pubkeys array of recipients containing public-key certificates ('c') and permissions ('p'). For example: array(array('c' => 'file://../examples/data/cert/tcpdf.crt', 'p' => array('print')))
11027 * @public
11028 * @since 2.0.000 (2008-01-02)
11029 * @author Nicola Asuni
11030 */
11031 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) {
11032 if ($this->pdfa_mode) {
11033 // encryption is not allowed in PDF/A mode
11034 return;
11035 }
11036 $this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
11037 if (($pubkeys !== null) AND (is_array($pubkeys))) {
11038 // public-key mode
11039 $this->encryptdata['pubkeys'] = $pubkeys;
11040 if ($mode == 0) {
11041 // public-Key Security requires at least 128 bit
11042 $mode = 1;
11043 }
11044 if (!function_exists('openssl_pkcs7_encrypt')) {
11045 $this->Error('Public-Key Security requires openssl library.');
11046 }
11047 // Set Public-Key filter (available are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
11048 $this->encryptdata['pubkey'] = true;
11049 $this->encryptdata['Filter'] = 'Adobe.PubSec';
11050 $this->encryptdata['StmF'] = 'DefaultCryptFilter';
11051 $this->encryptdata['StrF'] = 'DefaultCryptFilter';
11052 } else {
11053 // standard mode (password mode)
11054 $this->encryptdata['pubkey'] = false;
11055 $this->encryptdata['Filter'] = 'Standard';
11056 $this->encryptdata['StmF'] = 'StdCF';
11057 $this->encryptdata['StrF'] = 'StdCF';
11058 }
11059 if ($mode > 1) { // AES
11060 if (!extension_loaded('openssl') && !extension_loaded('mcrypt')) {
11061 $this->Error('AES encryption requires openssl or mcrypt extension (http://www.php.net/manual/en/mcrypt.requirements.php).');
11062 }
11063 if (extension_loaded('openssl') && !in_array('aes-256-cbc', openssl_get_cipher_methods())) {
11064 $this->Error('AES encryption requires openssl/aes-256-cbc cypher.');
11065 }
11066 if (extension_loaded('mcrypt') && mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
11067 $this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
11068 }
11069 if (($mode == 3) AND !function_exists('hash')) {
11070 // the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
11071 $this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
11072 }
11073 }
11074 if ($owner_pass === null) {
11075 $owner_pass = md5(TCPDF_STATIC::getRandomSeed());
11076 }
11077 $this->encryptdata['user_password'] = $user_pass;
11078 $this->encryptdata['owner_password'] = $owner_pass;
11079 $this->encryptdata['mode'] = $mode;
11080 switch ($mode) {
11081 case 0: { // RC4 40 bit
11082 $this->encryptdata['V'] = 1;
11083 $this->encryptdata['Length'] = 40;
11084 $this->encryptdata['CF']['CFM'] = 'V2';
11085 break;
11086 }
11087 case 1: { // RC4 128 bit
11088 $this->encryptdata['V'] = 2;
11089 $this->encryptdata['Length'] = 128;
11090 $this->encryptdata['CF']['CFM'] = 'V2';
11091 if ($this->encryptdata['pubkey']) {
11092 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
11093 $this->encryptdata['Recipients'] = array();
11094 }
11095 break;
11096 }
11097 case 2: { // AES 128 bit
11098 $this->encryptdata['V'] = 4;
11099 $this->encryptdata['Length'] = 128;
11100 $this->encryptdata['CF']['CFM'] = 'AESV2';
11101 $this->encryptdata['CF']['Length'] = 16;
11102 if ($this->encryptdata['pubkey']) {
11103 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
11104 $this->encryptdata['Recipients'] = array();
11105 }
11106 break;
11107 }
11108 case 3: { // AES 256 bit
11109 $this->encryptdata['V'] = 5;
11110 $this->encryptdata['Length'] = 256;
11111 $this->encryptdata['CF']['CFM'] = 'AESV3';
11112 $this->encryptdata['CF']['Length'] = 32;
11113 if ($this->encryptdata['pubkey']) {
11114 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
11115 $this->encryptdata['Recipients'] = array();
11116 }
11117 break;
11118 }
11119 }
11120 $this->encrypted = true;
11121 $this->encryptdata['fileid'] = TCPDF_STATIC::convertHexStringToString($this->file_id);
11122 $this->_generateencryptionkey();
11123 }
11124
11125 // END OF ENCRYPTION FUNCTIONS -------------------------
11126
11127 // START TRANSFORMATIONS SECTION -----------------------
11128
11129 /**
11130 * Starts a 2D tranformation saving current graphic state.
11131 * This function must be called before scaling, mirroring, translation, rotation and skewing.
11132 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11133 * @public
11134 * @since 2.1.000 (2008-01-07)
11135 * @see StartTransform(), StopTransform()
11136 */
11137 public function StartTransform() {
11138 if ($this->state != 2) {
11139 return;
11140 }
11141 $this->_outSaveGraphicsState();
11142 if ($this->inxobj) {
11143 // we are inside an XObject template
11144 $this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
11145 } else {
11146 $this->transfmrk[$this->page][] = $this->pagelen[$this->page];
11147 }
11148 ++$this->transfmatrix_key;
11149 $this->transfmatrix[$this->transfmatrix_key] = array();
11150 }
11151
11152 /**
11153 * Stops a 2D tranformation restoring previous graphic state.
11154 * This function must be called after scaling, mirroring, translation, rotation and skewing.
11155 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11156 * @public
11157 * @since 2.1.000 (2008-01-07)
11158 * @see StartTransform(), StopTransform()
11159 */
11160 public function StopTransform() {
11161 if ($this->state != 2) {
11162 return;
11163 }
11164 $this->_outRestoreGraphicsState();
11165 if (isset($this->transfmatrix[$this->transfmatrix_key])) {
11166 array_pop($this->transfmatrix[$this->transfmatrix_key]);
11167 --$this->transfmatrix_key;
11168 }
11169 if ($this->inxobj) {
11170 // we are inside an XObject template
11171 array_pop($this->xobjects[$this->xobjid]['transfmrk']);
11172 } else {
11173 array_pop($this->transfmrk[$this->page]);
11174 }
11175 }
11176 /**
11177 * Horizontal Scaling.
11178 * @param float $s_x scaling factor for width as percent. 0 is not allowed.
11179 * @param int $x abscissa of the scaling center. Default is current x position
11180 * @param int $y ordinate of the scaling center. Default is current y position
11181 * @public
11182 * @since 2.1.000 (2008-01-07)
11183 * @see StartTransform(), StopTransform()
11184 */
11185 public function ScaleX($s_x, $x='', $y='') {
11186 $this->Scale($s_x, 100, $x, $y);
11187 }
11188
11189 /**
11190 * Vertical Scaling.
11191 * @param float $s_y scaling factor for height as percent. 0 is not allowed.
11192 * @param int $x abscissa of the scaling center. Default is current x position
11193 * @param int $y ordinate of the scaling center. Default is current y position
11194 * @public
11195 * @since 2.1.000 (2008-01-07)
11196 * @see StartTransform(), StopTransform()
11197 */
11198 public function ScaleY($s_y, $x='', $y='') {
11199 $this->Scale(100, $s_y, $x, $y);
11200 }
11201
11202 /**
11203 * Vertical and horizontal proportional Scaling.
11204 * @param float $s scaling factor for width and height as percent. 0 is not allowed.
11205 * @param int $x abscissa of the scaling center. Default is current x position
11206 * @param int $y ordinate of the scaling center. Default is current y position
11207 * @public
11208 * @since 2.1.000 (2008-01-07)
11209 * @see StartTransform(), StopTransform()
11210 */
11211 public function ScaleXY($s, $x='', $y='') {
11212 $this->Scale($s, $s, $x, $y);
11213 }
11214
11215 /**
11216 * Vertical and horizontal non-proportional Scaling.
11217 * @param float $s_x scaling factor for width as percent. 0 is not allowed.
11218 * @param float $s_y scaling factor for height as percent. 0 is not allowed.
11219 * @param float|null $x abscissa of the scaling center. Default is current x position
11220 * @param float|null $y ordinate of the scaling center. Default is current y position
11221 * @public
11222 * @since 2.1.000 (2008-01-07)
11223 * @see StartTransform(), StopTransform()
11224 */
11225 public function Scale($s_x, $s_y, $x=null, $y=null) {
11226 if (TCPDF_STATIC::empty_string($x)) {
11227 $x = $this->x;
11228 }
11229 if (TCPDF_STATIC::empty_string($y)) {
11230 $y = $this->y;
11231 }
11232 if (($s_x == 0) OR ($s_y == 0)) {
11233 $this->Error('Please do not use values equal to zero for scaling');
11234 }
11235 $y = ($this->h - $y) * $this->k;
11236 $x *= $this->k;
11237 //calculate elements of transformation matrix
11238 $s_x /= 100;
11239 $s_y /= 100;
11240 $tm = array();
11241 $tm[0] = $s_x;
11242 $tm[1] = 0;
11243 $tm[2] = 0;
11244 $tm[3] = $s_y;
11245 $tm[4] = $x * (1 - $s_x);
11246 $tm[5] = $y * (1 - $s_y);
11247 //scale the coordinate system
11248 $this->Transform($tm);
11249 }
11250
11251 /**
11252 * Horizontal Mirroring.
11253 * @param float|null $x abscissa of the point. Default is current x position
11254 * @public
11255 * @since 2.1.000 (2008-01-07)
11256 * @see StartTransform(), StopTransform()
11257 */
11258 public function MirrorH($x=null) {
11259 $this->Scale(-100, 100, $x);
11260 }
11261
11262 /**
11263 * Verical Mirroring.
11264 * @param float|null $y ordinate of the point. Default is current y position
11265 * @public
11266 * @since 2.1.000 (2008-01-07)
11267 * @see StartTransform(), StopTransform()
11268 */
11269 public function MirrorV($y=null) {
11270 $this->Scale(100, -100, null, $y);
11271 }
11272
11273 /**
11274 * Point reflection mirroring.
11275 * @param float|null $x abscissa of the point. Default is current x position
11276 * @param float|null $y ordinate of the point. Default is current y position
11277 * @public
11278 * @since 2.1.000 (2008-01-07)
11279 * @see StartTransform(), StopTransform()
11280 */
11281 public function MirrorP($x=null,$y=null) {
11282 $this->Scale(-100, -100, $x, $y);
11283 }
11284
11285 /**
11286 * Reflection against a straight line through point (x, y) with the gradient angle (angle).
11287 * @param float $angle gradient angle of the straight line. Default is 0 (horizontal line).
11288 * @param float|null $x abscissa of the point. Default is current x position
11289 * @param float|null $y ordinate of the point. Default is current y position
11290 * @public
11291 * @since 2.1.000 (2008-01-07)
11292 * @see StartTransform(), StopTransform()
11293 */
11294 public function MirrorL($angle=0, $x=null,$y=null) {
11295 $this->Scale(-100, 100, $x, $y);
11296 $this->Rotate(-2*($angle-90), $x, $y);
11297 }
11298
11299 /**
11300 * Translate graphic object horizontally.
11301 * @param int $t_x movement to the right (or left for RTL)
11302 * @public
11303 * @since 2.1.000 (2008-01-07)
11304 * @see StartTransform(), StopTransform()
11305 */
11306 public function TranslateX($t_x) {
11307 $this->Translate($t_x, 0);
11308 }
11309
11310 /**
11311 * Translate graphic object vertically.
11312 * @param int $t_y movement to the bottom
11313 * @public
11314 * @since 2.1.000 (2008-01-07)
11315 * @see StartTransform(), StopTransform()
11316 */
11317 public function TranslateY($t_y) {
11318 $this->Translate(0, $t_y);
11319 }
11320
11321 /**
11322 * Translate graphic object horizontally and vertically.
11323 * @param int $t_x movement to the right
11324 * @param int $t_y movement to the bottom
11325 * @public
11326 * @since 2.1.000 (2008-01-07)
11327 * @see StartTransform(), StopTransform()
11328 */
11329 public function Translate($t_x, $t_y) {
11330 //calculate elements of transformation matrix
11331 $tm = array();
11332 $tm[0] = 1;
11333 $tm[1] = 0;
11334 $tm[2] = 0;
11335 $tm[3] = 1;
11336 $tm[4] = $t_x * $this->k;
11337 $tm[5] = -$t_y * $this->k;
11338 //translate the coordinate system
11339 $this->Transform($tm);
11340 }
11341
11342 /**
11343 * Rotate object.
11344 * @param float $angle angle in degrees for counter-clockwise rotation
11345 * @param float|null $x abscissa of the rotation center. Default is current x position
11346 * @param float|null $y ordinate of the rotation center. Default is current y position
11347 * @public
11348 * @since 2.1.000 (2008-01-07)
11349 * @see StartTransform(), StopTransform()
11350 */
11351 public function Rotate($angle, $x=null, $y=null) {
11352 if (TCPDF_STATIC::empty_string($x)) {
11353 $x = $this->x;
11354 }
11355 if (TCPDF_STATIC::empty_string($y)) {
11356 $y = $this->y;
11357 }
11358 $y = ($this->h - $y) * $this->k;
11359 $x *= $this->k;
11360 //calculate elements of transformation matrix
11361 $tm = array();
11362 $tm[0] = cos(deg2rad($angle));
11363 $tm[1] = sin(deg2rad($angle));
11364 $tm[2] = -$tm[1];
11365 $tm[3] = $tm[0];
11366 $tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
11367 $tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
11368 //rotate the coordinate system around ($x,$y)
11369 $this->Transform($tm);
11370 }
11371
11372 /**
11373 * Skew horizontally.
11374 * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11375 * @param float|null $x abscissa of the skewing center. default is current x position
11376 * @param float|null $y ordinate of the skewing center. default is current y position
11377 * @public
11378 * @since 2.1.000 (2008-01-07)
11379 * @see StartTransform(), StopTransform()
11380 */
11381 public function SkewX($angle_x, $x=null, $y=null) {
11382 $this->Skew($angle_x, 0, $x, $y);
11383 }
11384
11385 /**
11386 * Skew vertically.
11387 * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11388 * @param float|null $x abscissa of the skewing center. default is current x position
11389 * @param float|null $y ordinate of the skewing center. default is current y position
11390 * @public
11391 * @since 2.1.000 (2008-01-07)
11392 * @see StartTransform(), StopTransform()
11393 */
11394 public function SkewY($angle_y, $x=null, $y=null) {
11395 $this->Skew(0, $angle_y, $x, $y);
11396 }
11397
11398 /**
11399 * Skew.
11400 * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11401 * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11402 * @param float|null $x abscissa of the skewing center. default is current x position
11403 * @param float|null $y ordinate of the skewing center. default is current y position
11404 * @public
11405 * @since 2.1.000 (2008-01-07)
11406 * @see StartTransform(), StopTransform()
11407 */
11408 public function Skew($angle_x, $angle_y, $x=null, $y=null) {
11409 if (TCPDF_STATIC::empty_string($x)) {
11410 $x = $this->x;
11411 }
11412 if (TCPDF_STATIC::empty_string($y)) {
11413 $y = $this->y;
11414 }
11415 if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
11416 $this->Error('Please use values between -90 and +90 degrees for Skewing.');
11417 }
11418 $x *= $this->k;
11419 $y = ($this->h - $y) * $this->k;
11420 //calculate elements of transformation matrix
11421 $tm = array();
11422 $tm[0] = 1;
11423 $tm[1] = tan(deg2rad($angle_y));
11424 $tm[2] = tan(deg2rad($angle_x));
11425 $tm[3] = 1;
11426 $tm[4] = -$tm[2] * $y;
11427 $tm[5] = -$tm[1] * $x;
11428 //skew the coordinate system
11429 $this->Transform($tm);
11430 }
11431
11432 /**
11433 * Apply graphic transformations.
11434 * @param array $tm transformation matrix
11435 * @protected
11436 * @since 2.1.000 (2008-01-07)
11437 * @see StartTransform(), StopTransform()
11438 */
11439 protected function Transform($tm) {
11440 if ($this->state != 2) {
11441 return;
11442 }
11443 $this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
11444 // add tranformation matrix
11445 $this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
11446 // update transformation mark
11447 if ($this->inxobj) {
11448 // we are inside an XObject template
11449 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
11450 $key = key($this->xobjects[$this->xobjid]['transfmrk']);
11451 $this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
11452 }
11453 } elseif (end($this->transfmrk[$this->page]) !== false) {
11454 $key = key($this->transfmrk[$this->page]);
11455 $this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
11456 }
11457 }
11458
11459 // END TRANSFORMATIONS SECTION -------------------------
11460
11461 // START GRAPHIC FUNCTIONS SECTION ---------------------
11462 // The following section is based on the code provided by David Hernandez Sanz
11463
11464 /**
11465 * 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.
11466 * @param float $width The width.
11467 * @public
11468 * @since 1.0
11469 * @see Line(), Rect(), Cell(), MultiCell()
11470 */
11471 public function setLineWidth($width) {
11472 //Set line width
11473 $this->LineWidth = $width;
11474 $this->linestyleWidth = sprintf('%F w', ($width * $this->k));
11475 if ($this->state == 2) {
11476 $this->_out($this->linestyleWidth);
11477 }
11478 }
11479
11480 /**
11481 * Returns the current the line width.
11482 * @return int Line width
11483 * @public
11484 * @since 2.1.000 (2008-01-07)
11485 * @see Line(), SetLineWidth()
11486 */
11487 public function GetLineWidth() {
11488 return $this->LineWidth;
11489 }
11490
11491 /**
11492 * Set line style.
11493 * @param array $style Line style. Array with keys among the following:
11494 * <ul>
11495 * <li>width (float): Width of the line in user units.</li>
11496 * <li>cap (string): Type of cap to put on the line. Possible values are:
11497 * butt, round, square. The difference between "square" and "butt" is that
11498 * "square" projects a flat end past the end of the line.</li>
11499 * <li>join (string): Type of join. Possible values are: miter, round,
11500 * bevel.</li>
11501 * <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
11502 * series of length values, which are the lengths of the on and off dashes.
11503 * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
11504 * 1 off, 2 on, 1 off, ...</li>
11505 * <li>phase (integer): Modifier on the dash pattern which is used to shift
11506 * the point at which the pattern starts.</li>
11507 * <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>
11508 * </ul>
11509 * @param boolean $ret if true do not send the command.
11510 * @return string the PDF command
11511 * @public
11512 * @since 2.1.000 (2008-01-08)
11513 */
11514 public function setLineStyle($style, $ret=false) {
11515 $s = ''; // string to be returned
11516 if (!is_array($style)) {
11517 return $s;
11518 }
11519 if (isset($style['width'])) {
11520 $this->LineWidth = $style['width'];
11521 $this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
11522 $s .= $this->linestyleWidth.' ';
11523 }
11524 if (isset($style['cap'])) {
11525 $ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
11526 if (isset($ca[$style['cap']])) {
11527 $this->linestyleCap = $ca[$style['cap']].' J';
11528 $s .= $this->linestyleCap.' ';
11529 }
11530 }
11531 if (isset($style['join'])) {
11532 $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
11533 if (isset($ja[$style['join']])) {
11534 $this->linestyleJoin = $ja[$style['join']].' j';
11535 $s .= $this->linestyleJoin.' ';
11536 }
11537 }
11538 if (isset($style['dash'])) {
11539 $dash_string = '';
11540 if ($style['dash']) {
11541 if (preg_match('/^.+,/', $style['dash']) > 0) {
11542 $tab = explode(',', $style['dash']);
11543 } else {
11544 $tab = array($style['dash']);
11545 }
11546 $dash_string = '';
11547 foreach ($tab as $i => $v) {
11548 if ($i) {
11549 $dash_string .= ' ';
11550 }
11551 $dash_string .= sprintf('%F', $v);
11552 }
11553 }
11554 if (!isset($style['phase']) OR !$style['dash']) {
11555 $style['phase'] = 0;
11556 }
11557 $this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
11558 $s .= $this->linestyleDash.' ';
11559 }
11560 if (isset($style['color'])) {
11561 $s .= $this->setDrawColorArray($style['color'], true).' ';
11562 }
11563 if (!$ret AND ($this->state == 2)) {
11564 $this->_out($s);
11565 }
11566 return $s;
11567 }
11568
11569 /**
11570 * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
11571 * @param float $x Abscissa of point.
11572 * @param float $y Ordinate of point.
11573 * @protected
11574 * @since 2.1.000 (2008-01-08)
11575 */
11576 protected function _outPoint($x, $y) {
11577 if ($this->state == 2) {
11578 $this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
11579 }
11580 }
11581
11582 /**
11583 * Append a straight line segment from the current point to the point (x, y).
11584 * The new current point shall be (x, y).
11585 * @param float $x Abscissa of end point.
11586 * @param float $y Ordinate of end point.
11587 * @protected
11588 * @since 2.1.000 (2008-01-08)
11589 */
11590 protected function _outLine($x, $y) {
11591 if ($this->state == 2) {
11592 $this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
11593 }
11594 }
11595
11596 /**
11597 * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
11598 * @param float $x Abscissa of upper-left corner.
11599 * @param float $y Ordinate of upper-left corner.
11600 * @param float $w Width.
11601 * @param float $h Height.
11602 * @param string $op options
11603 * @protected
11604 * @since 2.1.000 (2008-01-08)
11605 */
11606 protected function _outRect($x, $y, $w, $h, $op) {
11607 if ($this->state == 2) {
11608 $this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k), (($this->h - $y) * $this->k), ($w * $this->k), (-$h * $this->k), $op));
11609 }
11610 }
11611
11612 /**
11613 * 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.
11614 * The new current point shall be (x3, y3).
11615 * @param float $x1 Abscissa of control point 1.
11616 * @param float $y1 Ordinate of control point 1.
11617 * @param float $x2 Abscissa of control point 2.
11618 * @param float $y2 Ordinate of control point 2.
11619 * @param float $x3 Abscissa of end point.
11620 * @param float $y3 Ordinate of end point.
11621 * @protected
11622 * @since 2.1.000 (2008-01-08)
11623 */
11624 protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
11625 if ($this->state == 2) {
11626 $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)));
11627 }
11628 }
11629
11630 /**
11631 * 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.
11632 * The new current point shall be (x3, y3).
11633 * @param float $x2 Abscissa of control point 2.
11634 * @param float $y2 Ordinate of control point 2.
11635 * @param float $x3 Abscissa of end point.
11636 * @param float $y3 Ordinate of end point.
11637 * @protected
11638 * @since 4.9.019 (2010-04-26)
11639 */
11640 protected function _outCurveV($x2, $y2, $x3, $y3) {
11641 if ($this->state == 2) {
11642 $this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11643 }
11644 }
11645
11646 /**
11647 * 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.
11648 * The new current point shall be (x3, y3).
11649 * @param float $x1 Abscissa of control point 1.
11650 * @param float $y1 Ordinate of control point 1.
11651 * @param float $x3 Abscissa of end point.
11652 * @param float $y3 Ordinate of end point.
11653 * @protected
11654 * @since 2.1.000 (2008-01-08)
11655 */
11656 protected function _outCurveY($x1, $y1, $x3, $y3) {
11657 if ($this->state == 2) {
11658 $this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11659 }
11660 }
11661
11662 /**
11663 * Draws a line between two points.
11664 * @param float $x1 Abscissa of first point.
11665 * @param float $y1 Ordinate of first point.
11666 * @param float $x2 Abscissa of second point.
11667 * @param float $y2 Ordinate of second point.
11668 * @param array $style Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
11669 * @public
11670 * @since 1.0
11671 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
11672 */
11673 public function Line($x1, $y1, $x2, $y2, $style=array()) {
11674 if ($this->state != 2) {
11675 return;
11676 }
11677 if (is_array($style)) {
11678 $this->setLineStyle($style);
11679 }
11680 $this->_outPoint($x1, $y1);
11681 $this->_outLine($x2, $y2);
11682 $this->_out('S');
11683 }
11684
11685 /**
11686 * Draws a rectangle.
11687 * @param float $x Abscissa of upper-left corner.
11688 * @param float $y Ordinate of upper-left corner.
11689 * @param float $w Width.
11690 * @param float $h Height.
11691 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11692 * @param array $border_style Border style of rectangle. Array with keys among the following:
11693 * <ul>
11694 * <li>all: Line style of all borders. Array like for SetLineStyle().</li>
11695 * <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
11696 * </ul>
11697 * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
11698 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11699 * @public
11700 * @since 1.0
11701 * @see SetLineStyle()
11702 */
11703 public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
11704 if ($this->state != 2) {
11705 return;
11706 }
11707 if (empty($style)) {
11708 $style = 'S';
11709 }
11710 if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
11711 // set background color
11712 $this->setFillColorArray($fill_color);
11713 }
11714 if (!empty($border_style)) {
11715 if (isset($border_style['all']) AND !empty($border_style['all'])) {
11716 //set global style for border
11717 $this->setLineStyle($border_style['all']);
11718 $border_style = array();
11719 } else {
11720 // remove stroke operator from style
11721 $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*' );
11722 if (isset($opnostroke[$style])) {
11723 $style = $opnostroke[$style];
11724 }
11725 }
11726 }
11727 if (!empty($style)) {
11728 $op = TCPDF_STATIC::getPathPaintOperator($style);
11729 $this->_outRect($x, $y, $w, $h, $op);
11730 }
11731 if (!empty($border_style)) {
11732 $border_style2 = array();
11733 foreach ($border_style as $line => $value) {
11734 $length = strlen($line);
11735 for ($i = 0; $i < $length; ++$i) {
11736 $border_style2[$line[$i]] = $value;
11737 }
11738 }
11739 $border_style = $border_style2;
11740 if (isset($border_style['L']) AND $border_style['L']) {
11741 $this->Line($x, $y, $x, $y + $h, $border_style['L']);
11742 }
11743 if (isset($border_style['T']) AND $border_style['T']) {
11744 $this->Line($x, $y, $x + $w, $y, $border_style['T']);
11745 }
11746 if (isset($border_style['R']) AND $border_style['R']) {
11747 $this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
11748 }
11749 if (isset($border_style['B']) AND $border_style['B']) {
11750 $this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
11751 }
11752 }
11753 }
11754
11755 /**
11756 * Draws a Bezier curve.
11757 * The Bezier curve is a tangent to the line between the control points at
11758 * either end of the curve.
11759 * @param float $x0 Abscissa of start point.
11760 * @param float $y0 Ordinate of start point.
11761 * @param float $x1 Abscissa of control point 1.
11762 * @param float $y1 Ordinate of control point 1.
11763 * @param float $x2 Abscissa of control point 2.
11764 * @param float $y2 Ordinate of control point 2.
11765 * @param float $x3 Abscissa of end point.
11766 * @param float $y3 Ordinate of end point.
11767 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11768 * @param array $line_style Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11769 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11770 * @public
11771 * @see SetLineStyle()
11772 * @since 2.1.000 (2008-01-08)
11773 */
11774 public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
11775 if ($this->state != 2) {
11776 return;
11777 }
11778 if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11779 $this->setFillColorArray($fill_color);
11780 }
11781 $op = TCPDF_STATIC::getPathPaintOperator($style);
11782 if ($line_style) {
11783 $this->setLineStyle($line_style);
11784 }
11785 $this->_outPoint($x0, $y0);
11786 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11787 $this->_out($op);
11788 }
11789
11790 /**
11791 * Draws a poly-Bezier curve.
11792 * Each Bezier curve segment is a tangent to the line between the control points at
11793 * either end of the curve.
11794 * @param float $x0 Abscissa of start point.
11795 * @param float $y0 Ordinate of start point.
11796 * @param float[] $segments An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
11797 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11798 * @param array $line_style Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11799 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11800 * @public
11801 * @see SetLineStyle()
11802 * @since 3.0008 (2008-05-12)
11803 */
11804 public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
11805 if ($this->state != 2) {
11806 return;
11807 }
11808 if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11809 $this->setFillColorArray($fill_color);
11810 }
11811 $op = TCPDF_STATIC::getPathPaintOperator($style);
11812 if ($op == 'f') {
11813 $line_style = array();
11814 }
11815 if ($line_style) {
11816 $this->setLineStyle($line_style);
11817 }
11818 $this->_outPoint($x0, $y0);
11819 foreach ($segments as $segment) {
11820 list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
11821 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11822 }
11823 $this->_out($op);
11824 }
11825
11826 /**
11827 * Draws an ellipse.
11828 * An ellipse is formed from n Bezier curves.
11829 * @param float $x0 Abscissa of center point.
11830 * @param float $y0 Ordinate of center point.
11831 * @param float $rx Horizontal radius.
11832 * @param float $ry Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11833 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
11834 * @param float $astart Angle start of draw line. Default value: 0.
11835 * @param float $afinish Angle finish of draw line. Default value: 360.
11836 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11837 * @param array $line_style Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
11838 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11839 * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse.
11840 * @author Nicola Asuni
11841 * @public
11842 * @since 2.1.000 (2008-01-08)
11843 */
11844 public function Ellipse($x0, $y0, $rx, $ry=0, $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11845 if ($this->state != 2) {
11846 return;
11847 }
11848 if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
11849 $ry = $rx;
11850 }
11851 if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11852 $this->setFillColorArray($fill_color);
11853 }
11854 $op = TCPDF_STATIC::getPathPaintOperator($style);
11855 if ($op == 'f') {
11856 $line_style = array();
11857 }
11858 if ($line_style) {
11859 $this->setLineStyle($line_style);
11860 }
11861 $this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
11862 $this->_out($op);
11863 }
11864
11865 /**
11866 * Append an elliptical arc to the current path.
11867 * An ellipse is formed from n Bezier curves.
11868 * @param float $xc Abscissa of center point.
11869 * @param float $yc Ordinate of center point.
11870 * @param float $rx Horizontal radius.
11871 * @param float $ry Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11872 * @param float $xang Angle between the X-axis and the major axis of the ellipse. Default value: 0.
11873 * @param float $angs Angle start of draw line. Default value: 0.
11874 * @param float $angf Angle finish of draw line. Default value: 360.
11875 * @param boolean $pie if true do not mark the border point (used to draw pie sectors).
11876 * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse.
11877 * @param boolean $startpoint if true output a starting point.
11878 * @param boolean $ccw if true draws in counter-clockwise.
11879 * @param boolean $svg if true the angles are in svg mode (already calculated).
11880 * @return array bounding box coordinates (x min, y min, x max, y max)
11881 * @author Nicola Asuni
11882 * @protected
11883 * @since 4.9.019 (2010-04-26)
11884 */
11885 protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
11886 if (($rx <= 0) OR ($ry < 0)) {
11887 return;
11888 }
11889 $k = $this->k;
11890 if ($nc < 2) {
11891 $nc = 2;
11892 }
11893 $xmin = 2147483647;
11894 $ymin = 2147483647;
11895 $xmax = 0;
11896 $ymax = 0;
11897 if ($pie) {
11898 // center of the arc
11899 $this->_outPoint($xc, $yc);
11900 }
11901 $xang = deg2rad((float) $xang);
11902 $angs = deg2rad((float) $angs);
11903 $angf = deg2rad((float) $angf);
11904 if ($svg) {
11905 $as = $angs;
11906 $af = $angf;
11907 } else {
11908 $as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
11909 $af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
11910 }
11911 if ($as < 0) {
11912 $as += (2 * M_PI);
11913 }
11914 if ($af < 0) {
11915 $af += (2 * M_PI);
11916 }
11917 if ($ccw AND ($as > $af)) {
11918 // reverse rotation
11919 $as -= (2 * M_PI);
11920 } elseif (!$ccw AND ($as < $af)) {
11921 // reverse rotation
11922 $af -= (2 * M_PI);
11923 }
11924 $total_angle = ($af - $as);
11925 if ($nc < 2) {
11926 $nc = 2;
11927 }
11928 // total arcs to draw
11929 $nc *= (2 * abs($total_angle) / M_PI);
11930 $nc = round($nc) + 1;
11931 // angle of each arc
11932 $arcang = ($total_angle / $nc);
11933 // center point in PDF coordinates
11934 $x0 = $xc;
11935 $y0 = ($this->h - $yc);
11936 // starting angle
11937 $ang = $as;
11938 $alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
11939 $cos_xang = cos($xang);
11940 $sin_xang = sin($xang);
11941 $cos_ang = cos($ang);
11942 $sin_ang = sin($ang);
11943 // first arc point
11944 $px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11945 $py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11946 // first Bezier control point
11947 $qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11948 $qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11949 if ($pie) {
11950 // line from center to arc starting point
11951 $this->_outLine($px1, $this->h - $py1);
11952 } elseif ($startpoint) {
11953 // arc starting point
11954 $this->_outPoint($px1, $this->h - $py1);
11955 }
11956 // draw arcs
11957 for ($i = 1; $i <= $nc; ++$i) {
11958 // starting angle
11959 $ang = $as + ($i * $arcang);
11960 if ($i == $nc) {
11961 $ang = $af;
11962 }
11963 $cos_ang = cos($ang);
11964 $sin_ang = sin($ang);
11965 // second arc point
11966 $px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11967 $py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11968 // second Bezier control point
11969 $qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11970 $qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11971 // draw arc
11972 $cx1 = ($px1 + $qx1);
11973 $cy1 = ($this->h - ($py1 + $qy1));
11974 $cx2 = ($px2 - $qx2);
11975 $cy2 = ($this->h - ($py2 - $qy2));
11976 $cx3 = $px2;
11977 $cy3 = ($this->h - $py2);
11978 $this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
11979 // get bounding box coordinates
11980 $xmin = min($xmin, $cx1, $cx2, $cx3);
11981 $ymin = min($ymin, $cy1, $cy2, $cy3);
11982 $xmax = max($xmax, $cx1, $cx2, $cx3);
11983 $ymax = max($ymax, $cy1, $cy2, $cy3);
11984 // move to next point
11985 $px1 = $px2;
11986 $py1 = $py2;
11987 $qx1 = $qx2;
11988 $qy1 = $qy2;
11989 }
11990 if ($pie) {
11991 $this->_outLine($xc, $yc);
11992 // get bounding box coordinates
11993 $xmin = min($xmin, $xc);
11994 $ymin = min($ymin, $yc);
11995 $xmax = max($xmax, $xc);
11996 $ymax = max($ymax, $yc);
11997 }
11998 return array($xmin, $ymin, $xmax, $ymax);
11999 }
12000
12001 /**
12002 * Draws a circle.
12003 * A circle is formed from n Bezier curves.
12004 * @param float $x0 Abscissa of center point.
12005 * @param float $y0 Ordinate of center point.
12006 * @param float $r Radius.
12007 * @param float $angstr Angle start of draw line. Default value: 0.
12008 * @param float $angend Angle finish of draw line. Default value: 360.
12009 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12010 * @param array $line_style Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
12011 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12012 * @param integer $nc Number of curves used to draw a 90 degrees portion of circle.
12013 * @public
12014 * @since 2.1.000 (2008-01-08)
12015 */
12016 public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
12017 $this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
12018 }
12019
12020 /**
12021 * Draws a polygonal line
12022 * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
12023 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12024 * @param array $line_style Line style of polygon. Array with keys among the following:
12025 * <ul>
12026 * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
12027 * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
12028 * </ul>
12029 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
12030 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
12031 * @since 4.8.003 (2009-09-15)
12032 * @public
12033 */
12034 public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
12035 $this->Polygon($p, $style, $line_style, $fill_color, false);
12036 }
12037
12038 /**
12039 * Draws a polygon.
12040 * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
12041 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12042 * @param array $line_style Line style of polygon. Array with keys among the following:
12043 * <ul>
12044 * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
12045 * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
12046 * </ul>
12047 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
12048 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
12049 * @param boolean $closed if true the polygon is closes, otherwise will remain open
12050 * @public
12051 * @since 2.1.000 (2008-01-08)
12052 */
12053 public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
12054 if ($this->state != 2) {
12055 return;
12056 }
12057 $nc = count($p); // number of coordinates
12058 $np = $nc / 2; // number of points
12059 if ($closed) {
12060 // close polygon by adding the first 2 points at the end (one line)
12061 for ($i = 0; $i < 4; ++$i) {
12062 $p[$nc + $i] = $p[$i];
12063 }
12064 // copy style for the last added line
12065 if (isset($line_style[0])) {
12066 $line_style[$np] = $line_style[0];
12067 }
12068 $nc += 4;
12069 }
12070 if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
12071 $this->setFillColorArray($fill_color);
12072 }
12073 $op = TCPDF_STATIC::getPathPaintOperator($style);
12074 if ($op == 'f') {
12075 $line_style = array();
12076 }
12077 $draw = true;
12078 if ($line_style) {
12079 if (isset($line_style['all'])) {
12080 $this->setLineStyle($line_style['all']);
12081 } else {
12082 $draw = false;
12083 if ($op == 'B') {
12084 // draw fill
12085 $op = 'f';
12086 $this->_outPoint($p[0], $p[1]);
12087 for ($i = 2; $i < $nc; $i = $i + 2) {
12088 $this->_outLine($p[$i], $p[$i + 1]);
12089 }
12090 $this->_out($op);
12091 }
12092 // draw outline
12093 $this->_outPoint($p[0], $p[1]);
12094 for ($i = 2; $i < $nc; $i = $i + 2) {
12095 $line_num = ($i / 2) - 1;
12096 if (isset($line_style[$line_num])) {
12097 if ($line_style[$line_num] != 0) {
12098 if (is_array($line_style[$line_num])) {
12099 $this->_out('S');
12100 $this->setLineStyle($line_style[$line_num]);
12101 $this->_outPoint($p[$i - 2], $p[$i - 1]);
12102 $this->_outLine($p[$i], $p[$i + 1]);
12103 $this->_out('S');
12104 $this->_outPoint($p[$i], $p[$i + 1]);
12105 } else {
12106 $this->_outLine($p[$i], $p[$i + 1]);
12107 }
12108 }
12109 } else {
12110 $this->_outLine($p[$i], $p[$i + 1]);
12111 }
12112 }
12113 $this->_out($op);
12114 }
12115 }
12116 if ($draw) {
12117 $this->_outPoint($p[0], $p[1]);
12118 for ($i = 2; $i < $nc; $i = $i + 2) {
12119 $this->_outLine($p[$i], $p[$i + 1]);
12120 }
12121 $this->_out($op);
12122 }
12123 }
12124
12125 /**
12126 * Draws a regular polygon.
12127 * @param float $x0 Abscissa of center point.
12128 * @param float $y0 Ordinate of center point.
12129 * @param float $r Radius of inscribed circle.
12130 * @param integer $ns Number of sides.
12131 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
12132 * @param boolean $draw_circle Draw inscribed circle or not. Default value: false.
12133 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12134 * @param array $line_style Line style of polygon sides. Array with keys among the following:
12135 * <ul>
12136 * <li>all: Line style of all sides. Array like for SetLineStyle().</li>
12137 * <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
12138 * </ul>
12139 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12140 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12141 * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are:
12142 * <ul>
12143 * <li>D or empty string: Draw (default).</li>
12144 * <li>F: Fill.</li>
12145 * <li>DF or FD: Draw and fill.</li>
12146 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12147 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12148 * </ul>
12149 * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12150 * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12151 * @public
12152 * @since 2.1.000 (2008-01-08)
12153 */
12154 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()) {
12155 if (3 > $ns) {
12156 $ns = 3;
12157 }
12158 if ($draw_circle) {
12159 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12160 }
12161 $p = array();
12162 for ($i = 0; $i < $ns; ++$i) {
12163 $a = $angle + ($i * 360 / $ns);
12164 $a_rad = deg2rad((float) $a);
12165 $p[] = $x0 + ($r * sin($a_rad));
12166 $p[] = $y0 + ($r * cos($a_rad));
12167 }
12168 $this->Polygon($p, $style, $line_style, $fill_color);
12169 }
12170
12171 /**
12172 * Draws a star polygon
12173 * @param float $x0 Abscissa of center point.
12174 * @param float $y0 Ordinate of center point.
12175 * @param float $r Radius of inscribed circle.
12176 * @param integer $nv Number of vertices.
12177 * @param integer $ng Number of gap (if ($ng % $nv = 1) then is a regular polygon).
12178 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
12179 * @param boolean $draw_circle Draw inscribed circle or not. Default value is false.
12180 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12181 * @param array $line_style Line style of polygon sides. Array with keys among the following:
12182 * <ul>
12183 * <li>all: Line style of all sides. Array like for
12184 * SetLineStyle().</li>
12185 * <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
12186 * </ul>
12187 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12188 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12189 * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are:
12190 * <ul>
12191 * <li>D or empty string: Draw (default).</li>
12192 * <li>F: Fill.</li>
12193 * <li>DF or FD: Draw and fill.</li>
12194 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12195 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12196 * </ul>
12197 * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12198 * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12199 * @public
12200 * @since 2.1.000 (2008-01-08)
12201 */
12202 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()) {
12203 if ($nv < 2) {
12204 $nv = 2;
12205 }
12206 if ($draw_circle) {
12207 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12208 }
12209 $p2 = array();
12210 $visited = array();
12211 for ($i = 0; $i < $nv; ++$i) {
12212 $a = $angle + ($i * 360 / $nv);
12213 $a_rad = deg2rad((float) $a);
12214 $p2[] = $x0 + ($r * sin($a_rad));
12215 $p2[] = $y0 + ($r * cos($a_rad));
12216 $visited[] = false;
12217 }
12218 $p = array();
12219 $i = 0;
12220 do {
12221 $p[] = $p2[$i * 2];
12222 $p[] = $p2[($i * 2) + 1];
12223 $visited[$i] = true;
12224 $i += $ng;
12225 $i %= $nv;
12226 } while (!$visited[$i]);
12227 $this->Polygon($p, $style, $line_style, $fill_color);
12228 }
12229
12230 /**
12231 * Draws a rounded rectangle.
12232 * @param float $x Abscissa of upper-left corner.
12233 * @param float $y Ordinate of upper-left corner.
12234 * @param float $w Width.
12235 * @param float $h Height.
12236 * @param float $r the radius of the circle used to round off the corners of the rectangle.
12237 * @param string $round_corner Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
12238 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12239 * @param array $border_style Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12240 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
12241 * @public
12242 * @since 2.1.000 (2008-01-08)
12243 */
12244 public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12245 $this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
12246 }
12247
12248 /**
12249 * Draws a rounded rectangle.
12250 * @param float $x Abscissa of upper-left corner.
12251 * @param float $y Ordinate of upper-left corner.
12252 * @param float $w Width.
12253 * @param float $h Height.
12254 * @param float $rx the x-axis radius of the ellipse used to round off the corners of the rectangle.
12255 * @param float $ry the y-axis radius of the ellipse used to round off the corners of the rectangle.
12256 * @param string $round_corner Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
12257 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12258 * @param array $border_style Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12259 * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
12260 * @public
12261 * @since 4.9.019 (2010-04-22)
12262 */
12263 public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12264 if ($this->state != 2) {
12265 return;
12266 }
12267 if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
12268 // Not rounded
12269 $this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
12270 return;
12271 }
12272 // Rounded
12273 if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
12274 $this->setFillColorArray($fill_color);
12275 }
12276 $op = TCPDF_STATIC::getPathPaintOperator($style);
12277 if ($op == 'f') {
12278 $border_style = array();
12279 }
12280 if ($border_style) {
12281 $this->setLineStyle($border_style);
12282 }
12283 $MyArc = 4 / 3 * (sqrt(2) - 1);
12284 $this->_outPoint($x + $rx, $y);
12285 $xc = $x + $w - $rx;
12286 $yc = $y + $ry;
12287 $this->_outLine($xc, $y);
12288 if ($round_corner[0]) {
12289 $this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
12290 } else {
12291 $this->_outLine($x + $w, $y);
12292 }
12293 $xc = $x + $w - $rx;
12294 $yc = $y + $h - $ry;
12295 $this->_outLine($x + $w, $yc);
12296 if ($round_corner[1]) {
12297 $this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
12298 } else {
12299 $this->_outLine($x + $w, $y + $h);
12300 }
12301 $xc = $x + $rx;
12302 $yc = $y + $h - $ry;
12303 $this->_outLine($xc, $y + $h);
12304 if ($round_corner[2]) {
12305 $this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
12306 } else {
12307 $this->_outLine($x, $y + $h);
12308 }
12309 $xc = $x + $rx;
12310 $yc = $y + $ry;
12311 $this->_outLine($x, $yc);
12312 if ($round_corner[3]) {
12313 $this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
12314 } else {
12315 $this->_outLine($x, $y);
12316 $this->_outLine($x + $rx, $y);
12317 }
12318 $this->_out($op);
12319 }
12320
12321 /**
12322 * Draws a grahic arrow.
12323 * @param float $x0 Abscissa of first point.
12324 * @param float $y0 Ordinate of first point.
12325 * @param float $x1 Abscissa of second point.
12326 * @param float $y1 Ordinate of second point.
12327 * @param int $head_style (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
12328 * @param float $arm_size length of arrowhead arms
12329 * @param int $arm_angle angle between an arm and the shaft
12330 * @author Piotr Galecki,2026 Nicola Asuni, Andy Meier
12331 * @since 4.6.018 (2009-07-10)
12332 */
12333 public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
12334 // getting arrow direction angle
12335 // 0 deg angle is when both arms go along X axis. angle grows clockwise.
12336 $dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
12337 if ($dir_angle < 0) {
12338 $dir_angle += (2 * M_PI);
12339 }
12340 $arm_angle = deg2rad($arm_angle);
12341 $sx1 = $x1;
12342 $sy1 = $y1;
12343 if ($head_style > 0) {
12344 // calculate the stopping point for the arrow shaft
12345 $sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
12346 $sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
12347 }
12348 // main arrow line / shaft
12349 $this->Line($x0, $y0, $sx1, $sy1);
12350 // left arrowhead arm tip
12351 $x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
12352 $y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
12353 // right arrowhead arm tip
12354 $x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
12355 $y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
12356 $mode = 'D';
12357 $style = array();
12358 switch ($head_style) {
12359 case 0: {
12360 // draw only arrowhead arms
12361 $mode = 'D';
12362 $style = array(1, 1, 0);
12363 break;
12364 }
12365 case 1: {
12366 // draw closed arrowhead, but no fill
12367 $mode = 'D';
12368 break;
12369 }
12370 case 2: {
12371 // closed and filled arrowhead
12372 $mode = 'DF';
12373 break;
12374 }
12375 case 3: {
12376 // filled arrowhead
12377 $mode = 'F';
12378 break;
12379 }
12380 }
12381 $this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
12382 }
12383
12384 // END GRAPHIC FUNCTIONS SECTION -----------------------
12385
12386 /**
12387 * Add a Named Destination.
12388 * NOTE: destination names are unique, so only last entry will be saved.
12389 * @param string $name Destination name.
12390 * @param float $y Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
12391 * @param int|string $page Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12392 * @param float $x X position in user units of the destiantion on the selected page (default = -1 = current position;).
12393 * @return string|false Stripped named destination identifier or false in case of error.
12394 * @public
12395 * @author Christian Deligant,2026 Nicola Asuni
12396 * @since 5.9.097 (2011-06-23)
12397 */
12398 public function setDestination($name, $y=-1, $page='', $x=-1) {
12399 // remove unsupported characters
12400 $name = TCPDF_STATIC::encodeNameObject($name);
12401 if (TCPDF_STATIC::empty_string($name)) {
12402 return false;
12403 }
12404 if ($y == -1) {
12405 $y = $this->GetY();
12406 } elseif ($y < 0) {
12407 $y = 0;
12408 } elseif ($y > $this->h) {
12409 $y = $this->h;
12410 }
12411 if ($x == -1) {
12412 $x = $this->GetX();
12413 } elseif ($x < 0) {
12414 $x = 0;
12415 } elseif ($x > $this->w) {
12416 $x = $this->w;
12417 }
12418 $fixed = false;
12419 if (!empty($page) AND (substr($page, 0, 1) == '*')) {
12420 $page = intval(substr($page, 1));
12421 // this page number will not be changed when moving/add/deleting pages
12422 $fixed = true;
12423 }
12424 if (empty($page)) {
12425 $page = $this->PageNo();
12426 if (empty($page)) {
12427 return;
12428 }
12429 }
12430 $this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
12431 return $name;
12432 }
12433
12434 /**
12435 * Return the Named Destination array.
12436 * @return array Named Destination array.
12437 * @public
12438 * @author Nicola Asuni
12439 * @since 5.9.097 (2011-06-23)
12440 */
12441 public function getDestination() {
12442 return $this->dests;
12443 }
12444
12445 /**
12446 * Insert Named Destinations.
12447 * @protected
12448 * @author Johannes G\FCntert,2026 Nicola Asuni
12449 * @since 5.9.098 (2011-06-23)
12450 */
12451 protected function _putdests() {
12452 if (empty($this->dests)) {
12453 return;
12454 }
12455 $this->n_dests = $this->_newobj();
12456 $out = ' <<';
12457 foreach($this->dests as $name => $o) {
12458 $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)));
12459 }
12460 $out .= ' >>';
12461 $out .= "\n".'endobj';
12462 $this->_out($out);
12463 }
12464
12465 /**
12466 * Adds a bookmark - alias for Bookmark().
12467 * @param string $txt Bookmark description.
12468 * @param int $level Bookmark level (minimum value is 0).
12469 * @param float $y Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12470 * @param int|string $page Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12471 * @param string $style Font style: B = Bold, I = Italic, BI = Bold + Italic.
12472 * @param array $color RGB color array (values from 0 to 255).
12473 * @param float $x X position in user units of the bookmark on the selected page (default = -1 = current position;).
12474 * @param mixed $link URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
12475 * @public
12476 */
12477 public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12478 $this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
12479 }
12480
12481 /**
12482 * Adds a bookmark.
12483 * @param string $txt Bookmark description.
12484 * @param int $level Bookmark level (minimum value is 0).
12485 * @param float $y Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12486 * @param int|string $page Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12487 * @param string $style Font style: B = Bold, I = Italic, BI = Bold + Italic.
12488 * @param array $color RGB color array (values from 0 to 255).
12489 * @param float $x X position in user units of the bookmark on the selected page (default = -1 = current position;).
12490 * @param mixed $link URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
12491 * @public
12492 * @since 2.1.002 (2008-02-12)
12493 */
12494 public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12495 if ($level < 0) {
12496 $level = 0;
12497 }
12498 if (isset($this->outlines[0])) {
12499 $lastoutline = end($this->outlines);
12500 $maxlevel = $lastoutline['l'] + 1;
12501 } else {
12502 $maxlevel = 0;
12503 }
12504 if ($level > $maxlevel) {
12505 $level = $maxlevel;
12506 }
12507 if ($y == -1) {
12508 $y = $this->GetY();
12509 } elseif ($y < 0) {
12510 $y = 0;
12511 } elseif ($y > $this->h) {
12512 $y = $this->h;
12513 }
12514 if ($x == -1) {
12515 $x = $this->GetX();
12516 } elseif ($x < 0) {
12517 $x = 0;
12518 } elseif ($x > $this->w) {
12519 $x = $this->w;
12520 }
12521 $fixed = false;
12522 $pageAsString = (string) $page;
12523 if ($pageAsString && $pageAsString[0] == '*') {
12524 $page = intval(substr($page, 1));
12525 // this page number will not be changed when moving/add/deleting pages
12526 $fixed = true;
12527 }
12528 if (empty($page)) {
12529 $page = $this->PageNo();
12530 if (empty($page)) {
12531 return;
12532 }
12533 }
12534 $this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
12535 }
12536
12537 /**
12538 * Sort bookmarks for page and key.
12539 * @protected
12540 * @since 5.9.119 (2011-09-19)
12541 */
12542 protected function sortBookmarks() {
12543 // get sorting columns
12544 $outline_p = array();
12545 $outline_y = array();
12546 foreach ($this->outlines as $key => $row) {
12547 $outline_p[$key] = $row['p'];
12548 $outline_k[$key] = $key;
12549 }
12550 // sort outlines by page and original position
12551 array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
12552 }
12553
12554 /**
12555 * Create a bookmark PDF string.
12556 * @protected
12557 * @author Olivier Plathey,2026 Nicola Asuni
12558 * @since 2.1.002 (2008-02-12)
12559 */
12560 protected function _putbookmarks() {
12561 $nb = count($this->outlines);
12562 if ($nb == 0) {
12563 return;
12564 }
12565 // sort bookmarks
12566 $this->sortBookmarks();
12567 $lru = array();
12568 $level = 0;
12569 foreach ($this->outlines as $i => $o) {
12570 if ($o['l'] > 0) {
12571 $parent = $lru[($o['l'] - 1)];
12572 //Set parent and last pointers
12573 $this->outlines[$i]['parent'] = $parent;
12574 $this->outlines[$parent]['last'] = $i;
12575 if ($o['l'] > $level) {
12576 //Level increasing: set first pointer
12577 $this->outlines[$parent]['first'] = $i;
12578 }
12579 } else {
12580 $this->outlines[$i]['parent'] = $nb;
12581 }
12582 if (($o['l'] <= $level) AND ($i > 0)) {
12583 //Set prev and next pointers
12584 $prev = $lru[$o['l']];
12585 $this->outlines[$prev]['next'] = $i;
12586 $this->outlines[$i]['prev'] = $prev;
12587 }
12588 $lru[$o['l']] = $i;
12589 $level = $o['l'];
12590 }
12591 //Outline items
12592 $n = $this->n + 1;
12593 $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';
12594 foreach ($this->outlines as $i => $o) {
12595 $oid = $this->_newobj();
12596 // covert HTML title to string
12597 $title = preg_replace($nltags, "\n", $o['t']);
12598 $title = preg_replace("/[\r]+/si", '', $title);
12599 $title = preg_replace("/[\n]+/si", "\n", $title);
12600 $title = strip_tags($title);
12601 $title = $this->stringTrim($title);
12602 $out = '<</Title '.$this->_textstring($title, $oid);
12603 $out .= ' /Parent '.($n + $o['parent']).' 0 R';
12604 if (isset($o['prev'])) {
12605 $out .= ' /Prev '.($n + $o['prev']).' 0 R';
12606 }
12607 if (isset($o['next'])) {
12608 $out .= ' /Next '.($n + $o['next']).' 0 R';
12609 }
12610 if (isset($o['first'])) {
12611 $out .= ' /First '.($n + $o['first']).' 0 R';
12612 }
12613 if (isset($o['last'])) {
12614 $out .= ' /Last '.($n + $o['last']).' 0 R';
12615 }
12616 if (isset($o['u']) AND !empty($o['u'])) {
12617 // link
12618 if (is_string($o['u'])) {
12619 if ($o['u'][0] == '#') {
12620 // internal destination
12621 $out .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($o['u'], 1));
12622 } elseif ($o['u'][0] == '%') {
12623 // embedded PDF file
12624 $filename = basename(substr($o['u'], 1));
12625 $out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
12626 } elseif ($o['u'][0] == '*') {
12627 // embedded generic file
12628 $filename = basename(substr($o['u'], 1));
12629 $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});';
12630 $out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
12631 } else {
12632 // external URI link
12633 $out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
12634 }
12635 } elseif (isset($this->links[$o['u']])) {
12636 // internal link ID
12637 $l = $this->links[$o['u']];
12638 if (isset($this->page_obj_id[($l['p'])])) {
12639 $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)));
12640 }
12641 }
12642 } elseif (isset($this->page_obj_id[($o['p'])])) {
12643 // link to a page
12644 $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)));
12645 }
12646 // set font style
12647 $style = 0;
12648 if (!empty($o['s'])) {
12649 // bold
12650 if (strpos($o['s'], 'B') !== false) {
12651 $style |= 2;
12652 }
12653 // oblique
12654 if (strpos($o['s'], 'I') !== false) {
12655 $style |= 1;
12656 }
12657 }
12658 $out .= sprintf(' /F %d', $style);
12659 // set bookmark color
12660 if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
12661 $color = array_values($o['c']);
12662 $out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
12663 } else {
12664 // black
12665 $out .= ' /C [0.0 0.0 0.0]';
12666 }
12667 $out .= ' /Count 0'; // normally closed item
12668 $out .= ' >>';
12669 $out .= "\n".'endobj';
12670 $this->_out($out);
12671 }
12672 //Outline root
12673 $this->OutlineRoot = $this->_newobj();
12674 $this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
12675 }
12676
12677 // --- JAVASCRIPT ------------------------------------------------------
12678
12679 /**
12680 * Adds a javascript
12681 * @param string $script Javascript code
12682 * @public
12683 * @author Johannes G\FCntert,2026 Nicola Asuni
12684 * @since 2.1.002 (2008-02-12)
12685 */
12686 public function IncludeJS($script) {
12687 $this->javascript .= $script;
12688 }
12689
12690 /**
12691 * Adds a javascript object and return object ID
12692 * @param string $script Javascript code
12693 * @param boolean $onload if true executes this object when opening the document
12694 * @return int internal object ID
12695 * @public
12696 * @author Nicola Asuni
12697 * @since 4.8.000 (2009-09-07)
12698 */
12699 public function addJavascriptObject($script, $onload=false) {
12700 if ($this->pdfa_mode) {
12701 // javascript is not allowed in PDF/A mode
12702 return false;
12703 }
12704 ++$this->n;
12705 $this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
12706 return $this->n;
12707 }
12708
12709 /**
12710 * Create a javascript PDF string.
12711 * @protected
12712 * @author Johannes G\FCntert,2026 Nicola Asuni
12713 * @since 2.1.002 (2008-02-12)
12714 */
12715 protected function _putjavascript() {
12716 if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
12717 return;
12718 }
12719 if (strpos($this->javascript, 'this.addField') > 0) {
12720 if (!$this->ur['enabled']) {
12721 //$this->setUserRights();
12722 }
12723 // the following two lines are used to avoid form fields duplication after saving
12724 // The addField method only works when releasing user rights (UR3)
12725 $jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
12726 $jsb = "getField('tcpdfdocsaved').value='saved';";
12727 $this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
12728 }
12729 // name tree for javascript
12730 $this->n_js = '<< /Names [';
12731 if (!empty($this->javascript)) {
12732 $this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
12733 }
12734 if (!empty($this->js_objects)) {
12735 foreach ($this->js_objects as $key => $val) {
12736 if ($val['onload']) {
12737 $this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
12738 }
12739 }
12740 }
12741 $this->n_js .= ' ] >>';
12742 // default Javascript object
12743 if (!empty($this->javascript)) {
12744 $obj_id = $this->_newobj();
12745 $out = '<< /S /JavaScript';
12746 $out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
12747 $out .= ' >>';
12748 $out .= "\n".'endobj';
12749 $this->_out($out);
12750 }
12751 // additional Javascript objects
12752 if (!empty($this->js_objects)) {
12753 foreach ($this->js_objects as $key => $val) {
12754 $out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
12755 $this->_out($out);
12756 }
12757 }
12758 }
12759
12760 /**
12761 * Adds a javascript form field.
12762 * @param string $type field type
12763 * @param string $name field name
12764 * @param int $x horizontal position
12765 * @param int $y vertical position
12766 * @param int $w width
12767 * @param int $h height
12768 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12769 * @protected
12770 * @author Denis Van Nuffelen,2026 Nicola Asuni
12771 * @since 2.1.002 (2008-02-12)
12772 */
12773 protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
12774 if ($this->rtl) {
12775 $x = $x - $w;
12776 }
12777 // the followind avoid fields duplication after saving the document
12778 $this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
12779 $k = $this->k;
12780 $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";
12781 $this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
12782 foreach($prop as $key => $val) {
12783 if (strcmp(substr($key, -5), 'Color') == 0) {
12784 $val = TCPDF_COLORS::_JScolor($val);
12785 } else {
12786 $val = "'".$val."'";
12787 }
12788 $this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
12789 }
12790 if ($this->rtl) {
12791 $this->x -= $w;
12792 } else {
12793 $this->x += $w;
12794 }
12795 $this->javascript .= '}';
12796 }
12797
12798 // --- FORM FIELDS -----------------------------------------------------
12799
12800
12801
12802 /**
12803 * Set default properties for form fields.
12804 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12805 * @public
12806 * @author Nicola Asuni
12807 * @since 4.8.000 (2009-09-06)
12808 */
12809 public function setFormDefaultProp($prop=array()) {
12810 $this->default_form_prop = $prop;
12811 }
12812
12813 /**
12814 * Return the default properties for form fields.
12815 * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12816 * @public
12817 * @author Nicola Asuni
12818 * @since 4.8.000 (2009-09-06)
12819 */
12820 public function getFormDefaultProp() {
12821 return $this->default_form_prop;
12822 }
12823
12824 /**
12825 * Creates a text field
12826 * @param string $name field name
12827 * @param float $w Width of the rectangle
12828 * @param float $h Height of the rectangle
12829 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12830 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
12831 * @param float|null $x Abscissa of the upper-left corner of the rectangle
12832 * @param float|null $y Ordinate of the upper-left corner of the rectangle
12833 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12834 * @public
12835 * @author Nicola Asuni
12836 * @since 4.8.000 (2009-09-07)
12837 */
12838 public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
12839 if (TCPDF_STATIC::empty_string($x)) {
12840 $x = $this->x;
12841 }
12842 if (TCPDF_STATIC::empty_string($y)) {
12843 $y = $this->y;
12844 }
12845 // check page for no-write regions and adapt page margins if necessary
12846 list($x, $y) = $this->checkPageRegions($h, $x, $y);
12847 if ($js) {
12848 $this->_addfield('text', $name, $x, $y, $w, $h, $prop);
12849 return;
12850 }
12851 // get default style
12852 $prop = array_merge($this->getFormDefaultProp(), $prop);
12853 // get annotation data
12854 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12855 // set default appearance stream
12856 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12857 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12858 $popt['da'] = $fontstyle;
12859 // build appearance stream
12860 $popt['ap'] = array();
12861 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12862 $text = '';
12863 if (isset($prop['value']) AND !empty($prop['value'])) {
12864 $text = $prop['value'];
12865 } elseif (isset($opt['v']) AND !empty($opt['v'])) {
12866 $text = $opt['v'];
12867 }
12868 $tmpid = $this->startTemplate($w, $h, false);
12869 $align = '';
12870 if (isset($popt['q'])) {
12871 switch ($popt['q']) {
12872 case 0: {
12873 $align = 'L';
12874 break;
12875 }
12876 case 1: {
12877 $align = 'C';
12878 break;
12879 }
12880 case 2: {
12881 $align = 'R';
12882 break;
12883 }
12884 default: {
12885 $align = '';
12886 break;
12887 }
12888 }
12889 }
12890 $this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12891 $this->endTemplate();
12892 --$this->n;
12893 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12894 unset($this->xobjects[$tmpid]);
12895 $popt['ap']['n'] .= 'Q EMC';
12896 // merge options
12897 $opt = array_merge($popt, $opt);
12898 // remove some conflicting options
12899 unset($opt['bs']);
12900 // set remaining annotation data
12901 $opt['Subtype'] = 'Widget';
12902 $opt['ft'] = 'Tx';
12903 $opt['t'] = $name;
12904 // Additional annotation's parameters (check _putannotsobj() method):
12905 //$opt['f']
12906 //$opt['as']
12907 //$opt['bs']
12908 //$opt['be']
12909 //$opt['c']
12910 //$opt['border']
12911 //$opt['h']
12912 //$opt['mk'];
12913 //$opt['mk']['r']
12914 //$opt['mk']['bc'];
12915 //$opt['mk']['bg'];
12916 unset($opt['mk']['ca']);
12917 unset($opt['mk']['rc']);
12918 unset($opt['mk']['ac']);
12919 unset($opt['mk']['i']);
12920 unset($opt['mk']['ri']);
12921 unset($opt['mk']['ix']);
12922 unset($opt['mk']['if']);
12923 //$opt['mk']['if']['sw'];
12924 //$opt['mk']['if']['s'];
12925 //$opt['mk']['if']['a'];
12926 //$opt['mk']['if']['fb'];
12927 unset($opt['mk']['tp']);
12928 //$opt['tu']
12929 //$opt['tm']
12930 //$opt['ff']
12931 //$opt['v']
12932 //$opt['dv']
12933 //$opt['a']
12934 //$opt['aa']
12935 //$opt['q']
12936 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12937 if ($this->rtl) {
12938 $this->x -= $w;
12939 } else {
12940 $this->x += $w;
12941 }
12942 }
12943
12944 /**
12945 * Creates a RadioButton field.
12946 * @param string $name Field name.
12947 * @param int $w Width of the radio button.
12948 * @param array $prop Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12949 * @param array $opt Annotation parameters. Possible values are described on official PDF32000_2008 reference.
12950 * @param string $onvalue Value to be returned if selected.
12951 * @param boolean $checked Define the initial state.
12952 * @param float|null $x Abscissa of the upper-left corner of the rectangle
12953 * @param float|null $y Ordinate of the upper-left corner of the rectangle
12954 * @param boolean $js If true put the field using JavaScript (requires Acrobat Writer to be rendered).
12955 * @public
12956 * @author Nicola Asuni
12957 * @since 4.8.000 (2009-09-07)
12958 */
12959 public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x=null, $y=null, $js=false) {
12960 if (TCPDF_STATIC::empty_string($x)) {
12961 $x = $this->x;
12962 }
12963 if (TCPDF_STATIC::empty_string($y)) {
12964 $y = $this->y;
12965 }
12966 // check page for no-write regions and adapt page margins if necessary
12967 list($x, $y) = $this->checkPageRegions($w, $x, $y);
12968 if ($js) {
12969 $this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
12970 return;
12971 }
12972 if (TCPDF_STATIC::empty_string($onvalue)) {
12973 $onvalue = 'On';
12974 }
12975 if ($checked) {
12976 $defval = $onvalue;
12977 } else {
12978 $defval = 'Off';
12979 }
12980 // set font
12981 $font = 'zapfdingbats';
12982 if ($this->pdfa_mode) {
12983 // all fonts must be embedded
12984 $font = 'pdfa'.$font;
12985 }
12986 $this->AddFont($font);
12987 $tmpfont = $this->getFontBuffer($font);
12988 // set data for parent group
12989 if (!isset($this->radiobutton_groups[$this->page])) {
12990 $this->radiobutton_groups[$this->page] = array();
12991 }
12992 if (!isset($this->radiobutton_groups[$this->page][$name])) {
12993 $this->radiobutton_groups[$this->page][$name] = array();
12994 ++$this->n;
12995 $this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
12996 $this->radio_groups[] = $this->n;
12997 }
12998 $kid = ($this->n + 1);
12999 // save object ID to be added on Kids entry on parent object
13000 $this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
13001 // get default style
13002 $prop = array_merge($this->getFormDefaultProp(), $prop);
13003 $prop['NoToggleToOff'] = 'true';
13004 $prop['Radio'] = 'true';
13005 $prop['borderStyle'] = 'inset';
13006 // get annotation data
13007 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13008 // set additional default options
13009 $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
13010 $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
13011 $popt['da'] = $fontstyle;
13012 // build appearance stream
13013 $popt['ap'] = array();
13014 $popt['ap']['n'] = array();
13015 $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
13016 $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
13017 $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);
13018 $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);
13019 if (!isset($popt['mk'])) {
13020 $popt['mk'] = array();
13021 }
13022 $popt['mk']['ca'] = '(l)';
13023 // merge options
13024 $opt = array_merge($popt, $opt);
13025 // set remaining annotation data
13026 $opt['Subtype'] = 'Widget';
13027 $opt['ft'] = 'Btn';
13028 if ($checked) {
13029 $opt['v'] = array('/'.$onvalue);
13030 $opt['as'] = $onvalue;
13031 } else {
13032 $opt['as'] = 'Off';
13033 }
13034 // store readonly flag
13035 if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
13036 $this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
13037 }
13038 $this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
13039 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13040 if ($this->rtl) {
13041 $this->x -= $w;
13042 } else {
13043 $this->x += $w;
13044 }
13045 }
13046
13047 /**
13048 * Creates a List-box field
13049 * @param string $name field name
13050 * @param int $w width
13051 * @param int $h height
13052 * @param array $values array containing the list of values.
13053 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13054 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13055 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13056 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13057 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13058 * @public
13059 * @author Nicola Asuni
13060 * @since 4.8.000 (2009-09-07)
13061 */
13062 public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
13063 if (TCPDF_STATIC::empty_string($x)) {
13064 $x = $this->x;
13065 }
13066 if (TCPDF_STATIC::empty_string($y)) {
13067 $y = $this->y;
13068 }
13069 // check page for no-write regions and adapt page margins if necessary
13070 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13071 if ($js) {
13072 $this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
13073 $s = '';
13074 foreach ($values as $value) {
13075 if (is_array($value)) {
13076 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13077 } else {
13078 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13079 }
13080 }
13081 $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13082 return;
13083 }
13084 // get default style
13085 $prop = array_merge($this->getFormDefaultProp(), $prop);
13086 // get annotation data
13087 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13088 // set additional default values
13089 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13090 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13091 $popt['da'] = $fontstyle;
13092 // build appearance stream
13093 $popt['ap'] = array();
13094 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13095 $text = '';
13096 foreach($values as $item) {
13097 if (is_array($item)) {
13098 $text .= $item[1]."\n";
13099 } else {
13100 $text .= $item."\n";
13101 }
13102 }
13103 $tmpid = $this->startTemplate($w, $h, false);
13104 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13105 $this->endTemplate();
13106 --$this->n;
13107 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13108 unset($this->xobjects[$tmpid]);
13109 $popt['ap']['n'] .= 'Q EMC';
13110 // merge options
13111 $opt = array_merge($popt, $opt);
13112 // set remaining annotation data
13113 $opt['Subtype'] = 'Widget';
13114 $opt['ft'] = 'Ch';
13115 $opt['t'] = $name;
13116 $opt['opt'] = $values;
13117 unset($opt['mk']['ca']);
13118 unset($opt['mk']['rc']);
13119 unset($opt['mk']['ac']);
13120 unset($opt['mk']['i']);
13121 unset($opt['mk']['ri']);
13122 unset($opt['mk']['ix']);
13123 unset($opt['mk']['if']);
13124 unset($opt['mk']['tp']);
13125 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13126 if ($this->rtl) {
13127 $this->x -= $w;
13128 } else {
13129 $this->x += $w;
13130 }
13131 }
13132
13133 /**
13134 * Creates a Combo-box field
13135 * @param string $name field name
13136 * @param int $w width
13137 * @param int $h height
13138 * @param array $values array containing the list of values.
13139 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13140 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13141 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13142 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13143 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13144 * @public
13145 * @author Nicola Asuni
13146 * @since 4.8.000 (2009-09-07)
13147 */
13148 public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
13149 if (TCPDF_STATIC::empty_string($x)) {
13150 $x = $this->x;
13151 }
13152 if (TCPDF_STATIC::empty_string($y)) {
13153 $y = $this->y;
13154 }
13155 // check page for no-write regions and adapt page margins if necessary
13156 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13157 if ($js) {
13158 $this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
13159 $s = '';
13160 foreach ($values as $value) {
13161 if (is_array($value)) {
13162 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13163 } else {
13164 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13165 }
13166 }
13167 $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13168 return;
13169 }
13170 // get default style
13171 $prop = array_merge($this->getFormDefaultProp(), $prop);
13172 $prop['Combo'] = true;
13173 // get annotation data
13174 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13175 // set additional default options
13176 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13177 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13178 $popt['da'] = $fontstyle;
13179 // build appearance stream
13180 $popt['ap'] = array();
13181 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13182 $text = '';
13183 foreach($values as $item) {
13184 if (is_array($item)) {
13185 $text .= $item[1]."\n";
13186 } else {
13187 $text .= $item."\n";
13188 }
13189 }
13190 $tmpid = $this->startTemplate($w, $h, false);
13191 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13192 $this->endTemplate();
13193 --$this->n;
13194 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13195 unset($this->xobjects[$tmpid]);
13196 $popt['ap']['n'] .= 'Q EMC';
13197 // merge options
13198 $opt = array_merge($popt, $opt);
13199 // set remaining annotation data
13200 $opt['Subtype'] = 'Widget';
13201 $opt['ft'] = 'Ch';
13202 $opt['t'] = $name;
13203 $opt['opt'] = $values;
13204 unset($opt['mk']['ca']);
13205 unset($opt['mk']['rc']);
13206 unset($opt['mk']['ac']);
13207 unset($opt['mk']['i']);
13208 unset($opt['mk']['ri']);
13209 unset($opt['mk']['ix']);
13210 unset($opt['mk']['if']);
13211 unset($opt['mk']['tp']);
13212 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13213 if ($this->rtl) {
13214 $this->x -= $w;
13215 } else {
13216 $this->x += $w;
13217 }
13218 }
13219
13220 /**
13221 * Creates a CheckBox field
13222 * @param string $name field name
13223 * @param int $w width
13224 * @param boolean $checked define the initial state.
13225 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13226 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13227 * @param string $onvalue value to be returned if selected.
13228 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13229 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13230 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13231 * @public
13232 * @author Nicola Asuni
13233 * @since 4.8.000 (2009-09-07)
13234 */
13235 public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x=null, $y=null, $js=false) {
13236 if (TCPDF_STATIC::empty_string($x)) {
13237 $x = $this->x;
13238 }
13239 if (TCPDF_STATIC::empty_string($y)) {
13240 $y = $this->y;
13241 }
13242 // check page for no-write regions and adapt page margins if necessary
13243 list($x, $y) = $this->checkPageRegions($w, $x, $y);
13244 if ($js) {
13245 $this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
13246 return;
13247 }
13248 if (!isset($prop['value'])) {
13249 $prop['value'] = array('Yes');
13250 }
13251 // get default style
13252 $prop = array_merge($this->getFormDefaultProp(), $prop);
13253 $prop['borderStyle'] = 'inset';
13254 // get annotation data
13255 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13256 // set additional default options
13257 $font = 'zapfdingbats';
13258 if ($this->pdfa_mode) {
13259 // all fonts must be embedded
13260 $font = 'pdfa'.$font;
13261 }
13262 $this->AddFont($font);
13263 $tmpfont = $this->getFontBuffer($font);
13264 $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
13265 $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
13266 $popt['da'] = $fontstyle;
13267 // build appearance stream
13268 $popt['ap'] = array();
13269 $popt['ap']['n'] = array();
13270 $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
13271 $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
13272 $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);
13273 $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);
13274 // merge options
13275 $opt = array_merge($popt, $opt);
13276 // set remaining annotation data
13277 $opt['Subtype'] = 'Widget';
13278 $opt['ft'] = 'Btn';
13279 $opt['t'] = $name;
13280 if (TCPDF_STATIC::empty_string($onvalue)) {
13281 $onvalue = 'Yes';
13282 }
13283 $opt['opt'] = array($onvalue);
13284 if ($checked) {
13285 $opt['v'] = array('/Yes');
13286 $opt['as'] = 'Yes';
13287 } else {
13288 $opt['v'] = array('/Off');
13289 $opt['as'] = 'Off';
13290 }
13291 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13292 if ($this->rtl) {
13293 $this->x -= $w;
13294 } else {
13295 $this->x += $w;
13296 }
13297 }
13298
13299 /**
13300 * Creates a button field
13301 * @param string $name field name
13302 * @param int $w width
13303 * @param int $h height
13304 * @param string $caption caption.
13305 * @param mixed $action action triggered by pressing the button. Use a string to specify a javascript action. Use an array to specify a form action options as on section 12.7.5 of PDF32000_2008.
13306 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13307 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13308 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13309 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13310 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13311 * @public
13312 * @author Nicola Asuni
13313 * @since 4.8.000 (2009-09-07)
13314 */
13315 public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
13316 if (TCPDF_STATIC::empty_string($x)) {
13317 $x = $this->x;
13318 }
13319 if (TCPDF_STATIC::empty_string($y)) {
13320 $y = $this->y;
13321 }
13322 // check page for no-write regions and adapt page margins if necessary
13323 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13324 if ($js) {
13325 $this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
13326 $this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
13327 $this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
13328 $this->javascript .= 'f'.$name.".highlight='push';\n";
13329 $this->javascript .= 'f'.$name.".print=false;\n";
13330 return;
13331 }
13332 // get default style
13333 $prop = array_merge($this->getFormDefaultProp(), $prop);
13334 $prop['Pushbutton'] = 'true';
13335 $prop['highlight'] = 'push';
13336 $prop['display'] = 'display.noPrint';
13337 // get annotation data
13338 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13339 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13340 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13341 $popt['da'] = $fontstyle;
13342 // build appearance stream
13343 $popt['ap'] = array();
13344 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13345 $tmpid = $this->startTemplate($w, $h, false);
13346 $bw = (2 / $this->k); // border width
13347 $border = array(
13348 'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13349 'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
13350 'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13351 'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
13352 $this->setFillColor(204);
13353 $this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
13354 $this->endTemplate();
13355 --$this->n;
13356 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13357 unset($this->xobjects[$tmpid]);
13358 $popt['ap']['n'] .= 'Q EMC';
13359 // set additional default options
13360 if (!isset($popt['mk'])) {
13361 $popt['mk'] = array();
13362 }
13363 $ann_obj_id = ($this->n + 1);
13364 if (!empty($action) AND !is_array($action)) {
13365 $ann_obj_id = ($this->n + 2);
13366 }
13367 $popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
13368 $popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
13369 $popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
13370 // merge options
13371 $opt = array_merge($popt, $opt);
13372 // set remaining annotation data
13373 $opt['Subtype'] = 'Widget';
13374 $opt['ft'] = 'Btn';
13375 $opt['t'] = $caption;
13376 $opt['v'] = $name;
13377 if (!empty($action)) {
13378 if (is_array($action)) {
13379 // form action options as on section 12.7.5 of PDF32000_2008.
13380 $opt['aa'] = '/D <<';
13381 $bmode = array('SubmitForm', 'ResetForm', 'ImportData');
13382 foreach ($action AS $key => $val) {
13383 if (($key == 'S') AND in_array($val, $bmode)) {
13384 $opt['aa'] .= ' /S /'.$val;
13385 } elseif (($key == 'F') AND (!empty($val))) {
13386 $opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
13387 } elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
13388 $opt['aa'] .= ' /Fields [';
13389 foreach ($val AS $field) {
13390 $opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
13391 }
13392 $opt['aa'] .= ']';
13393 } elseif (($key == 'Flags')) {
13394 $ff = 0;
13395 if (is_array($val)) {
13396 foreach ($val AS $flag) {
13397 switch ($flag) {
13398 case 'Include/Exclude': {
13399 $ff += 1 << 0;
13400 break;
13401 }
13402 case 'IncludeNoValueFields': {
13403 $ff += 1 << 1;
13404 break;
13405 }
13406 case 'ExportFormat': {
13407 $ff += 1 << 2;
13408 break;
13409 }
13410 case 'GetMethod': {
13411 $ff += 1 << 3;
13412 break;
13413 }
13414 case 'SubmitCoordinates': {
13415 $ff += 1 << 4;
13416 break;
13417 }
13418 case 'XFDF': {
13419 $ff += 1 << 5;
13420 break;
13421 }
13422 case 'IncludeAppendSaves': {
13423 $ff += 1 << 6;
13424 break;
13425 }
13426 case 'IncludeAnnotations': {
13427 $ff += 1 << 7;
13428 break;
13429 }
13430 case 'SubmitPDF': {
13431 $ff += 1 << 8;
13432 break;
13433 }
13434 case 'CanonicalFormat': {
13435 $ff += 1 << 9;
13436 break;
13437 }
13438 case 'ExclNonUserAnnots': {
13439 $ff += 1 << 10;
13440 break;
13441 }
13442 case 'ExclFKey': {
13443 $ff += 1 << 11;
13444 break;
13445 }
13446 case 'EmbedForm': {
13447 $ff += 1 << 13;
13448 break;
13449 }
13450 }
13451 }
13452 } else {
13453 $ff = intval($val);
13454 }
13455 $opt['aa'] .= ' /Flags '.$ff;
13456 }
13457 }
13458 $opt['aa'] .= ' >>';
13459 } else {
13460 // Javascript action or raw action command
13461 $js_obj_id = $this->addJavascriptObject($action);
13462 $opt['aa'] = '/D '.$js_obj_id.' 0 R';
13463 }
13464 }
13465 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13466 if ($this->rtl) {
13467 $this->x -= $w;
13468 } else {
13469 $this->x += $w;
13470 }
13471 }
13472
13473 // --- END FORMS FIELDS ------------------------------------------------
13474
13475 /**
13476 * Add certification signature (DocMDP or UR3)
13477 * You can set only one signature type
13478 * @protected
13479 * @author Nicola Asuni
13480 * @since 4.6.008 (2009-05-07)
13481 */
13482 protected function _putsignature() {
13483 if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
13484 return;
13485 }
13486 $sigobjid = ($this->sig_obj_id + 1);
13487 $out = $this->_getobj($sigobjid)."\n";
13488 $out .= '<< /Type /Sig';
13489 $out .= ' /Filter /Adobe.PPKLite';
13490 $out .= ' /SubFilter /adbe.pkcs7.detached';
13491 $out .= ' '.TCPDF_STATIC::$byterange_string;
13492 $out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
13493 if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
13494 $out .= ' /Reference ['; // array of signature reference dictionaries
13495 $out .= ' << /Type /SigRef';
13496 if ($this->signature_data['cert_type'] > 0) {
13497 $out .= ' /TransformMethod /DocMDP';
13498 $out .= ' /TransformParams <<';
13499 $out .= ' /Type /TransformParams';
13500 $out .= ' /P '.$this->signature_data['cert_type'];
13501 $out .= ' /V /1.2';
13502 } else {
13503 $out .= ' /TransformMethod /UR3';
13504 $out .= ' /TransformParams <<';
13505 $out .= ' /Type /TransformParams';
13506 $out .= ' /V /2.2';
13507 if (!TCPDF_STATIC::empty_string($this->ur['document'])) {
13508 $out .= ' /Document['.$this->ur['document'].']';
13509 }
13510 if (!TCPDF_STATIC::empty_string($this->ur['form'])) {
13511 $out .= ' /Form['.$this->ur['form'].']';
13512 }
13513 if (!TCPDF_STATIC::empty_string($this->ur['signature'])) {
13514 $out .= ' /Signature['.$this->ur['signature'].']';
13515 }
13516 if (!TCPDF_STATIC::empty_string($this->ur['annots'])) {
13517 $out .= ' /Annots['.$this->ur['annots'].']';
13518 }
13519 if (!TCPDF_STATIC::empty_string($this->ur['ef'])) {
13520 $out .= ' /EF['.$this->ur['ef'].']';
13521 }
13522 if (!TCPDF_STATIC::empty_string($this->ur['formex'])) {
13523 $out .= ' /FormEX['.$this->ur['formex'].']';
13524 }
13525 }
13526 $out .= ' >>'; // close TransformParams
13527 // optional digest data (values must be calculated and replaced later)
13528 //$out .= ' /Data ********** 0 R';
13529 //$out .= ' /DigestMethod/MD5';
13530 //$out .= ' /DigestLocation[********** 34]';
13531 //$out .= ' /DigestValue<********************************>';
13532 $out .= ' >>';
13533 $out .= ' ]'; // end of reference
13534 }
13535 if (isset($this->signature_data['info']['Name']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Name'])) {
13536 $out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
13537 }
13538 if (isset($this->signature_data['info']['Location']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Location'])) {
13539 $out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
13540 }
13541 if (isset($this->signature_data['info']['Reason']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Reason'])) {
13542 $out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
13543 }
13544 if (isset($this->signature_data['info']['ContactInfo']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['ContactInfo'])) {
13545 $out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
13546 }
13547 $out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
13548 $out .= ' >>';
13549 $out .= "\n".'endobj';
13550 $this->_out($out);
13551 }
13552
13553 /**
13554 * Set User's Rights for PDF Reader
13555 * WARNING: This is experimental and currently do not work.
13556 * Check the PDF Reference 8.7.1 Transform Methods,
13557 * Table 8.105 Entries in the UR transform parameters dictionary
13558 * @param boolean $enable if true enable user's rights on PDF reader
13559 * @param string $document Names specifying additional document-wide usage rights for the document. The only defined value is "/FullSave", which permits a user to save the document along with modified form and/or annotation data.
13560 * @param string $annots Names specifying additional annotation-related usage rights for the document. Valid names in PDF 1.5 and later are /Create/Delete/Modify/Copy/Import/Export, which permit the user to perform the named operation on annotations.
13561 * @param string $form Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
13562 * @param string $signature Names specifying additional signature-related usage rights for the document. The only defined value is /Modify, which permits a user to apply a digital signature to an existing signature form field or clear a signed signature form field.
13563 * @param string $ef Names specifying additional usage rights for named embedded files in the document. Valid names are /Create/Delete/Modify/Import, which permit the user to perform the named operation on named embedded files
13564 Names specifying additional embedded-files-related usage rights for the document.
13565 * @param string $formex Names specifying additional form-field-related usage rights. The only valid name is BarcodePlaintext, which permits text form field data to be encoded as a plaintext two-dimensional barcode.
13566 * @public
13567 * @author Nicola Asuni
13568 * @since 2.9.000 (2008-03-26)
13569 */
13570 public function setUserRights(
13571 $enable=true,
13572 $document='/FullSave',
13573 $annots='/Create/Delete/Modify/Copy/Import/Export',
13574 $form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
13575 $signature='/Modify',
13576 $ef='/Create/Delete/Modify/Import',
13577 $formex='') {
13578 $this->ur['enabled'] = $enable;
13579 $this->ur['document'] = $document;
13580 $this->ur['annots'] = $annots;
13581 $this->ur['form'] = $form;
13582 $this->ur['signature'] = $signature;
13583 $this->ur['ef'] = $ef;
13584 $this->ur['formex'] = $formex;
13585 if (!$this->sign) {
13586 $this->setSignature('', '', '', '', 0, array());
13587 }
13588 }
13589
13590 /**
13591 * Enable document signature (requires the OpenSSL Library).
13592 * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
13593 * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13594 * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13595 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
13596 * @param mixed $signing_cert signing certificate (string or filename prefixed with 'file://')
13597 * @param mixed $private_key private key (string or filename prefixed with 'file://')
13598 * @param string $private_key_password password
13599 * @param string $extracerts specifies the name of a file containing a bunch of extra certificates to include in the signature which can for example be used to help the recipient to verify the certificate that you used.
13600 * @param int $cert_type The access permissions granted for this document. Valid values shall be: 1 = No changes to the document shall be permitted; any change to the document shall invalidate the signature; 2 = Permitted changes shall be filling in forms, instantiating page templates, and signing; other changes shall invalidate the signature; 3 = Permitted changes shall be the same as for 2, as well as annotation creation, deletion, and modification; other changes shall invalidate the signature.
13601 * @param array $info array of option information: Name, Location, Reason, ContactInfo.
13602 * @param string $approval Enable approval signature eg. for PDF incremental update
13603 * @public
13604 * @author Nicola Asuni
13605 * @since 4.6.005 (2009-04-24)
13606 */
13607 public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array(), $approval='') {
13608 // to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13609 // to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13610 // to convert pfx certificate to pem: openssl
13611 // OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
13612 $this->sign = true;
13613 ++$this->n;
13614 $this->sig_obj_id = $this->n; // signature widget
13615 ++$this->n; // signature object ($this->sig_obj_id + 1)
13616 $this->signature_data = array();
13617 if (strlen($signing_cert) == 0) {
13618 $this->Error('Please provide a certificate file and password!');
13619 }
13620 if (strlen($private_key) == 0) {
13621 $private_key = $signing_cert;
13622 }
13623 $this->signature_data['signcert'] = $signing_cert;
13624 $this->signature_data['privkey'] = $private_key;
13625 $this->signature_data['password'] = $private_key_password;
13626 $this->signature_data['extracerts'] = $extracerts;
13627 $this->signature_data['cert_type'] = $cert_type;
13628 $this->signature_data['info'] = $info;
13629 $this->signature_data['approval'] = $approval;
13630 }
13631
13632 /**
13633 * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
13634 * @param float $x Abscissa of the upper-left corner.
13635 * @param float $y Ordinate of the upper-left corner.
13636 * @param float $w Width of the signature area.
13637 * @param float $h Height of the signature area.
13638 * @param int $page option page number (if < 0 the current page is used).
13639 * @param string $name Name of the signature.
13640 * @public
13641 * @author Nicola Asuni
13642 * @since 5.3.011 (2010-06-17)
13643 */
13644 public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13645 $this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13646 }
13647
13648 /**
13649 * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
13650 * @param float $x Abscissa of the upper-left corner.
13651 * @param float $y Ordinate of the upper-left corner.
13652 * @param float $w Width of the signature area.
13653 * @param float $h Height of the signature area.
13654 * @param int $page option page number (if < 0 the current page is used).
13655 * @param string $name Name of the signature.
13656 * @public
13657 * @author Nicola Asuni
13658 * @since 5.9.101 (2011-07-06)
13659 */
13660 public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13661 ++$this->n;
13662 $this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13663 }
13664
13665 /**
13666 * Get the array that defines the signature appearance (page and rectangle coordinates).
13667 * @param float $x Abscissa of the upper-left corner.
13668 * @param float $y Ordinate of the upper-left corner.
13669 * @param float $w Width of the signature area.
13670 * @param float $h Height of the signature area.
13671 * @param int $page option page number (if < 0 the current page is used).
13672 * @param string $name Name of the signature.
13673 * @return array Array defining page and rectangle coordinates of signature appearance.
13674 * @protected
13675 * @author Nicola Asuni
13676 * @since 5.9.101 (2011-07-06)
13677 */
13678 protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13679 $sigapp = array();
13680 if (($page < 1) OR ($page > $this->numpages)) {
13681 $sigapp['page'] = $this->page;
13682 } else {
13683 $sigapp['page'] = intval($page);
13684 }
13685 if (empty($name)) {
13686 $sigapp['name'] = 'Signature';
13687 } else {
13688 $sigapp['name'] = $name;
13689 }
13690 $a = $x * $this->k;
13691 $b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
13692 $c = $w * $this->k;
13693 $d = $h * $this->k;
13694 $sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
13695 return $sigapp;
13696 }
13697
13698 /**
13699 * Enable document timestamping (requires the OpenSSL Library).
13700 * The trusted timestamping improve document security that means that no one should be able to change the document once it has been recorded.
13701 * Use with digital signature only!
13702 * @param string $tsa_host Time Stamping Authority (TSA) server (prefixed with 'https://')
13703 * @param string $tsa_username Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional)
13704 * @param string $tsa_password Specifies the password for TSA authorization (optional)
13705 * @param string $tsa_cert Specifies the location of TSA certificate for authorization (optional for cURL)
13706 * @public
13707 * @author Richard Stockinger
13708 * @since 6.0.090 (2014-06-16)
13709 */
13710 public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') {
13711 $this->tsa_data = array();
13712 if (!function_exists('curl_init')) {
13713 $this->Error('Please enable cURL PHP extension!');
13714 }
13715 if (strlen($tsa_host) == 0) {
13716 $this->Error('Please specify the host of Time Stamping Authority (TSA)!');
13717 }
13718 $this->tsa_data['tsa_host'] = $tsa_host;
13719 if (is_file($tsa_username)) {
13720 $this->tsa_data['tsa_auth'] = $tsa_username;
13721 } else {
13722 $this->tsa_data['tsa_username'] = $tsa_username;
13723 }
13724 $this->tsa_data['tsa_password'] = $tsa_password;
13725 $this->tsa_data['tsa_cert'] = $tsa_cert;
13726 $this->tsa_timestamp = true;
13727 }
13728
13729 /**
13730 * NOT YET IMPLEMENTED
13731 * Request TSA for a timestamp
13732 * @param string $signature Digital signature as binary string
13733 * @return string Timestamped digital signature
13734 * @protected
13735 * @author Richard Stockinger
13736 * @since 6.0.090 (2014-06-16)
13737 */
13738 protected function applyTSA($signature) {
13739 if (!$this->tsa_timestamp) {
13740 return $signature;
13741 }
13742 //@TODO: implement this feature
13743 return $signature;
13744 }
13745
13746 /**
13747 * Create a new page group.
13748 * NOTE: call this function before calling AddPage()
13749 * @param int|null $page starting group page (leave empty for next page).
13750 * @public
13751 * @since 3.0.000 (2008-03-27)
13752 */
13753 public function startPageGroup($page=null) {
13754 if (empty($page)) {
13755 $page = $this->page + 1;
13756 }
13757 $this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
13758 }
13759
13760 /**
13761 * Set the starting page number.
13762 * @param int $num Starting page number.
13763 * @since 5.9.093 (2011-06-16)
13764 * @public
13765 */
13766 public function setStartingPageNumber($num=1) {
13767 $this->starting_page_number = max(0, intval($num));
13768 }
13769
13770 /**
13771 * Returns the string alias used right align page numbers.
13772 * If the current font is unicode type, the returned string wil contain an additional open curly brace.
13773 * @return string
13774 * @since 5.9.099 (2011-06-27)
13775 * @public
13776 */
13777 public function getAliasRightShift() {
13778 // calculate aproximatively the ratio between widths of aliases and replacements.
13779 $ref = '{'.TCPDF_STATIC::$alias_right_shift.'}{'.TCPDF_STATIC::$alias_tot_pages.'}{'.TCPDF_STATIC::$alias_num_page.'}';
13780 $rep = str_repeat(' ', $this->GetNumChars($ref));
13781 $wrep = $this->GetStringWidth($rep);
13782 if ($wrep > 0) {
13783 $wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
13784 } else {
13785 $wdiff = 1;
13786 }
13787 $sdiff = sprintf('%F', $wdiff);
13788 $alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
13789 if ($this->isUnicodeFont()) {
13790 $alias = '{'.$alias;
13791 }
13792 return $alias;
13793 }
13794
13795 /**
13796 * Returns the string alias used for the total number of pages.
13797 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13798 * This alias will be replaced by the total number of pages in the document.
13799 * @return string
13800 * @since 4.0.018 (2008-08-08)
13801 * @public
13802 */
13803 public function getAliasNbPages() {
13804 if ($this->isUnicodeFont()) {
13805 return '{'.TCPDF_STATIC::$alias_tot_pages.'}';
13806 }
13807 return TCPDF_STATIC::$alias_tot_pages;
13808 }
13809
13810 /**
13811 * Returns the string alias used for the page number.
13812 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13813 * This alias will be replaced by the page number.
13814 * @return string
13815 * @since 4.5.000 (2009-01-02)
13816 * @public
13817 */
13818 public function getAliasNumPage() {
13819 if ($this->isUnicodeFont()) {
13820 return '{'.TCPDF_STATIC::$alias_num_page.'}';
13821 }
13822 return TCPDF_STATIC::$alias_num_page;
13823 }
13824
13825 /**
13826 * Return the alias for the total number of pages in the current page group.
13827 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13828 * This alias will be replaced by the total number of pages in this group.
13829 * @return string alias of the current page group
13830 * @public
13831 * @since 3.0.000 (2008-03-27)
13832 */
13833 public function getPageGroupAlias() {
13834 if ($this->isUnicodeFont()) {
13835 return '{'.TCPDF_STATIC::$alias_group_tot_pages.'}';
13836 }
13837 return TCPDF_STATIC::$alias_group_tot_pages;
13838 }
13839
13840 /**
13841 * Return the alias for the page number on the current page group.
13842 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13843 * This alias will be replaced by the page number (relative to the belonging group).
13844 * @return string alias of the current page group
13845 * @public
13846 * @since 4.5.000 (2009-01-02)
13847 */
13848 public function getPageNumGroupAlias() {
13849 if ($this->isUnicodeFont()) {
13850 return '{'.TCPDF_STATIC::$alias_group_num_page.'}';
13851 }
13852 return TCPDF_STATIC::$alias_group_num_page;
13853 }
13854
13855 /**
13856 * Return the current page in the group.
13857 * @return int current page in the group
13858 * @public
13859 * @since 3.0.000 (2008-03-27)
13860 */
13861 public function getGroupPageNo() {
13862 return $this->pagegroups[$this->currpagegroup];
13863 }
13864
13865 /**
13866 * Returns the current group page number formatted as a string.
13867 * @public
13868 * @since 4.3.003 (2008-11-18)
13869 * @see PaneNo(), formatPageNumber()
13870 */
13871 public function getGroupPageNoFormatted() {
13872 return TCPDF_STATIC::formatPageNumber($this->getGroupPageNo());
13873 }
13874
13875 /**
13876 * Returns the current page number formatted as a string.
13877 * @public
13878 * @since 4.2.005 (2008-11-06)
13879 * @see PaneNo(), formatPageNumber()
13880 */
13881 public function PageNoFormatted() {
13882 return TCPDF_STATIC::formatPageNumber($this->PageNo());
13883 }
13884
13885 /**
13886 * Put pdf layers.
13887 * @protected
13888 * @since 3.0.000 (2008-03-27)
13889 */
13890 protected function _putocg() {
13891 if (empty($this->pdflayers)) {
13892 return;
13893 }
13894 foreach ($this->pdflayers as $key => $layer) {
13895 $this->pdflayers[$key]['objid'] = $this->_newobj();
13896 $out = '<< /Type /OCG';
13897 $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
13898 $out .= ' /Usage <<';
13899 if (isset($layer['print']) AND ($layer['print'] !== NULL)) {
13900 $out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
13901 }
13902 $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
13903 $out .= ' >> >>';
13904 $out .= "\n".'endobj';
13905 $this->_out($out);
13906 }
13907 }
13908
13909 /**
13910 * Start a new pdf layer.
13911 * @param string $name Layer name (only a-z letters and numbers). Leave empty for automatic name.
13912 * @param boolean|null $print Set to TRUE to print this layer, FALSE to not print and NULL to not set this option
13913 * @param boolean $view Set to true to view this layer.
13914 * @param boolean $lock If true lock the layer
13915 * @public
13916 * @since 5.9.102 (2011-07-13)
13917 */
13918 public function startLayer($name='', $print=true, $view=true, $lock=true) {
13919 if ($this->state != 2) {
13920 return;
13921 }
13922 $layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
13923 if (empty($name)) {
13924 $name = $layer;
13925 } else {
13926 $name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
13927 }
13928 $this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view, 'lock' => $lock);
13929 $this->openMarkedContent = true;
13930 $this->_out('/OC /'.$layer.' BDC');
13931 }
13932
13933 /**
13934 * End the current PDF layer.
13935 * @public
13936 * @since 5.9.102 (2011-07-13)
13937 */
13938 public function endLayer() {
13939 if ($this->state != 2) {
13940 return;
13941 }
13942 if ($this->openMarkedContent) {
13943 // close existing open marked-content layer
13944 $this->_out('EMC');
13945 $this->openMarkedContent = false;
13946 }
13947 }
13948
13949 /**
13950 * Set the visibility of the successive elements.
13951 * This can be useful, for instance, to put a background
13952 * image or color that will show on screen but won't print.
13953 * @param string $v visibility mode. Legal values are: all, print, screen or view.
13954 * @public
13955 * @since 3.0.000 (2008-03-27)
13956 */
13957 public function setVisibility($v) {
13958 if ($this->state != 2) {
13959 return;
13960 }
13961 $this->endLayer();
13962 switch($v) {
13963 case 'print': {
13964 $this->startLayer('Print', true, false);
13965 break;
13966 }
13967 case 'view':
13968 case 'screen': {
13969 $this->startLayer('View', false, true);
13970 break;
13971 }
13972 case 'all': {
13973 $this->_out('');
13974 break;
13975 }
13976 default: {
13977 $this->Error('Incorrect visibility: '.$v);
13978 break;
13979 }
13980 }
13981 }
13982
13983 /**
13984 * Add transparency parameters to the current extgstate
13985 * @param array $parms parameters
13986 * @return int|void the number of extgstates
13987 * @protected
13988 * @since 3.0.000 (2008-03-27)
13989 */
13990 protected function addExtGState($parms) {
13991 if (($this->pdfa_mode && $this->pdfa_version < 2) || ($this->state != 2)) {
13992 // transparency is not allowed in PDF/A-1 mode
13993 return;
13994 }
13995 // check if this ExtGState already exist
13996 foreach ($this->extgstates as $i => $ext) {
13997 if ($ext['parms'] == $parms) {
13998 if ($this->inxobj) {
13999 // we are inside an XObject template
14000 $this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
14001 }
14002 // return reference to existing ExtGState
14003 return $i;
14004 }
14005 }
14006 $n = (count($this->extgstates) + 1);
14007 $this->extgstates[$n] = array('parms' => $parms);
14008 if ($this->inxobj) {
14009 // we are inside an XObject template
14010 $this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
14011 }
14012 return $n;
14013 }
14014
14015 /**
14016 * Add an extgstate
14017 * @param int $gs extgstate
14018 * @protected
14019 * @since 3.0.000 (2008-03-27)
14020 */
14021 protected function setExtGState($gs) {
14022 if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) {
14023 // transparency is not allowed in PDF/A-1 mode
14024 return;
14025 }
14026 $this->_out(sprintf('/GS%d gs', $gs));
14027 }
14028
14029 /**
14030 * Put extgstates for object transparency
14031 * @protected
14032 * @since 3.0.000 (2008-03-27)
14033 */
14034 protected function _putextgstates() {
14035 foreach ($this->extgstates as $i => $ext) {
14036 $this->extgstates[$i]['n'] = $this->_newobj();
14037 $out = '<< /Type /ExtGState';
14038 foreach ($ext['parms'] as $k => $v) {
14039 if (is_float($v)) {
14040 $v = sprintf('%F', $v);
14041 } elseif ($v === true) {
14042 $v = 'true';
14043 } elseif ($v === false) {
14044 $v = 'false';
14045 }
14046 $out .= ' /'.$k.' '.$v;
14047 }
14048 $out .= ' >>';
14049 $out .= "\n".'endobj';
14050 $this->_out($out);
14051 }
14052 }
14053
14054 /**
14055 * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
14056 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14057 * @param boolean $stroking If true apply overprint for stroking operations.
14058 * @param boolean|null $nonstroking If true apply overprint for painting operations other than stroking.
14059 * @param integer $mode Overprint mode: (0 = each source colour component value replaces the value previously painted for the corresponding device colorant; 1 = a tint value of 0.0 for a source colour component shall leave the corresponding component of the previously painted colour unchanged).
14060 * @public
14061 * @since 5.9.152 (2012-03-23)
14062 */
14063 public function setOverprint($stroking=true, $nonstroking=null, $mode=0) {
14064 if ($this->state != 2) {
14065 return;
14066 }
14067 $stroking = $stroking ? true : false;
14068 if (TCPDF_STATIC::empty_string($nonstroking)) {
14069 // default value if not set
14070 $nonstroking = $stroking;
14071 } else {
14072 $nonstroking = $nonstroking ? true : false;
14073 }
14074 if (($mode != 0) AND ($mode != 1)) {
14075 $mode = 0;
14076 }
14077 $this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
14078 $gs = $this->addExtGState($this->overprint);
14079 $this->setExtGState($gs);
14080 }
14081
14082 /**
14083 * Get the overprint mode array (OP, op, OPM).
14084 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14085 * @return array<string,bool|int>
14086 * @public
14087 * @since 5.9.152 (2012-03-23)
14088 */
14089 public function getOverprint() {
14090 return $this->overprint;
14091 }
14092
14093 /**
14094 * Set alpha for stroking (CA) and non-stroking (ca) operations.
14095 * @param float $stroking Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
14096 * @param string $bm blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity
14097 * @param float|null $nonstroking Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
14098 * @param boolean $ais
14099 * @public
14100 * @since 3.0.000 (2008-03-27)
14101 */
14102 public function setAlpha($stroking=1, $bm='Normal', $nonstroking=null, $ais=false) {
14103 if ($this->pdfa_mode && $this->pdfa_version < 2) {
14104 // transparency is not allowed in PDF/A-1 mode
14105 return;
14106 }
14107 $stroking = floatval($stroking);
14108 if (TCPDF_STATIC::empty_string($nonstroking)) {
14109 // default value if not set
14110 $nonstroking = $stroking;
14111 } else {
14112 $nonstroking = floatval($nonstroking);
14113 }
14114 if ($bm[0] == '/') {
14115 // remove trailing slash
14116 $bm = substr($bm, 1);
14117 }
14118 if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
14119 $bm = 'Normal';
14120 }
14121 $ais = $ais ? true : false;
14122 $this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
14123 $gs = $this->addExtGState($this->alpha);
14124 $this->setExtGState($gs);
14125 }
14126
14127 /**
14128 * Get the alpha mode array (CA, ca, BM, AIS).
14129 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14130 * @return array<string,bool|string>
14131 * @public
14132 * @since 5.9.152 (2012-03-23)
14133 */
14134 public function getAlpha() {
14135 return $this->alpha;
14136 }
14137
14138 /**
14139 * Set the default JPEG compression quality (1-100)
14140 * @param int $quality JPEG quality, integer between 1 and 100
14141 * @public
14142 * @since 3.0.000 (2008-03-27)
14143 */
14144 public function setJPEGQuality($quality) {
14145 if (($quality < 1) OR ($quality > 100)) {
14146 $quality = 75;
14147 }
14148 $this->jpeg_quality = intval($quality);
14149 }
14150
14151 /**
14152 * Set the default number of columns in a row for HTML tables.
14153 * @param int $cols number of columns
14154 * @public
14155 * @since 3.0.014 (2008-06-04)
14156 */
14157 public function setDefaultTableColumns($cols=4) {
14158 $this->default_table_columns = intval($cols);
14159 }
14160
14161 /**
14162 * Set the height of the cell (line height) respect the font height.
14163 * @param float $h cell proportion respect font height (typical value = 1.25).
14164 * @public
14165 * @since 3.0.014 (2008-06-04)
14166 */
14167 public function setCellHeightRatio($h) {
14168 $this->cell_height_ratio = $h;
14169 }
14170
14171 /**
14172 * return the height of cell repect font height.
14173 * @public
14174 * @return float
14175 * @since 4.0.012 (2008-07-24)
14176 */
14177 public function getCellHeightRatio() {
14178 return $this->cell_height_ratio;
14179 }
14180
14181 /**
14182 * Set the PDF version (check PDF reference for valid values).
14183 * @param string $version PDF document version.
14184 * @public
14185 * @since 3.1.000 (2008-06-09)
14186 */
14187 public function setPDFVersion($version='1.7') {
14188 if ($this->pdfa_mode && $this->pdfa_version == 1 ) {
14189 // PDF/A-1 mode
14190 $this->PDFVersion = '1.4';
14191 } elseif ($this->pdfa_mode && $this->pdfa_version >= 2 ) {
14192 // PDF/A-2 mode
14193 $this->PDFVersion = '1.7';
14194 } else {
14195 $this->PDFVersion = $version;
14196 }
14197 }
14198
14199 /**
14200 * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
14201 * (see Section 8.1 of PDF reference, "Viewer Preferences").
14202 * <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>
14203 * @param array $preferences array of options.
14204 * @author Nicola Asuni
14205 * @public
14206 * @since 3.1.000 (2008-06-09)
14207 */
14208 public function setViewerPreferences($preferences) {
14209 $this->viewer_preferences = $preferences;
14210 }
14211
14212 /**
14213 * Paints color transition registration bars
14214 * @param float $x abscissa of the top left corner of the rectangle.
14215 * @param float $y ordinate of the top left corner of the rectangle.
14216 * @param float $w width of the rectangle.
14217 * @param float $h height of the rectangle.
14218 * @param boolean $transition if true prints tcolor transitions to white.
14219 * @param boolean $vertical if true prints bar vertically.
14220 * @param string $colors colors to print separated by comma. Valid values are: A,W,R,G,B,C,M,Y,K,RGB,CMYK,ALL,ALLSPOT,<SPOT_COLOR_NAME>. Where: A = grayscale black, W = grayscale white, R = RGB red, G RGB green, B RGB blue, C = CMYK cyan, M = CMYK magenta, Y = CMYK yellow, K = CMYK key/black, RGB = RGB registration color, CMYK = CMYK registration color, ALL = Spot registration color, ALLSPOT = print all defined spot colors, <SPOT_COLOR_NAME> = name of the spot color to print.
14221 * @author Nicola Asuni
14222 * @since 4.9.000 (2010-03-26)
14223 * @public
14224 */
14225 public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
14226 if (strpos($colors, 'ALLSPOT') !== false) {
14227 // expand spot colors
14228 $spot_colors = '';
14229 foreach ($this->spot_colors as $spot_color_name => $v) {
14230 $spot_colors .= ','.$spot_color_name;
14231 }
14232 if (!empty($spot_colors)) {
14233 $spot_colors = substr($spot_colors, 1);
14234 $colors = str_replace('ALLSPOT', $spot_colors, $colors);
14235 } else {
14236 $colors = str_replace('ALLSPOT', 'NONE', $colors);
14237 }
14238 }
14239 $bars = explode(',', $colors);
14240 $numbars = count($bars); // number of bars to print
14241 if ($numbars <= 0) {
14242 return;
14243 }
14244 // set bar measures
14245 if ($vertical) {
14246 $coords = array(0, 0, 0, 1);
14247 $wb = $w / $numbars; // bar width
14248 $hb = $h; // bar height
14249 $xd = $wb; // delta x
14250 $yd = 0; // delta y
14251 } else {
14252 $coords = array(1, 0, 0, 0);
14253 $wb = $w; // bar width
14254 $hb = $h / $numbars; // bar height
14255 $xd = 0; // delta x
14256 $yd = $hb; // delta y
14257 }
14258 $xb = $x;
14259 $yb = $y;
14260 foreach ($bars as $col) {
14261 switch ($col) {
14262 // set transition colors
14263 case 'A': { // BLACK (GRAYSCALE)
14264 $col_a = array(255);
14265 $col_b = array(0);
14266 break;
14267 }
14268 case 'W': { // WHITE (GRAYSCALE)
14269 $col_a = array(0);
14270 $col_b = array(255);
14271 break;
14272 }
14273 case 'R': { // RED (RGB)
14274 $col_a = array(255,255,255);
14275 $col_b = array(255,0,0);
14276 break;
14277 }
14278 case 'G': { // GREEN (RGB)
14279 $col_a = array(255,255,255);
14280 $col_b = array(0,255,0);
14281 break;
14282 }
14283 case 'B': { // BLUE (RGB)
14284 $col_a = array(255,255,255);
14285 $col_b = array(0,0,255);
14286 break;
14287 }
14288 case 'C': { // CYAN (CMYK)
14289 $col_a = array(0,0,0,0);
14290 $col_b = array(100,0,0,0);
14291 break;
14292 }
14293 case 'M': { // MAGENTA (CMYK)
14294 $col_a = array(0,0,0,0);
14295 $col_b = array(0,100,0,0);
14296 break;
14297 }
14298 case 'Y': { // YELLOW (CMYK)
14299 $col_a = array(0,0,0,0);
14300 $col_b = array(0,0,100,0);
14301 break;
14302 }
14303 case 'K': { // KEY - BLACK (CMYK)
14304 $col_a = array(0,0,0,0);
14305 $col_b = array(0,0,0,100);
14306 break;
14307 }
14308 case 'RGB': { // BLACK REGISTRATION (RGB)
14309 $col_a = array(255,255,255);
14310 $col_b = array(0,0,0);
14311 break;
14312 }
14313 case 'CMYK': { // BLACK REGISTRATION (CMYK)
14314 $col_a = array(0,0,0,0);
14315 $col_b = array(100,100,100,100);
14316 break;
14317 }
14318 case 'ALL': { // SPOT COLOR REGISTRATION
14319 $col_a = array(0,0,0,0,'None');
14320 $col_b = array(100,100,100,100,'All');
14321 break;
14322 }
14323 case 'NONE': { // SKIP THIS COLOR
14324 $col_a = array(0,0,0,0,'None');
14325 $col_b = array(0,0,0,0,'None');
14326 break;
14327 }
14328 default: { // SPECIFIC SPOT COLOR NAME
14329 $col_a = array(0,0,0,0,'None');
14330 $col_b = TCPDF_COLORS::getSpotColor($col, $this->spot_colors);
14331 if ($col_b === false) {
14332 // in case of error defaults to the registration color
14333 $col_b = array(100,100,100,100,'All');
14334 }
14335 break;
14336 }
14337 }
14338 if ($col != 'NONE') {
14339 if ($transition) {
14340 // color gradient
14341 $this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
14342 } else {
14343 $this->setFillColorArray($col_b);
14344 // colored rectangle
14345 $this->Rect($xb, $yb, $wb, $hb, 'F', array());
14346 }
14347 $xb += $xd;
14348 $yb += $yd;
14349 }
14350 }
14351 }
14352
14353 /**
14354 * Paints crop marks.
14355 * @param float $x abscissa of the crop mark center.
14356 * @param float $y ordinate of the crop mark center.
14357 * @param float $w width of the crop mark.
14358 * @param float $h height of the crop mark.
14359 * @param string $type type of crop mark, one symbol per type separated by comma: T = TOP, F = BOTTOM, L = LEFT, R = RIGHT, TL = A = TOP-LEFT, TR = B = TOP-RIGHT, BL = C = BOTTOM-LEFT, BR = D = BOTTOM-RIGHT.
14360 * @param array $color crop mark color (default spot registration color).
14361 * @author Nicola Asuni
14362 * @since 4.9.000 (2010-03-26)
14363 * @public
14364 */
14365 public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
14366 $this->setLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
14367 $type = strtoupper($type);
14368 $type = preg_replace('/[^A-Z\-\,]*/', '', $type);
14369 // split type in single components
14370 $type = str_replace('-', ',', $type);
14371 $type = str_replace('TL', 'T,L', $type);
14372 $type = str_replace('TR', 'T,R', $type);
14373 $type = str_replace('BL', 'F,L', $type);
14374 $type = str_replace('BR', 'F,R', $type);
14375 $type = str_replace('A', 'T,L', $type);
14376 $type = str_replace('B', 'T,R', $type);
14377 $type = str_replace('T,RO', 'BO', $type);
14378 $type = str_replace('C', 'F,L', $type);
14379 $type = str_replace('D', 'F,R', $type);
14380 $crops = explode(',', strtoupper($type));
14381 // remove duplicates
14382 $crops = array_unique($crops);
14383 $dw = ($w / 4); // horizontal space to leave before the intersection point
14384 $dh = ($h / 4); // vertical space to leave before the intersection point
14385 foreach ($crops as $crop) {
14386 switch ($crop) {
14387 case 'T':
14388 case 'TOP': {
14389 $x1 = $x;
14390 $y1 = ($y - $h);
14391 $x2 = $x;
14392 $y2 = ($y - $dh);
14393 break;
14394 }
14395 case 'F':
14396 case 'BOTTOM': {
14397 $x1 = $x;
14398 $y1 = ($y + $dh);
14399 $x2 = $x;
14400 $y2 = ($y + $h);
14401 break;
14402 }
14403 case 'L':
14404 case 'LEFT': {
14405 $x1 = ($x - $w);
14406 $y1 = $y;
14407 $x2 = ($x - $dw);
14408 $y2 = $y;
14409 break;
14410 }
14411 case 'R':
14412 case 'RIGHT': {
14413 $x1 = ($x + $dw);
14414 $y1 = $y;
14415 $x2 = ($x + $w);
14416 $y2 = $y;
14417 break;
14418 }
14419 }
14420 $this->Line($x1, $y1, $x2, $y2);
14421 }
14422 }
14423
14424 /**
14425 * Paints a registration mark
14426 * @param float $x abscissa of the registration mark center.
14427 * @param float $y ordinate of the registration mark center.
14428 * @param float $r radius of the crop mark.
14429 * @param boolean $double if true print two concentric crop marks.
14430 * @param array $cola crop mark color (default spot registration color 'All').
14431 * @param array $colb second crop mark color (default spot registration color 'None').
14432 * @author Nicola Asuni
14433 * @since 4.9.000 (2010-03-26)
14434 * @public
14435 */
14436 public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
14437 $line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
14438 $this->setFillColorArray($cola);
14439 $this->PieSector($x, $y, $r, 90, 180, 'F');
14440 $this->PieSector($x, $y, $r, 270, 360, 'F');
14441 $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14442 if ($double) {
14443 $ri = $r * 0.5;
14444 $this->setFillColorArray($colb);
14445 $this->PieSector($x, $y, $ri, 90, 180, 'F');
14446 $this->PieSector($x, $y, $ri, 270, 360, 'F');
14447 $this->setFillColorArray($cola);
14448 $this->PieSector($x, $y, $ri, 0, 90, 'F');
14449 $this->PieSector($x, $y, $ri, 180, 270, 'F');
14450 $this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
14451 }
14452 }
14453
14454 /**
14455 * Paints a CMYK registration mark
14456 * @param float $x abscissa of the registration mark center.
14457 * @param float $y ordinate of the registration mark center.
14458 * @param float $r radius of the crop mark.
14459 * @author Nicola Asuni
14460 * @since 6.0.038 (2013-09-30)
14461 * @public
14462 */
14463 public function registrationMarkCMYK($x, $y, $r) {
14464 // line width
14465 $lw = max((0.5 / $this->k),($r / 8));
14466 // internal radius
14467 $ri = ($r * 0.6);
14468 // external radius
14469 $re = ($r * 1.3);
14470 // Cyan
14471 $this->setFillColorArray(array(100,0,0,0));
14472 $this->PieSector($x, $y, $ri, 270, 360, 'F');
14473 // Magenta
14474 $this->setFillColorArray(array(0,100,0,0));
14475 $this->PieSector($x, $y, $ri, 0, 90, 'F');
14476 // Yellow
14477 $this->setFillColorArray(array(0,0,100,0));
14478 $this->PieSector($x, $y, $ri, 90, 180, 'F');
14479 // Key - black
14480 $this->setFillColorArray(array(0,0,0,100));
14481 $this->PieSector($x, $y, $ri, 180, 270, 'F');
14482 // registration color
14483 $line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
14484 $this->setFillColorArray(array(100,100,100,100,'All'));
14485 // external circle
14486 $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14487 // cross lines
14488 $this->Line($x, ($y - $re), $x, ($y - $ri));
14489 $this->Line($x, ($y + $ri), $x, ($y + $re));
14490 $this->Line(($x - $re), $y, ($x - $ri), $y);
14491 $this->Line(($x + $ri), $y, ($x + $re), $y);
14492 }
14493
14494 /**
14495 * Paints a linear colour gradient.
14496 * @param float $x abscissa of the top left corner of the rectangle.
14497 * @param float $y ordinate of the top left corner of the rectangle.
14498 * @param float $w width of the rectangle.
14499 * @param float $h height of the rectangle.
14500 * @param array $col1 first color (Grayscale, RGB or CMYK components).
14501 * @param array $col2 second color (Grayscale, RGB or CMYK components).
14502 * @param array $coords array of the form (x1, y1, x2, y2) which defines the gradient vector (see linear_gradient_coords.jpg). The default value is from left to right (x1=0, y1=0, x2=1, y2=0).
14503 * @author Andreas W\FCrmser,2026 Nicola Asuni
14504 * @since 3.1.000 (2008-06-09)
14505 * @public
14506 */
14507 public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
14508 $this->Clip($x, $y, $w, $h);
14509 $this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14510 }
14511
14512 /**
14513 * Paints a radial colour gradient.
14514 * @param float $x abscissa of the top left corner of the rectangle.
14515 * @param float $y ordinate of the top left corner of the rectangle.
14516 * @param float $w width of the rectangle.
14517 * @param float $h height of the rectangle.
14518 * @param array $col1 first color (Grayscale, RGB or CMYK components).
14519 * @param array $col2 second color (Grayscale, RGB or CMYK components).
14520 * @param array $coords array of the form (fx, fy, cx, cy, r) where (fx, fy) is the starting point of the gradient with color1, (cx, cy) is the center of the circle with color2, and r is the radius of the circle (see radial_gradient_coords.jpg). (fx, fy) should be inside the circle, otherwise some areas will not be defined.
14521 * @author Andreas W\FCrmser,2026 Nicola Asuni
14522 * @since 3.1.000 (2008-06-09)
14523 * @public
14524 */
14525 public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
14526 $this->Clip($x, $y, $w, $h);
14527 $this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14528 }
14529
14530 /**
14531 * Paints a coons patch mesh.
14532 * @param float $x abscissa of the top left corner of the rectangle.
14533 * @param float $y ordinate of the top left corner of the rectangle.
14534 * @param float $w width of the rectangle.
14535 * @param float $h height of the rectangle.
14536 * @param array $col1 first color (lower left corner) (RGB components).
14537 * @param array $col2 second color (lower right corner) (RGB components).
14538 * @param array $col3 third color (upper right corner) (RGB components).
14539 * @param array $col4 fourth color (upper left corner) (RGB components).
14540 * @param array $coords <ul><li>for one patch mesh: array(float x1, float y1, .... float x12, float y12): 12 pairs of coordinates (normally from 0 to 1) which specify the Bezier control points that define the patch. First pair is the lower left edge point, next is its right control point (control point 2). Then the other points are defined in the order: control point 1, edge point, control point 2 going counter-clockwise around the patch. Last (x12, y12) is the first edge point's left control point (control point 1).</li><li>for two or more patch meshes: array[number of patches]: arrays with the following keys for each patch: f: where to put that patch (0 = first patch, 1, 2, 3 = right, top and left of precedent patch - I didn't figure this out completely - just try and error ;-) points: 12 pairs of coordinates of the Bezier control points as above for the first patch, 8 pairs of coordinates for the following patches, ignoring the coordinates already defined by the precedent patch (I also didn't figure out the order of these - also: try and see what's happening) colors: must be 4 colors for the first patch, 2 colors for the following patches</li></ul>
14541 * @param array $coords_min minimum value used by the coordinates. If a coordinate's value is smaller than this it will be cut to coords_min. default: 0
14542 * @param array $coords_max maximum value used by the coordinates. If a coordinate's value is greater than this it will be cut to coords_max. default: 1
14543 * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14544 * @author Andreas W\FCrmser,2026 Nicola Asuni
14545 * @since 3.1.000 (2008-06-09)
14546 * @public
14547 */
14548 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) {
14549 if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) {
14550 return;
14551 }
14552 $this->Clip($x, $y, $w, $h);
14553 $n = count($this->gradients) + 1;
14554 $this->gradients[$n] = array();
14555 $this->gradients[$n]['type'] = 6; //coons patch mesh
14556 $this->gradients[$n]['coords'] = array();
14557 $this->gradients[$n]['antialias'] = $antialias;
14558 $this->gradients[$n]['colors'] = array();
14559 $this->gradients[$n]['transparency'] = false;
14560 //check the coords array if it is the simple array or the multi patch array
14561 if (!isset($coords[0]['f'])) {
14562 //simple array -> convert to multi patch array
14563 if (!isset($col1[1])) {
14564 $col1[1] = $col1[2] = $col1[0];
14565 }
14566 if (!isset($col2[1])) {
14567 $col2[1] = $col2[2] = $col2[0];
14568 }
14569 if (!isset($col3[1])) {
14570 $col3[1] = $col3[2] = $col3[0];
14571 }
14572 if (!isset($col4[1])) {
14573 $col4[1] = $col4[2] = $col4[0];
14574 }
14575 $patch_array[0]['f'] = 0;
14576 $patch_array[0]['points'] = $coords;
14577 $patch_array[0]['colors'][0]['r'] = $col1[0];
14578 $patch_array[0]['colors'][0]['g'] = $col1[1];
14579 $patch_array[0]['colors'][0]['b'] = $col1[2];
14580 $patch_array[0]['colors'][1]['r'] = $col2[0];
14581 $patch_array[0]['colors'][1]['g'] = $col2[1];
14582 $patch_array[0]['colors'][1]['b'] = $col2[2];
14583 $patch_array[0]['colors'][2]['r'] = $col3[0];
14584 $patch_array[0]['colors'][2]['g'] = $col3[1];
14585 $patch_array[0]['colors'][2]['b'] = $col3[2];
14586 $patch_array[0]['colors'][3]['r'] = $col4[0];
14587 $patch_array[0]['colors'][3]['g'] = $col4[1];
14588 $patch_array[0]['colors'][3]['b'] = $col4[2];
14589 } else {
14590 //multi patch array
14591 $patch_array = $coords;
14592 }
14593 $bpcd = 65535; //16 bits per coordinate
14594 //build the data stream
14595 $this->gradients[$n]['stream'] = '';
14596 $count_patch = count($patch_array);
14597 for ($i=0; $i < $count_patch; ++$i) {
14598 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
14599 $count_points = count($patch_array[$i]['points']);
14600 for ($j=0; $j < $count_points; ++$j) {
14601 //each point as 16 bit
14602 $patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
14603 if ($patch_array[$i]['points'][$j] < 0) {
14604 $patch_array[$i]['points'][$j] = 0;
14605 }
14606 if ($patch_array[$i]['points'][$j] > $bpcd) {
14607 $patch_array[$i]['points'][$j] = $bpcd;
14608 }
14609 $this->gradients[$n]['stream'] .= chr((int) floor($patch_array[$i]['points'][$j] / 256));
14610 $this->gradients[$n]['stream'] .= chr((int) floor(intval($patch_array[$i]['points'][$j]) % 256));
14611 }
14612 $count_cols = count($patch_array[$i]['colors']);
14613 for ($j=0; $j < $count_cols; ++$j) {
14614 //each color component as 8 bit
14615 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
14616 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
14617 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
14618 }
14619 }
14620 //paint the gradient
14621 $this->_out('/Sh'.$n.' sh');
14622 //restore previous Graphic State
14623 $this->_outRestoreGraphicsState();
14624 if ($this->inxobj) {
14625 // we are inside an XObject template
14626 $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14627 }
14628 }
14629
14630 /**
14631 * Set a rectangular clipping area.
14632 * @param float $x abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
14633 * @param float $y ordinate of the top left corner of the rectangle.
14634 * @param float $w width of the rectangle.
14635 * @param float $h height of the rectangle.
14636 * @author Andreas W\FCrmser,2026 Nicola Asuni
14637 * @since 3.1.000 (2008-06-09)
14638 * @protected
14639 */
14640 protected function Clip($x, $y, $w, $h) {
14641 if ($this->state != 2) {
14642 return;
14643 }
14644 if ($this->rtl) {
14645 $x = $this->w - $x - $w;
14646 }
14647 //save current Graphic State
14648 $s = 'q';
14649 //set clipping area
14650 $s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
14651 //set up transformation matrix for gradient
14652 $s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
14653 $this->_out($s);
14654 }
14655
14656 /**
14657 * Output gradient.
14658 * @param int $type type of gradient (1 Function-based shading; 2 Axial shading; 3 Radial shading; 4 Free-form Gouraud-shaded triangle mesh; 5 Lattice-form Gouraud-shaded triangle mesh; 6 Coons patch mesh; 7 Tensor-product patch mesh). (Not all types are currently supported)
14659 * @param array $coords array of coordinates.
14660 * @param array $stops array gradient color components: color = array of GRAY, RGB or CMYK color components; offset = (0 to 1) represents a location along the gradient vector; exponent = exponent of the exponential interpolation function (default = 1).
14661 * @param array $background An array of colour components appropriate to the colour space, specifying a single background colour value.
14662 * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14663 * @author Nicola Asuni
14664 * @since 3.1.000 (2008-06-09)
14665 * @public
14666 */
14667 public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
14668 if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) {
14669 return;
14670 }
14671 $n = count($this->gradients) + 1;
14672 $this->gradients[$n] = array();
14673 $this->gradients[$n]['type'] = $type;
14674 $this->gradients[$n]['coords'] = $coords;
14675 $this->gradients[$n]['antialias'] = $antialias;
14676 $this->gradients[$n]['colors'] = array();
14677 $this->gradients[$n]['transparency'] = false;
14678 // color space
14679 $numcolspace = count($stops[0]['color']);
14680 $bcolor = array_values($background);
14681 switch($numcolspace) {
14682 case 5: // SPOT
14683 case 4: { // CMYK
14684 $this->gradients[$n]['colspace'] = 'DeviceCMYK';
14685 if (!empty($background)) {
14686 $this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
14687 }
14688 break;
14689 }
14690 case 3: { // RGB
14691 $this->gradients[$n]['colspace'] = 'DeviceRGB';
14692 if (!empty($background)) {
14693 $this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
14694 }
14695 break;
14696 }
14697 case 1: { // GRAY SCALE
14698 $this->gradients[$n]['colspace'] = 'DeviceGray';
14699 if (!empty($background)) {
14700 $this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
14701 }
14702 break;
14703 }
14704 }
14705 $num_stops = count($stops);
14706 $last_stop_id = $num_stops - 1;
14707 foreach ($stops as $key => $stop) {
14708 $this->gradients[$n]['colors'][$key] = array();
14709 // offset represents a location along the gradient vector
14710 if (isset($stop['offset'])) {
14711 $this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
14712 } else {
14713 if ($key == 0) {
14714 $this->gradients[$n]['colors'][$key]['offset'] = 0;
14715 } elseif ($key == $last_stop_id) {
14716 $this->gradients[$n]['colors'][$key]['offset'] = 1;
14717 } else {
14718 $offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
14719 $this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
14720 }
14721 }
14722 if (isset($stop['opacity'])) {
14723 $this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
14724 if ((!($this->pdfa_mode && $this->pdfa_version < 2)) AND ($stop['opacity'] < 1)) {
14725 $this->gradients[$n]['transparency'] = true;
14726 }
14727 } else {
14728 $this->gradients[$n]['colors'][$key]['opacity'] = 1;
14729 }
14730 // exponent for the exponential interpolation function
14731 if (isset($stop['exponent'])) {
14732 $this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
14733 } else {
14734 $this->gradients[$n]['colors'][$key]['exponent'] = 1;
14735 }
14736 // set colors
14737 $color = array_values($stop['color']);
14738 switch($numcolspace) {
14739 case 5: // SPOT
14740 case 4: { // CMYK
14741 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
14742 break;
14743 }
14744 case 3: { // RGB
14745 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
14746 break;
14747 }
14748 case 1: { // GRAY SCALE
14749 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
14750 break;
14751 }
14752 }
14753 }
14754 if ($this->gradients[$n]['transparency']) {
14755 // paint luminosity gradient
14756 $this->_out('/TGS'.$n.' gs');
14757 }
14758 //paint the gradient
14759 $this->_out('/Sh'.$n.' sh');
14760 //restore previous Graphic State
14761 $this->_outRestoreGraphicsState();
14762 if ($this->inxobj) {
14763 // we are inside an XObject template
14764 $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14765 }
14766 }
14767
14768 /**
14769 * Output gradient shaders.
14770 * @author Nicola Asuni
14771 * @since 3.1.000 (2008-06-09)
14772 * @protected
14773 */
14774 function _putshaders() {
14775 if ($this->pdfa_mode && $this->pdfa_version < 2) {
14776 return;
14777 }
14778 $idt = count($this->gradients); //index for transparency gradients
14779 foreach ($this->gradients as $id => $grad) {
14780 if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
14781 $fc = $this->_newobj();
14782 $out = '<<';
14783 $out .= ' /FunctionType 3';
14784 $out .= ' /Domain [0 1]';
14785 $functions = '';
14786 $bounds = '';
14787 $encode = '';
14788 $i = 1;
14789 $num_cols = count($grad['colors']);
14790 $lastcols = $num_cols - 1;
14791 for ($i = 1; $i < $num_cols; ++$i) {
14792 $functions .= ($fc + $i).' 0 R ';
14793 if ($i < $lastcols) {
14794 $bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
14795 }
14796 $encode .= '0 1 ';
14797 }
14798 $out .= ' /Functions ['.trim($functions).']';
14799 $out .= ' /Bounds ['.trim($bounds).']';
14800 $out .= ' /Encode ['.trim($encode).']';
14801 $out .= ' >>';
14802 $out .= "\n".'endobj';
14803 $this->_out($out);
14804 for ($i = 1; $i < $num_cols; ++$i) {
14805 $this->_newobj();
14806 $out = '<<';
14807 $out .= ' /FunctionType 2';
14808 $out .= ' /Domain [0 1]';
14809 $out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
14810 $out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
14811 $out .= ' /N '.$grad['colors'][$i]['exponent'];
14812 $out .= ' >>';
14813 $out .= "\n".'endobj';
14814 $this->_out($out);
14815 }
14816 // set transparency functions
14817 if ($grad['transparency']) {
14818 $ft = $this->_newobj();
14819 $out = '<<';
14820 $out .= ' /FunctionType 3';
14821 $out .= ' /Domain [0 1]';
14822 $functions = '';
14823 $i = 1;
14824 $num_cols = count($grad['colors']);
14825 for ($i = 1; $i < $num_cols; ++$i) {
14826 $functions .= ($ft + $i).' 0 R ';
14827 }
14828 $out .= ' /Functions ['.trim($functions).']';
14829 $out .= ' /Bounds ['.trim($bounds).']';
14830 $out .= ' /Encode ['.trim($encode).']';
14831 $out .= ' >>';
14832 $out .= "\n".'endobj';
14833 $this->_out($out);
14834 for ($i = 1; $i < $num_cols; ++$i) {
14835 $this->_newobj();
14836 $out = '<<';
14837 $out .= ' /FunctionType 2';
14838 $out .= ' /Domain [0 1]';
14839 $out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
14840 $out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
14841 $out .= ' /N '.$grad['colors'][$i]['exponent'];
14842 $out .= ' >>';
14843 $out .= "\n".'endobj';
14844 $this->_out($out);
14845 }
14846 }
14847 }
14848 // set shading object
14849 $this->_newobj();
14850 $out = '<< /ShadingType '.$grad['type'];
14851 if (isset($grad['colspace'])) {
14852 $out .= ' /ColorSpace /'.$grad['colspace'];
14853 } else {
14854 $out .= ' /ColorSpace /DeviceRGB';
14855 }
14856 if (isset($grad['background']) AND !empty($grad['background'])) {
14857 $out .= ' /Background ['.$grad['background'].']';
14858 }
14859 if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
14860 $out .= ' /AntiAlias true';
14861 }
14862 if ($grad['type'] == 2) {
14863 $out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
14864 $out .= ' /Domain [0 1]';
14865 $out .= ' /Function '.$fc.' 0 R';
14866 $out .= ' /Extend [true true]';
14867 $out .= ' >>';
14868 } elseif ($grad['type'] == 3) {
14869 //x0, y0, r0, x1, y1, r1
14870 //at this this time radius of inner circle is 0
14871 $out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
14872 $out .= ' /Domain [0 1]';
14873 $out .= ' /Function '.$fc.' 0 R';
14874 $out .= ' /Extend [true true]';
14875 $out .= ' >>';
14876 } elseif ($grad['type'] == 6) {
14877 $out .= ' /BitsPerCoordinate 16';
14878 $out .= ' /BitsPerComponent 8';
14879 $out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
14880 $out .= ' /BitsPerFlag 8';
14881 $stream = $this->_getrawstream($grad['stream']);
14882 $out .= ' /Length '.strlen($stream);
14883 $out .= ' >>';
14884 $out .= ' stream'."\n".$stream."\n".'endstream';
14885 }
14886 $out .= "\n".'endobj';
14887 $this->_out($out);
14888 if ($grad['transparency']) {
14889 $shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
14890 $shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
14891 }
14892 $this->gradients[$id]['id'] = $this->n;
14893 // set pattern object
14894 $this->_newobj();
14895 $out = '<< /Type /Pattern /PatternType 2';
14896 $out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
14897 $out .= ' >>';
14898 $out .= "\n".'endobj';
14899 $this->_out($out);
14900 $this->gradients[$id]['pattern'] = $this->n;
14901 // set shading and pattern for transparency mask
14902 if ($grad['transparency']) {
14903 // luminosity pattern
14904 $idgs = $id + $idt;
14905 $this->_newobj();
14906 $this->_out($shading_transparency);
14907 $this->gradients[$idgs]['id'] = $this->n;
14908 $this->_newobj();
14909 $out = '<< /Type /Pattern /PatternType 2';
14910 $out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
14911 $out .= ' >>';
14912 $out .= "\n".'endobj';
14913 $this->_out($out);
14914 $this->gradients[$idgs]['pattern'] = $this->n;
14915 // luminosity XObject
14916 $oid = $this->_newobj();
14917 $this->xobjects['LX'.$oid] = array('n' => $oid);
14918 $filter = '';
14919 $stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
14920 if ($this->compress) {
14921 $filter = ' /Filter /FlateDecode';
14922 $stream = gzcompress($stream);
14923 }
14924 $stream = $this->_getrawstream($stream);
14925 $out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
14926 $out .= ' /Length '.strlen($stream);
14927 $rect = sprintf('%F %F', $this->wPt, $this->hPt);
14928 $out .= ' /BBox [0 0 '.$rect.']';
14929 $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
14930 $out .= ' /Resources <<';
14931 $out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
14932 $out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
14933 $out .= ' >>';
14934 $out .= ' >> ';
14935 $out .= ' stream'."\n".$stream."\n".'endstream';
14936 $out .= "\n".'endobj';
14937 $this->_out($out);
14938 // SMask
14939 $this->_newobj();
14940 $out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
14941 $this->_out($out);
14942 // ExtGState
14943 $this->_newobj();
14944 $out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
14945 $this->_out($out);
14946 $this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
14947 }
14948 }
14949 }
14950
14951 /**
14952 * Draw the sector of a circle.
14953 * It can be used for instance to render pie charts.
14954 * @param float $xc abscissa of the center.
14955 * @param float $yc ordinate of the center.
14956 * @param float $r radius.
14957 * @param float $a start angle (in degrees).
14958 * @param float $b end angle (in degrees).
14959 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
14960 * @param float $cw indicates whether to go clockwise (default: true).
14961 * @param float $o origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). Default: 90.
14962 * @author Maxime Delorme,2026 Nicola Asuni
14963 * @since 3.1.000 (2008-06-09)
14964 * @public
14965 */
14966 public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
14967 $this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
14968 }
14969
14970 /**
14971 * Draw the sector of an ellipse.
14972 * It can be used for instance to render pie charts.
14973 * @param float $xc abscissa of the center.
14974 * @param float $yc ordinate of the center.
14975 * @param float $rx the x-axis radius.
14976 * @param float $ry the y-axis radius.
14977 * @param float $a start angle (in degrees).
14978 * @param float $b end angle (in degrees).
14979 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
14980 * @param float $cw indicates whether to go clockwise.
14981 * @param float $o origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
14982 * @param integer $nc Number of curves used to draw a 90 degrees portion of arc.
14983 * @author Maxime Delorme,2026 Nicola Asuni
14984 * @since 3.1.000 (2008-06-09)
14985 * @public
14986 */
14987 public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
14988 if ($this->state != 2) {
14989 return;
14990 }
14991 if ($this->rtl) {
14992 $xc = ($this->w - $xc);
14993 }
14994 $op = TCPDF_STATIC::getPathPaintOperator($style);
14995 if ($op == 'f') {
14996 $line_style = array();
14997 }
14998 if ($cw) {
14999 $d = $b;
15000 $b = (360 - $a + $o);
15001 $a = (360 - $d + $o);
15002 } else {
15003 $b += $o;
15004 $a += $o;
15005 }
15006 $this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
15007 $this->_out($op);
15008 }
15009
15010 /**
15011 * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
15012 * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
15013 * Only vector drawing is supported, not text or bitmap.
15014 * 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).
15015 * @param string $file Name of the file containing the image or a '@' character followed by the EPS/AI data string.
15016 * @param float|null $x Abscissa of the upper-left corner.
15017 * @param float|null $y Ordinate of the upper-left corner.
15018 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
15019 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
15020 * @param mixed $link URL or identifier returned by AddLink().
15021 * @param boolean $useBoundingBox specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
15022 * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
15023 * @param string $palign Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
15024 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
15025 * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
15026 * @param boolean $fixoutvals if true remove values outside the bounding box.
15027 * @author Valentin Schmidt,2026 Nicola Asuni
15028 * @since 3.1.000 (2008-06-09)
15029 * @public
15030 */
15031 public function ImageEps($file, $x=null, $y=null, $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
15032 if ($this->state != 2) {
15033 return;
15034 }
15035 if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
15036 // convert EPS to raster image using GD or ImageMagick libraries
15037 return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
15038 }
15039 if (TCPDF_STATIC::empty_string($x)) {
15040 $x = $this->x;
15041 }
15042 if (TCPDF_STATIC::empty_string($y)) {
15043 $y = $this->y;
15044 }
15045 // check page for no-write regions and adapt page margins if necessary
15046 list($x, $y) = $this->checkPageRegions($h, $x, $y);
15047 $k = $this->k;
15048 if ($file[0] === '@') { // image from string
15049 $data = substr($file, 1);
15050 } else { // EPS/AI file
15051 $data = $this->getCachedFileContents($file);
15052 }
15053 if ($data === FALSE) {
15054 $this->Error('EPS file not found: '.$file);
15055 }
15056 $regs = array();
15057 // EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
15058 preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
15059 if (count($regs) > 1) {
15060 $version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
15061 if (strpos($version_str, 'Adobe Illustrator') !== false) {
15062 $versexp = explode(' ', $version_str);
15063 $version = (float)array_pop($versexp);
15064 if ($version >= 9) {
15065 $this->Error('This version of Adobe Illustrator file is not supported: '.$file);
15066 }
15067 }
15068 }
15069 // strip binary bytes in front of PS-header
15070 $start = strpos($data, '%!PS-Adobe');
15071 if ($start > 0) {
15072 $data = substr($data, $start);
15073 }
15074 // find BoundingBox params
15075 preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
15076 if (count($regs) > 1) {
15077 list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
15078 } else {
15079 $this->Error('No BoundingBox found in EPS/AI file: '.$file);
15080 }
15081 $start = strpos($data, '%%EndSetup');
15082 if ($start === false) {
15083 $start = strpos($data, '%%EndProlog');
15084 }
15085 if ($start === false) {
15086 $start = strpos($data, '%%BoundingBox');
15087 }
15088 $data = substr($data, $start);
15089 $end = strpos($data, '%%PageTrailer');
15090 if ($end===false) {
15091 $end = strpos($data, 'showpage');
15092 }
15093 if ($end) {
15094 $data = substr($data, 0, $end);
15095 }
15096 // calculate image width and height on document
15097 if (($w <= 0) AND ($h <= 0)) {
15098 $w = ($x2 - $x1) / $k;
15099 $h = ($y2 - $y1) / $k;
15100 } elseif ($w <= 0) {
15101 $w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
15102 } elseif ($h <= 0) {
15103 $h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
15104 }
15105 // fit the image on available space
15106 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
15107 if ($this->rasterize_vector_images) {
15108 // convert EPS to raster image using GD or ImageMagick libraries
15109 return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
15110 }
15111 // set scaling factors
15112 $scale_x = $w / (($x2 - $x1) / $k);
15113 $scale_y = $h / (($y2 - $y1) / $k);
15114 // set alignment
15115 $this->img_rb_y = $y + $h;
15116 // set alignment
15117 if ($this->rtl) {
15118 if ($palign == 'L') {
15119 $ximg = $this->lMargin;
15120 } elseif ($palign == 'C') {
15121 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15122 } elseif ($palign == 'R') {
15123 $ximg = $this->w - $this->rMargin - $w;
15124 } else {
15125 $ximg = $x - $w;
15126 }
15127 $this->img_rb_x = $ximg;
15128 } else {
15129 if ($palign == 'L') {
15130 $ximg = $this->lMargin;
15131 } elseif ($palign == 'C') {
15132 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15133 } elseif ($palign == 'R') {
15134 $ximg = $this->w - $this->rMargin - $w;
15135 } else {
15136 $ximg = $x;
15137 }
15138 $this->img_rb_x = $ximg + $w;
15139 }
15140 if ($useBoundingBox) {
15141 $dx = $ximg * $k - $x1;
15142 $dy = $y * $k - $y1;
15143 } else {
15144 $dx = $ximg * $k;
15145 $dy = $y * $k;
15146 }
15147 // save the current graphic state
15148 $this->_out('q'.$this->epsmarker);
15149 // translate
15150 $this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
15151 // scale
15152 $this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
15153 // handle pc/unix/mac line endings
15154 $lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
15155 $u=0;
15156 $cnt = count($lines);
15157 for ($i=0; $i < $cnt; ++$i) {
15158 $line = $lines[$i];
15159 if (($line == '') OR ($line[0] == '%')) {
15160 continue;
15161 }
15162 $len = strlen($line);
15163 // check for spot color names
15164 $color_name = '';
15165 if (strcasecmp('x', substr(trim($line), -1)) == 0) {
15166 if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
15167 // extract spot color name
15168 $color_name = $matches[0];
15169 // remove color name from string
15170 $line = str_replace(' '.$color_name, '', $line);
15171 // remove pharentesis from color name
15172 $color_name = substr($color_name, 1, -1);
15173 }
15174 }
15175 $chunks = explode(' ', $line);
15176 $cmd = trim(array_pop($chunks));
15177 // RGB
15178 if (($cmd == 'Xa') OR ($cmd == 'XA')) {
15179 $b = array_pop($chunks);
15180 $g = array_pop($chunks);
15181 $r = array_pop($chunks);
15182 $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!
15183 continue;
15184 }
15185 $skip = false;
15186 if ($fixoutvals) {
15187 // check for values outside the bounding box
15188 switch ($cmd) {
15189 case 'm':
15190 case 'l':
15191 case 'L': {
15192 // skip values outside bounding box
15193 foreach ($chunks as $key => $val) {
15194 if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
15195 $skip = true;
15196 } elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
15197 $skip = true;
15198 }
15199 }
15200 }
15201 }
15202 }
15203 switch ($cmd) {
15204 case 'm':
15205 case 'l':
15206 case 'v':
15207 case 'y':
15208 case 'c':
15209 case 'k':
15210 case 'K':
15211 case 'g':
15212 case 'G':
15213 case 's':
15214 case 'S':
15215 case 'J':
15216 case 'j':
15217 case 'w':
15218 case 'M':
15219 case 'd':
15220 case 'n': {
15221 if ($skip) {
15222 break;
15223 }
15224 $this->_out($line);
15225 break;
15226 }
15227 case 'x': {// custom fill color
15228 if (empty($color_name)) {
15229 // CMYK color
15230 list($col_c, $col_m, $col_y, $col_k) = $chunks;
15231 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
15232 } else {
15233 // Spot Color (CMYK + tint)
15234 list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15235 $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15236 $color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15237 $this->_out($color_cmd);
15238 }
15239 break;
15240 }
15241 case 'X': { // custom stroke color
15242 if (empty($color_name)) {
15243 // CMYK color
15244 list($col_c, $col_m, $col_y, $col_k) = $chunks;
15245 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
15246 } else {
15247 // Spot Color (CMYK + tint)
15248 list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15249 $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15250 $color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15251 $this->_out($color_cmd);
15252 }
15253 break;
15254 }
15255 case 'Y':
15256 case 'N':
15257 case 'V':
15258 case 'L':
15259 case 'C': {
15260 if ($skip) {
15261 break;
15262 }
15263 $line[($len - 1)] = strtolower($cmd);
15264 $this->_out($line);
15265 break;
15266 }
15267 case 'b':
15268 case 'B': {
15269 $this->_out($cmd . '*');
15270 break;
15271 }
15272 case 'f':
15273 case 'F': {
15274 if ($u > 0) {
15275 $isU = false;
15276 $max = min(($i + 5), $cnt);
15277 for ($j = ($i + 1); $j < $max; ++$j) {
15278 $isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
15279 }
15280 if ($isU) {
15281 $this->_out('f*');
15282 }
15283 } else {
15284 $this->_out('f*');
15285 }
15286 break;
15287 }
15288 case '*u': {
15289 ++$u;
15290 break;
15291 }
15292 case '*U': {
15293 --$u;
15294 break;
15295 }
15296 }
15297 }
15298 // restore previous graphic state
15299 $this->_out($this->epsmarker.'Q');
15300 if (!empty($border)) {
15301 $bx = $this->x;
15302 $by = $this->y;
15303 $this->x = $ximg;
15304 if ($this->rtl) {
15305 $this->x += $w;
15306 }
15307 $this->y = $y;
15308 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
15309 $this->x = $bx;
15310 $this->y = $by;
15311 }
15312 if ($link) {
15313 $this->Link($ximg, $y, $w, $h, $link, 0);
15314 }
15315 // set pointer to align the next text/objects
15316 switch($align) {
15317 case 'T':{
15318 $this->y = $y;
15319 $this->x = $this->img_rb_x;
15320 break;
15321 }
15322 case 'M':{
15323 $this->y = $y + round($h/2);
15324 $this->x = $this->img_rb_x;
15325 break;
15326 }
15327 case 'B':{
15328 $this->y = $this->img_rb_y;
15329 $this->x = $this->img_rb_x;
15330 break;
15331 }
15332 case 'N':{
15333 $this->setY($this->img_rb_y);
15334 break;
15335 }
15336 default:{
15337 break;
15338 }
15339 }
15340 $this->endlinex = $this->img_rb_x;
15341 }
15342
15343 /**
15344 * Set document barcode.
15345 * @param string $bc barcode
15346 * @public
15347 */
15348 public function setBarcode($bc='') {
15349 $this->barcode = $bc;
15350 }
15351
15352 /**
15353 * Get current barcode.
15354 * @return string
15355 * @public
15356 * @since 4.0.012 (2008-07-24)
15357 */
15358 public function getBarcode() {
15359 return $this->barcode;
15360 }
15361
15362 /**
15363 * Print a Linear Barcode.
15364 * @param string $code code to print
15365 * @param string $type type of barcode (see tcpdf_barcodes_1d.php for supported formats).
15366 * @param float|null $x x position in user units (null = current x position)
15367 * @param float|null $y y position in user units (null = current y position)
15368 * @param float|null $w width in user units (null = remaining page width)
15369 * @param float|null $h height in user units (null = remaining page height)
15370 * @param float|null $xres width of the smallest bar in user units (null = default value = 0.4mm)
15371 * @param array $style array of options:<ul>
15372 * <li>boolean $style['border'] if true prints a border</li>
15373 * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
15374 * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
15375 * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
15376 * <li>array $style['fgcolor'] color array for bars and text</li>
15377 * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
15378 * <li>boolean $style['text'] if true prints text below the barcode</li>
15379 * <li>string $style['label'] override default label</li>
15380 * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
15381 * <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>
15382 * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
15383 * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
15384 * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
15385 * <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>
15386 * <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>
15387 * @param string $align Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
15388 * @author Nicola Asuni
15389 * @since 3.1.000 (2008-06-09)
15390 * @public
15391 */
15392 public function write1DBarcode($code, $type, $x=null, $y=null, $w=null, $h=null, $xres=null, $style=array(), $align='') {
15393 if (TCPDF_STATIC::empty_string(trim($code))) {
15394 return;
15395 }
15396 require_once(dirname(__FILE__).'/tcpdf_barcodes_1d.php');
15397 // save current graphic settings
15398 $gvars = $this->getGraphicVars();
15399 // create new barcode object
15400 $barcodeobj = new TCPDFBarcode($code, $type);
15401 $arrcode = $barcodeobj->getBarcodeArray();
15402 if (empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
15403 $this->Error('Error in 1D barcode string');
15404 }
15405 if ($arrcode['maxh'] <= 0) {
15406 $arrcode['maxh'] = 1;
15407 }
15408 // set default values
15409 if (!isset($style['position'])) {
15410 $style['position'] = '';
15411 } elseif ($style['position'] == 'S') {
15412 // keep this for backward compatibility
15413 $style['position'] = '';
15414 $style['stretch'] = true;
15415 }
15416 if (!isset($style['fitwidth'])) {
15417 if (!isset($style['stretch'])) {
15418 $style['fitwidth'] = true;
15419 } else {
15420 $style['fitwidth'] = false;
15421 }
15422 }
15423 if ($style['fitwidth']) {
15424 // disable stretch
15425 $style['stretch'] = false;
15426 }
15427 if (!isset($style['stretch'])) {
15428 if (($w === '') OR ($w <= 0)) {
15429 $style['stretch'] = false;
15430 } else {
15431 $style['stretch'] = true;
15432 }
15433 }
15434 if (!isset($style['fgcolor'])) {
15435 $style['fgcolor'] = array(0,0,0); // default black
15436 }
15437 if (!isset($style['bgcolor'])) {
15438 $style['bgcolor'] = false; // default transparent
15439 }
15440 if (!isset($style['border'])) {
15441 $style['border'] = false;
15442 }
15443 $fontsize = 0;
15444 if (!isset($style['text'])) {
15445 $style['text'] = false;
15446 }
15447 if ($style['text'] AND isset($style['font'])) {
15448 if (isset($style['fontsize'])) {
15449 $fontsize = $style['fontsize'];
15450 }
15451 $this->setFont($style['font'], '', $fontsize);
15452 }
15453 if (!isset($style['stretchtext'])) {
15454 $style['stretchtext'] = 4;
15455 }
15456 if (TCPDF_STATIC::empty_string($x)) {
15457 $x = $this->x;
15458 }
15459 if (TCPDF_STATIC::empty_string($y)) {
15460 $y = $this->y;
15461 }
15462 // check page for no-write regions and adapt page margins if necessary
15463 list($x, $y) = $this->checkPageRegions($h, $x, $y);
15464 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
15465 if ($this->rtl) {
15466 $w = $x - $this->lMargin;
15467 } else {
15468 $w = $this->w - $this->rMargin - $x;
15469 }
15470 }
15471 // padding
15472 if (!isset($style['padding'])) {
15473 $padding = 0;
15474 } elseif ($style['padding'] === 'auto') {
15475 $padding = 10 * ($w / ($arrcode['maxw'] + 20));
15476 } else {
15477 $padding = floatval($style['padding']);
15478 }
15479 // horizontal padding
15480 if (!isset($style['hpadding'])) {
15481 $hpadding = $padding;
15482 } elseif ($style['hpadding'] === 'auto') {
15483 $hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
15484 } else {
15485 $hpadding = floatval($style['hpadding']);
15486 }
15487 // vertical padding
15488 if (!isset($style['vpadding'])) {
15489 $vpadding = $padding;
15490 } elseif ($style['vpadding'] === 'auto') {
15491 $vpadding = ($hpadding / 2);
15492 } else {
15493 $vpadding = floatval($style['vpadding']);
15494 }
15495 // calculate xres (single bar width)
15496 $max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
15497 if ($style['stretch']) {
15498 $xres = $max_xres;
15499 } else {
15500 if (TCPDF_STATIC::empty_string($xres)) {
15501 $xres = (0.141 * $this->k); // default bar width = 0.4 mm
15502 }
15503 if ($xres > $max_xres) {
15504 // correct xres to fit on $w
15505 $xres = $max_xres;
15506 }
15507 if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
15508 OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
15509 $hpadding = 10 * $xres;
15510 if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
15511 $vpadding = ($hpadding / 2);
15512 }
15513 }
15514 }
15515 if ($style['fitwidth']) {
15516 $wold = $w;
15517 $w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
15518 if (isset($style['cellfitalign'])) {
15519 switch ($style['cellfitalign']) {
15520 case 'L': {
15521 if ($this->rtl) {
15522 $x -= ($wold - $w);
15523 }
15524 break;
15525 }
15526 case 'R': {
15527 if (!$this->rtl) {
15528 $x += ($wold - $w);
15529 }
15530 break;
15531 }
15532 case 'C': {
15533 if ($this->rtl) {
15534 $x -= (($wold - $w) / 2);
15535 } else {
15536 $x += (($wold - $w) / 2);
15537 }
15538 break;
15539 }
15540 default : {
15541 break;
15542 }
15543 }
15544 }
15545 }
15546 $text_height = $this->getCellHeight($fontsize / $this->k);
15547 // height
15548 if (TCPDF_STATIC::empty_string($h) OR ($h <= 0)) {
15549 // set default height
15550 $h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
15551 }
15552 $barh = $h - $text_height - (2 * $vpadding);
15553 if ($barh <=0) {
15554 // try to reduce font or padding to fit barcode on available height
15555 if ($text_height > $h) {
15556 $fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
15557 $text_height = $this->getCellHeight($fontsize / $this->k);
15558 $this->setFont($style['font'], '', $fontsize);
15559 }
15560 if ($vpadding > 0) {
15561 $vpadding = (($h - $text_height) / 4);
15562 }
15563 $barh = $h - $text_height - (2 * $vpadding);
15564 }
15565 // fit the barcode on available space
15566 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15567 // set alignment
15568 $this->img_rb_y = $y + $h;
15569 // set alignment
15570 if ($this->rtl) {
15571 if ($style['position'] == 'L') {
15572 $xpos = $this->lMargin;
15573 } elseif ($style['position'] == 'C') {
15574 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15575 } elseif ($style['position'] == 'R') {
15576 $xpos = $this->w - $this->rMargin - $w;
15577 } else {
15578 $xpos = $x - $w;
15579 }
15580 $this->img_rb_x = $xpos;
15581 } else {
15582 if ($style['position'] == 'L') {
15583 $xpos = $this->lMargin;
15584 } elseif ($style['position'] == 'C') {
15585 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15586 } elseif ($style['position'] == 'R') {
15587 $xpos = $this->w - $this->rMargin - $w;
15588 } else {
15589 $xpos = $x;
15590 }
15591 $this->img_rb_x = $xpos + $w;
15592 }
15593 $xpos_rect = $xpos;
15594 if (!isset($style['align'])) {
15595 $style['align'] = 'C';
15596 }
15597 switch ($style['align']) {
15598 case 'L': {
15599 $xpos = $xpos_rect + $hpadding;
15600 break;
15601 }
15602 case 'R': {
15603 $xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
15604 break;
15605 }
15606 case 'C':
15607 default : {
15608 $xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
15609 break;
15610 }
15611 }
15612 $xpos_text = $xpos;
15613 // barcode is always printed in LTR direction
15614 $tempRTL = $this->rtl;
15615 $this->rtl = false;
15616 // print background color
15617 if ($style['bgcolor']) {
15618 $this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15619 } elseif ($style['border']) {
15620 $this->Rect($xpos_rect, $y, $w, $h, 'D');
15621 }
15622 // set foreground color
15623 $this->setDrawColorArray($style['fgcolor']);
15624 $this->setTextColorArray($style['fgcolor']);
15625 // print bars
15626 foreach ($arrcode['bcode'] as $k => $v) {
15627 $bw = ($v['w'] * $xres);
15628 if ($v['t']) {
15629 // draw a vertical bar
15630 $ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
15631 $this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
15632 }
15633 $xpos += $bw;
15634 }
15635 // print text
15636 if ($style['text']) {
15637 if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
15638 $label = $style['label'];
15639 } else {
15640 $label = $code;
15641 }
15642 $txtwidth = ($arrcode['maxw'] * $xres);
15643 if ($this->GetStringWidth($label) > $txtwidth) {
15644 $style['stretchtext'] = 2;
15645 }
15646 // print text
15647 $this->x = $xpos_text;
15648 $this->y = $y + $vpadding + $barh;
15649 $cellpadding = $this->cell_padding;
15650 $this->setCellPadding(0);
15651 $this->Cell($txtwidth, 0, $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
15652 $this->cell_padding = $cellpadding;
15653 }
15654 // restore original direction
15655 $this->rtl = $tempRTL;
15656 // restore previous settings
15657 $this->setGraphicVars($gvars);
15658 // set pointer to align the next text/objects
15659 switch($align) {
15660 case 'T':{
15661 $this->y = $y;
15662 $this->x = $this->img_rb_x;
15663 break;
15664 }
15665 case 'M':{
15666 $this->y = $y + round($h / 2);
15667 $this->x = $this->img_rb_x;
15668 break;
15669 }
15670 case 'B':{
15671 $this->y = $this->img_rb_y;
15672 $this->x = $this->img_rb_x;
15673 break;
15674 }
15675 case 'N':{
15676 $this->setY($this->img_rb_y);
15677 break;
15678 }
15679 default:{
15680 break;
15681 }
15682 }
15683 $this->endlinex = $this->img_rb_x;
15684 }
15685
15686 /**
15687 * Print 2D Barcode.
15688 * @param string $code code to print
15689 * @param string $type type of barcode (see tcpdf_barcodes_2d.php for supported formats).
15690 * @param float|null $x x position in user units
15691 * @param float|null $y y position in user units
15692 * @param float|null $w width in user units
15693 * @param float|null $h height in user units
15694 * @param array $style array of options:<ul>
15695 * <li>boolean $style['border'] if true prints a border around the barcode</li>
15696 * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
15697 * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
15698 * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
15699 * <li>int $style['module_width'] width of a single module in points</li>
15700 * <li>int $style['module_height'] height of a single module in points</li>
15701 * <li>array $style['fgcolor'] color array for bars and text</li>
15702 * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
15703 * <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li>
15704 * @param string $align Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
15705 * @param boolean $distort if true distort the barcode to fit width and height, otherwise preserve aspect ratio
15706 * @author Nicola Asuni
15707 * @since 4.5.037 (2009-04-07)
15708 * @public
15709 */
15710 public function write2DBarcode($code, $type, $x=null, $y=null, $w=null, $h=null, $style=array(), $align='', $distort=false) {
15711 if (TCPDF_STATIC::empty_string(trim($code))) {
15712 return;
15713 }
15714 require_once(dirname(__FILE__).'/tcpdf_barcodes_2d.php');
15715 // save current graphic settings
15716 $gvars = $this->getGraphicVars();
15717 // create new barcode object
15718 $barcodeobj = new TCPDF2DBarcode($code, $type);
15719 $arrcode = $barcodeobj->getBarcodeArray();
15720 if (empty($arrcode) OR !isset($arrcode['num_rows']) OR ($arrcode['num_rows'] == 0) OR !isset($arrcode['num_cols']) OR ($arrcode['num_cols'] == 0)) {
15721 $this->Error('Error in 2D barcode string');
15722 }
15723 // set default values
15724 if (!isset($style['position'])) {
15725 $style['position'] = '';
15726 }
15727 if (!isset($style['fgcolor'])) {
15728 $style['fgcolor'] = array(0,0,0); // default black
15729 }
15730 if (!isset($style['bgcolor'])) {
15731 $style['bgcolor'] = false; // default transparent
15732 }
15733 if (!isset($style['border'])) {
15734 $style['border'] = false;
15735 }
15736 // padding
15737 if (!isset($style['padding'])) {
15738 $style['padding'] = 0;
15739 } elseif ($style['padding'] === 'auto') {
15740 $style['padding'] = 4;
15741 }
15742 if (!isset($style['hpadding'])) {
15743 $style['hpadding'] = $style['padding'];
15744 } elseif ($style['hpadding'] === 'auto') {
15745 $style['hpadding'] = 4;
15746 }
15747 if (!isset($style['vpadding'])) {
15748 $style['vpadding'] = $style['padding'];
15749 } elseif ($style['vpadding'] === 'auto') {
15750 $style['vpadding'] = 4;
15751 }
15752 $hpad = (2 * $style['hpadding']);
15753 $vpad = (2 * $style['vpadding']);
15754 // cell (module) dimension
15755 if (!isset($style['module_width'])) {
15756 $style['module_width'] = 1; // width of a single module in points
15757 }
15758 if (!isset($style['module_height'])) {
15759 $style['module_height'] = 1; // height of a single module in points
15760 }
15761 if (TCPDF_STATIC::empty_string($x)) {
15762 $x = $this->x;
15763 }
15764 if (TCPDF_STATIC::empty_string($y)) {
15765 $y = $this->y;
15766 }
15767 // check page for no-write regions and adapt page margins if necessary
15768 list($x, $y) = $this->checkPageRegions($h, $x, $y);
15769 // number of barcode columns and rows
15770 $rows = $arrcode['num_rows'];
15771 $cols = $arrcode['num_cols'];
15772 if (($rows <= 0) || ($cols <= 0)){
15773 $this->Error('Error in 2D barcode string');
15774 }
15775 // module width and height
15776 $mw = $style['module_width'];
15777 $mh = $style['module_height'];
15778 if (($mw <= 0) OR ($mh <= 0)) {
15779 $this->Error('Error in 2D barcode string');
15780 }
15781 // get max dimensions
15782 if ($this->rtl) {
15783 $maxw = $x - $this->lMargin;
15784 } else {
15785 $maxw = $this->w - $this->rMargin - $x;
15786 }
15787 $maxh = ($this->h - $this->tMargin - $this->bMargin);
15788 $ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
15789 $ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
15790 if (!$distort) {
15791 if (($maxw * $ratioHW) > $maxh) {
15792 $maxw = $maxh * $ratioWH;
15793 }
15794 if (($maxh * $ratioWH) > $maxw) {
15795 $maxh = $maxw * $ratioHW;
15796 }
15797 }
15798 // set maximum dimensions
15799 if ($w > $maxw) {
15800 $w = $maxw;
15801 }
15802 if ($h > $maxh) {
15803 $h = $maxh;
15804 }
15805 // set dimensions
15806 if ((TCPDF_STATIC::empty_string($w) OR ($w <= 0)) AND (TCPDF_STATIC::empty_string($h) OR ($h <= 0))) {
15807 $w = ($cols + $hpad) * ($mw / $this->k);
15808 $h = ($rows + $vpad) * ($mh / $this->k);
15809 } elseif (($w === '') OR ($w <= 0)) {
15810 $w = $h * $ratioWH;
15811 } elseif (($h === '') OR ($h <= 0)) {
15812 $h = $w * $ratioHW;
15813 }
15814 // barcode size (excluding padding)
15815 $bw = ($w * $cols) / ($cols + $hpad);
15816 $bh = ($h * $rows) / ($rows + $vpad);
15817 // dimension of single barcode cell unit
15818 $cw = $bw / $cols;
15819 $ch = $bh / $rows;
15820 if (!$distort) {
15821 if (($cw / $ch) > ($mw / $mh)) {
15822 // correct horizontal distortion
15823 $cw = $ch * $mw / $mh;
15824 $bw = $cw * $cols;
15825 $style['hpadding'] = ($w - $bw) / (2 * $cw);
15826 } else {
15827 // correct vertical distortion
15828 $ch = $cw * $mh / $mw;
15829 $bh = $ch * $rows;
15830 $style['vpadding'] = ($h - $bh) / (2 * $ch);
15831 }
15832 }
15833 // fit the barcode on available space
15834 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15835 // set alignment
15836 $this->img_rb_y = $y + $h;
15837 // set alignment
15838 if ($this->rtl) {
15839 if ($style['position'] == 'L') {
15840 $xpos = $this->lMargin;
15841 } elseif ($style['position'] == 'C') {
15842 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15843 } elseif ($style['position'] == 'R') {
15844 $xpos = $this->w - $this->rMargin - $w;
15845 } else {
15846 $xpos = $x - $w;
15847 }
15848 $this->img_rb_x = $xpos;
15849 } else {
15850 if ($style['position'] == 'L') {
15851 $xpos = $this->lMargin;
15852 } elseif ($style['position'] == 'C') {
15853 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15854 } elseif ($style['position'] == 'R') {
15855 $xpos = $this->w - $this->rMargin - $w;
15856 } else {
15857 $xpos = $x;
15858 }
15859 $this->img_rb_x = $xpos + $w;
15860 }
15861 $xstart = $xpos + ($style['hpadding'] * $cw);
15862 $ystart = $y + ($style['vpadding'] * $ch);
15863 // barcode is always printed in LTR direction
15864 $tempRTL = $this->rtl;
15865 $this->rtl = false;
15866 // print background color
15867 if ($style['bgcolor']) {
15868 $this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15869 } elseif ($style['border']) {
15870 $this->Rect($xpos, $y, $w, $h, 'D');
15871 }
15872 // set foreground color
15873 $this->setDrawColorArray($style['fgcolor']);
15874 // print barcode cells
15875 // for each row
15876 for ($r = 0; $r < $rows; ++$r) {
15877 $xr = $xstart;
15878 // for each column
15879 for ($c = 0; $c < $cols; ++$c) {
15880 if ($arrcode['bcode'][$r][$c] == 1) {
15881 // draw a single barcode cell
15882 $this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
15883 }
15884 $xr += $cw;
15885 }
15886 $ystart += $ch;
15887 }
15888 // restore original direction
15889 $this->rtl = $tempRTL;
15890 // restore previous settings
15891 $this->setGraphicVars($gvars);
15892 // set pointer to align the next text/objects
15893 switch($align) {
15894 case 'T':{
15895 $this->y = $y;
15896 $this->x = $this->img_rb_x;
15897 break;
15898 }
15899 case 'M':{
15900 $this->y = $y + round($h/2);
15901 $this->x = $this->img_rb_x;
15902 break;
15903 }
15904 case 'B':{
15905 $this->y = $this->img_rb_y;
15906 $this->x = $this->img_rb_x;
15907 break;
15908 }
15909 case 'N':{
15910 $this->setY($this->img_rb_y);
15911 break;
15912 }
15913 default:{
15914 break;
15915 }
15916 }
15917 $this->endlinex = $this->img_rb_x;
15918 }
15919
15920 /**
15921 * Returns an array containing current margins:
15922 * <ul>
15923 <li>$ret['left'] = left margin</li>
15924 <li>$ret['right'] = right margin</li>
15925 <li>$ret['top'] = top margin</li>
15926 <li>$ret['bottom'] = bottom margin</li>
15927 <li>$ret['header'] = header margin</li>
15928 <li>$ret['footer'] = footer margin</li>
15929 <li>$ret['cell'] = cell padding array</li>
15930 <li>$ret['padding_left'] = cell left padding</li>
15931 <li>$ret['padding_top'] = cell top padding</li>
15932 <li>$ret['padding_right'] = cell right padding</li>
15933 <li>$ret['padding_bottom'] = cell bottom padding</li>
15934 * </ul>
15935 * @return array containing all margins measures
15936 * @public
15937 * @since 3.2.000 (2008-06-23)
15938 */
15939 public function getMargins() {
15940 $ret = array(
15941 'left' => $this->lMargin,
15942 'right' => $this->rMargin,
15943 'top' => $this->tMargin,
15944 'bottom' => $this->bMargin,
15945 'header' => $this->header_margin,
15946 'footer' => $this->footer_margin,
15947 'cell' => $this->cell_padding,
15948 'padding_left' => $this->cell_padding['L'],
15949 'padding_top' => $this->cell_padding['T'],
15950 'padding_right' => $this->cell_padding['R'],
15951 'padding_bottom' => $this->cell_padding['B']
15952 );
15953 return $ret;
15954 }
15955
15956 /**
15957 * Returns an array containing original margins:
15958 * <ul>
15959 <li>$ret['left'] = left margin</li>
15960 <li>$ret['right'] = right margin</li>
15961 * </ul>
15962 * @return array containing all margins measures
15963 * @public
15964 * @since 4.0.012 (2008-07-24)
15965 */
15966 public function getOriginalMargins() {
15967 $ret = array(
15968 'left' => $this->original_lMargin,
15969 'right' => $this->original_rMargin
15970 );
15971 return $ret;
15972 }
15973
15974 /**
15975 * Returns the current font size.
15976 * @return float current font size
15977 * @public
15978 * @since 3.2.000 (2008-06-23)
15979 */
15980 public function getFontSize() {
15981 return $this->FontSize;
15982 }
15983
15984 /**
15985 * Returns the current font size in points unit.
15986 * @return int current font size in points unit
15987 * @public
15988 * @since 3.2.000 (2008-06-23)
15989 */
15990 public function getFontSizePt() {
15991 return $this->FontSizePt;
15992 }
15993
15994 /**
15995 * Returns the current font family name.
15996 * @return string current font family name
15997 * @public
15998 * @since 4.3.008 (2008-12-05)
15999 */
16000 public function getFontFamily() {
16001 return $this->FontFamily;
16002 }
16003
16004 /**
16005 * Returns the current font style.
16006 * @return string current font style
16007 * @public
16008 * @since 4.3.008 (2008-12-05)
16009 */
16010 public function getFontStyle() {
16011 return $this->FontStyle;
16012 }
16013
16014 /**
16015 * Cleanup HTML code (requires HTML Tidy library).
16016 * @param string $html htmlcode to fix
16017 * @param string $default_css CSS commands to add
16018 * @param array|null $tagvs parameters for setHtmlVSpace method
16019 * @param array|null $tidy_options options for tidy_parse_string function
16020 * @return string XHTML code cleaned up
16021 * @author Nicola Asuni
16022 * @public
16023 * @since 5.9.017 (2010-11-16)
16024 * @see setHtmlVSpace()
16025 */
16026 public function fixHTMLCode($html, $default_css='', $tagvs=null, $tidy_options=null) {
16027 return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces);
16028 }
16029
16030 /**
16031 * Returns the border width from CSS property
16032 * @param string $width border width
16033 * @return int with in user units
16034 * @protected
16035 * @since 5.7.000 (2010-08-02)
16036 */
16037 protected function getCSSBorderWidth($width) {
16038 if ($width == 'thin') {
16039 $width = (2 / $this->k);
16040 } elseif ($width == 'medium') {
16041 $width = (4 / $this->k);
16042 } elseif ($width == 'thick') {
16043 $width = (6 / $this->k);
16044 } else {
16045 $width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
16046 }
16047 return $width;
16048 }
16049
16050 /**
16051 * Returns the border dash style from CSS property
16052 * @param string $style border style to convert
16053 * @return int sash style (return -1 in case of none or hidden border)
16054 * @protected
16055 * @since 5.7.000 (2010-08-02)
16056 */
16057 protected function getCSSBorderDashStyle($style) {
16058 switch (strtolower($style)) {
16059 case 'none':
16060 case 'hidden': {
16061 $dash = -1;
16062 break;
16063 }
16064 case 'dotted': {
16065 $dash = 1;
16066 break;
16067 }
16068 case 'dashed': {
16069 $dash = 3;
16070 break;
16071 }
16072 case 'double':
16073 case 'groove':
16074 case 'ridge':
16075 case 'inset':
16076 case 'outset':
16077 case 'solid':
16078 default: {
16079 $dash = 0;
16080 break;
16081 }
16082 }
16083 return $dash;
16084 }
16085
16086 /**
16087 * Returns the border style array from CSS border properties
16088 * @param string $cssborder border properties
16089 * @return array containing border properties
16090 * @protected
16091 * @since 5.7.000 (2010-08-02)
16092 */
16093 protected function getCSSBorderStyle($cssborder) {
16094 $bprop = preg_split('/[\s]+/', trim($cssborder));
16095 $count = count($bprop);
16096 if ($count > 0 && $bprop[$count - 1] === '!important') {
16097 unset($bprop[$count - 1]);
16098 --$count;
16099 }
16100
16101 $border = array(); // value to be returned
16102 switch ($count) {
16103 case 2: {
16104 $width = 'medium';
16105 $style = $bprop[0];
16106 $color = $bprop[1];
16107 break;
16108 }
16109 case 1: {
16110 $width = 'medium';
16111 $style = $bprop[0];
16112 $color = 'black';
16113 break;
16114 }
16115 case 0: {
16116 $width = 'medium';
16117 $style = 'solid';
16118 $color = 'black';
16119 break;
16120 }
16121 default: {
16122 $width = $bprop[0];
16123 $style = $bprop[1];
16124 $color = $bprop[2];
16125 break;
16126 }
16127 }
16128 if ($style == 'none') {
16129 return array();
16130 }
16131 $border['cap'] = 'square';
16132 $border['join'] = 'miter';
16133 $border['dash'] = $this->getCSSBorderDashStyle($style);
16134 if ($border['dash'] < 0) {
16135 return array();
16136 }
16137 $border['width'] = $this->getCSSBorderWidth($width);
16138 $border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
16139 return $border;
16140 }
16141
16142 /**
16143 * Get the internal Cell padding from CSS attribute.
16144 * @param string $csspadding padding properties
16145 * @param float $width width of the containing element
16146 * @return array of cell paddings
16147 * @public
16148 * @since 5.9.000 (2010-10-04)
16149 */
16150 public function getCSSPadding($csspadding, $width=0) {
16151 $padding = preg_split('/[\s]+/', trim($csspadding));
16152 $cell_padding = array(); // value to be returned
16153 switch (count($padding)) {
16154 case 4: {
16155 $cell_padding['T'] = $padding[0];
16156 $cell_padding['R'] = $padding[1];
16157 $cell_padding['B'] = $padding[2];
16158 $cell_padding['L'] = $padding[3];
16159 break;
16160 }
16161 case 3: {
16162 $cell_padding['T'] = $padding[0];
16163 $cell_padding['R'] = $padding[1];
16164 $cell_padding['B'] = $padding[2];
16165 $cell_padding['L'] = $padding[1];
16166 break;
16167 }
16168 case 2: {
16169 $cell_padding['T'] = $padding[0];
16170 $cell_padding['R'] = $padding[1];
16171 $cell_padding['B'] = $padding[0];
16172 $cell_padding['L'] = $padding[1];
16173 break;
16174 }
16175 case 1: {
16176 $cell_padding['T'] = $padding[0];
16177 $cell_padding['R'] = $padding[0];
16178 $cell_padding['B'] = $padding[0];
16179 $cell_padding['L'] = $padding[0];
16180 break;
16181 }
16182 default: {
16183 return $this->cell_padding;
16184 }
16185 }
16186 if ($width == 0) {
16187 $width = $this->w - $this->lMargin - $this->rMargin;
16188 }
16189 $cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
16190 $cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
16191 $cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
16192 $cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
16193 return $cell_padding;
16194 }
16195
16196 /**
16197 * Get the internal Cell margin from CSS attribute.
16198 * @param string $cssmargin margin properties
16199 * @param float $width width of the containing element
16200 * @return array of cell margins
16201 * @public
16202 * @since 5.9.000 (2010-10-04)
16203 */
16204 public function getCSSMargin($cssmargin, $width=0) {
16205 $margin = preg_split('/[\s]+/', trim($cssmargin));
16206 $cell_margin = array(); // value to be returned
16207 switch (count($margin)) {
16208 case 4: {
16209 $cell_margin['T'] = $margin[0];
16210 $cell_margin['R'] = $margin[1];
16211 $cell_margin['B'] = $margin[2];
16212 $cell_margin['L'] = $margin[3];
16213 break;
16214 }
16215 case 3: {
16216 $cell_margin['T'] = $margin[0];
16217 $cell_margin['R'] = $margin[1];
16218 $cell_margin['B'] = $margin[2];
16219 $cell_margin['L'] = $margin[1];
16220 break;
16221 }
16222 case 2: {
16223 $cell_margin['T'] = $margin[0];
16224 $cell_margin['R'] = $margin[1];
16225 $cell_margin['B'] = $margin[0];
16226 $cell_margin['L'] = $margin[1];
16227 break;
16228 }
16229 case 1: {
16230 $cell_margin['T'] = $margin[0];
16231 $cell_margin['R'] = $margin[0];
16232 $cell_margin['B'] = $margin[0];
16233 $cell_margin['L'] = $margin[0];
16234 break;
16235 }
16236 default: {
16237 return $this->cell_margin;
16238 }
16239 }
16240 if ($width == 0) {
16241 $width = $this->w - $this->lMargin - $this->rMargin;
16242 }
16243 $cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
16244 $cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
16245 $cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
16246 $cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
16247 return $cell_margin;
16248 }
16249
16250 /**
16251 * Get the border-spacing from CSS attribute.
16252 * @param string $cssbspace border-spacing CSS properties
16253 * @param float $width width of the containing element
16254 * @return array of border spacings
16255 * @public
16256 * @since 5.9.010 (2010-10-27)
16257 */
16258 public function getCSSBorderMargin($cssbspace, $width=0) {
16259 $space = preg_split('/[\s]+/', trim($cssbspace));
16260 $border_spacing = array(); // value to be returned
16261 switch (count($space)) {
16262 case 2: {
16263 $border_spacing['H'] = $space[0];
16264 $border_spacing['V'] = $space[1];
16265 break;
16266 }
16267 case 1: {
16268 $border_spacing['H'] = $space[0];
16269 $border_spacing['V'] = $space[0];
16270 break;
16271 }
16272 default: {
16273 return array('H' => 0, 'V' => 0);
16274 }
16275 }
16276 if ($width == 0) {
16277 $width = $this->w - $this->lMargin - $this->rMargin;
16278 }
16279 $border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
16280 $border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
16281 return $border_spacing;
16282 }
16283
16284 /**
16285 * Returns the letter-spacing value from CSS value
16286 * @param string $spacing letter-spacing value
16287 * @param float $parent font spacing (tracking) value of the parent element
16288 * @return float quantity to increases or decreases the space between characters in a text.
16289 * @protected
16290 * @since 5.9.000 (2010-10-02)
16291 */
16292 protected function getCSSFontSpacing($spacing, $parent=0) {
16293 $val = 0; // value to be returned
16294 $spacing = trim($spacing);
16295 switch ($spacing) {
16296 case 'normal': {
16297 $val = 0;
16298 break;
16299 }
16300 case 'inherit': {
16301 if ($parent == 'normal') {
16302 $val = 0;
16303 } else {
16304 $val = $parent;
16305 }
16306 break;
16307 }
16308 default: {
16309 $val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
16310 }
16311 }
16312 return $val;
16313 }
16314
16315 /**
16316 * Returns the percentage of font stretching from CSS value
16317 * @param string $stretch stretch mode
16318 * @param float $parent stretch value of the parent element
16319 * @return float font stretching percentage
16320 * @protected
16321 * @since 5.9.000 (2010-10-02)
16322 */
16323 protected function getCSSFontStretching($stretch, $parent=100) {
16324 $val = 100; // value to be returned
16325 $stretch = trim($stretch);
16326 switch ($stretch) {
16327 case 'ultra-condensed': {
16328 $val = 40;
16329 break;
16330 }
16331 case 'extra-condensed': {
16332 $val = 55;
16333 break;
16334 }
16335 case 'condensed': {
16336 $val = 70;
16337 break;
16338 }
16339 case 'semi-condensed': {
16340 $val = 85;
16341 break;
16342 }
16343 case 'normal': {
16344 $val = 100;
16345 break;
16346 }
16347 case 'semi-expanded': {
16348 $val = 115;
16349 break;
16350 }
16351 case 'expanded': {
16352 $val = 130;
16353 break;
16354 }
16355 case 'extra-expanded': {
16356 $val = 145;
16357 break;
16358 }
16359 case 'ultra-expanded': {
16360 $val = 160;
16361 break;
16362 }
16363 case 'wider': {
16364 $val = ($parent + 10);
16365 break;
16366 }
16367 case 'narrower': {
16368 $val = ($parent - 10);
16369 break;
16370 }
16371 case 'inherit': {
16372 if ($parent == 'normal') {
16373 $val = 100;
16374 } else {
16375 $val = $parent;
16376 }
16377 break;
16378 }
16379 default: {
16380 $val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
16381 }
16382 }
16383 return $val;
16384 }
16385
16386 /**
16387 * Convert HTML string containing font size value to points
16388 * @param string $val String containing font size value and unit.
16389 * @param float $refsize Reference font size in points.
16390 * @param float $parent_size Parent font size in points.
16391 * @param string $defaultunit Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
16392 * @return float value in points
16393 * @public
16394 */
16395 public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
16396 $refsize = TCPDF_FONTS::getFontRefSize($refsize);
16397 $parent_size = TCPDF_FONTS::getFontRefSize($parent_size, $refsize);
16398 switch ($val) {
16399 case 'xx-small': {
16400 $size = ($refsize - 4);
16401 break;
16402 }
16403 case 'x-small': {
16404 $size = ($refsize - 3);
16405 break;
16406 }
16407 case 'small': {
16408 $size = ($refsize - 2);
16409 break;
16410 }
16411 case 'medium': {
16412 $size = $refsize;
16413 break;
16414 }
16415 case 'large': {
16416 $size = ($refsize + 2);
16417 break;
16418 }
16419 case 'x-large': {
16420 $size = ($refsize + 4);
16421 break;
16422 }
16423 case 'xx-large': {
16424 $size = ($refsize + 6);
16425 break;
16426 }
16427 case 'smaller': {
16428 $size = ($parent_size - 3);
16429 break;
16430 }
16431 case 'larger': {
16432 $size = ($parent_size + 3);
16433 break;
16434 }
16435 default: {
16436 $parentSize = $this->getHTMLUnitToUnits($parent_size, $refsize, $defaultunit, true);
16437 $size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
16438 }
16439 }
16440 return $size;
16441 }
16442
16443 /**
16444 * Returns the HTML DOM array.
16445 * @param string $html html code
16446 * @return array
16447 * @protected
16448 * @since 3.2.000 (2008-06-20)
16449 */
16450 protected function getHtmlDomArray($html) {
16451 // set inheritable properties for the first void element
16452 // 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
16453 $dom = array(
16454 array(
16455 'tag' => false,
16456 'block' => false,
16457 'value' => '',
16458 'parent' => 0,
16459 'hide' => false,
16460 'fontname' => $this->FontFamily,
16461 'fontstyle' => $this->FontStyle,
16462 'fontsize' => $this->FontSizePt,
16463 'font-stretch' => $this->font_stretching,
16464 'letter-spacing' => $this->font_spacing,
16465 'stroke' => $this->textstrokewidth,
16466 'fill' => (($this->textrendermode % 2) == 0),
16467 'clip' => ($this->textrendermode > 3),
16468 'line-height' => $this->cell_height_ratio,
16469 'bgcolor' => false,
16470 'fgcolor' => $this->fgcolor, // color
16471 'strokecolor' => $this->strokecolor,
16472 'align' => '',
16473 'listtype' => '',
16474 'text-indent' => 0,
16475 'text-transform' => '',
16476 'border' => array(),
16477 'dir' => $this->rtl?'rtl':'ltr',
16478 'width' => 0,
16479 'height' => 0,
16480 'x' => 0,
16481 'y' => 0,
16482 'w' => 0,
16483 'h' => 0,
16484 'l' => 0,
16485 't' => 0,
16486 'r' => 0,
16487 'b' => 0,
16488 'padding' => array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0),
16489 'margin' => array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0),
16490 'border-spacing' => array('H' => 0, 'V' => 0),
16491 'border-collapse' => 'separate',
16492 )
16493 );
16494
16495 if($html === '' || $html === null) {
16496 return $dom;
16497 }
16498 // array of CSS styles ( selector => properties).
16499 $css = array();
16500 // get CSS array defined at previous call
16501 $matches = array();
16502 if (preg_match_all('/<cssarray>([^\<]*?)<\/cssarray>/is', $html, $matches) > 0) {
16503 if (isset($matches[1][0])) {
16504 $css = array_merge($css, json_decode($this->unhtmlentities($matches[1][0]), true));
16505 }
16506 $html = preg_replace('/<cssarray>(.*?)<\/cssarray>/is', '', $html);
16507 }
16508 // extract external CSS files
16509 $matches = array();
16510 if (preg_match_all('/<link([^\>]*?)>/is', $html, $matches) > 0) {
16511 foreach ($matches[1] as $key => $link) {
16512 $type = array();
16513 if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
16514 $type = array();
16515 preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
16516 // get 'all' and 'print' media, other media types are discarded
16517 // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16518 if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16519 $type = array();
16520 if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
16521 // read CSS data file
16522 $cssdata = $this->getCachedFileContents(trim($type[1]));
16523 if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
16524 $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16525 }
16526 }
16527 }
16528 }
16529 }
16530 }
16531 // extract style tags
16532 $matches = array();
16533 if (preg_match_all('/<style([^\>]*?)>([^\<]*?)<\/style>/is', $html, $matches) > 0) {
16534 foreach ($matches[1] as $key => $media) {
16535 $type = array();
16536 preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
16537 // get 'all' and 'print' media, other media types are discarded
16538 // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16539 if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16540 $cssdata = $matches[2][$key];
16541 $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16542 }
16543 }
16544 }
16545 // create a special tag to contain the CSS array (used for table content)
16546 $csstagarray = '<cssarray>'.htmlentities(json_encode($css)).'</cssarray>';
16547 // remove head and style blocks
16548 $html = preg_replace('/<head([^\>]*?)>(.*?)<\/head>/is', '', $html);
16549 $html = preg_replace('/<style([^\>]*?)>([^\<]*?)<\/style>/is', '', $html);
16550 // define block tags
16551 $blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
16552 // define self-closing tags
16553 $selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
16554 // remove all unsupported tags (the line below lists all supported tags)
16555 $html = strip_tags($html, '<marker/><a><b><blockquote><body><br><br/><dd><del><div><dl><dt><em><font><form><h1><h2><h3><h4><h5><h6><hr><hr/><i><img><input><label><li><ol><option><p><pre><s><select><small><span><strike><strong><sub><sup><table><tablehead><tcpdf><td><textarea><th><thead><tr><tt><u><ul>');
16556 //replace some blank characters
16557 $html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
16558 $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);
16559 $html = preg_replace('@(\r\n|\r)@', "\n", $html);
16560 $repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
16561 $html = strtr($html, $repTable);
16562 $offset = 0;
16563 while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
16564 $html_a = substr($html, 0, $offset);
16565 $html_b = substr($html, $offset, ($pos - $offset + 6));
16566 while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
16567 // preserve newlines on <pre> tag
16568 $html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
16569 }
16570 while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
16571 // preserve spaces on <pre> tag
16572 $html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
16573 }
16574 $html = $html_a.$html_b.substr($html, $pos + 6);
16575 $offset = strlen($html_a.$html_b);
16576 }
16577 $offset = 0;
16578 while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
16579 $html_a = substr($html, 0, $offset);
16580 $html_b = substr($html, $offset, ($pos - $offset + 11));
16581 while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
16582 // preserve newlines on <textarea> tag
16583 $html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
16584 $html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
16585 }
16586 $html = $html_a.$html_b.substr($html, $pos + 11);
16587 $offset = strlen($html_a.$html_b);
16588 }
16589 $html = preg_replace('/([\s]*)<option/si', '<option', $html);
16590 $html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
16591 $offset = 0;
16592 while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
16593 $html_a = substr($html, 0, $offset);
16594 $html_b = substr($html, $offset, ($pos - $offset + 9));
16595 while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
16596 $html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
16597 $html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
16598 }
16599 $html = $html_a.$html_b.substr($html, $pos + 9);
16600 $offset = strlen($html_a.$html_b);
16601 }
16602 if (preg_match("'</select'si", $html)) {
16603 $html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
16604 $html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
16605 }
16606 $html = str_replace("\n", ' ', $html);
16607 // restore textarea newlines
16608 $html = str_replace('<TBR>', "\n", $html);
16609 // remove extra spaces from code
16610 $html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
16611 $html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
16612 $html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
16613 $html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
16614 $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);
16615 $html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
16616 $html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
16617 $html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
16618 $html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
16619 $html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
16620 $html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
16621 $html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
16622 $html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
16623 $html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
16624 $html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
16625 $html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
16626 $html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
16627 $html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
16628 $html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
16629 // trim string
16630 $html = $this->stringTrim($html);
16631 // fix br tag after li
16632 $html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
16633 // fix first image tag alignment
16634 $html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
16635 // pattern for generic tag
16636 $tagpattern = '/(<[^>]+>)/';
16637 // explodes the string
16638 $a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
16639 // count elements
16640 $maxel = count($a);
16641 $elkey = 0;
16642 $thead = false; // true when we are inside the THEAD tag
16643 $key = 1;
16644 $level = array();
16645 array_push($level, 0); // root
16646 while ($elkey < $maxel) {
16647 $dom[$key] = array();
16648 $element = $a[$elkey];
16649 $dom[$key]['elkey'] = $elkey;
16650 if (preg_match($tagpattern, $element)) {
16651 // html tag
16652 $element = substr($element, 1, -1);
16653 // get tag name
16654 preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
16655 $tagname = strtolower($tag[1]);
16656 // check if we are inside a table header
16657 if ($tagname == 'thead') {
16658 if ($element[0] == '/') {
16659 $thead = false;
16660 } else {
16661 $thead = true;
16662 }
16663 ++$elkey;
16664 continue;
16665 }
16666 $dom[$key]['tag'] = true;
16667 $dom[$key]['value'] = $tagname;
16668 if (in_array($dom[$key]['value'], $blocktags)) {
16669 $dom[$key]['block'] = true;
16670 } else {
16671 $dom[$key]['block'] = false;
16672 }
16673 if ($element[0] == '/') {
16674 // *** closing html tag
16675 $dom[$key]['opening'] = false;
16676 $dom[$key]['parent'] = end($level);
16677 array_pop($level);
16678 $dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
16679 $dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
16680 $dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
16681 $dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
16682 $dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
16683 $dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
16684 $dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
16685 $dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
16686 $dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
16687 $dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
16688 $dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
16689 $dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
16690 $dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
16691 $dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
16692 $dom[$key]['text-transform'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['text-transform'];
16693 $dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
16694 if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
16695 $dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
16696 }
16697 // set the number of columns in table tag
16698 if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
16699 $dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
16700 }
16701 if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
16702 $dom[($dom[$key]['parent'])]['content'] = $csstagarray;
16703 for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
16704 $dom[($dom[$key]['parent'])]['content'] .= stripslashes($a[$dom[$i]['elkey']]);
16705 }
16706 $key = $i;
16707 // mark nested tables
16708 $dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
16709 // remove thead sections from nested tables
16710 $dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
16711 $dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
16712 }
16713 // store header rows on a new table
16714 if (
16715 ($dom[$key]['value'] === 'tr')
16716 && !empty($dom[($dom[$key]['parent'])]['thead'])
16717 && ($dom[($dom[$key]['parent'])]['thead'] === true)
16718 ) {
16719 if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
16720 $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
16721 }
16722 for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
16723 $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
16724 }
16725 if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
16726 $dom[($dom[$key]['parent'])]['attribute'] = array();
16727 }
16728 // header elements must be always contained in a single page
16729 $dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
16730 }
16731 if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
16732 // remove the nobr attributes from the table header
16733 $dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
16734 $dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
16735 }
16736 } else {
16737 // *** opening or self-closing html tag
16738 $dom[$key]['opening'] = true;
16739 $dom[$key]['parent'] = end($level);
16740 if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
16741 // self-closing tag
16742 $dom[$key]['self'] = true;
16743 } else {
16744 // opening tag
16745 array_push($level, $key);
16746 $dom[$key]['self'] = false;
16747 }
16748 // copy some values from parent
16749 $parentkey = 0;
16750 if ($key > 0) {
16751 $parentkey = $dom[$key]['parent'];
16752 $dom[$key]['hide'] = $dom[$parentkey]['hide'];
16753 $dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
16754 $dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
16755 $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
16756 $dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
16757 $dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
16758 $dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
16759 $dom[$key]['fill'] = $dom[$parentkey]['fill'];
16760 $dom[$key]['clip'] = $dom[$parentkey]['clip'];
16761 $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16762 $dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
16763 $dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
16764 $dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
16765 $dom[$key]['align'] = $dom[$parentkey]['align'];
16766 $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16767 $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16768 $dom[$key]['text-transform'] = $dom[$parentkey]['text-transform'];
16769 $dom[$key]['border'] = array();
16770 $dom[$key]['dir'] = $dom[$parentkey]['dir'];
16771 }
16772 // get attributes
16773 preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
16774 $dom[$key]['attribute'] = array(); // reset attribute array
16775 foreach($attr_array[1] as $id => $name) {
16776 $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
16777 }
16778 if (!empty($css)) {
16779 // merge CSS style to current style
16780 list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC::getCSSdataArray($dom, $key, $css);
16781 $dom[$key]['attribute']['style'] = TCPDF_STATIC::getTagStyleFromCSSarray($dom[$key]['cssdata']);
16782 }
16783 // split style attributes
16784 if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
16785 // get style attributes
16786 preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
16787 $dom[$key]['style'] = array(); // reset style attribute array
16788 foreach($style_array[1] as $id => $name) {
16789 // in case of duplicate attribute the last replace the previous
16790 $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
16791 }
16792 // --- get some style attributes ---
16793 // text direction
16794 if (isset($dom[$key]['style']['direction'])) {
16795 $dom[$key]['dir'] = $dom[$key]['style']['direction'];
16796 }
16797 // display
16798 if (isset($dom[$key]['style']['display'])) {
16799 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
16800 }
16801 // font family
16802 if (isset($dom[$key]['style']['font-family'])) {
16803 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
16804 }
16805 // list-style-type
16806 if (isset($dom[$key]['style']['list-style-type'])) {
16807 $dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
16808 if ($dom[$key]['listtype'] == 'inherit') {
16809 $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16810 }
16811 }
16812 // text-indent
16813 if (isset($dom[$key]['style']['text-indent'])) {
16814 $dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
16815 if ($dom[$key]['text-indent'] == 'inherit') {
16816 $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16817 }
16818 }
16819 // text-transform
16820 if (isset($dom[$key]['style']['text-transform'])) {
16821 $dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
16822 }
16823 // font size
16824 if (isset($dom[$key]['style']['font-size'])) {
16825 $fsize = trim($dom[$key]['style']['font-size']);
16826 $dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
16827 }
16828 // font-stretch
16829 if (isset($dom[$key]['style']['font-stretch'])) {
16830 $dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
16831 }
16832 // letter-spacing
16833 if (isset($dom[$key]['style']['letter-spacing'])) {
16834 $dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
16835 }
16836 // line-height (internally is the cell height ratio)
16837 if (isset($dom[$key]['style']['line-height'])) {
16838 $lineheight = trim($dom[$key]['style']['line-height']);
16839 switch ($lineheight) {
16840 // A normal line height. This is default
16841 case 'normal': {
16842 $dom[$key]['line-height'] = $dom[0]['line-height'];
16843 break;
16844 }
16845 case 'inherit': {
16846 $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16847 }
16848 default: {
16849 if (is_numeric($lineheight)) {
16850 // convert to percentage of font height
16851 $lineheight = ($lineheight * 100).'%';
16852 }
16853 $dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
16854 if (substr($lineheight, -1) !== '%') {
16855 if ($dom[$key]['fontsize'] <= 0) {
16856 $dom[$key]['line-height'] = 1;
16857 } else {
16858 $dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding['T'] - $this->cell_padding['B']) / $dom[$key]['fontsize']);
16859 }
16860 }
16861 }
16862 }
16863 }
16864 // font style
16865 if (isset($dom[$key]['style']['font-weight'])) {
16866 if (strtolower($dom[$key]['style']['font-weight'][0]) == 'n') {
16867 if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
16868 $dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
16869 }
16870 } elseif (strtolower($dom[$key]['style']['font-weight'][0]) == 'b') {
16871 $dom[$key]['fontstyle'] .= 'B';
16872 }
16873 }
16874 if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style'][0]) == 'i')) {
16875 $dom[$key]['fontstyle'] .= 'I';
16876 }
16877 // font color
16878 if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['color']))) {
16879 $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors);
16880 } elseif ($dom[$key]['value'] == 'a') {
16881 $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16882 }
16883 // background color
16884 if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['background-color']))) {
16885 $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors);
16886 }
16887 // text-decoration
16888 if (isset($dom[$key]['style']['text-decoration'])) {
16889 $decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
16890 foreach ($decors as $dec) {
16891 $dec = trim($dec);
16892 if (!TCPDF_STATIC::empty_string($dec)) {
16893 if ($dec[0] == 'u') {
16894 // underline
16895 $dom[$key]['fontstyle'] .= 'U';
16896 } elseif ($dec[0] == 'l') {
16897 // line-through
16898 $dom[$key]['fontstyle'] .= 'D';
16899 } elseif ($dec[0] == 'o') {
16900 // overline
16901 $dom[$key]['fontstyle'] .= 'O';
16902 }
16903 }
16904 }
16905 } elseif ($dom[$key]['value'] == 'a') {
16906 $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16907 }
16908 // check for width attribute
16909 if (isset($dom[$key]['style']['width'])) {
16910 $dom[$key]['width'] = $dom[$key]['style']['width'];
16911 }
16912 // check for height attribute
16913 if (isset($dom[$key]['style']['height'])) {
16914 $dom[$key]['height'] = $dom[$key]['style']['height'];
16915 }
16916 // check for text alignment
16917 if (isset($dom[$key]['style']['text-align'][0])) {
16918 $dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align'][0]);
16919 }
16920 // check for CSS border properties
16921 if (isset($dom[$key]['style']['border'])) {
16922 $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
16923 if (!empty($borderstyle)) {
16924 $dom[$key]['border']['LTRB'] = $borderstyle;
16925 }
16926 }
16927 if (isset($dom[$key]['style']['border-color'])) {
16928 $brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
16929 if (isset($brd_colors[3])) {
16930 $dom[$key]['border']['L']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[3], $this->spot_colors);
16931 }
16932 if (isset($brd_colors[1])) {
16933 $dom[$key]['border']['R']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[1], $this->spot_colors);
16934 }
16935 if (isset($brd_colors[0])) {
16936 $dom[$key]['border']['T']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[0], $this->spot_colors);
16937 }
16938 if (isset($brd_colors[2])) {
16939 $dom[$key]['border']['B']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[2], $this->spot_colors);
16940 }
16941 }
16942 if (isset($dom[$key]['style']['border-width'])) {
16943 $brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
16944 if (isset($brd_widths[3])) {
16945 $dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
16946 }
16947 if (isset($brd_widths[1])) {
16948 $dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
16949 }
16950 if (isset($brd_widths[0])) {
16951 $dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
16952 }
16953 if (isset($brd_widths[2])) {
16954 $dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
16955 }
16956 }
16957 if (isset($dom[$key]['style']['border-style'])) {
16958 $brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
16959 if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
16960 $dom[$key]['border']['L']['cap'] = 'square';
16961 $dom[$key]['border']['L']['join'] = 'miter';
16962 $dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
16963 if ($dom[$key]['border']['L']['dash'] < 0) {
16964 $dom[$key]['border']['L'] = array();
16965 }
16966 }
16967 if (isset($brd_styles[1])) {
16968 $dom[$key]['border']['R']['cap'] = 'square';
16969 $dom[$key]['border']['R']['join'] = 'miter';
16970 $dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
16971 if ($dom[$key]['border']['R']['dash'] < 0) {
16972 $dom[$key]['border']['R'] = array();
16973 }
16974 }
16975 if (isset($brd_styles[0])) {
16976 $dom[$key]['border']['T']['cap'] = 'square';
16977 $dom[$key]['border']['T']['join'] = 'miter';
16978 $dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
16979 if ($dom[$key]['border']['T']['dash'] < 0) {
16980 $dom[$key]['border']['T'] = array();
16981 }
16982 }
16983 if (isset($brd_styles[2])) {
16984 $dom[$key]['border']['B']['cap'] = 'square';
16985 $dom[$key]['border']['B']['join'] = 'miter';
16986 $dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
16987 if ($dom[$key]['border']['B']['dash'] < 0) {
16988 $dom[$key]['border']['B'] = array();
16989 }
16990 }
16991 }
16992 $cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
16993 foreach ($cellside as $bsk => $bsv) {
16994 if (isset($dom[$key]['style']['border-'.$bsv])) {
16995 $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
16996 if (!empty($borderstyle)) {
16997 $dom[$key]['border'][$bsk] = $borderstyle;
16998 }
16999 }
17000 if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
17001 $dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors);
17002 }
17003 if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
17004 $dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
17005 }
17006 if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
17007 $dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
17008 if ($dom[$key]['border'][$bsk]['dash'] < 0) {
17009 $dom[$key]['border'][$bsk] = array();
17010 }
17011 }
17012 }
17013 // check for CSS padding properties
17014 if (isset($dom[$key]['style']['padding'])) {
17015 $dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
17016 } else {
17017 $dom[$key]['padding'] = $this->cell_padding;
17018 }
17019 foreach ($cellside as $psk => $psv) {
17020 if (isset($dom[$key]['style']['padding-'.$psv])) {
17021 $dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
17022 }
17023 }
17024 // check for CSS margin properties
17025 if (isset($dom[$key]['style']['margin'])) {
17026 $dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
17027 } else {
17028 $dom[$key]['margin'] = $this->cell_margin;
17029 }
17030 foreach ($cellside as $psk => $psv) {
17031 if (isset($dom[$key]['style']['margin-'.$psv])) {
17032 $dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
17033 }
17034 }
17035 // check for CSS border-spacing properties
17036 if (isset($dom[$key]['style']['border-spacing'])) {
17037 $dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
17038 }
17039 // page-break-inside
17040 if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
17041 $dom[$key]['attribute']['nobr'] = 'true';
17042 }
17043 // page-break-before
17044 if (isset($dom[$key]['style']['page-break-before'])) {
17045 if ($dom[$key]['style']['page-break-before'] == 'always') {
17046 $dom[$key]['attribute']['pagebreak'] = 'true';
17047 } elseif ($dom[$key]['style']['page-break-before'] == 'left') {
17048 $dom[$key]['attribute']['pagebreak'] = 'left';
17049 } elseif ($dom[$key]['style']['page-break-before'] == 'right') {
17050 $dom[$key]['attribute']['pagebreak'] = 'right';
17051 }
17052 }
17053 // page-break-after
17054 if (isset($dom[$key]['style']['page-break-after'])) {
17055 if ($dom[$key]['style']['page-break-after'] == 'always') {
17056 $dom[$key]['attribute']['pagebreakafter'] = 'true';
17057 } elseif ($dom[$key]['style']['page-break-after'] == 'left') {
17058 $dom[$key]['attribute']['pagebreakafter'] = 'left';
17059 } elseif ($dom[$key]['style']['page-break-after'] == 'right') {
17060 $dom[$key]['attribute']['pagebreakafter'] = 'right';
17061 }
17062 }
17063 }
17064 if (isset($dom[$key]['attribute']['display'])) {
17065 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
17066 }
17067 if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
17068 $borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
17069 if (!empty($borderstyle)) {
17070 $dom[$key]['border']['LTRB'] = $borderstyle;
17071 }
17072 }
17073 // check for font tag
17074 if ($dom[$key]['value'] == 'font') {
17075 // font family
17076 if (isset($dom[$key]['attribute']['face'])) {
17077 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
17078 }
17079 // font size
17080 if (isset($dom[$key]['attribute']['size'])) {
17081 if ($key > 0) {
17082 if ($dom[$key]['attribute']['size'][0] == '+') {
17083 $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
17084 } elseif ($dom[$key]['attribute']['size'][0] == '-') {
17085 $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
17086 } else {
17087 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
17088 }
17089 } else {
17090 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
17091 }
17092 }
17093 }
17094 // force natural alignment for lists
17095 if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
17096 AND (!isset($dom[$key]['align']) OR TCPDF_STATIC::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
17097 if ($this->rtl) {
17098 $dom[$key]['align'] = 'R';
17099 } else {
17100 $dom[$key]['align'] = 'L';
17101 }
17102 }
17103 if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
17104 if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
17105 $dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
17106 }
17107 }
17108 if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
17109 $dom[$key]['fontstyle'] .= 'B';
17110 }
17111 if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
17112 $dom[$key]['fontstyle'] .= 'I';
17113 }
17114 if ($dom[$key]['value'] == 'u') {
17115 $dom[$key]['fontstyle'] .= 'U';
17116 }
17117 if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
17118 $dom[$key]['fontstyle'] .= 'D';
17119 }
17120 if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
17121 $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
17122 }
17123 if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
17124 $dom[$key]['fontname'] = $this->default_monospaced_font;
17125 }
17126 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)) {
17127 // headings h1, h2, h3, h4, h5, h6
17128 if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
17129 $headsize = (4 - intval($dom[$key]['value'][1])) * 2;
17130 $dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
17131 }
17132 if (!isset($dom[$key]['style']['font-weight'])) {
17133 $dom[$key]['fontstyle'] .= 'B';
17134 }
17135 }
17136 if (($dom[$key]['value'] == 'table')) {
17137 $dom[$key]['rows'] = 0; // number of rows
17138 $dom[$key]['trids'] = array(); // IDs of TR elements
17139 $dom[$key]['thead'] = ''; // table header rows
17140 }
17141 if (($dom[$key]['value'] == 'tr')) {
17142 $dom[$key]['cols'] = 0;
17143 if ($thead) {
17144 $dom[$key]['thead'] = true;
17145 // rows on thead block are printed as a separate table
17146 } else {
17147 $dom[$key]['thead'] = false;
17148 $parent = $dom[$key]['parent'];
17149
17150 if (!isset($dom[$parent]['rows'])) {
17151 $dom[$parent]['rows'] = 0;
17152 }
17153 // store the number of rows on table element
17154 ++$dom[$parent]['rows'];
17155
17156 if (!isset($dom[$parent]['trids'])) {
17157 $dom[$parent]['trids'] = array();
17158 }
17159
17160 // store the TR elements IDs on table element
17161 array_push($dom[$parent]['trids'], $key);
17162 }
17163 }
17164 if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
17165 if (isset($dom[$key]['attribute']['colspan'])) {
17166 $colspan = intval($dom[$key]['attribute']['colspan']);
17167 } else {
17168 $colspan = 1;
17169 }
17170 $dom[$key]['attribute']['colspan'] = $colspan;
17171 $dom[($dom[$key]['parent'])]['cols'] += $colspan;
17172 }
17173 // text direction
17174 if (isset($dom[$key]['attribute']['dir'])) {
17175 $dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
17176 }
17177 // set foreground color attribute
17178 if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['color']))) {
17179 $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors);
17180 } elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
17181 $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
17182 }
17183 // set background color attribute
17184 if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['bgcolor']))) {
17185 $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors);
17186 }
17187 // set stroke color attribute
17188 if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['strokecolor']))) {
17189 $dom[$key]['strokecolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors);
17190 }
17191 // check for width attribute
17192 if (isset($dom[$key]['attribute']['width'])) {
17193 $dom[$key]['width'] = $dom[$key]['attribute']['width'];
17194 }
17195 // check for height attribute
17196 if (isset($dom[$key]['attribute']['height'])) {
17197 $dom[$key]['height'] = $dom[$key]['attribute']['height'];
17198 }
17199 // check for text alignment
17200 if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
17201 $dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align'][0]);
17202 }
17203 // check for text rendering mode (the following attributes do not exist in HTML)
17204 if (isset($dom[$key]['attribute']['stroke'])) {
17205 // font stroke width
17206 $dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
17207 }
17208 if (isset($dom[$key]['attribute']['fill'])) {
17209 // font fill
17210 if ($dom[$key]['attribute']['fill'] == 'true') {
17211 $dom[$key]['fill'] = true;
17212 } else {
17213 $dom[$key]['fill'] = false;
17214 }
17215 }
17216 if (isset($dom[$key]['attribute']['clip'])) {
17217 // clipping mode
17218 if ($dom[$key]['attribute']['clip'] == 'true') {
17219 $dom[$key]['clip'] = true;
17220 } else {
17221 $dom[$key]['clip'] = false;
17222 }
17223 }
17224 } // end opening tag
17225 } else {
17226 // text
17227 $dom[$key]['tag'] = false;
17228 $dom[$key]['block'] = false;
17229 $dom[$key]['parent'] = end($level);
17230 $dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
17231 if (!empty($dom[$dom[$key]['parent']]['text-transform'])) {
17232 // text-transform for unicode requires mb_convert_case (Multibyte String Functions)
17233 if (function_exists('mb_convert_case')) {
17234 $ttm = array('capitalize' => MB_CASE_TITLE, 'uppercase' => MB_CASE_UPPER, 'lowercase' => MB_CASE_LOWER);
17235 if (isset($ttm[$dom[$dom[$key]['parent']]['text-transform']])) {
17236 $element = mb_convert_case($element, $ttm[$dom[$dom[$key]['parent']]['text-transform']], $this->encoding);
17237 }
17238 } elseif (!$this->isunicode) {
17239 switch ($dom[$dom[$key]['parent']]['text-transform']) {
17240 case 'capitalize': {
17241 $element = ucwords(strtolower($element));
17242 break;
17243 }
17244 case 'uppercase': {
17245 $element = strtoupper($element);
17246 break;
17247 }
17248 case 'lowercase': {
17249 $element = strtolower($element);
17250 break;
17251 }
17252 }
17253 }
17254 $element = preg_replace("/&NBSP;/i", "&nbsp;", $element);
17255 }
17256 $dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
17257 }
17258 ++$elkey;
17259 ++$key;
17260 }
17261 return $dom;
17262 }
17263
17264 /**
17265 * Returns the string used to find spaces
17266 * @return string
17267 * @protected
17268 * @author Nicola Asuni
17269 * @since 4.8.024 (2010-01-15)
17270 */
17271 protected function getSpaceString() {
17272 $spacestr = chr(32);
17273 if ($this->isUnicodeFont()) {
17274 $spacestr = chr(0).chr(32);
17275 }
17276 return $spacestr;
17277 }
17278
17279 /**
17280 * Calculates the hash value of the given data.
17281 *
17282 * @param string $data The data to be hashed.
17283 * @return string The hashed value of the data.
17284 */
17285 protected function hashTCPDFtag($data) {
17286 return hash_hmac('sha256', $data, $this->hash_key, false);
17287 }
17288
17289 /**
17290 * Serialize data to be used with TCPDF tag in HTML code.
17291 * @param string $method TCPDF method name
17292 * @param array $params Method parameters
17293 * @return string Serialized data
17294 * @public static
17295 */
17296 public function serializeTCPDFtag($method, $params=array()) {
17297 $data = array('m' => $method, 'p' => $params);
17298 $encoded = urlencode(json_encode($data));
17299 $hash = $this->hashTCPDFtag($encoded);
17300 return strlen($hash).'+'.$hash.'+'.$encoded;
17301 }
17302
17303 /**
17304 * Unserialize data to be used with TCPDF tag in HTML code.
17305 * @param string $data serialized data
17306 * @return array containing unserialized data
17307 * @protected static
17308 */
17309 protected function unserializeTCPDFtag($data) {
17310 $hpos = strpos($data, '+');
17311 $hlen = intval(substr($data, 0, $hpos));
17312 $hash = substr($data, $hpos + 1, $hlen);
17313 $encoded = substr($data, $hpos + 2 + $hlen);
17314 if (!hash_equals( $this->hashTCPDFtag($encoded), $hash)) {
17315 $this->Error('Invalid parameters');
17316 }
17317 return json_decode(urldecode($encoded), true);
17318 }
17319
17320 /**
17321 * Check if a TCPDF tag is allowed
17322 * @param string $method TCPDF method name
17323 * @return boolean
17324 * @protected
17325 */
17326 protected function allowedTCPDFtag($method) {
17327 if (defined('K_ALLOWED_TCPDF_TAGS')) {
17328 return (strpos(K_ALLOWED_TCPDF_TAGS, '|'.$method.'|') !== false);
17329 }
17330 return false;
17331 }
17332
17333 /**
17334 * Prints a cell (rectangular area) with optional borders, background color and html text string.
17335 * 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 />
17336 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
17337 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17338 * 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
17339 * NOTE: all the HTML attributes must be enclosed in double-quote.
17340 * @param float $w Cell width. If 0, the cell extends up to the right margin.
17341 * @param float $h Cell minimum height. The cell extends automatically if needed.
17342 * @param float|null $x upper-left corner X coordinate
17343 * @param float|null $y upper-left corner Y coordinate
17344 * @param string $html html text to print. Default value: empty string.
17345 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
17346 * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL language)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul> Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
17347 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
17348 * @param boolean $reseth if true reset the last cell height (default true).
17349 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
17350 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width.
17351 * @see Multicell(), writeHTML()
17352 * @public
17353 */
17354 public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
17355 return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
17356 }
17357
17358 /**
17359 * Allows to preserve some HTML formatting (limited support).<br />
17360 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17361 * 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
17362 * NOTE: all the HTML attributes must be enclosed in double-quote.
17363 * @param string $html text to display
17364 * @param boolean $ln if true add a new line after text (default = true)
17365 * @param boolean $fill Indicates if the background must be painted (true) or transparent (false).
17366 * @param boolean $reseth if true reset the last cell height (default false).
17367 * @param boolean $cell if true add the current left (or right for RTL) padding to each Write (default false).
17368 * @param string $align Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
17369 * @public
17370 */
17371 public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
17372 $gvars = $this->getGraphicVars();
17373 // store current values
17374 $prev_cell_margin = $this->cell_margin;
17375 $prev_cell_padding = $this->cell_padding;
17376 $prevPage = $this->page;
17377 $prevlMargin = $this->lMargin;
17378 $prevrMargin = $this->rMargin;
17379 $curfontname = $this->FontFamily;
17380 $curfontstyle = $this->FontStyle;
17381 $curfontsize = $this->FontSizePt;
17382 $curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
17383 $curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
17384 $curfontstretcing = $this->font_stretching;
17385 $curfonttracking = $this->font_spacing;
17386 $this->newline = true;
17387 $newline = true;
17388 $startlinepage = $this->page;
17389 $minstartliney = $this->y;
17390 $maxbottomliney = 0;
17391 $startlinex = $this->x;
17392 $startliney = $this->y;
17393 $yshift = 0;
17394 $loop = 0;
17395 $curpos = 0;
17396 $this_method_vars = array();
17397 $undo = false;
17398 $fontaligned = false;
17399 $reverse_dir = false; // true when the text direction is reversed
17400 $this->premode = false;
17401 if ($this->inxobj) {
17402 // we are inside an XObject template
17403 $pask = count($this->xobjects[$this->xobjid]['annotations']);
17404 } elseif (isset($this->PageAnnots[$this->page])) {
17405 $pask = count($this->PageAnnots[$this->page]);
17406 } else {
17407 $pask = 0;
17408 }
17409 if ($this->inxobj) {
17410 // we are inside an XObject template
17411 $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17412 } elseif (!$this->InFooter) {
17413 if (isset($this->footerlen[$this->page])) {
17414 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17415 } else {
17416 $this->footerpos[$this->page] = $this->pagelen[$this->page];
17417 }
17418 $startlinepos = $this->footerpos[$this->page];
17419 } else {
17420 // we are inside the footer
17421 $startlinepos = $this->pagelen[$this->page];
17422 }
17423 $lalign = $align;
17424 $plalign = $align;
17425 if ($this->rtl) {
17426 $w = $this->x - $this->lMargin;
17427 } else {
17428 $w = $this->w - $this->rMargin - $this->x;
17429 }
17430 $w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
17431 if ($cell) {
17432 if ($this->rtl) {
17433 $this->x -= $this->cell_padding['R'];
17434 $this->lMargin += $this->cell_padding['L'];
17435 } else {
17436 $this->x += $this->cell_padding['L'];
17437 $this->rMargin += $this->cell_padding['R'];
17438 }
17439 }
17440 if ($this->customlistindent >= 0) {
17441 $this->listindent = $this->customlistindent;
17442 } else {
17443 $this->listindent = $this->GetStringWidth('000000');
17444 }
17445 $this->listindentlevel = 0;
17446 // save previous states
17447 $prev_cell_height_ratio = $this->cell_height_ratio;
17448 $prev_listnum = $this->listnum;
17449 $prev_listordered = $this->listordered;
17450 $prev_listcount = $this->listcount;
17451 $prev_lispacer = $this->lispacer;
17452 $this->listnum = 0;
17453 $this->listordered = array();
17454 $this->listcount = array();
17455 $this->lispacer = '';
17456 if ((TCPDF_STATIC::empty_string($this->lasth)) OR ($reseth)) {
17457 // reset row height
17458 $this->resetLastH();
17459 }
17460 $dom = $this->getHtmlDomArray($html);
17461 $maxel = count($dom);
17462 $key = 0;
17463 while ($key < $maxel) {
17464 if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND $dom[$key]['hide']) {
17465 // store the node key
17466 $hidden_node_key = $key;
17467 if ($dom[$key]['self']) {
17468 // skip just this self-closing tag
17469 ++$key;
17470 } else {
17471 // skip this and all children tags
17472 while (($key < $maxel) AND (!$dom[$key]['tag'] OR $dom[$key]['opening'] OR ($dom[$key]['parent'] != $hidden_node_key))) {
17473 // skip hidden objects
17474 ++$key;
17475 }
17476 ++$key;
17477 }
17478 }
17479 if ($key == $maxel) break;
17480 if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND !empty($dom[$key]['attribute']['id'])) {
17481 $this->setDestination($dom[$key]['attribute']['id']);
17482 }
17483 if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
17484 // check for pagebreak
17485 if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
17486 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17487 $this->checkPageBreak($this->PageBreakTrigger + 1);
17488 $this->htmlvspace = ($this->PageBreakTrigger + 1);
17489 }
17490 if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
17491 OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
17492 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17493 $this->checkPageBreak($this->PageBreakTrigger + 1);
17494 $this->htmlvspace = ($this->PageBreakTrigger + 1);
17495 }
17496 }
17497 if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
17498 if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
17499 $dom[$key]['attribute']['nobr'] = false;
17500 } else {
17501 // store current object
17502 $this->startTransaction();
17503 // save this method vars
17504 $this_method_vars['html'] = $html;
17505 $this_method_vars['ln'] = $ln;
17506 $this_method_vars['fill'] = $fill;
17507 $this_method_vars['reseth'] = $reseth;
17508 $this_method_vars['cell'] = $cell;
17509 $this_method_vars['align'] = $align;
17510 $this_method_vars['gvars'] = $gvars;
17511 $this_method_vars['prevPage'] = $prevPage;
17512 $this_method_vars['prev_cell_margin'] = $prev_cell_margin;
17513 $this_method_vars['prev_cell_padding'] = $prev_cell_padding;
17514 $this_method_vars['prevlMargin'] = $prevlMargin;
17515 $this_method_vars['prevrMargin'] = $prevrMargin;
17516 $this_method_vars['curfontname'] = $curfontname;
17517 $this_method_vars['curfontstyle'] = $curfontstyle;
17518 $this_method_vars['curfontsize'] = $curfontsize;
17519 $this_method_vars['curfontascent'] = $curfontascent;
17520 $this_method_vars['curfontdescent'] = $curfontdescent;
17521 $this_method_vars['curfontstretcing'] = $curfontstretcing;
17522 $this_method_vars['curfonttracking'] = $curfonttracking;
17523 $this_method_vars['minstartliney'] = $minstartliney;
17524 $this_method_vars['maxbottomliney'] = $maxbottomliney;
17525 $this_method_vars['yshift'] = $yshift;
17526 $this_method_vars['startlinepage'] = $startlinepage;
17527 $this_method_vars['startlinepos'] = $startlinepos;
17528 $this_method_vars['startlinex'] = $startlinex;
17529 $this_method_vars['startliney'] = $startliney;
17530 $this_method_vars['newline'] = $newline;
17531 $this_method_vars['loop'] = $loop;
17532 $this_method_vars['curpos'] = $curpos;
17533 $this_method_vars['pask'] = $pask;
17534 $this_method_vars['lalign'] = $lalign;
17535 $this_method_vars['plalign'] = $plalign;
17536 $this_method_vars['w'] = $w;
17537 $this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
17538 $this_method_vars['prev_listnum'] = $prev_listnum;
17539 $this_method_vars['prev_listordered'] = $prev_listordered;
17540 $this_method_vars['prev_listcount'] = $prev_listcount;
17541 $this_method_vars['prev_lispacer'] = $prev_lispacer;
17542 $this_method_vars['fontaligned'] = $fontaligned;
17543 $this_method_vars['key'] = $key;
17544 $this_method_vars['dom'] = $dom;
17545 }
17546 }
17547 // print THEAD block
17548 if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
17549 if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC::empty_string($dom[$dom[$key]['parent']]['thead'])) {
17550 $this->inthead = true;
17551 // print table header (thead)
17552 $this->writeHTML($this->thead, false, false, false, false, '');
17553 // check if we are on a new page or on a new column
17554 if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
17555 // we are on a new page or on a new column and the total object height is less than the available vertical space.
17556 // restore previous object
17557 $this->rollbackTransaction(true);
17558 // restore previous values
17559 foreach ($this_method_vars as $vkey => $vval) {
17560 $$vkey = $vval;
17561 }
17562 // disable table header
17563 $tmp_thead = $this->thead;
17564 $this->thead = '';
17565 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17566 $pre_y = $this->y;
17567 if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
17568 // fix for multicolumn mode
17569 $startliney = $this->y;
17570 }
17571 $this->start_transaction_page = $this->page;
17572 $this->start_transaction_y = $this->y;
17573 // restore table header
17574 $this->thead = $tmp_thead;
17575 // fix table border properties
17576 if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
17577 $tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
17578 } elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
17579 $tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
17580 } else {
17581 $tmp_cellspacing = 0;
17582 }
17583 $dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
17584 $dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
17585 $dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
17586 $xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
17587 $dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
17588 $dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
17589 // print table header (thead)
17590 $this->writeHTML($this->thead, false, false, false, false, '');
17591 }
17592 }
17593 // move $key index forward to skip THEAD block
17594 while ( ($key < $maxel) AND (!(
17595 ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
17596 OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
17597 ++$key;
17598 }
17599 }
17600 if ($dom[$key]['tag'] OR ($key == 0)) {
17601 if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
17602 $dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
17603 }
17604 // vertically align image in line
17605 if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
17606 // get image height
17607 $imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], ($dom[$key]['fontsize'] / $this->k), 'px');
17608 $autolinebreak = false;
17609 if (!empty($dom[$key]['width'])) {
17610 $imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], ($dom[$key]['fontsize'] / $this->k), 'px', false);
17611 if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
17612 AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
17613 OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
17614 // add automatic line break
17615 $autolinebreak = true;
17616 $this->Ln('', $cell);
17617 if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
17618 // go back to evaluate this line break
17619 --$key;
17620 }
17621 }
17622 }
17623 if (!$autolinebreak) {
17624 if ($this->inPageBody()) {
17625 $pre_y = $this->y;
17626 // check for page break
17627 if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
17628 // fix for multicolumn mode
17629 $startliney = $this->y;
17630 }
17631 }
17632 if ($this->page > $startlinepage) {
17633 // fix line splitted over two pages
17634 if (isset($this->footerlen[$startlinepage])) {
17635 $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17636 }
17637 // line to be moved one page forward
17638 $pagebuff = $this->getPageBuffer($startlinepage);
17639 $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17640 $tstart = substr($pagebuff, 0, $startlinepos);
17641 $tend = substr($this->getPageBuffer($startlinepage), $curpos);
17642 // remove line from previous page
17643 $this->setPageBuffer($startlinepage, $tstart.''.$tend);
17644 $pagebuff = $this->getPageBuffer($this->page);
17645 $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17646 $tend = substr($pagebuff, $this->cntmrk[$this->page]);
17647 // add line start to current page
17648 $yshift = ($minstartliney - $this->y);
17649 if ($fontaligned) {
17650 $yshift += ($curfontsize / $this->k);
17651 }
17652 $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17653 $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17654 // shift the annotations and links
17655 if (isset($this->PageAnnots[$this->page])) {
17656 $next_pask = count($this->PageAnnots[$this->page]);
17657 } else {
17658 $next_pask = 0;
17659 }
17660 if (isset($this->PageAnnots[$startlinepage])) {
17661 foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17662 if ($pak >= $pask) {
17663 $this->PageAnnots[$this->page][] = $pac;
17664 unset($this->PageAnnots[$startlinepage][$pak]);
17665 $npak = count($this->PageAnnots[$this->page]) - 1;
17666 $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17667 }
17668 }
17669 }
17670 $pask = $next_pask;
17671 $startlinepos = $this->cntmrk[$this->page];
17672 $startlinepage = $this->page;
17673 $startliney = $this->y;
17674 $this->newline = false;
17675 }
17676 $this->y += ($this->getCellHeight($curfontsize / $this->k) - ($curfontdescent * $this->cell_height_ratio) - $imgh);
17677 $minstartliney = min($this->y, $minstartliney);
17678 $maxbottomliney = ($startliney + $this->getCellHeight($curfontsize / $this->k));
17679 }
17680 } elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
17681 // account for different font size
17682 $pfontname = $curfontname;
17683 $pfontstyle = $curfontstyle;
17684 $pfontsize = $curfontsize;
17685 $fontname = (isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname);
17686 $fontstyle = (isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle);
17687 $fontsize = (isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize);
17688 $fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
17689 $fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
17690 if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
17691 OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17692 OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
17693 if (($key < ($maxel - 1)) AND (
17694 ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
17695 OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17696 OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize)
17697 AND ($fontsize >= 0) AND ($curfontsize >= 0)
17698 AND (($fontsize != $curfontsize) OR ($fontstyle != $curfontstyle) OR ($fontname != $curfontname)))
17699 )) {
17700 if ($this->page > $startlinepage) {
17701 // fix lines splitted over two pages
17702 if (isset($this->footerlen[$startlinepage])) {
17703 $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17704 }
17705 // line to be moved one page forward
17706 $pagebuff = $this->getPageBuffer($startlinepage);
17707 $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17708 $tstart = substr($pagebuff, 0, $startlinepos);
17709 $tend = substr($this->getPageBuffer($startlinepage), $curpos);
17710 // remove line start from previous page
17711 $this->setPageBuffer($startlinepage, $tstart.''.$tend);
17712 $pagebuff = $this->getPageBuffer($this->page);
17713 $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17714 $tend = substr($pagebuff, $this->cntmrk[$this->page]);
17715 // add line start to current page
17716 $yshift = ($minstartliney - $this->y);
17717 $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17718 $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17719 // shift the annotations and links
17720 if (isset($this->PageAnnots[$this->page])) {
17721 $next_pask = count($this->PageAnnots[$this->page]);
17722 } else {
17723 $next_pask = 0;
17724 }
17725 if (isset($this->PageAnnots[$startlinepage])) {
17726 foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17727 if ($pak >= $pask) {
17728 $this->PageAnnots[$this->page][] = $pac;
17729 unset($this->PageAnnots[$startlinepage][$pak]);
17730 $npak = count($this->PageAnnots[$this->page]) - 1;
17731 $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17732 }
17733 }
17734 }
17735 $pask = $next_pask;
17736 $startlinepos = $this->cntmrk[$this->page];
17737 $startlinepage = $this->page;
17738 $startliney = $this->y;
17739 }
17740 if (!isset($dom[$key]['line-height'])) {
17741 $dom[$key]['line-height'] = $this->cell_height_ratio;
17742 }
17743 if (!$dom[$key]['block']) {
17744 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']))) {
17745 $this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
17746 }
17747 if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
17748 $current_line_align_data = array($key, $minstartliney, $maxbottomliney);
17749 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)))) {
17750 $minstartliney = min($this->y, $line_align_data[1]);
17751 $maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $line_align_data[2]);
17752 } else {
17753 $minstartliney = min($this->y, $minstartliney);
17754 $maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $maxbottomliney);
17755 }
17756 $line_align_data = $current_line_align_data;
17757 }
17758 }
17759 $this->cell_height_ratio = $dom[$key]['line-height'];
17760 $fontaligned = true;
17761 }
17762 $this->setFont($fontname, $fontstyle, $fontsize);
17763 // reset row height
17764 $this->resetLastH();
17765 $curfontname = $fontname;
17766 $curfontstyle = $fontstyle;
17767 $curfontsize = $fontsize;
17768 $curfontascent = $fontascent;
17769 $curfontdescent = $fontdescent;
17770 }
17771 }
17772 // set text rendering mode
17773 $textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
17774 $textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
17775 $textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
17776 $this->setTextRenderingMode($textstroke, $textfill, $textclip);
17777 if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
17778 $this->setFontStretching($dom[$key]['font-stretch']);
17779 }
17780 if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
17781 $this->setFontSpacing($dom[$key]['letter-spacing']);
17782 }
17783 if (($plalign == 'J') AND $dom[$key]['block']) {
17784 $plalign = '';
17785 }
17786 // get current position on page buffer
17787 $curpos = $this->pagelen[$startlinepage];
17788 if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
17789 $this->setFillColorArray($dom[$key]['bgcolor']);
17790 $wfill = true;
17791 } else {
17792 $wfill = $fill | false;
17793 }
17794 if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
17795 $this->setTextColorArray($dom[$key]['fgcolor']);
17796 }
17797 if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
17798 $this->setDrawColorArray($dom[$key]['strokecolor']);
17799 }
17800 if (isset($dom[$key]['align'])) {
17801 $lalign = $dom[$key]['align'];
17802 }
17803 if (TCPDF_STATIC::empty_string($lalign)) {
17804 $lalign = $align;
17805 }
17806 }
17807 // align lines
17808 if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
17809 $newline = true;
17810 $fontaligned = false;
17811 // we are at the beginning of a new line
17812 if (isset($startlinex)) {
17813 $yshift = ($minstartliney - $startliney);
17814 if (($yshift > 0) OR ($this->page > $startlinepage)) {
17815 $yshift = 0;
17816 }
17817 $t_x = 0;
17818 // the last line must be shifted to be aligned as requested
17819 $linew = abs($this->endlinex - $startlinex);
17820 if ($this->inxobj) {
17821 // we are inside an XObject template
17822 $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
17823 if (isset($opentagpos)) {
17824 $midpos = $opentagpos;
17825 } else {
17826 $midpos = 0;
17827 }
17828 if ($midpos > 0) {
17829 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
17830 $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
17831 } else {
17832 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
17833 $pend = '';
17834 }
17835 } else {
17836 $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
17837 if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17838 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17839 $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
17840 } elseif (isset($opentagpos)) {
17841 $midpos = $opentagpos;
17842 } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17843 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17844 $midpos = $this->footerpos[$startlinepage];
17845 } else {
17846 $midpos = 0;
17847 }
17848 if ($midpos > 0) {
17849 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
17850 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
17851 } else {
17852 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
17853 $pend = '';
17854 }
17855 }
17856 if ((((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
17857 // calculate shifting amount
17858 $tw = $w;
17859 if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
17860 $tw += $this->cell_padding['R'];
17861 }
17862 if ($this->lMargin != $prevlMargin) {
17863 $tw += ($prevlMargin - $this->lMargin);
17864 }
17865 if ($this->rMargin != $prevrMargin) {
17866 $tw += ($prevrMargin - $this->rMargin);
17867 }
17868 $one_space_width = $this->GetStringWidth(chr(32));
17869 $no = 0; // number of spaces on a line contained on a single block
17870 if ($this->isRTLTextDir()) { // RTL
17871 // remove left space if exist
17872 $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
17873 if ($pos1 > 0) {
17874 $pos1 = intval($pos1);
17875 if ($this->isUnicodeFont()) {
17876 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
17877 $spacelen = 2;
17878 } else {
17879 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
17880 $spacelen = 1;
17881 }
17882 if ($pos1 == $pos2) {
17883 $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
17884 if (substr($pmid, $pos1, 4) == '[()]') {
17885 $linew -= $one_space_width;
17886 } elseif ($pos1 == strpos($pmid, '[(')) {
17887 $no = 1;
17888 }
17889 }
17890 }
17891 } else { // LTR
17892 // remove right space if exist
17893 $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
17894 if ($pos1 > 0) {
17895 $pos1 = intval($pos1);
17896 if ($this->isUnicodeFont()) {
17897 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
17898 $spacelen = 2;
17899 } else {
17900 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
17901 $spacelen = 1;
17902 }
17903 if ($pos1 == $pos2) {
17904 $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
17905 $linew -= $one_space_width;
17906 }
17907 }
17908 }
17909 $mdiff = ($tw - $linew);
17910 if ($plalign == 'C') {
17911 if ($this->rtl) {
17912 $t_x = -($mdiff / 2);
17913 } else {
17914 $t_x = ($mdiff / 2);
17915 }
17916 } elseif ($plalign == 'R') {
17917 // right alignment on LTR document
17918 $t_x = $mdiff;
17919 } elseif ($plalign == 'L') {
17920 // left alignment on RTL document
17921 $t_x = -$mdiff;
17922 } elseif (($plalign == 'J') AND ($plalign == $lalign)) {
17923 // Justification
17924 if ($this->isRTLTextDir()) {
17925 // align text on the left
17926 $t_x = -$mdiff;
17927 }
17928 $ns = 0; // number of spaces
17929 $pmidtemp = $pmid;
17930 // escape special characters
17931 $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
17932 $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
17933 // search spaces
17934 if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
17935 $spacestr = $this->getSpaceString();
17936 $maxkk = count($lnstring[1]) - 1;
17937 for ($kk=0; $kk <= $maxkk; ++$kk) {
17938 // restore special characters
17939 $lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
17940 $lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
17941 // store number of spaces on the strings
17942 $lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
17943 // count total spaces on line
17944 $ns += $lnstring[2][$kk];
17945 $lnstring[3][$kk] = $ns;
17946 }
17947 if ($ns == 0) {
17948 $ns = 1;
17949 }
17950 // calculate additional space to add to each existing space
17951 $spacewidth = ($mdiff / ($ns - $no)) * $this->k;
17952 if ($this->FontSize <= 0) {
17953 $this->FontSize = 1;
17954 }
17955 $spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
17956 if ($this->font_spacing != 0) {
17957 // fixed spacing mode
17958 $osw = -1000 * $this->font_spacing / $this->FontSize;
17959 $spacewidthu += $osw;
17960 }
17961 $nsmax = $ns;
17962 $ns = 0;
17963 reset($lnstring);
17964 $offset = 0;
17965 $strcount = 0;
17966 $prev_epsposbeg = 0;
17967 $textpos = 0;
17968 if ($this->isRTLTextDir()) {
17969 $textpos = $this->wPt;
17970 }
17971 while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
17972 // check if we are inside a string section '[( ... )]'
17973 $stroffset = strpos($pmid, '[(', $offset);
17974 if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
17975 // set offset to the end of string section
17976 $offset = strpos($pmid, ')]', $stroffset);
17977 while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
17978 $offset = strpos($pmid, ')]', ($offset + 1));
17979 }
17980 if ($offset === false) {
17981 $this->Error('HTML Justification: malformed PDF code.');
17982 }
17983 continue;
17984 }
17985 if ($this->isRTLTextDir()) {
17986 $spacew = ($spacewidth * ($nsmax - $ns));
17987 } else {
17988 $spacew = ($spacewidth * $ns);
17989 }
17990 $offset = $strpiece[2][1] + strlen($strpiece[2][0]);
17991 $epsposend = strpos($pmid, $this->epsmarker.'Q', $offset);
17992 if ($epsposend !== null) {
17993 $epsposend += strlen($this->epsmarker.'Q');
17994 $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
17995 if ($epsposbeg === null) {
17996 $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
17997 $prev_epsposbeg = $epsposbeg;
17998 }
17999 if (($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend)) {
18000 // shift EPS images
18001 $trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
18002 $pmid_b = substr($pmid, 0, $epsposbeg);
18003 $pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
18004 $pmid_e = substr($pmid, $epsposend);
18005 $pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
18006 $offset = $epsposend;
18007 continue;
18008 }
18009 }
18010 $currentxpos = 0;
18011 // shift blocks of code
18012 switch ($strpiece[2][0]) {
18013 case 'Td':
18014 case 'cm':
18015 case 'm':
18016 case 'l': {
18017 // get current X position
18018 preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
18019 if (!isset($xmatches[1])) {
18020 break;
18021 }
18022 $currentxpos = $xmatches[1];
18023 $textpos = $currentxpos;
18024 if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
18025 $ns = $lnstring[3][$strcount];
18026 if ($this->isRTLTextDir()) {
18027 $spacew = ($spacewidth * ($nsmax - $ns));
18028 }
18029 ++$strcount;
18030 }
18031 // justify block
18032 if (preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $pmatch) == 1) {
18033 $newpmid = sprintf('%F',(floatval($pmatch[1]) + $spacew)).' '.$pmatch[2].' x*#!#*x'.$pmatch[3].$pmatch[4];
18034 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
18035 unset($pmatch, $newpmid);
18036 }
18037 break;
18038 }
18039 case 're': {
18040 // justify block
18041 if (!TCPDF_STATIC::empty_string($this->lispacer)) {
18042 $this->lispacer = '';
18043 break;
18044 }
18045 preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
18046 if (!isset($xmatches[1])) {
18047 break;
18048 }
18049 $currentxpos = $xmatches[1];
18050 $x_diff = 0;
18051 $w_diff = 0;
18052 if ($this->isRTLTextDir()) { // RTL
18053 if ($currentxpos < $textpos) {
18054 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
18055 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
18056 } else {
18057 if ($strcount > 0) {
18058 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
18059 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
18060 }
18061 }
18062 } else { // LTR
18063 if ($currentxpos > $textpos) {
18064 if ($strcount > 0) {
18065 $x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
18066 }
18067 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
18068 } else {
18069 if ($strcount > 1) {
18070 $x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
18071 }
18072 if ($strcount > 0) {
18073 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
18074 }
18075 }
18076 }
18077 if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $pmatch) == 1) {
18078 $newx = sprintf('%F',(floatval($pmatch[1]) + $x_diff));
18079 $neww = sprintf('%F',(floatval($pmatch[3]) + $w_diff));
18080 $newpmid = $newx.' '.$pmatch[2].' '.$neww.' '.$pmatch[4].' x*#!#*x'.$pmatch[5].$pmatch[6];
18081 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
18082 unset($pmatch, $newpmid, $newx, $neww);
18083 }
18084 break;
18085 }
18086 case 'c': {
18087 // get current X position
18088 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);
18089 if (!isset($xmatches[1])) {
18090 break;
18091 }
18092 $currentxpos = $xmatches[1];
18093 // justify block
18094 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) {
18095 $newx1 = sprintf('%F',(floatval($pmatch[1]) + $spacew));
18096 $newx2 = sprintf('%F',(floatval($pmatch[3]) + $spacew));
18097 $newx3 = sprintf('%F',(floatval($pmatch[5]) + $spacew));
18098 $newpmid = $newx1.' '.$pmatch[2].' '.$newx2.' '.$pmatch[4].' '.$newx3.' '.$pmatch[6].' x*#!#*x'.$pmatch[7].$pmatch[8];
18099 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
18100 unset($pmatch, $newpmid, $newx1, $newx2, $newx3);
18101 }
18102 break;
18103 }
18104 }
18105 // shift the annotations and links
18106 $cxpos = ($currentxpos / $this->k);
18107 $lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
18108 if ($this->inxobj) {
18109 // we are inside an XObject template
18110 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18111 if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
18112 if ($cxpos > $lmpos) {
18113 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
18114 $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18115 } else {
18116 $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18117 }
18118 break;
18119 }
18120 }
18121 } elseif (isset($this->PageAnnots[$this->page])) {
18122 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18123 if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
18124 if ($cxpos > $lmpos) {
18125 $this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
18126 $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18127 } else {
18128 $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18129 }
18130 break;
18131 }
18132 }
18133 }
18134 } // end of while
18135 // remove markers
18136 $pmid = str_replace('x*#!#*x', '', $pmid);
18137 if ($this->isUnicodeFont()) {
18138 // multibyte characters
18139 $spacew = $spacewidthu;
18140 if ($this->font_stretching != 100) {
18141 // word spacing is affected by stretching
18142 $spacew /= ($this->font_stretching / 100);
18143 }
18144 // escape special characters
18145 $pos = 0;
18146 $pmid = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmid);
18147 $pmid = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmid);
18148 if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmid, $pamatch) > 0) {
18149 foreach($pamatch[0] as $pk => $pmatch) {
18150 $replace = $pamatch[1][$pk];
18151 $replace = str_replace('#!#OP#!#', '(', $replace);
18152 $replace = str_replace('#!#CP#!#', ')', $replace);
18153 $newpmid = '[('.str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacew).' (', $replace).')]';
18154 $pos = strpos($pmid, $pmatch, $pos);
18155 if ($pos !== FALSE) {
18156 $pmid = substr_replace($pmid, $newpmid, $pos, strlen($pmatch));
18157 }
18158 ++$pos;
18159 }
18160 unset($pamatch);
18161 }
18162 if ($this->inxobj) {
18163 // we are inside an XObject template
18164 $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
18165 } else {
18166 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
18167 }
18168 $endlinepos = strlen($pstart."\n".$pmid."\n");
18169 } else {
18170 // non-unicode (single-byte characters)
18171 if ($this->font_stretching != 100) {
18172 // word spacing (Tw) is affected by stretching
18173 $spacewidth /= ($this->font_stretching / 100);
18174 }
18175 $rs = sprintf('%F Tw', $spacewidth);
18176 $pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
18177 if ($this->inxobj) {
18178 // we are inside an XObject template
18179 $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
18180 } else {
18181 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
18182 }
18183 $endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
18184 }
18185 }
18186 } // end of J
18187 } // end if $startlinex
18188 if (($t_x != 0) OR ($yshift < 0)) {
18189 // shift the line
18190 $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18191 $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18192 $endlinepos = strlen($pstart);
18193 if ($this->inxobj) {
18194 // we are inside an XObject template
18195 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18196 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18197 if ($pak >= $pask) {
18198 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18199 $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18200 }
18201 }
18202 } else {
18203 $this->setPageBuffer($startlinepage, $pstart.$pend);
18204 // shift the annotations and links
18205 if (isset($this->PageAnnots[$this->page])) {
18206 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18207 if ($pak >= $pask) {
18208 $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18209 $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18210 }
18211 }
18212 }
18213 }
18214 $this->y -= $yshift;
18215 }
18216 }
18217 $pbrk = $this->checkPageBreak($this->lasth);
18218 $this->newline = false;
18219 $startlinex = $this->x;
18220 $startliney = $this->y;
18221 if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
18222 $startliney -= ((0.3 * $this->FontSizePt) / $this->k);
18223 } elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
18224 $startliney -= (($this->FontSizePt / 0.7) / $this->k);
18225 } else {
18226 $minstartliney = $startliney;
18227 $maxbottomliney = ($this->y + $this->getCellHeight($fontsize / $this->k));
18228 }
18229 $startlinepage = $this->page;
18230 if (isset($endlinepos) AND (!$pbrk)) {
18231 $startlinepos = $endlinepos;
18232 } else {
18233 if ($this->inxobj) {
18234 // we are inside an XObject template
18235 $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
18236 } elseif (!$this->InFooter) {
18237 if (isset($this->footerlen[$this->page])) {
18238 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18239 } else {
18240 $this->footerpos[$this->page] = $this->pagelen[$this->page];
18241 }
18242 $startlinepos = $this->footerpos[$this->page];
18243 } else {
18244 $startlinepos = $this->pagelen[$this->page];
18245 }
18246 }
18247 unset($endlinepos);
18248 $plalign = $lalign;
18249 if (isset($this->PageAnnots[$this->page])) {
18250 $pask = count($this->PageAnnots[$this->page]);
18251 } else {
18252 $pask = 0;
18253 }
18254 if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
18255 AND (isset($this->emptypagemrk[$this->page]))
18256 AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
18257 $this->setFont($fontname, $fontstyle, $fontsize);
18258 if ($wfill) {
18259 $this->setFillColorArray($this->bgcolor);
18260 }
18261 }
18262 } // end newline
18263 if (isset($opentagpos)) {
18264 unset($opentagpos);
18265 }
18266 if ($dom[$key]['tag']) {
18267 if ($dom[$key]['opening']) {
18268 // get text indentation (if any)
18269 if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
18270 $this->textindent = $dom[$key]['text-indent'];
18271 $this->newline = true;
18272 }
18273 // table
18274 if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
18275 // available page width
18276 if ($this->rtl) {
18277 $wtmp = $this->x - $this->lMargin;
18278 } else {
18279 $wtmp = $this->w - $this->rMargin - $this->x;
18280 }
18281 // get cell spacing
18282 if (isset($dom[$key]['attribute']['cellspacing'])) {
18283 $clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
18284 $cellspacing = array('H' => $clsp, 'V' => $clsp);
18285 } elseif (isset($dom[$key]['border-spacing'])) {
18286 $cellspacing = $dom[$key]['border-spacing'];
18287 } else {
18288 $cellspacing = array('H' => 0, 'V' => 0);
18289 }
18290 // table width
18291 if (isset($dom[$key]['width'])) {
18292 $table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
18293 } else {
18294 $table_width = $wtmp;
18295 }
18296 $table_width -= (2 * $cellspacing['H']);
18297 if (!$this->inthead) {
18298 $this->y += $cellspacing['V'];
18299 }
18300 if ($this->rtl) {
18301 $cellspacingx = -$cellspacing['H'];
18302 } else {
18303 $cellspacingx = $cellspacing['H'];
18304 }
18305 // total table width without cellspaces
18306 $table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
18307 // minimum column width
18308 $table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
18309 // array of custom column widths
18310 $table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
18311 }
18312 // table row
18313 if ($dom[$key]['value'] == 'tr') {
18314 // reset column counter
18315 $colid = 0;
18316 }
18317 // table cell
18318 if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18319 $trid = $dom[$key]['parent'];
18320 $table_el = $dom[$trid]['parent'];
18321 if (!isset($dom[$table_el]['cols'])) {
18322 $dom[$table_el]['cols'] = $dom[$trid]['cols'];
18323 }
18324 // store border info
18325 $tdborder = 0;
18326 if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
18327 $tdborder = $dom[$key]['border'];
18328 }
18329 $colspan = intval($dom[$key]['attribute']['colspan']);
18330 if ($colspan <= 0) {
18331 $colspan = 1;
18332 }
18333 $old_cell_padding = $this->cell_padding;
18334 if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
18335 $crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
18336 $current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
18337 } elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
18338 $current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
18339 } else {
18340 $current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
18341 }
18342 $this->cell_padding = $current_cell_padding;
18343 if (isset($dom[$key]['height'])) {
18344 // minimum cell height
18345 $cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
18346 } else {
18347 $cellh = 0;
18348 }
18349 if (isset($dom[$key]['content'])) {
18350 $cell_content = $dom[$key]['content'];
18351 } else {
18352 $cell_content = '&nbsp;';
18353 }
18354 $tagtype = $dom[$key]['value'];
18355 $parentid = $key;
18356 while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
18357 // move $key index forward
18358 ++$key;
18359 }
18360 if (!isset($dom[$trid]['startpage'])) {
18361 $dom[$trid]['startpage'] = $this->page;
18362 } else {
18363 $this->setPage($dom[$trid]['startpage']);
18364 }
18365 if (!isset($dom[$trid]['startcolumn'])) {
18366 $dom[$trid]['startcolumn'] = $this->current_column;
18367 } elseif ($this->current_column != $dom[$trid]['startcolumn']) {
18368 $tmpx = $this->x;
18369 $this->selectColumn($dom[$trid]['startcolumn']);
18370 $this->x = $tmpx;
18371 }
18372 if (!isset($dom[$trid]['starty'])) {
18373 $dom[$trid]['starty'] = $this->y;
18374 } else {
18375 $this->y = $dom[$trid]['starty'];
18376 }
18377 if (!isset($dom[$trid]['startx'])) {
18378 $dom[$trid]['startx'] = $this->x;
18379 $this->x += $cellspacingx;
18380 } else {
18381 $this->x += ($cellspacingx / 2);
18382 }
18383 if (isset($dom[$parentid]['attribute']['rowspan'])) {
18384 $rowspan = intval($dom[$parentid]['attribute']['rowspan']);
18385 } else {
18386 $rowspan = 1;
18387 }
18388 // skip row-spanned cells started on the previous rows
18389 if (isset($dom[$table_el]['rowspans'])) {
18390 $rsk = 0;
18391 $rskmax = count($dom[$table_el]['rowspans']);
18392 while ($rsk < $rskmax) {
18393 $trwsp = $dom[$table_el]['rowspans'][$rsk];
18394 $rsstartx = $trwsp['startx'];
18395 $rsendx = $trwsp['endx'];
18396 // account for margin changes
18397 if ($trwsp['startpage'] < $this->page) {
18398 if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
18399 $dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
18400 $rsstartx -= $dl;
18401 $rsendx -= $dl;
18402 } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
18403 $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
18404 $rsstartx += $dl;
18405 $rsendx += $dl;
18406 }
18407 }
18408 if (($trwsp['rowspan'] > 0)
18409 AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
18410 AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
18411 AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
18412 // set the starting X position of the current cell
18413 $this->x = $rsendx + $cellspacingx;
18414 // increment column indicator
18415 $colid += $trwsp['colspan'];
18416 if (($trwsp['rowspan'] == 1)
18417 AND (isset($dom[$trid]['endy']))
18418 AND (isset($dom[$trid]['endpage']))
18419 AND (isset($dom[$trid]['endcolumn']))
18420 AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
18421 AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18422 // set ending Y position for row
18423 $dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18424 $dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
18425 }
18426 $rsk = 0;
18427 } else {
18428 ++$rsk;
18429 }
18430 }
18431 }
18432 if (isset($dom[$parentid]['width'])) {
18433 // user specified width
18434 $cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
18435 $tmpcw = ($cellw / $colspan);
18436 for ($i = 0; $i < $colspan; ++$i) {
18437 $table_colwidths[($colid + $i)] = $tmpcw;
18438 }
18439 } else {
18440 // inherit column width
18441 $cellw = 0;
18442 for ($i = 0; $i < $colspan; ++$i) {
18443 $cellw += (isset($table_colwidths[($colid + $i)]) ? $table_colwidths[($colid + $i)] : 0);
18444 }
18445 }
18446 $cellw += (($colspan - 1) * $cellspacing['H']);
18447 // increment column indicator
18448 $colid += $colspan;
18449 // add rowspan information to table element
18450 if ($rowspan > 1) {
18451 $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));
18452 }
18453 $cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
18454 if ($rowspan > 1) {
18455 $dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
18456 }
18457 // push background colors
18458 if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
18459 $dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
18460 }
18461 // store border info
18462 if (!empty($tdborder)) {
18463 $dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
18464 }
18465 $prevLastH = $this->lasth;
18466 // store some info for multicolumn mode
18467 if ($this->rtl) {
18468 $this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
18469 } else {
18470 $this->colxshift['x'] = $this->x - $this->lMargin;
18471 }
18472 $this->colxshift['s'] = $cellspacing;
18473 $this->colxshift['p'] = $current_cell_padding;
18474 // ****** write the cell content ******
18475 $this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
18476 // restore some values
18477 $this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
18478 $this->lasth = $prevLastH;
18479 $this->cell_padding = $old_cell_padding;
18480 $dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
18481 // update the end of row position
18482 if ($rowspan <= 1) {
18483 if (isset($dom[$trid]['endy'])) {
18484 if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
18485 $dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
18486 } elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
18487 $dom[$trid]['endy'] = $this->y;
18488 }
18489 } else {
18490 $dom[$trid]['endy'] = $this->y;
18491 }
18492 if (isset($dom[$trid]['endpage'])) {
18493 $dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
18494 } else {
18495 $dom[$trid]['endpage'] = $this->page;
18496 }
18497 if (isset($dom[$trid]['endcolumn'])) {
18498 $dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
18499 } else {
18500 $dom[$trid]['endcolumn'] = $this->current_column;
18501 }
18502 } else {
18503 // account for row-spanned cells
18504 $dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
18505 $dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
18506 $dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
18507 $dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
18508 }
18509 if (isset($dom[$table_el]['rowspans'])) {
18510 // update endy and endpage on rowspanned cells
18511 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
18512 if ($trwsp['rowspan'] > 0) {
18513 if (isset($dom[$trid]['endpage'])) {
18514 if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18515 $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18516 } elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
18517 $dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
18518 $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
18519 $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
18520 } else {
18521 $dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
18522 }
18523 }
18524 }
18525 }
18526 }
18527 $this->x += ($cellspacingx / 2);
18528 } else {
18529 // opening tag (or self-closing tag)
18530 if (!isset($opentagpos)) {
18531 if ($this->inxobj) {
18532 // we are inside an XObject template
18533 $opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
18534 } elseif (!$this->InFooter) {
18535 if (isset($this->footerlen[$this->page])) {
18536 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18537 } else {
18538 $this->footerpos[$this->page] = $this->pagelen[$this->page];
18539 }
18540 $opentagpos = $this->footerpos[$this->page];
18541 }
18542 }
18543 $dom = $this->openHTMLTagHandler($dom, $key, $cell);
18544 }
18545 } else { // closing tag
18546 $prev_numpages = $this->numpages;
18547 $old_bordermrk = $this->bordermrk[$this->page];
18548 $dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
18549 if ($this->bordermrk[$this->page] > $old_bordermrk) {
18550 $startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
18551 }
18552 if ($prev_numpages > $this->numpages) {
18553 $startlinepage = $this->page;
18554 }
18555 }
18556 } elseif (strlen($dom[$key]['value']) > 0) {
18557 // print list-item
18558 if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) {
18559 $this->setFont($pfontname, $pfontstyle, $pfontsize);
18560 $this->resetLastH();
18561 $minstartliney = $this->y;
18562 $maxbottomliney = ($startliney + $this->getCellHeight($this->FontSize));
18563 if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
18564 $this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
18565 }
18566 $this->setFont($curfontname, $curfontstyle, $curfontsize);
18567 $this->resetLastH();
18568 if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
18569 $pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
18570 $pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
18571 $this->y += ($this->getCellHeight(($pfontsize - $curfontsize) / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
18572 $minstartliney = min($this->y, $minstartliney);
18573 $maxbottomliney = max(($this->y + $this->getCellHeight($pfontsize / $this->k)), $maxbottomliney);
18574 }
18575 }
18576 // text
18577 $this->htmlvspace = 0;
18578 $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']);
18579 if ((!$this->premode) AND $this->isRTLTextDir() AND !$isRTLString) {
18580 // reverse spaces order
18581 $lsp = ''; // left spaces
18582 $rsp = ''; // right spaces
18583 if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18584 $lsp = $matches[1];
18585 }
18586 if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18587 $rsp = $matches[1];
18588 }
18589 $dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
18590 }
18591 if ($newline) {
18592 if (!$this->premode) {
18593 $prelen = strlen($dom[$key]['value']);
18594 if ($this->isRTLTextDir() AND !$isRTLString) {
18595 // right trim except non-breaking space
18596 $dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
18597 } else {
18598 // left trim except non-breaking space
18599 $dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
18600 }
18601 $postlen = strlen($dom[$key]['value']);
18602 if (($postlen == 0) AND ($prelen > 0)) {
18603 $dom[$key]['trimmed_space'] = true;
18604 }
18605 }
18606 $newline = false;
18607 $firstblock = true;
18608 } else {
18609 $firstblock = false;
18610 // replace empty multiple spaces string with a single space
18611 $dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
18612 }
18613 $strrest = '';
18614 if ($this->rtl) {
18615 $this->x -= $this->textindent;
18616 } else {
18617 $this->x += $this->textindent;
18618 }
18619 if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
18620 $strlinelen = $this->GetStringWidth($dom[$key]['value']);
18621 if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
18622 // HTML <a> Link
18623 $hrefcolor = '';
18624 if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
18625 $hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
18626 }
18627 $hrefstyle = -1;
18628 if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
18629 $hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
18630 }
18631 $strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
18632 } else {
18633 $wadj = 0; // space to leave for block continuity
18634 if ($this->rtl) {
18635 $cwa = ($this->x - $this->lMargin);
18636 } else {
18637 $cwa = ($this->w - $this->rMargin - $this->x);
18638 }
18639 if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
18640 // check the next text blocks for continuity
18641 $nkey = ($key + 1);
18642 $write_block = true;
18643 $same_textdir = true;
18644 $tmp_fontname = $this->FontFamily;
18645 $tmp_fontstyle = $this->FontStyle;
18646 $tmp_fontsize = $this->FontSizePt;
18647 while ($write_block AND isset($dom[$nkey])) {
18648 if ($dom[$nkey]['tag']) {
18649 if ($dom[$nkey]['block']) {
18650 // end of block
18651 $write_block = false;
18652 }
18653 $tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
18654 $tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
18655 $tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
18656 $same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
18657 } else {
18658 $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'+/', $this->re_space['m'], $dom[$nkey]['value']);
18659 if (isset($nextstr[0]) AND $same_textdir) {
18660 $wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
18661 if (isset($nextstr[1])) {
18662 $write_block = false;
18663 }
18664 }
18665 }
18666 ++$nkey;
18667 }
18668 }
18669 if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
18670 $wadj = 0;
18671 $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $dom[$key]['value']);
18672 $numblks = count($nextstr);
18673 if ($numblks > 1) {
18674 // try to split on blank spaces
18675 $wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
18676 } else {
18677 // set the entire block on new line
18678 $wadj = $this->GetStringWidth($nextstr[0]);
18679 }
18680 }
18681 // check for reversed text direction
18682 if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
18683 // LTR text on RTL direction or RTL text on LTR direction
18684 $reverse_dir = true;
18685 $this->rtl = !$this->rtl;
18686 $revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
18687 if ($this->rtl) {
18688 $this->x += $revshift;
18689 } else {
18690 $this->x -= $revshift;
18691 }
18692 $xws = $this->x;
18693 }
18694 // ****** write only until the end of the line and get the rest ******
18695 $strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
18696 // restore default direction
18697 if ($reverse_dir AND ($wadj == 0)) {
18698 $this->x = $xws; // @phpstan-ignore-line
18699 $this->rtl = !$this->rtl;
18700 $reverse_dir = false;
18701 }
18702 }
18703 }
18704 $this->textindent = 0;
18705 if (strlen($strrest) > 0) {
18706 // store the remaining string on the previous $key position
18707 $this->newline = true;
18708 if ($strrest == $dom[$key]['value']) {
18709 // used to avoid infinite loop
18710 ++$loop;
18711 } else {
18712 $loop = 0;
18713 }
18714 $dom[$key]['value'] = $strrest;
18715 if ($cell) {
18716 if ($this->rtl) {
18717 $this->x -= $this->cell_padding['R'];
18718 } else {
18719 $this->x += $this->cell_padding['L'];
18720 }
18721 }
18722 if ($loop < 3) {
18723 --$key;
18724 }
18725 } else {
18726 $loop = 0;
18727 // add the positive font spacing of the last character (if any)
18728 if ($this->font_spacing > 0) {
18729 if ($this->rtl) {
18730 $this->x -= $this->font_spacing;
18731 } else {
18732 $this->x += $this->font_spacing;
18733 }
18734 }
18735 }
18736 }
18737 ++$key;
18738 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')) {
18739 // check if we are on a new page or on a new column
18740 if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
18741 // we are on a new page or on a new column and the total object height is less than the available vertical space.
18742 // restore previous object
18743 $this->rollbackTransaction(true);
18744 // restore previous values
18745 foreach ($this_method_vars as $vkey => $vval) {
18746 $$vkey = $vval;
18747 }
18748 if (!empty($dom[$key]['thead'])) {
18749 $this->inthead = true;
18750 }
18751 // add a page (or trig AcceptPageBreak() for multicolumn mode)
18752 $pre_y = $this->y;
18753 if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
18754 $startliney = $this->y;
18755 }
18756 $undo = true; // avoid infinite loop
18757 } else {
18758 $undo = false;
18759 }
18760 }
18761 } // end for each $key
18762 // align the last line
18763 if (isset($startlinex)) {
18764 $yshift = ($minstartliney - $startliney);
18765 if (($yshift > 0) OR ($this->page > $startlinepage)) {
18766 $yshift = 0;
18767 }
18768 $t_x = 0;
18769 // the last line must be shifted to be aligned as requested
18770 $linew = abs($this->endlinex - $startlinex);
18771 if ($this->inxobj) {
18772 // we are inside an XObject template
18773 $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
18774 if (isset($opentagpos)) {
18775 $midpos = $opentagpos;
18776 } else {
18777 $midpos = 0;
18778 }
18779 if ($midpos > 0) {
18780 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
18781 $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
18782 } else {
18783 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
18784 $pend = '';
18785 }
18786 } else {
18787 $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
18788 if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18789 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18790 $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
18791 } elseif (isset($opentagpos)) {
18792 $midpos = $opentagpos;
18793 } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18794 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18795 $midpos = $this->footerpos[$startlinepage];
18796 } else {
18797 $midpos = 0;
18798 }
18799 if ($midpos > 0) {
18800 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
18801 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
18802 } else {
18803 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
18804 $pend = '';
18805 }
18806 }
18807 if ((((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
18808 // calculate shifting amount
18809 $tw = $w;
18810 if ($this->lMargin != $prevlMargin) {
18811 $tw += ($prevlMargin - $this->lMargin);
18812 }
18813 if ($this->rMargin != $prevrMargin) {
18814 $tw += ($prevrMargin - $this->rMargin);
18815 }
18816 $one_space_width = $this->GetStringWidth(chr(32));
18817 $no = 0; // number of spaces on a line contained on a single block
18818 if ($this->isRTLTextDir()) { // RTL
18819 // remove left space if exist
18820 $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
18821 if ($pos1 > 0) {
18822 $pos1 = intval($pos1);
18823 if ($this->isUnicodeFont()) {
18824 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
18825 $spacelen = 2;
18826 } else {
18827 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
18828 $spacelen = 1;
18829 }
18830 if ($pos1 == $pos2) {
18831 $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
18832 if (substr($pmid, $pos1, 4) == '[()]') {
18833 $linew -= $one_space_width;
18834 } elseif ($pos1 == strpos($pmid, '[(')) {
18835 $no = 1;
18836 }
18837 }
18838 }
18839 } else { // LTR
18840 // remove right space if exist
18841 $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
18842 if ($pos1 > 0) {
18843 $pos1 = intval($pos1);
18844 if ($this->isUnicodeFont()) {
18845 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
18846 $spacelen = 2;
18847 } else {
18848 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
18849 $spacelen = 1;
18850 }
18851 if ($pos1 == $pos2) {
18852 $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
18853 $linew -= $one_space_width;
18854 }
18855 }
18856 }
18857 $mdiff = ($tw - $linew);
18858 if ($plalign == 'C') {
18859 if ($this->rtl) {
18860 $t_x = -($mdiff / 2);
18861 } else {
18862 $t_x = ($mdiff / 2);
18863 }
18864 } elseif ($plalign == 'R') {
18865 // right alignment on LTR document
18866 $t_x = $mdiff;
18867 } elseif ($plalign == 'L') {
18868 // left alignment on RTL document
18869 $t_x = -$mdiff;
18870 }
18871 } // end if startlinex
18872 if (($t_x != 0) OR ($yshift < 0)) {
18873 // shift the line
18874 $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18875 $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18876 $endlinepos = strlen($pstart);
18877 if ($this->inxobj) {
18878 // we are inside an XObject template
18879 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18880 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18881 if ($pak >= $pask) {
18882 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18883 $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18884 }
18885 }
18886 } else {
18887 $this->setPageBuffer($startlinepage, $pstart.$pend);
18888 // shift the annotations and links
18889 if (isset($this->PageAnnots[$this->page])) {
18890 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18891 if ($pak >= $pask) {
18892 $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18893 $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18894 }
18895 }
18896 }
18897 }
18898 $this->y -= $yshift;
18899 $yshift = 0;
18900 }
18901 }
18902 // restore previous values
18903 $this->setGraphicVars($gvars);
18904 if ($this->num_columns > 1) {
18905 $this->selectColumn();
18906 } elseif ($this->page > $prevPage) {
18907 $this->lMargin = $this->pagedim[$this->page]['olm'];
18908 $this->rMargin = $this->pagedim[$this->page]['orm'];
18909 }
18910 // restore previous list state
18911 $this->cell_height_ratio = $prev_cell_height_ratio;
18912 $this->listnum = $prev_listnum;
18913 $this->listordered = $prev_listordered;
18914 $this->listcount = $prev_listcount;
18915 $this->lispacer = $prev_lispacer;
18916 if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
18917 $this->Ln($this->lasth);
18918 if (($this->y < $maxbottomliney) AND ($startlinepage == $this->page)) {
18919 $this->y = $maxbottomliney;
18920 }
18921 }
18922 unset($dom);
18923 }
18924
18925 /**
18926 * Check if the path is relative.
18927 * @param string $path path to check
18928 * @return boolean true if the path is relative
18929 * @protected
18930 * @since 6.9.1
18931 */
18932 protected function isRelativePath($path) {
18933 return (strpos(str_ireplace('%2E', '.', $this->unhtmlentities($path)), '..') !== false);
18934 }
18935
18936 /**
18937 * Check if it contains a non-allowed external protocol.
18938 * @param string $path path to check
18939 * @return boolean true if the protocol is not allowed.
18940 * @protected
18941 * @since 6.9.3
18942 */
18943 protected function hasExtForbiddenProtocol($path) {
18944 return ((strpos($path, '://') !== false)
18945 && (preg_match('|^https?://|', $path) !== 1));
18946 }
18947
18948 /**
18949 * Process opening tags.
18950 * @param array $dom html dom array
18951 * @param int $key current element id
18952 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
18953 * @return array $dom
18954 * @protected
18955 */
18956 protected function openHTMLTagHandler($dom, $key, $cell) {
18957 $tag = $dom[$key];
18958 $parent = $dom[($dom[$key]['parent'])];
18959 $firsttag = ($key == 1);
18960 // check for text direction attribute
18961 if (isset($tag['dir'])) {
18962 $this->setTempRTL($tag['dir']);
18963 } else {
18964 $this->tmprtl = false;
18965 }
18966 if ($tag['block']) {
18967 $hbz = 0; // distance from y to line bottom
18968 $hb = 0; // vertical space between block tags
18969 // calculate vertical space for block tags
18970 if (isset($this->tagvspaces[$tag['value']][0]['h']) && !empty($this->tagvspaces[$tag['value']][0]['h']) && ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
18971 $cur_h = $this->tagvspaces[$tag['value']][0]['h'];
18972 } elseif (isset($tag['fontsize'])) {
18973 $cur_h = $this->getCellHeight($tag['fontsize'] / $this->k);
18974 } else {
18975 $cur_h = $this->getCellHeight($this->FontSize);
18976 }
18977 if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
18978 $on = $this->tagvspaces[$tag['value']][0]['n'];
18979 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18980 $on = 0.6;
18981 } else {
18982 $on = 1;
18983 }
18984 if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br', 'hr')))) {
18985 $hb = 0;
18986 } else {
18987 $hb = ($on * $cur_h);
18988 }
18989 if (($this->htmlvspace <= 0) AND ($on > 0)) {
18990 if (isset($parent['fontsize'])) {
18991 $hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
18992 } else {
18993 $hbz = $this->getCellHeight($this->FontSize);
18994 }
18995 }
18996 if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
18997 // fix vertical space after table
18998 $hbz = 0;
18999 }
19000 // closing vertical space
19001 $hbc = 0;
19002 if (isset($this->tagvspaces[$tag['value']][1]['h']) && !empty($this->tagvspaces[$tag['value']][1]['h']) && ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
19003 $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
19004 } elseif (isset($parent['fontsize'])) {
19005 $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
19006 } else {
19007 $pre_h = $this->getCellHeight($this->FontSize);
19008 }
19009 if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
19010 $cn = $this->tagvspaces[$tag['value']][1]['n'];
19011 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19012 $cn = 0.6;
19013 } else {
19014 $cn = 1;
19015 }
19016 if (isset($this->tagvspaces[$tag['value']][1])) {
19017 $hbc = ($cn * $pre_h);
19018 }
19019 }
19020 // Opening tag
19021 switch($tag['value']) {
19022 case 'table': {
19023 $cp = 0;
19024 $cs = 0;
19025 $dom[$key]['rowspans'] = array();
19026 if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
19027 $this->htmlvspace = 0;
19028 // set table header
19029 if (!TCPDF_STATIC::empty_string($dom[$key]['thead'])) {
19030 // set table header
19031 $this->thead = $dom[$key]['thead'];
19032 if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
19033 $this->theadMargins = array();
19034 $this->theadMargins['cell_padding'] = $this->cell_padding;
19035 $this->theadMargins['lmargin'] = $this->lMargin;
19036 $this->theadMargins['rmargin'] = $this->rMargin;
19037 $this->theadMargins['page'] = $this->page;
19038 $this->theadMargins['cell'] = $cell;
19039 $this->theadMargins['gvars'] = $this->getGraphicVars();
19040 }
19041 }
19042 }
19043 // store current margins and page
19044 $dom[$key]['old_cell_padding'] = $this->cell_padding;
19045 if (isset($tag['attribute']['cellpadding'])) {
19046 $pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
19047 $this->setCellPadding($pad);
19048 } elseif (isset($tag['padding'])) {
19049 $this->cell_padding = $tag['padding'];
19050 }
19051 if (isset($tag['attribute']['cellspacing'])) {
19052 $cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
19053 } elseif (isset($tag['border-spacing'])) {
19054 $cs = $tag['border-spacing']['V'];
19055 }
19056 $prev_y = $this->y;
19057 if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
19058 $this->inthead = true;
19059 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19060 $this->checkPageBreak($this->PageBreakTrigger + 1);
19061 }
19062 break;
19063 }
19064 case 'tr': {
19065 // array of columns positions
19066 $dom[$key]['cellpos'] = array();
19067 break;
19068 }
19069 case 'hr': {
19070 if ((isset($tag['height'])) AND ($tag['height'] != '')) {
19071 $hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
19072 } else {
19073 $hrHeight = $this->GetLineWidth();
19074 }
19075 $this->addHTMLVertSpace($hbz, max($hb, ($hrHeight / 2)), $cell, $firsttag);
19076 $x = $this->GetX();
19077 $y = $this->GetY();
19078 $wtmp = $this->w - $this->lMargin - $this->rMargin;
19079 if ($cell) {
19080 $wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
19081 }
19082 if ((isset($tag['width'])) AND ($tag['width'] != '')) {
19083 $hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
19084 } else {
19085 $hrWidth = $wtmp;
19086 }
19087 $prevlinewidth = $this->GetLineWidth();
19088 $this->setLineWidth($hrHeight);
19089
19090 $lineStyle = array();
19091 if (isset($tag['fgcolor'])) {
19092 $lineStyle['color'] = $tag['fgcolor'];
19093 }
19094
19095 if (isset($tag['fgcolor'])) {
19096 $lineStyle['color'] = $tag['fgcolor'];
19097 }
19098
19099 if (isset($tag['style']['cap'])) {
19100 $lineStyle['cap'] = $tag['style']['cap'];
19101 }
19102
19103 if (isset($tag['style']['join'])) {
19104 $lineStyle['join'] = $tag['style']['join'];
19105 }
19106
19107 if (isset($tag['style']['dash'])) {
19108 $lineStyle['dash'] = $tag['style']['dash'];
19109 }
19110
19111 if (isset($tag['style']['phase'])) {
19112 $lineStyle['phase'] = $tag['style']['phase'];
19113 }
19114
19115 $lineStyle = array_filter($lineStyle);
19116
19117 $this->Line($x, $y, $x + $hrWidth, $y, $lineStyle);
19118 $this->setLineWidth($prevlinewidth);
19119 $this->addHTMLVertSpace(max($hbc, ($hrHeight / 2)), 0, $cell, !isset($dom[($key + 1)]));
19120 break;
19121 }
19122 case 'a': {
19123 if (array_key_exists('href', $tag['attribute'])) {
19124 $this->HREF['url'] = $tag['attribute']['href'];
19125 }
19126 break;
19127 }
19128 case 'img': {
19129 if (empty($tag['attribute']['src'])) {
19130 break;
19131 }
19132 $imgsrc = $tag['attribute']['src'];
19133 if ($imgsrc[0] === '@') {
19134 // data stream
19135 $imgsrc = '@'.base64_decode(substr($imgsrc, 1));
19136 $type = preg_match('/<svg\s+[^>]*[^>]*>.*<\/svg>/is', $imgsrc) ? 'svg' : '';
19137 } else if (preg_match('@^data:image/([^;]*);base64,(.*)@', $imgsrc, $reg)) {
19138 $imgsrc = '@'.base64_decode($reg[2]);
19139 $type = $reg[1];
19140 } elseif ($this->isRelativePath($imgsrc)) {
19141 // accessing parent folders is not allowed
19142 break;
19143 } elseif ( $this->allowLocalFiles && substr($imgsrc, 0, 7) === 'file://') {
19144 // get image type from a local file path
19145 $imgsrc = substr($imgsrc, 7);
19146 $type = TCPDF_IMAGES::getImageFileType($imgsrc);
19147 } elseif ($this->hasExtForbiddenProtocol($imgsrc)) {
19148 break;
19149 } else {
19150 if (($imgsrc[0] === '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
19151 // fix image path
19152 $findroot = strpos($imgsrc, $_SERVER['DOCUMENT_ROOT']);
19153 if (($findroot === false) OR ($findroot > 1)) {
19154 if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
19155 $imgsrc = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$imgsrc;
19156 } else {
19157 $imgsrc = $_SERVER['DOCUMENT_ROOT'].$imgsrc;
19158 }
19159 }
19160 $imgsrc = urldecode($imgsrc);
19161 $testscrtype = @parse_url($imgsrc);
19162 if (empty($testscrtype['query'])) {
19163 // convert URL to server path
19164 $imgsrc = str_replace(K_PATH_URL, K_PATH_MAIN, $imgsrc);
19165 } elseif (preg_match('|^https?://|', $imgsrc) !== 1) {
19166 // convert URL to server path
19167 $imgsrc = str_replace(K_PATH_MAIN, K_PATH_URL, $imgsrc);
19168 }
19169 }
19170 // get image type
19171 $type = TCPDF_IMAGES::getImageFileType($imgsrc);
19172 }
19173 if (!isset($tag['width'])) {
19174 $tag['width'] = 0;
19175 }
19176 if (!isset($tag['height'])) {
19177 $tag['height'] = 0;
19178 }
19179 //if (!isset($tag['attribute']['align'])) {
19180 // the only alignment supported is "bottom"
19181 // further development is required for other modes.
19182 $tag['attribute']['align'] = 'bottom';
19183 //}
19184 switch($tag['attribute']['align']) {
19185 case 'top': {
19186 $align = 'T';
19187 break;
19188 }
19189 case 'middle': {
19190 $align = 'M';
19191 break;
19192 }
19193 case 'bottom': {
19194 $align = 'B';
19195 break;
19196 }
19197 default: {
19198 $align = 'B';
19199 break;
19200 }
19201 }
19202 $prevy = $this->y;
19203 $xpos = $this->x;
19204 $imglink = '';
19205 if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
19206 $imglink = $this->HREF['url'];
19207 if ($imglink[0] == '#' AND isset($imglink[1]) AND is_numeric($imglink[1])) {
19208 // convert url to internal link
19209 $lnkdata = explode(',', $imglink);
19210 if (isset($lnkdata[0])) {
19211 $page = intval(substr($lnkdata[0], 1));
19212 if (empty($page) OR ($page <= 0)) {
19213 $page = $this->page;
19214 }
19215 if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
19216 $lnky = floatval($lnkdata[1]);
19217 } else {
19218 $lnky = 0;
19219 }
19220 $imglink = $this->AddLink();
19221 $this->setLink($imglink, $lnky, $page);
19222 }
19223 }
19224 }
19225 $border = 0;
19226 if (isset($tag['border']) AND !empty($tag['border'])) {
19227 // currently only support 1 (frame) or a combination of 'LTRB'
19228 $border = $tag['border'];
19229 }
19230 $iw = '';
19231 if (isset($tag['width'])) {
19232 $iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k), 'px', false);
19233 }
19234 $ih = '';
19235 if (isset($tag['height'])) {
19236 $ih = $this->getHTMLUnitToUnits($tag['height'], ($tag['fontsize'] / $this->k), 'px', false);
19237 }
19238 if (($type == 'eps') OR ($type == 'ai')) {
19239 $this->ImageEps($imgsrc, $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
19240 } elseif ($type == 'svg') {
19241 $this->ImageSVG($imgsrc, $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
19242 } else {
19243 $this->Image($imgsrc, $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
19244 }
19245 switch($align) {
19246 case 'T': {
19247 $this->y = $prevy;
19248 break;
19249 }
19250 case 'M': {
19251 $this->y = (($this->img_rb_y + $prevy - ($this->getCellHeight($tag['fontsize'] / $this->k))) / 2);
19252 break;
19253 }
19254 case 'B': {
19255 $this->y = $this->img_rb_y - ($this->getCellHeight($tag['fontsize'] / $this->k) - ($this->getFontDescent($tag['fontname'], $tag['fontstyle'], $tag['fontsize']) * $this->cell_height_ratio));
19256 break;
19257 }
19258 }
19259 break;
19260 }
19261 case 'dl': {
19262 ++$this->listnum;
19263 if ($this->listnum == 1) {
19264 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19265 } else {
19266 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19267 }
19268 break;
19269 }
19270 case 'dt': {
19271 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19272 break;
19273 }
19274 case 'dd': {
19275 if ($this->rtl) {
19276 $this->rMargin += $this->listindent;
19277 } else {
19278 $this->lMargin += $this->listindent;
19279 }
19280 ++$this->listindentlevel;
19281 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19282 break;
19283 }
19284 case 'ul':
19285 case 'ol': {
19286 ++$this->listnum;
19287 if ($tag['value'] == 'ol') {
19288 $this->listordered[$this->listnum] = true;
19289 } else {
19290 $this->listordered[$this->listnum] = false;
19291 }
19292 if (isset($tag['attribute']['start'])) {
19293 $this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
19294 } else {
19295 $this->listcount[$this->listnum] = 0;
19296 }
19297 if ($this->rtl) {
19298 $this->rMargin += $this->listindent;
19299 $this->x -= $this->listindent;
19300 } else {
19301 $this->lMargin += $this->listindent;
19302 $this->x += $this->listindent;
19303 }
19304 ++$this->listindentlevel;
19305 if ($this->listnum == 1) {
19306 if ($key > 1) {
19307 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19308 }
19309 } else {
19310 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19311 }
19312 break;
19313 }
19314 case 'li': {
19315 if ($key > 2) {
19316 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19317 }
19318 if ($this->listordered[$this->listnum]) {
19319 // ordered item
19320 if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19321 $this->lispacer = $parent['attribute']['type'];
19322 } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19323 $this->lispacer = $parent['listtype'];
19324 } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19325 $this->lispacer = $this->lisymbol;
19326 } else {
19327 $this->lispacer = '#';
19328 }
19329 ++$this->listcount[$this->listnum];
19330 if (isset($tag['attribute']['value'])) {
19331 $this->listcount[$this->listnum] = intval($tag['attribute']['value']);
19332 }
19333 } else {
19334 // unordered item
19335 if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19336 $this->lispacer = $parent['attribute']['type'];
19337 } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19338 $this->lispacer = $parent['listtype'];
19339 } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19340 $this->lispacer = $this->lisymbol;
19341 } else {
19342 $this->lispacer = '!';
19343 }
19344 }
19345 break;
19346 }
19347 case 'blockquote': {
19348 if ($this->rtl) {
19349 $this->rMargin += $this->listindent;
19350 } else {
19351 $this->lMargin += $this->listindent;
19352 }
19353 ++$this->listindentlevel;
19354 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19355 break;
19356 }
19357 case 'br': {
19358 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19359 break;
19360 }
19361 case 'div': {
19362 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19363 break;
19364 }
19365 case 'p': {
19366 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19367 break;
19368 }
19369 case 'pre': {
19370 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19371 $this->premode = true;
19372 break;
19373 }
19374 case 'sup': {
19375 $this->setXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
19376 break;
19377 }
19378 case 'sub': {
19379 $this->setXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
19380 break;
19381 }
19382 case 'h1':
19383 case 'h2':
19384 case 'h3':
19385 case 'h4':
19386 case 'h5':
19387 case 'h6': {
19388 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19389 break;
19390 }
19391 // Form fields (since 4.8.000 - 2009-09-07)
19392 case 'form': {
19393 if (isset($tag['attribute']['action'])) {
19394 $this->form_action = $tag['attribute']['action'];
19395 } else {
19396 $this->Error('Please explicitly set action attribute path!');
19397 }
19398 if (isset($tag['attribute']['enctype'])) {
19399 $this->form_enctype = $tag['attribute']['enctype'];
19400 } else {
19401 $this->form_enctype = 'application/x-www-form-urlencoded';
19402 }
19403 if (isset($tag['attribute']['method'])) {
19404 $this->form_mode = $tag['attribute']['method'];
19405 } else {
19406 $this->form_mode = 'post';
19407 }
19408 break;
19409 }
19410 case 'input': {
19411 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19412 $name = $tag['attribute']['name'];
19413 } else {
19414 break;
19415 }
19416 $prop = array();
19417 $opt = array();
19418 if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19419 $prop['readonly'] = true;
19420 }
19421 if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19422 $value = $tag['attribute']['value'];
19423 }
19424 if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC::empty_string($tag['attribute']['maxlength'])) {
19425 $opt['maxlen'] = intval($tag['attribute']['maxlength']);
19426 }
19427 $h = $this->getCellHeight($this->FontSize);
19428 if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19429 $w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
19430 } else {
19431 $w = $h;
19432 }
19433 if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
19434 $checked = true;
19435 } else {
19436 $checked = false;
19437 }
19438 if (isset($tag['align'])) {
19439 switch ($tag['align']) {
19440 case 'C': {
19441 $opt['q'] = 1;
19442 break;
19443 }
19444 case 'R': {
19445 $opt['q'] = 2;
19446 break;
19447 }
19448 case 'L':
19449 default: {
19450 break;
19451 }
19452 }
19453 }
19454 switch ($tag['attribute']['type']) {
19455 case 'text': {
19456 if (isset($value)) {
19457 $opt['v'] = $value;
19458 }
19459 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19460 break;
19461 }
19462 case 'password': {
19463 if (isset($value)) {
19464 $opt['v'] = $value;
19465 }
19466 $prop['password'] = 'true';
19467 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19468 break;
19469 }
19470 case 'checkbox': {
19471 if (!isset($value)) {
19472 break;
19473 }
19474 $this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
19475 break;
19476 }
19477 case 'radio': {
19478 if (!isset($value)) {
19479 break;
19480 }
19481 $this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
19482 break;
19483 }
19484 case 'submit': {
19485 if (!isset($value)) {
19486 $value = 'submit';
19487 }
19488 $w = $this->GetStringWidth($value) * 1.5;
19489 $h *= 1.6;
19490 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19491 $action = array();
19492 $action['S'] = 'SubmitForm';
19493 $action['F'] = $this->form_action;
19494 if ($this->form_enctype != 'FDF') {
19495 $action['Flags'] = array('ExportFormat');
19496 }
19497 if ($this->form_mode == 'get') {
19498 $action['Flags'] = array('GetMethod');
19499 }
19500 $this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
19501 break;
19502 }
19503 case 'reset': {
19504 if (!isset($value)) {
19505 $value = 'reset';
19506 }
19507 $w = $this->GetStringWidth($value) * 1.5;
19508 $h *= 1.6;
19509 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19510 $this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
19511 break;
19512 }
19513 case 'file': {
19514 $prop['fileSelect'] = 'true';
19515 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19516 if (!isset($value)) {
19517 $value = '*';
19518 }
19519 $w = $this->GetStringWidth($value) * 2;
19520 $h *= 1.2;
19521 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19522 $jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
19523 $this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19524 break;
19525 }
19526 case 'hidden': {
19527 if (isset($value)) {
19528 $opt['v'] = $value;
19529 }
19530 $opt['f'] = array('invisible', 'hidden');
19531 $this->TextField($name, 0, 0, $prop, $opt, '', '', false);
19532 break;
19533 }
19534 case 'image': {
19535 // THIS TYPE MUST BE FIXED
19536 if (isset($tag['attribute']['src']) AND !TCPDF_STATIC::empty_string($tag['attribute']['src'])) {
19537 $img = $tag['attribute']['src'];
19538 } else {
19539 break;
19540 }
19541 $value = 'img';
19542 //$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
19543 if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19544 $jsaction = $tag['attribute']['onclick'];
19545 } else {
19546 $jsaction = '';
19547 }
19548 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19549 break;
19550 }
19551 case 'button': {
19552 if (!isset($value)) {
19553 $value = ' ';
19554 }
19555 $w = $this->GetStringWidth($value) * 1.5;
19556 $h *= 1.6;
19557 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19558 if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19559 $jsaction = $tag['attribute']['onclick'];
19560 } else {
19561 $jsaction = '';
19562 }
19563 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19564 break;
19565 }
19566 }
19567 break;
19568 }
19569 case 'textarea': {
19570 $prop = array();
19571 $opt = array();
19572 if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19573 $prop['readonly'] = true;
19574 }
19575 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19576 $name = $tag['attribute']['name'];
19577 } else {
19578 break;
19579 }
19580 if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19581 $opt['v'] = $tag['attribute']['value'];
19582 }
19583 if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC::empty_string($tag['attribute']['cols'])) {
19584 $w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
19585 } else {
19586 $w = 40;
19587 }
19588 if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
19589 $h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize);
19590 } else {
19591 $h = 10;
19592 }
19593 $prop['multiline'] = 'true';
19594 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19595 break;
19596 }
19597 case 'select': {
19598 $h = $this->getCellHeight($this->FontSize);
19599 if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19600 $h *= ($tag['attribute']['size'] + 1);
19601 }
19602 $prop = array();
19603 $opt = array();
19604 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19605 $name = $tag['attribute']['name'];
19606 } else {
19607 break;
19608 }
19609 $w = 0;
19610 if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
19611 $options = explode('#!NwL!#', $tag['attribute']['opt']);
19612 $values = array();
19613 foreach ($options as $val) {
19614 if (strpos($val, '#!TaB!#') !== false) {
19615 $opts = explode('#!TaB!#', $val);
19616 $values[] = $opts;
19617 $w = max($w, $this->GetStringWidth($opts[1]));
19618 } else {
19619 $values[] = $val;
19620 $w = max($w, $this->GetStringWidth($val));
19621 }
19622 }
19623 } else {
19624 break;
19625 }
19626 $w *= 2;
19627 if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
19628 $prop['multipleSelection'] = 'true';
19629 $this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19630 } else {
19631 $this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19632 }
19633 break;
19634 }
19635 case 'tcpdf': {
19636 if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
19637 // Special tag used to call TCPDF methods
19638 // This tag is disabled by default by the K_TCPDF_CALLS_IN_HTML constant on TCPDF configuration file.
19639 // Please use this feature only if you are in control of the HTML content and you are sure that it does not contain any harmful code.
19640 if (!empty($tag['attribute']['data'])) {
19641 $tcpdf_tag_data = $this->unserializeTCPDFtag($tag['attribute']['data']);
19642 if ($this->allowedTCPDFtag($tcpdf_tag_data['m'])) {
19643 call_user_func_array(array($this, $tcpdf_tag_data['m']), $tcpdf_tag_data['p']);
19644 }
19645 $this->newline = true;
19646 }
19647 }
19648 break;
19649 }
19650 default: {
19651 break;
19652 }
19653 }
19654 // define tags that support borders and background colors
19655 $bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
19656 if (in_array($tag['value'], $bordertags)) {
19657 // set border
19658 $dom[$key]['borderposition'] = $this->getBorderStartPosition();
19659 }
19660 if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
19661 $pba = $dom[$key]['attribute']['pagebreakafter'];
19662 // check for pagebreak
19663 if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19664 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19665 $this->checkPageBreak($this->PageBreakTrigger + 1);
19666 }
19667 if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19668 OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19669 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19670 $this->checkPageBreak($this->PageBreakTrigger + 1);
19671 }
19672 }
19673 return $dom;
19674 }
19675
19676 /**
19677 * Process closing tags.
19678 * @param array $dom html dom array
19679 * @param int $key current element id
19680 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
19681 * @param int $maxbottomliney maximum y value of current line
19682 * @return array $dom
19683 * @protected
19684 */
19685 protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
19686 $tag = $dom[$key];
19687 $parent = $dom[($dom[$key]['parent'])];
19688 $lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
19689 $in_table_head = false;
19690 // maximum x position (used to draw borders)
19691 if ($this->rtl) {
19692 $xmax = $this->w;
19693 } else {
19694 $xmax = 0;
19695 }
19696 if ($tag['block']) {
19697 $hbz = 0; // distance from y to line bottom
19698 $hb = 0; // vertical space between block tags
19699 // calculate vertical space for block tags
19700 if (isset($this->tagvspaces[$tag['value']][1]['h']) && !empty($this->tagvspaces[$tag['value']][1]['h']) && ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
19701 $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
19702 } elseif (isset($parent['fontsize'])) {
19703 $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
19704 } else {
19705 $pre_h = $this->getCellHeight($this->FontSize);
19706 }
19707 if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
19708 $cn = $this->tagvspaces[$tag['value']][1]['n'];
19709 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19710 $cn = 0.6;
19711 } else {
19712 $cn = 1;
19713 }
19714 if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
19715 $hb = 0;
19716 } else {
19717 $hb = ($cn * $pre_h);
19718 }
19719 if ($maxbottomliney > $this->PageBreakTrigger) {
19720 $hbz = $this->getCellHeight($this->FontSize);
19721 } elseif ($this->y < $maxbottomliney) {
19722 $hbz = ($maxbottomliney - $this->y);
19723 }
19724 }
19725 // Closing tag
19726 switch($tag['value']) {
19727 case 'tr': {
19728 $table_el = $dom[($dom[$key]['parent'])]['parent'];
19729 if (!isset($parent['endy'])) {
19730 $dom[($dom[$key]['parent'])]['endy'] = $this->y;
19731 $parent['endy'] = $this->y;
19732 }
19733 if (!isset($parent['endpage'])) {
19734 $dom[($dom[$key]['parent'])]['endpage'] = $this->page;
19735 $parent['endpage'] = $this->page;
19736 }
19737 if (!isset($parent['endcolumn'])) {
19738 $dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
19739 $parent['endcolumn'] = $this->current_column;
19740 }
19741 // update row-spanned cells
19742 if (isset($dom[$table_el]['rowspans'])) {
19743 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19744 $dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
19745 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19746 if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
19747 $dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
19748 } elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
19749 $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19750 $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19751 $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19752 }
19753 }
19754 }
19755 // report new endy and endpage to the rowspanned cells
19756 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19757 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19758 $dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
19759 $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19760 $dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
19761 $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19762 $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
19763 $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19764 }
19765 }
19766 // update remaining rowspanned cells
19767 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19768 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19769 $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
19770 $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
19771 $dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
19772 }
19773 }
19774 }
19775 $prev_page = $this->page;
19776 $this->setPage($dom[($dom[$key]['parent'])]['endpage']);
19777 if ($this->num_columns > 1) {
19778 if (($prev_page < $this->page)
19779 AND ((($this->current_column == 0) AND ($dom[($dom[$key]['parent'])]['endcolumn'] == ($this->num_columns - 1)))
19780 OR ($this->current_column == $dom[($dom[$key]['parent'])]['endcolumn']))) {
19781 // page jump
19782 $this->selectColumn(0);
19783 $dom[($dom[$key]['parent'])]['endcolumn'] = 0;
19784 $dom[($dom[$key]['parent'])]['endy'] = $this->y;
19785 } else {
19786 $this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
19787 $this->y = $dom[($dom[$key]['parent'])]['endy'];
19788 }
19789 } else {
19790 $this->y = $dom[($dom[$key]['parent'])]['endy'];
19791 }
19792 if (isset($dom[$table_el]['attribute']['cellspacing'])) {
19793 $this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
19794 } elseif (isset($dom[$table_el]['border-spacing'])) {
19795 $this->y += $dom[$table_el]['border-spacing']['V'];
19796 }
19797 $this->Ln(0, $cell);
19798 if ($this->current_column == $parent['startcolumn']) {
19799 $this->x = $parent['startx'];
19800 }
19801 // account for booklet mode
19802 if ($this->page > $parent['startpage']) {
19803 if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
19804 $this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
19805 } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
19806 $this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
19807 }
19808 }
19809 break;
19810 }
19811 case 'tablehead':
19812 // closing tag used for the thead part
19813 $in_table_head = true;
19814 $this->inthead = false;
19815 case 'table': {
19816 $table_el = $parent;
19817 // set default border
19818 if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
19819 // set default border
19820 $border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
19821 } else {
19822 $border = 0;
19823 }
19824 $default_border = $border;
19825 // fix bottom line alignment of last line before page break
19826 foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
19827 // update row-spanned cells
19828 if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19829 foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19830 if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
19831 $dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
19832 }
19833 if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
19834 $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
19835 }
19836 }
19837 }
19838 if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
19839 $pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
19840 $dom[$prevtrkey]['endy'] = $pgendy;
19841 // update row-spanned cells
19842 if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19843 foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19844 if (($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
19845 $dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
19846 $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
19847 }
19848 }
19849 }
19850 }
19851 $prevtrkey = $trkey;
19852 $table_el = $dom[($dom[$key]['parent'])];
19853 }
19854 // for each row
19855 if (!empty($table_el['trids'])) {
19856 unset($xmax);
19857 }
19858 foreach ($table_el['trids'] as $j => $trkey) {
19859 $parent = $dom[$trkey];
19860 if (!isset($xmax)) {
19861 $xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
19862 }
19863 // for each cell on the row
19864 foreach ($parent['cellpos'] as $k => $cellpos) {
19865 if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
19866 $cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
19867 $cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
19868 $endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
19869 $startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
19870 $endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
19871 $startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
19872 $endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
19873 } else {
19874 $endy = $parent['endy'];
19875 $startpage = $parent['startpage'];
19876 $endpage = $parent['endpage'];
19877 $startcolumn = $parent['startcolumn'];
19878 $endcolumn = $parent['endcolumn'];
19879 }
19880 if ($this->num_columns == 0) {
19881 $this->num_columns = 1;
19882 }
19883 if (isset($cellpos['border'])) {
19884 $border = $cellpos['border'];
19885 }
19886 if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
19887 $this->setFillColorArray($cellpos['bgcolor']);
19888 $fill = true;
19889 } else {
19890 $fill = false;
19891 }
19892 $x = $cellpos['startx'];
19893 $y = $parent['starty'];
19894 $starty = $y;
19895 $w = abs($cellpos['endx'] - $cellpos['startx']);
19896 // get border modes
19897 $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
19898 $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
19899 $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19900 // design borders around HTML cells.
19901 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
19902 $ccode = '';
19903 $this->setPage($page);
19904 if ($this->num_columns < 2) {
19905 // single-column mode
19906 $this->x = $x;
19907 $this->y = $this->tMargin;
19908 }
19909 // account for margin changes
19910 if ($page > $startpage) {
19911 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
19912 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
19913 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
19914 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
19915 }
19916 }
19917 if ($startpage == $endpage) { // single page
19918 $deltacol = 0;
19919 $deltath = 0;
19920 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
19921 $this->selectColumn($column);
19922 if ($startcolumn == $endcolumn) { // single column
19923 $cborder = $border;
19924 $h = $endy - $parent['starty'];
19925 $this->y = $y;
19926 $this->x = $x;
19927 } elseif ($column == $startcolumn) { // first column
19928 $cborder = $border_start;
19929 $this->y = $starty;
19930 $this->x = $x;
19931 $h = $this->h - $this->y - $this->bMargin;
19932 if ($this->rtl) {
19933 $deltacol = $this->x + $this->rMargin - $this->w;
19934 } else {
19935 $deltacol = $this->x - $this->lMargin;
19936 }
19937 } elseif ($column == $endcolumn) { // end column
19938 $cborder = $border_end;
19939 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19940 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19941 }
19942 $this->x += $deltacol;
19943 $h = $endy - $this->y;
19944 } else { // middle column
19945 $cborder = $border_middle;
19946 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19947 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19948 }
19949 $this->x += $deltacol;
19950 $h = $this->h - $this->y - $this->bMargin;
19951 }
19952 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19953 } // end for each column
19954 } elseif ($page == $startpage) { // first page
19955 $deltacol = 0;
19956 $deltath = 0;
19957 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
19958 $this->selectColumn($column);
19959 if ($column == $startcolumn) { // first column
19960 $cborder = $border_start;
19961 $this->y = $starty;
19962 $this->x = $x;
19963 $h = $this->h - $this->y - $this->bMargin;
19964 if ($this->rtl) {
19965 $deltacol = $this->x + $this->rMargin - $this->w;
19966 } else {
19967 $deltacol = $this->x - $this->lMargin;
19968 }
19969 } else { // middle column
19970 $cborder = $border_middle;
19971 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19972 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19973 }
19974 $this->x += $deltacol;
19975 $h = $this->h - $this->y - $this->bMargin;
19976 }
19977 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19978 } // end for each column
19979 } elseif ($page == $endpage) { // last page
19980 $deltacol = 0;
19981 $deltath = 0;
19982 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
19983 $this->selectColumn($column);
19984 if ($column == $endcolumn) { // end column
19985 $cborder = $border_end;
19986 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19987 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19988 }
19989 $this->x += $deltacol;
19990 $h = $endy - $this->y;
19991 } else { // middle column
19992 $cborder = $border_middle;
19993 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19994 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19995 }
19996 $this->x += $deltacol;
19997 $h = $this->h - $this->y - $this->bMargin;
19998 }
19999 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20000 } // end for each column
20001 } else { // middle page
20002 $deltacol = 0;
20003 $deltath = 0;
20004 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
20005 $this->selectColumn($column);
20006 $cborder = $border_middle;
20007 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
20008 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
20009 }
20010 $this->x += $deltacol;
20011 $h = $this->h - $this->y - $this->bMargin;
20012 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20013 } // end for each column
20014 }
20015 if (!empty($cborder) OR !empty($fill)) {
20016 $offsetlen = strlen($ccode);
20017 // draw border and fill
20018 if ($this->inxobj) {
20019 // we are inside an XObject template
20020 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
20021 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
20022 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
20023 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
20024 } else {
20025 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
20026 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
20027 }
20028 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
20029 $pstart = substr($pagebuff, 0, $pagemark);
20030 $pend = substr($pagebuff, $pagemark);
20031 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
20032 } else {
20033 // draw border and fill
20034 if (end($this->transfmrk[$this->page]) !== false) {
20035 $pagemarkkey = key($this->transfmrk[$this->page]);
20036 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
20037 } elseif ($this->InFooter) {
20038 $pagemark = $this->footerpos[$this->page];
20039 } else {
20040 $pagemark = $this->intmrk[$this->page];
20041 }
20042 $pagebuff = $this->getPageBuffer($this->page);
20043 $pstart = substr($pagebuff, 0, $pagemark);
20044 $pend = substr($pagebuff, $pagemark);
20045 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
20046 }
20047 }
20048 } // end for each page
20049 // restore default border
20050 $border = $default_border;
20051 } // end for each cell on the row
20052 if (isset($table_el['attribute']['cellspacing'])) {
20053 $this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
20054 } elseif (isset($table_el['border-spacing'])) {
20055 $this->y += $table_el['border-spacing']['V'];
20056 }
20057 $this->Ln(0, $cell);
20058 $this->x = $parent['startx'];
20059 if ($endpage > $startpage) {
20060 if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
20061 $this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
20062 } elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
20063 $this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
20064 }
20065 }
20066 }
20067 if (!$in_table_head) { // we are not inside a thead section
20068 $this->cell_padding = isset($table_el['old_cell_padding']) ? $table_el['old_cell_padding'] : array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
20069 // reset row height
20070 $this->resetLastH();
20071 if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
20072 $plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
20073 if (($plendiff > 0) AND ($plendiff < 60)) {
20074 $pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
20075 if (substr($pagediff, 0, 5) == 'BT /F') {
20076 // the difference is only a font setting
20077 $plendiff = 0;
20078 }
20079 }
20080 if ($plendiff == 0) {
20081 // remove last blank page
20082 $this->deletePage($this->numpages);
20083 }
20084 }
20085 if (isset($this->theadMargins['top'])) {
20086 // restore top margin
20087 $this->tMargin = $this->theadMargins['top'];
20088 }
20089 if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
20090 // reset main table header
20091 $this->thead = '';
20092 $this->theadMargins = array();
20093 $this->pagedim[$this->page]['tm'] = $this->tMargin;
20094 }
20095 }
20096 $parent = $table_el;
20097 break;
20098 }
20099 case 'a': {
20100 $this->HREF = array();
20101 break;
20102 }
20103 case 'sup': {
20104 $this->setXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
20105 break;
20106 }
20107 case 'sub': {
20108 $this->setXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
20109 break;
20110 }
20111 case 'div': {
20112 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20113 break;
20114 }
20115 case 'blockquote': {
20116 if ($this->rtl) {
20117 $this->rMargin -= $this->listindent;
20118 } else {
20119 $this->lMargin -= $this->listindent;
20120 }
20121 --$this->listindentlevel;
20122 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20123 break;
20124 }
20125 case 'p': {
20126 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20127 break;
20128 }
20129 case 'pre': {
20130 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20131 $this->premode = false;
20132 break;
20133 }
20134 case 'dl': {
20135 --$this->listnum;
20136 if ($this->listnum <= 0) {
20137 $this->listnum = 0;
20138 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20139 } else {
20140 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20141 }
20142 $this->resetLastH();
20143 break;
20144 }
20145 case 'dt': {
20146 $this->lispacer = '';
20147 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20148 break;
20149 }
20150 case 'dd': {
20151 $this->lispacer = '';
20152 if ($this->rtl) {
20153 $this->rMargin -= $this->listindent;
20154 } else {
20155 $this->lMargin -= $this->listindent;
20156 }
20157 --$this->listindentlevel;
20158 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20159 break;
20160 }
20161 case 'ul':
20162 case 'ol': {
20163 --$this->listnum;
20164 $this->lispacer = '';
20165 if ($this->rtl) {
20166 $this->rMargin -= $this->listindent;
20167 } else {
20168 $this->lMargin -= $this->listindent;
20169 }
20170 --$this->listindentlevel;
20171 if ($this->listnum <= 0) {
20172 $this->listnum = 0;
20173 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20174 } else {
20175 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20176 }
20177 $this->resetLastH();
20178 break;
20179 }
20180 case 'li': {
20181 $this->lispacer = '';
20182 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20183 break;
20184 }
20185 case 'h1':
20186 case 'h2':
20187 case 'h3':
20188 case 'h4':
20189 case 'h5':
20190 case 'h6': {
20191 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20192 break;
20193 }
20194 // Form fields (since 4.8.000 - 2009-09-07)
20195 case 'form': {
20196 $this->form_action = '';
20197 $this->form_enctype = 'application/x-www-form-urlencoded';
20198 break;
20199 }
20200 default : {
20201 break;
20202 }
20203 }
20204 // draw border and background (if any)
20205 $this->drawHTMLTagBorder($parent, $xmax);
20206 if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
20207 $pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
20208 // check for pagebreak
20209 if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
20210 // add a page (or trig AcceptPageBreak() for multicolumn mode)
20211 $this->checkPageBreak($this->PageBreakTrigger + 1);
20212 }
20213 if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
20214 OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
20215 // add a page (or trig AcceptPageBreak() for multicolumn mode)
20216 $this->checkPageBreak($this->PageBreakTrigger + 1);
20217 }
20218 }
20219 $this->tmprtl = false;
20220 return $dom;
20221 }
20222
20223 /**
20224 * Add vertical spaces if needed.
20225 * @param string $hbz Distance between current y and line bottom.
20226 * @param string $hb The height of the break.
20227 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
20228 * @param boolean $firsttag set to true when the tag is the first.
20229 * @param boolean $lasttag set to true when the tag is the last.
20230 * @protected
20231 */
20232 protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
20233 if ($firsttag) {
20234 $this->Ln(0, $cell);
20235 $this->htmlvspace = 0;
20236 return;
20237 }
20238 if ($lasttag) {
20239 $this->Ln($hbz, $cell);
20240 $this->htmlvspace = 0;
20241 return;
20242 }
20243 if ($hb < $this->htmlvspace) {
20244 $hd = 0;
20245 } else {
20246 $hd = $hb - $this->htmlvspace;
20247 $this->htmlvspace = $hb;
20248 }
20249 $this->Ln(($hbz + $hd), $cell);
20250 }
20251
20252 /**
20253 * Return the starting coordinates to draw an html border
20254 * @return array containing top-left border coordinates
20255 * @protected
20256 * @since 5.7.000 (2010-08-03)
20257 */
20258 protected function getBorderStartPosition() {
20259 if ($this->rtl) {
20260 $xmax = $this->lMargin;
20261 } else {
20262 $xmax = $this->w - $this->rMargin;
20263 }
20264 return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
20265 }
20266
20267 /**
20268 * Draw an HTML block border and fill
20269 * @param array $tag array of tag properties.
20270 * @param int $xmax end X coordinate for border.
20271 * @protected
20272 * @since 5.7.000 (2010-08-03)
20273 */
20274 protected function drawHTMLTagBorder($tag, $xmax) {
20275 if (!isset($tag['borderposition'])) {
20276 // nothing to draw
20277 return;
20278 }
20279 $prev_x = $this->x;
20280 $prev_y = $this->y;
20281 $prev_lasth = $this->lasth;
20282 $border = 0;
20283 $fill = false;
20284 $this->lasth = 0;
20285 if (isset($tag['border']) AND !empty($tag['border'])) {
20286 // get border style
20287 $border = $tag['border'];
20288 if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
20289 // border for table header
20290 $border = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20291 }
20292 }
20293 if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
20294 // get background color
20295 $old_bgcolor = $this->bgcolor;
20296 $this->setFillColorArray($tag['bgcolor']);
20297 $fill = true;
20298 }
20299 if (!$border AND !$fill) {
20300 // nothing to draw
20301 return;
20302 }
20303 if (isset($tag['attribute']['cellspacing'])) {
20304 $clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
20305 $cellspacing = array('H' => $clsp, 'V' => $clsp);
20306 } elseif (isset($tag['border-spacing'])) {
20307 $cellspacing = $tag['border-spacing'];
20308 } else {
20309 $cellspacing = array('H' => 0, 'V' => 0);
20310 }
20311 if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
20312 // draw the border externally respect the sqare edge.
20313 $border['mode'] = 'ext';
20314 }
20315 if ($this->rtl) {
20316 if ($xmax >= $tag['borderposition']['x']) {
20317 $xmax = $tag['borderposition']['xmax'];
20318 }
20319 $w = ($tag['borderposition']['x'] - $xmax);
20320 } else {
20321 if ($xmax <= $tag['borderposition']['x']) {
20322 $xmax = $tag['borderposition']['xmax'];
20323 }
20324 $w = ($xmax - $tag['borderposition']['x']);
20325 }
20326 if ($w <= 0) {
20327 return;
20328 }
20329 $w += $cellspacing['H'];
20330 $startpage = $tag['borderposition']['page'];
20331 $startcolumn = $tag['borderposition']['column'];
20332 $x = $tag['borderposition']['x'];
20333 $y = $tag['borderposition']['y'];
20334 $endpage = $this->page;
20335 $starty = $tag['borderposition']['y'] - $cellspacing['V'];
20336 $currentY = $this->y;
20337 $this->x = $x;
20338 // get latest column
20339 $endcolumn = $this->current_column;
20340 if ($this->num_columns == 0) {
20341 $this->num_columns = 1;
20342 }
20343 // get border modes
20344 $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
20345 $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
20346 $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20347 // temporary disable page regions
20348 $temp_page_regions = $this->page_regions;
20349 $this->page_regions = array();
20350 // design borders around HTML cells.
20351 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
20352 $ccode = '';
20353 $this->setPage($page);
20354 if ($this->num_columns < 2) {
20355 // single-column mode
20356 $this->x = $x;
20357 $this->y = $this->tMargin;
20358 }
20359 // account for margin changes
20360 if ($page > $startpage) {
20361 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
20362 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
20363 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
20364 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
20365 }
20366 }
20367 if ($startpage == $endpage) {
20368 // single page
20369 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
20370 $this->selectColumn($column);
20371 if ($startcolumn == $endcolumn) { // single column
20372 $cborder = $border;
20373 $h = ($currentY - $y) + $cellspacing['V'];
20374 $this->y = $starty;
20375 } elseif ($column == $startcolumn) { // first column
20376 $cborder = $border_start;
20377 $this->y = $starty;
20378 $h = $this->h - $this->y - $this->bMargin;
20379 } elseif ($column == $endcolumn) { // end column
20380 $cborder = $border_end;
20381 $h = $currentY - $this->y;
20382 } else { // middle column
20383 $cborder = $border_middle;
20384 $h = $this->h - $this->y - $this->bMargin;
20385 }
20386 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20387 } // end for each column
20388 } elseif ($page == $startpage) { // first page
20389 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
20390 $this->selectColumn($column);
20391 if ($column == $startcolumn) { // first column
20392 $cborder = $border_start;
20393 $this->y = $starty;
20394 $h = $this->h - $this->y - $this->bMargin;
20395 } else { // middle column
20396 $cborder = $border_middle;
20397 $h = $this->h - $this->y - $this->bMargin;
20398 }
20399 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20400 } // end for each column
20401 } elseif ($page == $endpage) { // last page
20402 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
20403 $this->selectColumn($column);
20404 if ($column == $endcolumn) {
20405 // end column
20406 $cborder = $border_end;
20407 $h = $currentY - $this->y;
20408 } else {
20409 // middle column
20410 $cborder = $border_middle;
20411 $h = $this->h - $this->y - $this->bMargin;
20412 }
20413 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20414 } // end for each column
20415 } else { // middle page
20416 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
20417 $this->selectColumn($column);
20418 $cborder = $border_middle;
20419 $h = $this->h - $this->y - $this->bMargin;
20420 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20421 } // end for each column
20422 }
20423 if ($cborder OR $fill) {
20424 $offsetlen = strlen($ccode);
20425 // draw border and fill
20426 if ($this->inxobj) {
20427 // we are inside an XObject template
20428 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
20429 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
20430 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
20431 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
20432 } else {
20433 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
20434 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
20435 }
20436 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
20437 $pstart = substr($pagebuff, 0, $pagemark);
20438 $pend = substr($pagebuff, $pagemark);
20439 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
20440 } else {
20441 if (end($this->transfmrk[$this->page]) !== false) {
20442 $pagemarkkey = key($this->transfmrk[$this->page]);
20443 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
20444 } elseif ($this->InFooter) {
20445 $pagemark = $this->footerpos[$this->page];
20446 } else {
20447 $pagemark = $this->intmrk[$this->page];
20448 }
20449 $pagebuff = $this->getPageBuffer($this->page);
20450 $pstart = substr($pagebuff, 0, $pagemark);
20451 $pend = substr($pagebuff, $pagemark);
20452 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
20453 $this->bordermrk[$this->page] += $offsetlen;
20454 $this->cntmrk[$this->page] += $offsetlen;
20455 }
20456 }
20457 } // end for each page
20458 // restore page regions
20459 $this->page_regions = $temp_page_regions;
20460 if (isset($old_bgcolor)) {
20461 // restore background color
20462 $this->setFillColorArray($old_bgcolor);
20463 }
20464 // restore pointer position
20465 $this->x = $prev_x;
20466 $this->y = $prev_y;
20467 $this->lasth = $prev_lasth;
20468 }
20469
20470 /**
20471 * Set the default bullet to be used as LI bullet symbol
20472 * @param string $symbol character or string to be used (legal values are: '' = automatic, '!' = auto bullet, '#' = auto numbering, 'disc', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek', 'img|type|width|height|image.ext')
20473 * @public
20474 * @since 4.0.028 (2008-09-26)
20475 */
20476 public function setLIsymbol($symbol='!') {
20477 // check for custom image symbol
20478 if (substr($symbol, 0, 4) == 'img|') {
20479 $this->lisymbol = $symbol;
20480 return;
20481 }
20482 $symbol = strtolower($symbol);
20483 $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');
20484 if (in_array($symbol, $valid_symbols)) {
20485 $this->lisymbol = $symbol;
20486 } else {
20487 $this->lisymbol = '';
20488 }
20489 }
20490
20491 /**
20492 * Set the booklet mode for double-sided pages.
20493 * @param boolean $booklet true set the booklet mode on, false otherwise.
20494 * @param float $inner Inner page margin.
20495 * @param float $outer Outer page margin.
20496 * @public
20497 * @since 4.2.000 (2008-10-29)
20498 */
20499 public function setBooklet($booklet=true, $inner=-1, $outer=-1) {
20500 $this->booklet = $booklet;
20501 if ($inner >= 0) {
20502 $this->lMargin = $inner;
20503 }
20504 if ($outer >= 0) {
20505 $this->rMargin = $outer;
20506 }
20507 }
20508
20509 /**
20510 * Swap the left and right margins.
20511 * @param boolean $reverse if true swap left and right margins.
20512 * @protected
20513 * @since 4.2.000 (2008-10-29)
20514 */
20515 protected function swapMargins($reverse=true) {
20516 if ($reverse) {
20517 // swap left and right margins
20518 $mtemp = $this->original_lMargin;
20519 $this->original_lMargin = $this->original_rMargin;
20520 $this->original_rMargin = $mtemp;
20521 $deltam = $this->original_lMargin - $this->original_rMargin;
20522 $this->lMargin += $deltam;
20523 $this->rMargin -= $deltam;
20524 }
20525 }
20526
20527 /**
20528 * Set the vertical spaces for HTML tags.
20529 * The array must have the following structure (example):
20530 * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
20531 * The first array level contains the tag names,
20532 * the second level contains 0 for opening tags or 1 for closing tags,
20533 * the third level contains the vertical space unit (h) and the number spaces to add (n).
20534 * If the h parameter is not specified, default values are used.
20535 * @param array $tagvs array of tags and relative vertical spaces.
20536 * @public
20537 * @since 4.2.001 (2008-10-30)
20538 */
20539 public function setHtmlVSpace($tagvs) {
20540 $this->tagvspaces = $tagvs;
20541 }
20542
20543 /**
20544 * Set custom width for list indentation.
20545 * @param float $width width of the indentation. Use negative value to disable it.
20546 * @public
20547 * @since 4.2.007 (2008-11-12)
20548 */
20549 public function setListIndentWidth($width) {
20550 return $this->customlistindent = floatval($width);
20551 }
20552
20553 /**
20554 * Set the top/bottom cell sides to be open or closed when the cell cross the page.
20555 * @param boolean $isopen if true keeps the top/bottom border open for the cell sides that cross the page.
20556 * @public
20557 * @since 4.2.010 (2008-11-14)
20558 */
20559 public function setOpenCell($isopen) {
20560 $this->opencell = $isopen;
20561 }
20562
20563 /**
20564 * Set the color and font style for HTML links.
20565 * @param array $color RGB array of colors
20566 * @param string $fontstyle additional font styles to add
20567 * @public
20568 * @since 4.4.003 (2008-12-09)
20569 */
20570 public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
20571 $this->htmlLinkColorArray = $color;
20572 $this->htmlLinkFontStyle = $fontstyle;
20573 }
20574
20575 /**
20576 * Convert HTML string containing value and unit of measure to user's units or points.
20577 * @param string $htmlval String containing values and unit.
20578 * @param string $refsize Reference value in points.
20579 * @param string $defaultunit Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
20580 * @param boolean $points If true returns points, otherwise returns value in user's units.
20581 * @return float value in user's unit or point if $points=true
20582 * @public
20583 * @since 4.4.004 (2008-12-10)
20584 */
20585 public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
20586 $supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
20587 $retval = 0;
20588 $value = 0;
20589 $unit = 'px';
20590 if ($points) {
20591 $k = 1;
20592 } else {
20593 $k = $this->k;
20594 }
20595 if (in_array($defaultunit, $supportedunits)) {
20596 $unit = $defaultunit;
20597 }
20598 if (is_numeric($htmlval)) {
20599 $value = floatval($htmlval);
20600 } elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
20601 $value = floatval($mnum[1]);
20602 if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
20603 if (in_array($munit[1], $supportedunits)) {
20604 $unit = $munit[1];
20605 }
20606 }
20607 }
20608 switch ($unit) {
20609 // percentage
20610 case '%': {
20611 $retval = (($value * $refsize) / 100);
20612 break;
20613 }
20614 // relative-size
20615 case 'em': {
20616 $retval = ($value * $refsize);
20617 break;
20618 }
20619 // height of lower case 'x' (about half the font-size)
20620 case 'ex': {
20621 $retval = ($value * ($refsize / 2));
20622 break;
20623 }
20624 // absolute-size
20625 case 'in': {
20626 $retval = (($value * $this->dpi) / $k);
20627 break;
20628 }
20629 // centimeters
20630 case 'cm': {
20631 $retval = (($value / 2.54 * $this->dpi) / $k);
20632 break;
20633 }
20634 // millimeters
20635 case 'mm': {
20636 $retval = (($value / 25.4 * $this->dpi) / $k);
20637 break;
20638 }
20639 // one pica is 12 points
20640 case 'pc': {
20641 $retval = (($value * 12) / $k);
20642 break;
20643 }
20644 // points
20645 case 'pt': {
20646 $retval = ($value / $k);
20647 break;
20648 }
20649 // pixels
20650 case 'px': {
20651 $retval = $this->pixelsToUnits($value);
20652 if ($points) {
20653 $retval *= $this->k;
20654 }
20655 break;
20656 }
20657 }
20658 return $retval;
20659 }
20660
20661 /**
20662 * Output an HTML list bullet or ordered item symbol
20663 * @param int $listdepth list nesting level
20664 * @param string $listtype type of list
20665 * @param float $size current font size
20666 * @protected
20667 * @since 4.4.004 (2008-12-10)
20668 */
20669 protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
20670 if ($this->state != 2) {
20671 return;
20672 }
20673 $size /= $this->k;
20674 $fill = '';
20675 $bgcolor = $this->bgcolor;
20676 $color = $this->fgcolor;
20677 $strokecolor = $this->strokecolor;
20678 $width = 0;
20679 $textitem = '';
20680 $tmpx = $this->x;
20681 $lspace = $this->GetStringWidth(' ');
20682 if ($listtype == '^') {
20683 // special symbol used for avoid justification of rect bullet
20684 $this->lispacer = '';
20685 return;
20686 } elseif ($listtype == '!') {
20687 // set default list type for unordered list
20688 $deftypes = array('disc', 'circle', 'square');
20689 $listtype = $deftypes[($listdepth - 1) % 3];
20690 } elseif ($listtype == '#') {
20691 // set default list type for ordered list
20692 $listtype = 'decimal';
20693 } elseif (substr($listtype, 0, 4) == 'img|') {
20694 // custom image type ('img|type|width|height|image.ext')
20695 $img = explode('|', $listtype);
20696 $listtype = 'img';
20697 }
20698 switch ($listtype) {
20699 // unordered types
20700 case 'none': {
20701 break;
20702 }
20703 case 'disc': {
20704 $r = $size / 6;
20705 $lspace += (2 * $r);
20706 if ($this->rtl) {
20707 $this->x += $lspace;
20708 } else {
20709 $this->x -= $lspace;
20710 }
20711 $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
20712 break;
20713 }
20714 case 'circle': {
20715 $r = $size / 6;
20716 $lspace += (2 * $r);
20717 if ($this->rtl) {
20718 $this->x += $lspace;
20719 } else {
20720 $this->x -= $lspace;
20721 }
20722 $prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
20723 $new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
20724 $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
20725 $this->_out($prev_line_style); // restore line settings
20726 break;
20727 }
20728 case 'square': {
20729 $l = $size / 3;
20730 $lspace += $l;
20731 if ($this->rtl) {;
20732 $this->x += $lspace;
20733 } else {
20734 $this->x -= $lspace;
20735 }
20736 $this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
20737 break;
20738 }
20739 case 'img': {
20740 // 1=>type, 2=>width, 3=>height, 4=>image.ext
20741 $lspace += $img[2];
20742 if ($this->rtl) {;
20743 $this->x += $lspace;
20744 } else {
20745 $this->x -= $lspace;
20746 }
20747 $imgtype = strtolower($img[1]);
20748 $prev_y = $this->y;
20749 switch ($imgtype) {
20750 case 'svg': {
20751 $this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
20752 break;
20753 }
20754 case 'ai':
20755 case 'eps': {
20756 $this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
20757 break;
20758 }
20759 default: {
20760 $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);
20761 break;
20762 }
20763 }
20764 $this->y = $prev_y;
20765 break;
20766 }
20767 // ordered types
20768 // $this->listcount[$this->listnum];
20769 // $textitem
20770 case '1':
20771 case 'decimal': {
20772 $textitem = $this->listcount[$this->listnum];
20773 break;
20774 }
20775 case 'decimal-leading-zero': {
20776 $textitem = sprintf('%02d', $this->listcount[$this->listnum]);
20777 break;
20778 }
20779 case 'i':
20780 case 'lower-roman': {
20781 $textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
20782 break;
20783 }
20784 case 'I':
20785 case 'upper-roman': {
20786 $textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
20787 break;
20788 }
20789 case 'a':
20790 case 'lower-alpha':
20791 case 'lower-latin': {
20792 $textitem = chr(97 + $this->listcount[$this->listnum] - 1);
20793 break;
20794 }
20795 case 'A':
20796 case 'upper-alpha':
20797 case 'upper-latin': {
20798 $textitem = chr(65 + $this->listcount[$this->listnum] - 1);
20799 break;
20800 }
20801 case 'lower-greek': {
20802 $textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
20803 break;
20804 }
20805 /*
20806 // Types to be implemented (special handling)
20807 case 'hebrew': {
20808 break;
20809 }
20810 case 'armenian': {
20811 break;
20812 }
20813 case 'georgian': {
20814 break;
20815 }
20816 case 'cjk-ideographic': {
20817 break;
20818 }
20819 case 'hiragana': {
20820 break;
20821 }
20822 case 'katakana': {
20823 break;
20824 }
20825 case 'hiragana-iroha': {
20826 break;
20827 }
20828 case 'katakana-iroha': {
20829 break;
20830 }
20831 */
20832 default: {
20833 $textitem = $this->listcount[$this->listnum];
20834 }
20835 }
20836 if (!TCPDF_STATIC::empty_string($textitem)) {
20837 // Check whether we need a new page or new column
20838 $prev_y = $this->y;
20839 $h = $this->getCellHeight($this->FontSize);
20840 if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
20841 $tmpx = $this->x;
20842 }
20843 // print ordered item
20844 if ($this->rtl) {
20845 $textitem = '.'.$textitem;
20846 } else {
20847 $textitem = $textitem.'.';
20848 }
20849 $lspace += $this->GetStringWidth($textitem);
20850 if ($this->rtl) {
20851 $this->x += $lspace;
20852 } else {
20853 $this->x -= $lspace;
20854 }
20855 $this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
20856 }
20857 $this->x = $tmpx;
20858 $this->lispacer = '^';
20859 // restore colors
20860 $this->setFillColorArray($bgcolor);
20861 $this->setDrawColorArray($strokecolor);
20862 $this->settextColorArray($color);
20863 }
20864
20865 /**
20866 * Returns current graphic variables as array.
20867 * @return array of graphic variables
20868 * @protected
20869 * @since 4.2.010 (2008-11-14)
20870 */
20871 protected function getGraphicVars() {
20872 $grapvars = array(
20873 'FontFamily' => $this->FontFamily,
20874 'FontStyle' => $this->FontStyle,
20875 'FontSizePt' => $this->FontSizePt,
20876 'rMargin' => $this->rMargin,
20877 'lMargin' => $this->lMargin,
20878 'cell_padding' => $this->cell_padding,
20879 'cell_margin' => $this->cell_margin,
20880 'LineWidth' => $this->LineWidth,
20881 'linestyleWidth' => $this->linestyleWidth,
20882 'linestyleCap' => $this->linestyleCap,
20883 'linestyleJoin' => $this->linestyleJoin,
20884 'linestyleDash' => $this->linestyleDash,
20885 'textrendermode' => $this->textrendermode,
20886 'textstrokewidth' => $this->textstrokewidth,
20887 'DrawColor' => $this->DrawColor,
20888 'FillColor' => $this->FillColor,
20889 'TextColor' => $this->TextColor,
20890 'ColorFlag' => $this->ColorFlag,
20891 'bgcolor' => $this->bgcolor,
20892 'fgcolor' => $this->fgcolor,
20893 'htmlvspace' => $this->htmlvspace,
20894 'listindent' => $this->listindent,
20895 'listindentlevel' => $this->listindentlevel,
20896 'listnum' => $this->listnum,
20897 'listordered' => $this->listordered,
20898 'listcount' => $this->listcount,
20899 'lispacer' => $this->lispacer,
20900 'cell_height_ratio' => $this->cell_height_ratio,
20901 'font_stretching' => $this->font_stretching,
20902 'font_spacing' => $this->font_spacing,
20903 'alpha' => $this->alpha,
20904 // extended
20905 'lasth' => $this->lasth,
20906 'tMargin' => $this->tMargin,
20907 'bMargin' => $this->bMargin,
20908 'AutoPageBreak' => $this->AutoPageBreak,
20909 'PageBreakTrigger' => $this->PageBreakTrigger,
20910 'x' => $this->x,
20911 'y' => $this->y,
20912 'w' => $this->w,
20913 'h' => $this->h,
20914 'wPt' => $this->wPt,
20915 'hPt' => $this->hPt,
20916 'fwPt' => $this->fwPt,
20917 'fhPt' => $this->fhPt,
20918 'page' => $this->page,
20919 'current_column' => $this->current_column,
20920 'num_columns' => $this->num_columns
20921 );
20922 return $grapvars;
20923 }
20924
20925 /**
20926 * Set graphic variables.
20927 * @param array $gvars array of graphic variablesto restore
20928 * @param boolean $extended if true restore extended graphic variables
20929 * @protected
20930 * @since 4.2.010 (2008-11-14)
20931 */
20932 protected function setGraphicVars($gvars, $extended=false) {
20933 if ($this->state != 2) {
20934 return;
20935 }
20936 $this->FontFamily = $gvars['FontFamily'];
20937 $this->FontStyle = $gvars['FontStyle'];
20938 $this->FontSizePt = $gvars['FontSizePt'];
20939 $this->rMargin = $gvars['rMargin'];
20940 $this->lMargin = $gvars['lMargin'];
20941 $this->cell_padding = $gvars['cell_padding'];
20942 $this->cell_margin = $gvars['cell_margin'];
20943 $this->LineWidth = $gvars['LineWidth'];
20944 $this->linestyleWidth = $gvars['linestyleWidth'];
20945 $this->linestyleCap = $gvars['linestyleCap'];
20946 $this->linestyleJoin = $gvars['linestyleJoin'];
20947 $this->linestyleDash = $gvars['linestyleDash'];
20948 $this->textrendermode = $gvars['textrendermode'];
20949 $this->textstrokewidth = $gvars['textstrokewidth'];
20950 $this->DrawColor = $gvars['DrawColor'];
20951 $this->FillColor = $gvars['FillColor'];
20952 $this->TextColor = $gvars['TextColor'];
20953 $this->ColorFlag = $gvars['ColorFlag'];
20954 $this->bgcolor = $gvars['bgcolor'];
20955 $this->fgcolor = $gvars['fgcolor'];
20956 $this->htmlvspace = $gvars['htmlvspace'];
20957 $this->listindent = $gvars['listindent'];
20958 $this->listindentlevel = $gvars['listindentlevel'];
20959 $this->listnum = $gvars['listnum'];
20960 $this->listordered = $gvars['listordered'];
20961 $this->listcount = $gvars['listcount'];
20962 $this->lispacer = $gvars['lispacer'];
20963 $this->cell_height_ratio = $gvars['cell_height_ratio'];
20964 $this->font_stretching = $gvars['font_stretching'];
20965 $this->font_spacing = $gvars['font_spacing'];
20966 $this->alpha = $gvars['alpha'];
20967 if ($extended) {
20968 // restore extended values
20969 $this->lasth = $gvars['lasth'];
20970 $this->tMargin = $gvars['tMargin'];
20971 $this->bMargin = $gvars['bMargin'];
20972 $this->AutoPageBreak = $gvars['AutoPageBreak'];
20973 $this->PageBreakTrigger = $gvars['PageBreakTrigger'];
20974 $this->x = $gvars['x'];
20975 $this->y = $gvars['y'];
20976 $this->w = $gvars['w'];
20977 $this->h = $gvars['h'];
20978 $this->wPt = $gvars['wPt'];
20979 $this->hPt = $gvars['hPt'];
20980 $this->fwPt = $gvars['fwPt'];
20981 $this->fhPt = $gvars['fhPt'];
20982 $this->page = $gvars['page'];
20983 $this->current_column = $gvars['current_column'];
20984 $this->num_columns = $gvars['num_columns'];
20985 }
20986 $this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
20987 if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
20988 $this->setFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
20989 }
20990 }
20991
20992 /**
20993 * Outputs the "save graphics state" operator 'q'
20994 * @protected
20995 */
20996 protected function _outSaveGraphicsState() {
20997 $this->_out('q');
20998 }
20999
21000 /**
21001 * Outputs the "restore graphics state" operator 'Q'
21002 * @protected
21003 */
21004 protected function _outRestoreGraphicsState() {
21005 $this->_out('Q');
21006 }
21007
21008 /**
21009 * Set buffer content (always append data).
21010 * @param string $data data
21011 * @protected
21012 * @since 4.5.000 (2009-01-02)
21013 */
21014 protected function setBuffer($data) {
21015 $this->bufferlen += strlen($data);
21016 $this->buffer .= $data;
21017 }
21018
21019 /**
21020 * Replace the buffer content
21021 * @param string $data data
21022 * @protected
21023 * @since 5.5.000 (2010-06-22)
21024 */
21025 protected function replaceBuffer($data) {
21026 $this->bufferlen = strlen($data);
21027 $this->buffer = $data;
21028 }
21029
21030 /**
21031 * Get buffer content.
21032 * @return string buffer content
21033 * @protected
21034 * @since 4.5.000 (2009-01-02)
21035 */
21036 protected function getBuffer() {
21037 return $this->buffer;
21038 }
21039
21040 /**
21041 * Set page buffer content.
21042 * @param int $page page number
21043 * @param string $data page data
21044 * @param boolean $append if true append data, false replace.
21045 * @protected
21046 * @since 4.5.000 (2008-12-31)
21047 */
21048 protected function setPageBuffer($page, $data, $append=false) {
21049 if ($append) {
21050 $this->pages[$page] .= $data;
21051 } else {
21052 $this->pages[$page] = $data;
21053 }
21054 if ($append AND isset($this->pagelen[$page])) {
21055 $this->pagelen[$page] += strlen($data);
21056 } else {
21057 $this->pagelen[$page] = strlen($data);
21058 }
21059 }
21060
21061 /**
21062 * Get page buffer content.
21063 * @param int $page page number
21064 * @return string page buffer content or false in case of error
21065 * @protected
21066 * @since 4.5.000 (2008-12-31)
21067 */
21068 protected function getPageBuffer($page) {
21069 if (isset($this->pages[$page])) {
21070 return $this->pages[$page];
21071 }
21072 return false;
21073 }
21074
21075 /**
21076 * Set image buffer content.
21077 * @param string $image image key
21078 * @param array $data image data
21079 * @return int image index number
21080 * @protected
21081 * @since 4.5.000 (2008-12-31)
21082 */
21083 protected function setImageBuffer($image, $data) {
21084 if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
21085 $this->imagekeys[$this->numimages] = $image;
21086 $data['i'] = $this->numimages;
21087 ++$this->numimages;
21088 }
21089 $this->images[$image] = $data;
21090 return $data['i'];
21091 }
21092
21093 /**
21094 * Set image buffer content for a specified sub-key.
21095 * @param string $image image key
21096 * @param string $key image sub-key
21097 * @param array $data image data
21098 * @protected
21099 * @since 4.5.000 (2008-12-31)
21100 */
21101 protected function setImageSubBuffer($image, $key, $data) {
21102 if (!isset($this->images[$image])) {
21103 $this->setImageBuffer($image, array());
21104 }
21105 $this->images[$image][$key] = $data;
21106 }
21107
21108 /**
21109 * Get image buffer content.
21110 * @param string $image image key
21111 * @return string|false image buffer content or false in case of error
21112 * @protected
21113 * @since 4.5.000 (2008-12-31)
21114 */
21115 protected function getImageBuffer($image) {
21116 if (isset($this->images[$image])) {
21117 return $this->images[$image];
21118 }
21119 return false;
21120 }
21121
21122 /**
21123 * Set font buffer content.
21124 * @param string $font font key
21125 * @param array $data font data
21126 * @protected
21127 * @since 4.5.000 (2009-01-02)
21128 */
21129 protected function setFontBuffer($font, $data) {
21130 $this->fonts[$font] = $data;
21131 if (!in_array($font, $this->fontkeys)) {
21132 $this->fontkeys[] = $font;
21133 // store object ID for current font
21134 ++$this->n;
21135 $this->font_obj_ids[$font] = $this->n;
21136 $this->setFontSubBuffer($font, 'n', $this->n);
21137 }
21138 }
21139
21140 /**
21141 * Set font buffer content.
21142 * @param string $font font key
21143 * @param string $key font sub-key
21144 * @param mixed $data font data
21145 * @protected
21146 * @since 4.5.000 (2009-01-02)
21147 */
21148 protected function setFontSubBuffer($font, $key, $data) {
21149 if (!isset($this->fonts[$font])) {
21150 $this->setFontBuffer($font, array());
21151 }
21152 $this->fonts[$font][$key] = $data;
21153 }
21154
21155 /**
21156 * Get font buffer content.
21157 * @param string $font font key
21158 * @return string|false font buffer content or false in case of error
21159 * @protected
21160 * @since 4.5.000 (2009-01-02)
21161 */
21162 protected function getFontBuffer($font) {
21163 if (isset($this->fonts[$font])) {
21164 return $this->fonts[$font];
21165 }
21166 return false;
21167 }
21168
21169 /**
21170 * Move a page to a previous position.
21171 * @param int $frompage number of the source page
21172 * @param int $topage number of the destination page (must be less than $frompage)
21173 * @return bool true in case of success, false in case of error.
21174 * @public
21175 * @since 4.5.000 (2009-01-02)
21176 */
21177 public function movePage($frompage, $topage) {
21178 if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
21179 return false;
21180 }
21181 if ($frompage == $this->page) {
21182 // close the page before moving it
21183 $this->endPage();
21184 }
21185 // move all page-related states
21186 $tmppage = $this->getPageBuffer($frompage);
21187 $tmppagedim = $this->pagedim[$frompage];
21188 $tmppagelen = $this->pagelen[$frompage];
21189 $tmpintmrk = $this->intmrk[$frompage];
21190 $tmpbordermrk = $this->bordermrk[$frompage];
21191 $tmpcntmrk = $this->cntmrk[$frompage];
21192 $tmppageobjects = $this->pageobjects[$frompage];
21193 if (isset($this->footerpos[$frompage])) {
21194 $tmpfooterpos = $this->footerpos[$frompage];
21195 }
21196 if (isset($this->footerlen[$frompage])) {
21197 $tmpfooterlen = $this->footerlen[$frompage];
21198 }
21199 if (isset($this->transfmrk[$frompage])) {
21200 $tmptransfmrk = $this->transfmrk[$frompage];
21201 }
21202 if (isset($this->PageAnnots[$frompage])) {
21203 $tmpannots = $this->PageAnnots[$frompage];
21204 }
21205 if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21206 for ($i = $frompage; $i > $topage; --$i) {
21207 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
21208 --$this->pagegroups[$this->newpagegroup[$i]];
21209 break;
21210 }
21211 }
21212 for ($i = $topage; $i > 0; --$i) {
21213 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
21214 ++$this->pagegroups[$this->newpagegroup[$i]];
21215 break;
21216 }
21217 }
21218 }
21219 for ($i = $frompage; $i > $topage; --$i) {
21220 $j = $i - 1;
21221 // shift pages down
21222 $this->setPageBuffer($i, $this->getPageBuffer($j));
21223 $this->pagedim[$i] = $this->pagedim[$j];
21224 $this->pagelen[$i] = $this->pagelen[$j];
21225 $this->intmrk[$i] = $this->intmrk[$j];
21226 $this->bordermrk[$i] = $this->bordermrk[$j];
21227 $this->cntmrk[$i] = $this->cntmrk[$j];
21228 $this->pageobjects[$i] = $this->pageobjects[$j];
21229 if (isset($this->footerpos[$j])) {
21230 $this->footerpos[$i] = $this->footerpos[$j];
21231 } elseif (isset($this->footerpos[$i])) {
21232 unset($this->footerpos[$i]);
21233 }
21234 if (isset($this->footerlen[$j])) {
21235 $this->footerlen[$i] = $this->footerlen[$j];
21236 } elseif (isset($this->footerlen[$i])) {
21237 unset($this->footerlen[$i]);
21238 }
21239 if (isset($this->transfmrk[$j])) {
21240 $this->transfmrk[$i] = $this->transfmrk[$j];
21241 } elseif (isset($this->transfmrk[$i])) {
21242 unset($this->transfmrk[$i]);
21243 }
21244 if (isset($this->PageAnnots[$j])) {
21245 $this->PageAnnots[$i] = $this->PageAnnots[$j];
21246 } elseif (isset($this->PageAnnots[$i])) {
21247 unset($this->PageAnnots[$i]);
21248 }
21249 if (isset($this->newpagegroup[$j])) {
21250 $this->newpagegroup[$i] = $this->newpagegroup[$j];
21251 unset($this->newpagegroup[$j]);
21252 }
21253 if ($this->currpagegroup == $j) {
21254 $this->currpagegroup = $i;
21255 }
21256 }
21257 $this->setPageBuffer($topage, $tmppage);
21258 $this->pagedim[$topage] = $tmppagedim;
21259 $this->pagelen[$topage] = $tmppagelen;
21260 $this->intmrk[$topage] = $tmpintmrk;
21261 $this->bordermrk[$topage] = $tmpbordermrk;
21262 $this->cntmrk[$topage] = $tmpcntmrk;
21263 $this->pageobjects[$topage] = $tmppageobjects;
21264 if (isset($tmpfooterpos)) {
21265 $this->footerpos[$topage] = $tmpfooterpos;
21266 } elseif (isset($this->footerpos[$topage])) {
21267 unset($this->footerpos[$topage]);
21268 }
21269 if (isset($tmpfooterlen)) {
21270 $this->footerlen[$topage] = $tmpfooterlen;
21271 } elseif (isset($this->footerlen[$topage])) {
21272 unset($this->footerlen[$topage]);
21273 }
21274 if (isset($tmptransfmrk)) {
21275 $this->transfmrk[$topage] = $tmptransfmrk;
21276 } elseif (isset($this->transfmrk[$topage])) {
21277 unset($this->transfmrk[$topage]);
21278 }
21279 if (isset($tmpannots)) {
21280 $this->PageAnnots[$topage] = $tmpannots;
21281 } elseif (isset($this->PageAnnots[$topage])) {
21282 unset($this->PageAnnots[$topage]);
21283 }
21284 // adjust outlines
21285 $tmpoutlines = $this->outlines;
21286 foreach ($tmpoutlines as $key => $outline) {
21287 if (!$outline['f']) {
21288 if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
21289 $this->outlines[$key]['p'] = ($outline['p'] + 1);
21290 } elseif ($outline['p'] == $frompage) {
21291 $this->outlines[$key]['p'] = $topage;
21292 }
21293 }
21294 }
21295 // adjust dests
21296 $tmpdests = $this->dests;
21297 foreach ($tmpdests as $key => $dest) {
21298 if (!$dest['f']) {
21299 if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
21300 $this->dests[$key]['p'] = ($dest['p'] + 1);
21301 } elseif ($dest['p'] == $frompage) {
21302 $this->dests[$key]['p'] = $topage;
21303 }
21304 }
21305 }
21306 // adjust links
21307 $tmplinks = $this->links;
21308 foreach ($tmplinks as $key => $link) {
21309 if (!$link['f']) {
21310 if (($link['p'] >= $topage) AND ($link['p'] < $frompage)) {
21311 $this->links[$key]['p'] = ($link['p'] + 1);
21312 } elseif ($link['p'] == $frompage) {
21313 $this->links[$key]['p'] = $topage;
21314 }
21315 }
21316 }
21317 // adjust javascript
21318 $jfrompage = $frompage;
21319 $jtopage = $topage;
21320 if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21321 foreach($pamatch[0] as $pk => $pmatch) {
21322 $pagenum = intval($pamatch[3][$pk]) + 1;
21323 if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
21324 $newpage = ($pagenum + 1);
21325 } elseif ($pagenum == $jfrompage) {
21326 $newpage = $jtopage;
21327 } else {
21328 $newpage = $pagenum;
21329 }
21330 --$newpage;
21331 $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21332 $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21333 }
21334 unset($pamatch);
21335 }
21336 // return to last page
21337 $this->lastPage(true);
21338 return true;
21339 }
21340
21341 /**
21342 * Remove the specified page.
21343 * @param int $page page to remove
21344 * @return bool true in case of success, false in case of error.
21345 * @public
21346 * @since 4.6.004 (2009-04-23)
21347 */
21348 public function deletePage($page) {
21349 if (($page < 1) OR ($page > $this->numpages)) {
21350 return false;
21351 }
21352 // delete current page
21353 unset($this->pages[$page]);
21354 unset($this->pagedim[$page]);
21355 unset($this->pagelen[$page]);
21356 unset($this->intmrk[$page]);
21357 unset($this->bordermrk[$page]);
21358 unset($this->cntmrk[$page]);
21359 foreach ($this->pageobjects[$page] as $oid) {
21360 if (isset($this->offsets[$oid])){
21361 unset($this->offsets[$oid]);
21362 }
21363 }
21364 unset($this->pageobjects[$page]);
21365 if (isset($this->footerpos[$page])) {
21366 unset($this->footerpos[$page]);
21367 }
21368 if (isset($this->footerlen[$page])) {
21369 unset($this->footerlen[$page]);
21370 }
21371 if (isset($this->transfmrk[$page])) {
21372 unset($this->transfmrk[$page]);
21373 }
21374 if (isset($this->PageAnnots[$page])) {
21375 unset($this->PageAnnots[$page]);
21376 }
21377 if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21378 for ($i = $page; $i > 0; --$i) {
21379 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
21380 --$this->pagegroups[$this->newpagegroup[$i]];
21381 break;
21382 }
21383 }
21384 }
21385 if (isset($this->pageopen[$page])) {
21386 unset($this->pageopen[$page]);
21387 }
21388 if ($page < $this->numpages) {
21389 // update remaining pages
21390 for ($i = $page; $i < $this->numpages; ++$i) {
21391 $j = $i + 1;
21392 // shift pages
21393 $this->setPageBuffer($i, $this->getPageBuffer($j));
21394 $this->pagedim[$i] = $this->pagedim[$j];
21395 $this->pagelen[$i] = $this->pagelen[$j];
21396 $this->intmrk[$i] = $this->intmrk[$j];
21397 $this->bordermrk[$i] = $this->bordermrk[$j];
21398 $this->cntmrk[$i] = $this->cntmrk[$j];
21399 $this->pageobjects[$i] = $this->pageobjects[$j];
21400 if (isset($this->footerpos[$j])) {
21401 $this->footerpos[$i] = $this->footerpos[$j];
21402 } elseif (isset($this->footerpos[$i])) {
21403 unset($this->footerpos[$i]);
21404 }
21405 if (isset($this->footerlen[$j])) {
21406 $this->footerlen[$i] = $this->footerlen[$j];
21407 } elseif (isset($this->footerlen[$i])) {
21408 unset($this->footerlen[$i]);
21409 }
21410 if (isset($this->transfmrk[$j])) {
21411 $this->transfmrk[$i] = $this->transfmrk[$j];
21412 } elseif (isset($this->transfmrk[$i])) {
21413 unset($this->transfmrk[$i]);
21414 }
21415 if (isset($this->PageAnnots[$j])) {
21416 $this->PageAnnots[$i] = $this->PageAnnots[$j];
21417 } elseif (isset($this->PageAnnots[$i])) {
21418 unset($this->PageAnnots[$i]);
21419 }
21420 if (isset($this->newpagegroup[$j])) {
21421 $this->newpagegroup[$i] = $this->newpagegroup[$j];
21422 unset($this->newpagegroup[$j]);
21423 }
21424 if ($this->currpagegroup == $j) {
21425 $this->currpagegroup = $i;
21426 }
21427 if (isset($this->pageopen[$j])) {
21428 $this->pageopen[$i] = $this->pageopen[$j];
21429 } elseif (isset($this->pageopen[$i])) {
21430 unset($this->pageopen[$i]);
21431 }
21432 }
21433 // remove last page
21434 unset($this->pages[$this->numpages]);
21435 unset($this->pagedim[$this->numpages]);
21436 unset($this->pagelen[$this->numpages]);
21437 unset($this->intmrk[$this->numpages]);
21438 unset($this->bordermrk[$this->numpages]);
21439 unset($this->cntmrk[$this->numpages]);
21440 foreach ($this->pageobjects[$this->numpages] as $oid) {
21441 if (isset($this->offsets[$oid])){
21442 unset($this->offsets[$oid]);
21443 }
21444 }
21445 unset($this->pageobjects[$this->numpages]);
21446 if (isset($this->footerpos[$this->numpages])) {
21447 unset($this->footerpos[$this->numpages]);
21448 }
21449 if (isset($this->footerlen[$this->numpages])) {
21450 unset($this->footerlen[$this->numpages]);
21451 }
21452 if (isset($this->transfmrk[$this->numpages])) {
21453 unset($this->transfmrk[$this->numpages]);
21454 }
21455 if (isset($this->PageAnnots[$this->numpages])) {
21456 unset($this->PageAnnots[$this->numpages]);
21457 }
21458 if (isset($this->newpagegroup[$this->numpages])) {
21459 unset($this->newpagegroup[$this->numpages]);
21460 }
21461 if ($this->currpagegroup == $this->numpages) {
21462 $this->currpagegroup = ($this->numpages - 1);
21463 }
21464 if (isset($this->pagegroups[$this->numpages])) {
21465 unset($this->pagegroups[$this->numpages]);
21466 }
21467 if (isset($this->pageopen[$this->numpages])) {
21468 unset($this->pageopen[$this->numpages]);
21469 }
21470 }
21471 --$this->numpages;
21472 $this->page = $this->numpages;
21473 // adjust outlines
21474 $tmpoutlines = $this->outlines;
21475 foreach ($tmpoutlines as $key => $outline) {
21476 if (!$outline['f']) {
21477 if ($outline['p'] > $page) {
21478 $this->outlines[$key]['p'] = $outline['p'] - 1;
21479 } elseif ($outline['p'] == $page) {
21480 unset($this->outlines[$key]);
21481 }
21482 }
21483 }
21484 // adjust dests
21485 $tmpdests = $this->dests;
21486 foreach ($tmpdests as $key => $dest) {
21487 if (!$dest['f']) {
21488 if ($dest['p'] > $page) {
21489 $this->dests[$key]['p'] = $dest['p'] - 1;
21490 } elseif ($dest['p'] == $page) {
21491 unset($this->dests[$key]);
21492 }
21493 }
21494 }
21495 // adjust links
21496 $tmplinks = $this->links;
21497 foreach ($tmplinks as $key => $link) {
21498 if (!$link['f']) {
21499 if ($link['p'] > $page) {
21500 $this->links[$key]['p'] = $link['p'] - 1;
21501 } elseif ($link['p'] == $page) {
21502 unset($this->links[$key]);
21503 }
21504 }
21505 }
21506 // adjust javascript
21507 $jpage = $page;
21508 if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21509 foreach($pamatch[0] as $pk => $pmatch) {
21510 $pagenum = intval($pamatch[3][$pk]) + 1;
21511 if ($pagenum >= $jpage) {
21512 $newpage = ($pagenum - 1);
21513 } elseif ($pagenum == $jpage) {
21514 $newpage = 1;
21515 } else {
21516 $newpage = $pagenum;
21517 }
21518 --$newpage;
21519 $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21520 $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21521 }
21522 unset($pamatch);
21523 }
21524 // return to last page
21525 if ($this->numpages > 0) {
21526 $this->lastPage(true);
21527 }
21528 return true;
21529 }
21530
21531 /**
21532 * Clone the specified page to a new page.
21533 * @param int $page number of page to copy (0 = current page)
21534 * @return bool true in case of success, false in case of error.
21535 * @public
21536 * @since 4.9.015 (2010-04-20)
21537 */
21538 public function copyPage($page=0) {
21539 if ($page == 0) {
21540 // default value
21541 $page = $this->page;
21542 }
21543 if (($page < 1) OR ($page > $this->numpages)) {
21544 return false;
21545 }
21546 // close the last page
21547 $this->endPage();
21548 // copy all page-related states
21549 ++$this->numpages;
21550 $this->page = $this->numpages;
21551 $this->setPageBuffer($this->page, $this->getPageBuffer($page));
21552 $this->pagedim[$this->page] = $this->pagedim[$page];
21553 $this->pagelen[$this->page] = $this->pagelen[$page];
21554 $this->intmrk[$this->page] = $this->intmrk[$page];
21555 $this->bordermrk[$this->page] = $this->bordermrk[$page];
21556 $this->cntmrk[$this->page] = $this->cntmrk[$page];
21557 $this->pageobjects[$this->page] = $this->pageobjects[$page];
21558 $this->pageopen[$this->page] = false;
21559 if (isset($this->footerpos[$page])) {
21560 $this->footerpos[$this->page] = $this->footerpos[$page];
21561 }
21562 if (isset($this->footerlen[$page])) {
21563 $this->footerlen[$this->page] = $this->footerlen[$page];
21564 }
21565 if (isset($this->transfmrk[$page])) {
21566 $this->transfmrk[$this->page] = $this->transfmrk[$page];
21567 }
21568 if (isset($this->PageAnnots[$page])) {
21569 $this->PageAnnots[$this->page] = $this->PageAnnots[$page];
21570 }
21571 if (isset($this->newpagegroup[$page])) {
21572 // start a new group
21573 $this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
21574 $this->currpagegroup = $this->newpagegroup[$this->page];
21575 $this->pagegroups[$this->currpagegroup] = 1;
21576 } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
21577 ++$this->pagegroups[$this->currpagegroup];
21578 }
21579 // copy outlines
21580 $tmpoutlines = $this->outlines;
21581 foreach ($tmpoutlines as $key => $outline) {
21582 if ($outline['p'] == $page) {
21583 $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']);
21584 }
21585 }
21586 // copy links
21587 $tmplinks = $this->links;
21588 foreach ($tmplinks as $key => $link) {
21589 if ($link['p'] == $page) {
21590 $this->links[] = array('p' => $this->page, 'y' => $link['y'], 'f' => $link['f']);
21591 }
21592 }
21593 // return to last page
21594 $this->lastPage(true);
21595 return true;
21596 }
21597
21598 /**
21599 * Output a Table of Content Index (TOC).
21600 * This method must be called after all Bookmarks were set.
21601 * Before calling this method you have to open the page using the addTOCPage() method.
21602 * After calling this method you have to call endTOCPage() to close the TOC page.
21603 * You can override this method to achieve different styles.
21604 * @param int|null $page page number where this TOC should be inserted (leave empty for current page).
21605 * @param string $numbersfont set the font for page numbers (please use monospaced font for better alignment).
21606 * @param string $filler string used to fill the space between text and page number.
21607 * @param string $toc_name name to use for TOC bookmark.
21608 * @param string $style Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21609 * @param array $color RGB color array for bookmark title (values from 0 to 255).
21610 * @public
21611 * @author Nicola Asuni
21612 * @since 4.5.000 (2009-01-02)
21613 * @see addTOCPage(), endTOCPage(), addHTMLTOC()
21614 */
21615 public function addTOC($page=null, $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
21616 $fontsize = $this->FontSizePt;
21617 $fontfamily = $this->FontFamily;
21618 $fontstyle = $this->FontStyle;
21619 $w = $this->w - $this->lMargin - $this->rMargin;
21620 $spacer = $this->GetStringWidth(chr(32)) * 4;
21621 $lmargin = $this->lMargin;
21622 $rmargin = $this->rMargin;
21623 $x_start = $this->GetX();
21624 $page_first = $this->page;
21625 $current_page = $this->page;
21626 $page_fill_start = false;
21627 $page_fill_end = false;
21628 $current_column = $this->current_column;
21629 if (TCPDF_STATIC::empty_string($numbersfont)) {
21630 $numbersfont = $this->default_monospaced_font;
21631 }
21632 if (TCPDF_STATIC::empty_string($filler)) {
21633 $filler = ' ';
21634 }
21635 if (TCPDF_STATIC::empty_string($page)) {
21636 $gap = ' ';
21637 } else {
21638 $gap = '';
21639 if ($page < 1) {
21640 $page = 1;
21641 }
21642 }
21643 $this->setFont($numbersfont, $fontstyle, $fontsize);
21644 $numwidth = $this->GetStringWidth('00000');
21645 $maxpage = 0; //used for pages on attached documents
21646 foreach ($this->outlines as $key => $outline) {
21647 // check for extra pages (used for attachments)
21648 if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
21649 $outline['p'] += ($this->page - $page_first);
21650 }
21651 if ($this->rtl) {
21652 $aligntext = 'R';
21653 $alignnum = 'L';
21654 } else {
21655 $aligntext = 'L';
21656 $alignnum = 'R';
21657 }
21658 if ($outline['l'] == 0) {
21659 $this->setFont($fontfamily, $outline['s'].'B', $fontsize);
21660 } else {
21661 $this->setFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
21662 }
21663 $this->setTextColorArray($outline['c']);
21664 // check for page break
21665 $this->checkPageBreak(2 * $this->getCellHeight($this->FontSize));
21666 // set margins and X position
21667 if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
21668 $this->lMargin = $lmargin;
21669 $this->rMargin = $rmargin;
21670 } else {
21671 if ($this->current_column != $current_column) {
21672 if ($this->rtl) {
21673 $x_start = $this->w - $this->columns[$this->current_column]['x'];
21674 } else {
21675 $x_start = $this->columns[$this->current_column]['x'];
21676 }
21677 }
21678 $lmargin = $this->lMargin;
21679 $rmargin = $this->rMargin;
21680 $current_page = $this->page;
21681 $current_column = $this->current_column;
21682 }
21683 $this->setX($x_start);
21684 $indent = ($spacer * $outline['l']);
21685 if ($this->rtl) {
21686 $this->x -= $indent;
21687 $this->rMargin = $this->w - $this->x;
21688 } else {
21689 $this->x += $indent;
21690 $this->lMargin = $this->x;
21691 }
21692 $link = $this->AddLink();
21693 $this->setLink($link, $outline['y'], $outline['p']);
21694 // write the text
21695 if ($this->rtl) {
21696 $txt = ' '.$outline['t'];
21697 } else {
21698 $txt = $outline['t'].' ';
21699 }
21700 $this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
21701 if ($this->rtl) {
21702 $tw = $this->x - $this->lMargin;
21703 } else {
21704 $tw = $this->w - $this->rMargin - $this->x;
21705 }
21706 $this->setFont($numbersfont, $fontstyle, $fontsize);
21707 if (TCPDF_STATIC::empty_string($page)) {
21708 $pagenum = $outline['p'];
21709 } else {
21710 // placemark to be replaced with the correct number
21711 $pagenum = '{#'.($outline['p']).'}';
21712 if ($this->isUnicodeFont()) {
21713 $pagenum = '{'.$pagenum.'}';
21714 }
21715 $maxpage = max($maxpage, $outline['p']);
21716 }
21717 $fw = ($tw - $this->GetStringWidth($pagenum.$filler));
21718 $wfiller = $this->GetStringWidth($filler);
21719 if ($wfiller > 0) {
21720 $numfills = floor($fw / $wfiller);
21721 } else {
21722 $numfills = 0;
21723 }
21724 if ($numfills > 0) {
21725 $rowfill = str_repeat($filler, $numfills);
21726 } else {
21727 $rowfill = '';
21728 }
21729 if ($this->rtl) {
21730 $pagenum = $pagenum.$gap.$rowfill;
21731 } else {
21732 $pagenum = $rowfill.$gap.$pagenum;
21733 }
21734 // write the number
21735 $this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
21736 }
21737 $page_last = $this->getPage();
21738 $numpages = ($page_last - $page_first + 1);
21739 // account for booklet mode
21740 if ($this->booklet) {
21741 // check if a blank page is required before TOC
21742 $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21743 $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21744 if ($page_fill_start) {
21745 // add a page at the end (to be moved before TOC)
21746 $this->addPage();
21747 ++$page_last;
21748 ++$numpages;
21749 }
21750 if ($page_fill_end) {
21751 // add a page at the end
21752 $this->addPage();
21753 ++$page_last;
21754 ++$numpages;
21755 }
21756 }
21757 $maxpage = max($maxpage, $page_last);
21758 if (!TCPDF_STATIC::empty_string($page)) {
21759 for ($p = $page_first; $p <= $page_last; ++$p) {
21760 // get page data
21761 $temppage = $this->getPageBuffer($p);
21762 for ($n = 1; $n <= $maxpage; ++$n) {
21763 // update page numbers
21764 $a = '{#'.$n.'}';
21765 // get page number aliases
21766 $pnalias = $this->getInternalPageNumberAliases($a);
21767 // calculate replacement number
21768 if (($n >= $page) AND ($n <= $this->numpages)) {
21769 $np = $n + $numpages;
21770 } else {
21771 $np = $n;
21772 }
21773 $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21774 $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21775 // replace aliases with numbers
21776 foreach ($pnalias['u'] as $u) {
21777 $sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
21778 if ($this->rtl) {
21779 $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21780 } else {
21781 $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21782 }
21783 $temppage = str_replace($u, $nr, $temppage);
21784 }
21785 foreach ($pnalias['a'] as $a) {
21786 $sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
21787 if ($this->rtl) {
21788 $nr = $na.' '.$sfill;
21789 } else {
21790 $nr = $sfill.' '.$na;
21791 }
21792 $temppage = str_replace($a, $nr, $temppage);
21793 }
21794 }
21795 // save changes
21796 $this->setPageBuffer($p, $temppage);
21797 }
21798 // move pages
21799 $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21800 if ($page_fill_start) {
21801 $this->movePage($page_last, $page_first);
21802 }
21803 for ($i = 0; $i < $numpages; ++$i) {
21804 $this->movePage($page_last, $page);
21805 }
21806 }
21807 }
21808
21809 /**
21810 * Output a Table Of Content Index (TOC) using HTML templates.
21811 * This method must be called after all Bookmarks were set.
21812 * Before calling this method you have to open the page using the addTOCPage() method.
21813 * After calling this method you have to call endTOCPage() to close the TOC page.
21814 * @param int|null $page page number where this TOC should be inserted (leave empty for current page).
21815 * @param string $toc_name name to use for TOC bookmark.
21816 * @param array $templates array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
21817 * @param boolean $correct_align if true correct the number alignment (numbers must be in monospaced font like courier and right aligned on LTR, or left aligned on RTL)
21818 * @param string $style Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21819 * @param array $color RGB color array for title (values from 0 to 255).
21820 * @public
21821 * @author Nicola Asuni
21822 * @since 5.0.001 (2010-05-06)
21823 * @see addTOCPage(), endTOCPage(), addTOC()
21824 */
21825 public function addHTMLTOC($page=null, $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
21826 $filler = ' ';
21827 $prev_htmlLinkColorArray = $this->htmlLinkColorArray;
21828 $prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
21829 // set new style for link
21830 $this->htmlLinkColorArray = array();
21831 $this->htmlLinkFontStyle = '';
21832 $page_first = $this->getPage();
21833 $page_fill_start = false;
21834 $page_fill_end = false;
21835 // get the font type used for numbers in each template
21836 $current_font = $this->FontFamily;
21837 foreach ($templates as $level => $html) {
21838 $dom = $this->getHtmlDomArray($html);
21839 foreach ($dom as $key => $value) {
21840 if ($value['value'] == '#TOC_PAGE_NUMBER#') {
21841 $this->setFont($dom[($key - 1)]['fontname']);
21842 $templates['F'.$level] = $this->isUnicodeFont();
21843 }
21844 }
21845 }
21846 $this->setFont($current_font);
21847 $maxpage = 0; //used for pages on attached documents
21848 foreach ($this->outlines as $key => $outline) {
21849 // get HTML template
21850 $row = $templates[$outline['l']];
21851 if (TCPDF_STATIC::empty_string($page)) {
21852 $pagenum = $outline['p'];
21853 } else {
21854 // placemark to be replaced with the correct number
21855 $pagenum = '{#'.($outline['p']).'}';
21856 if (isset($templates['F'.$outline['l']]) && $templates['F'.$outline['l']]) {
21857 $pagenum = '{'.$pagenum.'}';
21858 }
21859 $maxpage = max($maxpage, $outline['p']);
21860 }
21861 // replace templates with current values
21862 $row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
21863 $row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
21864 // add link to page
21865 $row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
21866 // write bookmark entry
21867 $this->writeHTML($row, false, false, true, false, '');
21868 }
21869 // restore link styles
21870 $this->htmlLinkColorArray = $prev_htmlLinkColorArray;
21871 $this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
21872 // move TOC page and replace numbers
21873 $page_last = $this->getPage();
21874 $numpages = ($page_last - $page_first + 1);
21875 // account for booklet mode
21876 if ($this->booklet) {
21877 // check if a blank page is required before TOC
21878 $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21879 $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21880 if ($page_fill_start) {
21881 // add a page at the end (to be moved before TOC)
21882 $this->addPage();
21883 ++$page_last;
21884 ++$numpages;
21885 }
21886 if ($page_fill_end) {
21887 // add a page at the end
21888 $this->addPage();
21889 ++$page_last;
21890 ++$numpages;
21891 }
21892 }
21893 $maxpage = max($maxpage, $page_last);
21894 if (!TCPDF_STATIC::empty_string($page)) {
21895 for ($p = $page_first; $p <= $page_last; ++$p) {
21896 // get page data
21897 $temppage = $this->getPageBuffer($p);
21898 for ($n = 1; $n <= $maxpage; ++$n) {
21899 // update page numbers
21900 $a = '{#'.$n.'}';
21901 // get page number aliases
21902 $pnalias = $this->getInternalPageNumberAliases($a);
21903 // calculate replacement number
21904 if ($n >= $page) {
21905 $np = $n + $numpages;
21906 } else {
21907 $np = $n;
21908 }
21909 $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21910 $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21911 // replace aliases with numbers
21912 foreach ($pnalias['u'] as $u) {
21913 if ($correct_align) {
21914 $sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
21915 if ($this->rtl) {
21916 $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21917 } else {
21918 $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21919 }
21920 } else {
21921 $nr = $nu;
21922 }
21923 $temppage = str_replace($u, $nr, $temppage);
21924 }
21925 foreach ($pnalias['a'] as $a) {
21926 if ($correct_align) {
21927 $sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
21928 if ($this->rtl) {
21929 $nr = $na.' '.$sfill;
21930 } else {
21931 $nr = $sfill.' '.$na;
21932 }
21933 } else {
21934 $nr = $na;
21935 }
21936 $temppage = str_replace($a, $nr, $temppage);
21937 }
21938 }
21939 // save changes
21940 $this->setPageBuffer($p, $temppage);
21941 }
21942 // move pages
21943 $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21944 if ($page_fill_start) {
21945 $this->movePage($page_last, $page_first);
21946 }
21947 for ($i = 0; $i < $numpages; ++$i) {
21948 $this->movePage($page_last, $page);
21949 }
21950 }
21951 }
21952
21953 /**
21954 * Stores a copy of the current TCPDF object used for undo operation.
21955 * @public
21956 * @since 4.5.029 (2009-03-19)
21957 */
21958 public function startTransaction() {
21959 if (isset($this->objcopy)) {
21960 // remove previous copy
21961 $this->commitTransaction();
21962 }
21963 // record current page number and Y position
21964 $this->start_transaction_page = $this->page;
21965 $this->start_transaction_y = $this->y;
21966 // clone current object
21967 $this->objcopy = TCPDF_STATIC::objclone($this);
21968 }
21969
21970 /**
21971 * Delete the copy of the current TCPDF object used for undo operation.
21972 * @public
21973 * @since 4.5.029 (2009-03-19)
21974 */
21975 public function commitTransaction() {
21976 if (isset($this->objcopy)) {
21977 $this->objcopy->_destroy(true, true);
21978 /* The unique file_id should not be used during cleanup again */
21979 $this->objcopy->file_id = NULL;
21980 unset($this->objcopy);
21981 }
21982 }
21983
21984 /**
21985 * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
21986 * @param boolean $self if true restores current class object to previous state without the need of reassignment via the returned value.
21987 * @return TCPDF object.
21988 * @public
21989 * @since 4.5.029 (2009-03-19)
21990 */
21991 public function rollbackTransaction($self=false) {
21992 if (!isset($this->objcopy)) {
21993 return $this;
21994 }
21995 $file_id = $this->file_id;
21996 $objcopy = $this->objcopy;
21997 $this->_destroy(true, true);
21998 if ($self) {
21999 $objvars = get_object_vars($objcopy);
22000 foreach ($objvars as $key => $value) {
22001 $this->$key = $value;
22002 }
22003 $objcopy->_destroy(true, true);
22004 unset($objcopy);
22005 return $this;
22006 }
22007 $this->file_id = $file_id;
22008 return $objcopy;
22009 }
22010
22011 // --- MULTI COLUMNS METHODS -----------------------
22012
22013 /**
22014 * Set multiple columns of the same size
22015 * @param int $numcols number of columns (set to zero to disable columns mode)
22016 * @param int $width column width
22017 * @param int|null $y column starting Y position (leave empty for current Y position)
22018 * @public
22019 * @since 4.9.001 (2010-03-28)
22020 */
22021 public function setEqualColumns($numcols=0, $width=0, $y=null) {
22022 $this->columns = array();
22023 if ($numcols < 2) {
22024 $numcols = 0;
22025 $this->columns = array();
22026 } else {
22027 // maximum column width
22028 $maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
22029 if (($width == 0) OR ($width > $maxwidth)) {
22030 $width = $maxwidth;
22031 }
22032 if (TCPDF_STATIC::empty_string($y)) {
22033 $y = $this->y;
22034 }
22035 // space between columns
22036 $space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
22037 // fill the columns array (with, space, starting Y position)
22038 for ($i = 0; $i < $numcols; ++$i) {
22039 $this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
22040 }
22041 }
22042 $this->num_columns = $numcols;
22043 $this->current_column = 0;
22044 $this->column_start_page = $this->page;
22045 $this->selectColumn(0);
22046 }
22047
22048 /**
22049 * Remove columns and reset page margins.
22050 * @public
22051 * @since 5.9.072 (2011-04-26)
22052 */
22053 public function resetColumns() {
22054 $this->lMargin = $this->original_lMargin;
22055 $this->rMargin = $this->original_rMargin;
22056 $this->setEqualColumns();
22057 }
22058
22059 /**
22060 * Set columns array.
22061 * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
22062 * @param array $columns
22063 * @public
22064 * @since 4.9.001 (2010-03-28)
22065 */
22066 public function setColumnsArray($columns) {
22067 $this->columns = $columns;
22068 $this->num_columns = count($columns);
22069 $this->current_column = 0;
22070 $this->column_start_page = $this->page;
22071 $this->selectColumn(0);
22072 }
22073
22074 /**
22075 * Set position at a given column
22076 * @param int|null $col column number (from 0 to getNumberOfColumns()-1); empty string = current column.
22077 * @public
22078 * @since 4.9.001 (2010-03-28)
22079 */
22080 public function selectColumn($col=null) {
22081 if (TCPDF_STATIC::empty_string($col)) {
22082 $col = $this->current_column;
22083 } elseif ($col >= $this->num_columns) {
22084 $col = 0;
22085 }
22086 $xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
22087 $enable_thead = false;
22088 if ($this->num_columns > 1) {
22089 if ($col != $this->current_column) {
22090 // move Y pointer at the top of the column
22091 if ($this->column_start_page == $this->page) {
22092 $this->y = $this->columns[$col]['y'];
22093 } else {
22094 $this->y = $this->tMargin;
22095 }
22096 // Avoid to write table headers more than once
22097 if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
22098 $enable_thead = true;
22099 $this->maxselcol['page'] = $this->page;
22100 $this->maxselcol['column'] = $col;
22101 }
22102 }
22103 $xshift = $this->colxshift;
22104 // set X position of the current column by case
22105 $listindent = ($this->listindentlevel * $this->listindent);
22106 // calculate column X position
22107 $colpos = 0;
22108 for ($i = 0; $i < $col; ++$i) {
22109 $colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
22110 }
22111 if ($this->rtl) {
22112 $x = $this->w - $this->original_rMargin - $colpos;
22113 $this->rMargin = ($this->w - $x + $listindent);
22114 $this->lMargin = ($x - $this->columns[$col]['w']);
22115 $this->x = $x - $listindent;
22116 } else {
22117 $x = $this->original_lMargin + $colpos;
22118 $this->lMargin = ($x + $listindent);
22119 $this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
22120 $this->x = $x + $listindent;
22121 }
22122 $this->columns[$col]['x'] = $x;
22123 }
22124 $this->current_column = $col;
22125 // fix for HTML mode
22126 $this->newline = true;
22127 // print HTML table header (if any)
22128 if ((!TCPDF_STATIC::empty_string($this->thead)) AND (!$this->inthead)) {
22129 if ($enable_thead) {
22130 // print table header
22131 $this->writeHTML($this->thead, false, false, false, false, '');
22132 $this->y += $xshift['s']['V'];
22133 // store end of header position
22134 if (!isset($this->columns[$col]['th'])) {
22135 $this->columns[$col]['th'] = array();
22136 }
22137 $this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
22138 $this->lasth = 0;
22139 } elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
22140 $this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
22141 }
22142 }
22143 // account for an html table cell over multiple columns
22144 if ($this->rtl) {
22145 $this->rMargin += $xshift['x'];
22146 $this->x -= ($xshift['x'] + $xshift['p']['R']);
22147 } else {
22148 $this->lMargin += $xshift['x'];
22149 $this->x += $xshift['x'] + $xshift['p']['L'];
22150 }
22151 }
22152
22153 /**
22154 * Return the current column number
22155 * @return int current column number
22156 * @public
22157 * @since 5.5.011 (2010-07-08)
22158 */
22159 public function getColumn() {
22160 return $this->current_column;
22161 }
22162
22163 /**
22164 * Return the current number of columns.
22165 * @return int number of columns
22166 * @public
22167 * @since 5.8.018 (2010-08-25)
22168 */
22169 public function getNumberOfColumns() {
22170 return $this->num_columns;
22171 }
22172
22173 /**
22174 * Set Text rendering mode.
22175 * @param int $stroke outline size in user units (0 = disable).
22176 * @param boolean $fill if true fills the text (default).
22177 * @param boolean $clip if true activate clipping mode
22178 * @public
22179 * @since 4.9.008 (2009-04-02)
22180 */
22181 public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
22182 // Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
22183 // convert text rendering parameters
22184 if ($stroke < 0 || !is_numeric($stroke)) {
22185 $stroke = 0;
22186 }
22187 if ($fill === true) {
22188 if ($stroke > 0) {
22189 if ($clip === true) {
22190 // Fill, then stroke text and add to path for clipping
22191 $textrendermode = 6;
22192 } else {
22193 // Fill, then stroke text
22194 $textrendermode = 2;
22195 }
22196 $textstrokewidth = $stroke;
22197 } else {
22198 if ($clip === true) {
22199 // Fill text and add to path for clipping
22200 $textrendermode = 4;
22201 } else {
22202 // Fill text
22203 $textrendermode = 0;
22204 }
22205 }
22206 } else {
22207 if ($stroke > 0) {
22208 if ($clip === true) {
22209 // Stroke text and add to path for clipping
22210 $textrendermode = 5;
22211 } else {
22212 // Stroke text
22213 $textrendermode = 1;
22214 }
22215 $textstrokewidth = $stroke;
22216 } else {
22217 if ($clip === true) {
22218 // Add text to path for clipping
22219 $textrendermode = 7;
22220 } else {
22221 // Neither fill nor stroke text (invisible)
22222 $textrendermode = 3;
22223 }
22224 }
22225 }
22226 $this->textrendermode = $textrendermode;
22227 $this->textstrokewidth = $stroke;
22228 }
22229
22230 /**
22231 * Set parameters for drop shadow effect for text.
22232 * @param array $params Array of parameters: enabled (boolean) set to true to enable shadow; depth_w (float) shadow width in user units; depth_h (float) shadow height in user units; color (array) shadow color or false to use the stroke color; opacity (float) Alpha value: real value from 0 (transparent) to 1 (opaque); blend_mode (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity.
22233 * @since 5.9.174 (2012-07-25)
22234 * @public
22235 */
22236 public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
22237 if (isset($params['enabled'])) {
22238 $this->txtshadow['enabled'] = $params['enabled']?true:false;
22239 } else {
22240 $this->txtshadow['enabled'] = false;
22241 }
22242 if (isset($params['depth_w'])) {
22243 $this->txtshadow['depth_w'] = floatval($params['depth_w']);
22244 } else {
22245 $this->txtshadow['depth_w'] = 0;
22246 }
22247 if (isset($params['depth_h'])) {
22248 $this->txtshadow['depth_h'] = floatval($params['depth_h']);
22249 } else {
22250 $this->txtshadow['depth_h'] = 0;
22251 }
22252 if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
22253 $this->txtshadow['color'] = $params['color'];
22254 } else {
22255 $this->txtshadow['color'] = $this->strokecolor;
22256 }
22257 if (isset($params['opacity'])) {
22258 $this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
22259 } else {
22260 $this->txtshadow['opacity'] = 1;
22261 }
22262 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'))) {
22263 $this->txtshadow['blend_mode'] = $params['blend_mode'];
22264 } else {
22265 $this->txtshadow['blend_mode'] = 'Normal';
22266 }
22267 if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
22268 $this->txtshadow['enabled'] = false;
22269 }
22270 }
22271
22272 /**
22273 * Return the text shadow parameters array.
22274 * @return array array of parameters.
22275 * @since 5.9.174 (2012-07-25)
22276 * @public
22277 */
22278 public function getTextShadow() {
22279 return $this->txtshadow;
22280 }
22281
22282 /**
22283 * Returns an array of chars containing soft hyphens.
22284 * @param array $word array of chars
22285 * @param array $patterns Array of hypenation patterns.
22286 * @param array $dictionary Array of words to be returned without applying the hyphenation algorithm.
22287 * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens.
22288 * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens.
22289 * @param int $charmin Minimum word length to apply the hyphenation algorithm.
22290 * @param int $charmax Maximum length of broken piece of word.
22291 * @return array text with soft hyphens
22292 * @author Nicola Asuni
22293 * @since 4.9.012 (2010-04-12)
22294 * @protected
22295 */
22296 protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22297 $hyphenword = array(); // hyphens positions
22298 $numchars = count($word);
22299 if ($numchars <= $charmin) {
22300 return $word;
22301 }
22302 $word_string = TCPDF_FONTS::UTF8ArrSubString($word, '', '', $this->isunicode);
22303 // some words will be returned as-is
22304 $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})(\]?)$/';
22305 if (preg_match($pattern, $word_string) > 0) {
22306 // email
22307 return $word;
22308 }
22309 $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})(\]?)$/';
22310 if (preg_match($pattern, $word_string) > 0) {
22311 // URL
22312 return $word;
22313 }
22314 if (isset($dictionary[$word_string])) {
22315 return TCPDF_FONTS::UTF8StringToArray($dictionary[$word_string], $this->isunicode, $this->CurrentFont);
22316 }
22317 // surround word with '_' characters
22318 $tmpword = array_merge(array(46), $word, array(46));
22319 $tmpnumchars = $numchars + 2;
22320 $maxpos = $tmpnumchars - 1;
22321 for ($pos = 0; $pos < $maxpos; ++$pos) {
22322 $imax = min(($tmpnumchars - $pos), $charmax);
22323 for ($i = 1; $i <= $imax; ++$i) {
22324 $subword = strtolower(TCPDF_FONTS::UTF8ArrSubString($tmpword, $pos, ($pos + $i), $this->isunicode));
22325 if (isset($patterns[$subword])) {
22326 $pattern = TCPDF_FONTS::UTF8StringToArray($patterns[$subword], $this->isunicode, $this->CurrentFont);
22327 $pattern_length = count($pattern);
22328 $digits = 1;
22329 for ($j = 0; $j < $pattern_length; ++$j) {
22330 // check if $pattern[$j] is a number = hyphenation level (only numbers from 1 to 5 are valid)
22331 if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
22332 if ($j == 0) {
22333 $zero = $pos - 1;
22334 } else {
22335 $zero = $pos + $j - $digits;
22336 }
22337 // get hyphenation level
22338 $level = ($pattern[$j] - 48);
22339 // if two levels from two different patterns match at the same point, the higher one is selected.
22340 if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] < $level)) {
22341 $hyphenword[$zero] = $level;
22342 }
22343 ++$digits;
22344 }
22345 }
22346 }
22347 }
22348 }
22349 $inserted = 0;
22350 $maxpos = $numchars - $rightmin;
22351 for ($i = $leftmin; $i <= $maxpos; ++$i) {
22352 // only odd levels indicate allowed hyphenation points
22353 if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
22354 // 173 = soft hyphen character
22355 array_splice($word, $i + $inserted, 0, 173);
22356 ++$inserted;
22357 }
22358 }
22359 return $word;
22360 }
22361
22362 /**
22363 * Returns text with soft hyphens.
22364 * @param string $text text to process
22365 * @param mixed $patterns Array of hypenation patterns or a TEX file containing hypenation patterns. TEX patterns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
22366 * @param array $dictionary Array of words to be returned without applying the hyphenation algorithm.
22367 * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens.
22368 * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens.
22369 * @param int $charmin Minimum word length to apply the hyphenation algorithm.
22370 * @param int $charmax Maximum length of broken piece of word.
22371 * @return string text with soft hyphens
22372 * @author Nicola Asuni
22373 * @since 4.9.012 (2010-04-12)
22374 * @public
22375 */
22376 public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22377 $text = $this->unhtmlentities($text);
22378 $word = array(); // last word
22379 $txtarr = array(); // text to be returned
22380 $intag = false; // true if we are inside an HTML tag
22381 $skip = false; // true to skip hyphenation
22382 if (!is_array($patterns)) {
22383 $patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patterns);
22384 }
22385 // get array of characters
22386 $unichars = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
22387 // for each char
22388 foreach ($unichars as $char) {
22389 if ((!$intag) AND (!$skip) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
22390 // letter character
22391 $word[] = $char;
22392 } else {
22393 // other type of character
22394 if (!TCPDF_STATIC::empty_string($word)) {
22395 // hypenate the word
22396 $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22397 $word = array();
22398 }
22399 $txtarr[] = $char;
22400 if (chr($char) == '<') {
22401 // we are inside an HTML tag
22402 $intag = true;
22403 } elseif ($intag AND (chr($char) == '>')) {
22404 // end of HTML tag
22405 $intag = false;
22406 // check for style tag
22407 $expected = array(115, 116, 121, 108, 101); // = 'style'
22408 $current = array_slice($txtarr, -6, 5); // last 5 chars
22409 $compare = array_diff($expected, $current);
22410 if (empty($compare)) {
22411 // check if it is a closing tag
22412 $expected = array(47); // = '/'
22413 $current = array_slice($txtarr, -7, 1);
22414 $compare = array_diff($expected, $current);
22415 if (empty($compare)) {
22416 // closing style tag
22417 $skip = false;
22418 } else {
22419 // opening style tag
22420 $skip = true;
22421 }
22422 }
22423 }
22424 }
22425 }
22426 if (!TCPDF_STATIC::empty_string($word)) {
22427 // hypenate the word
22428 $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22429 }
22430 // convert char array to string and return
22431 return TCPDF_FONTS::UTF8ArrSubString($txtarr, '', '', $this->isunicode);
22432 }
22433
22434 /**
22435 * Enable/disable rasterization of vector images using ImageMagick library.
22436 * @param boolean $mode if true enable rasterization, false otherwise.
22437 * @public
22438 * @since 5.0.000 (2010-04-27)
22439 */
22440 public function setRasterizeVectorImages($mode) {
22441 $this->rasterize_vector_images = $mode;
22442 }
22443
22444 /**
22445 * Enable or disable default option for font subsetting.
22446 * @param boolean $enable if true enable font subsetting by default.
22447 * @author Nicola Asuni
22448 * @public
22449 * @since 5.3.002 (2010-06-07)
22450 */
22451 public function setFontSubsetting($enable=true) {
22452 if ($this->pdfa_mode) {
22453 $this->font_subsetting = false;
22454 } else {
22455 $this->font_subsetting = $enable ? true : false;
22456 }
22457 }
22458
22459 /**
22460 * Return the default option for font subsetting.
22461 * @return bool default font subsetting state.
22462 * @author Nicola Asuni
22463 * @public
22464 * @since 5.3.002 (2010-06-07)
22465 */
22466 public function getFontSubsetting() {
22467 return $this->font_subsetting;
22468 }
22469
22470 /**
22471 * Left trim the input string
22472 * @param string $str string to trim
22473 * @param string $replace string that replace spaces.
22474 * @return string left trimmed string
22475 * @author Nicola Asuni
22476 * @public
22477 * @since 5.8.000 (2010-08-11)
22478 */
22479 public function stringLeftTrim($str, $replace='') {
22480 return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
22481 }
22482
22483 /**
22484 * Right trim the input string
22485 * @param string $str string to trim
22486 * @param string $replace string that replace spaces.
22487 * @return string right trimmed string
22488 * @author Nicola Asuni
22489 * @public
22490 * @since 5.8.000 (2010-08-11)
22491 */
22492 public function stringRightTrim($str, $replace='') {
22493 return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
22494 }
22495
22496 /**
22497 * Trim the input string
22498 * @param string $str string to trim
22499 * @param string $replace string that replace spaces.
22500 * @return string trimmed string
22501 * @author Nicola Asuni
22502 * @public
22503 * @since 5.8.000 (2010-08-11)
22504 */
22505 public function stringTrim($str, $replace='') {
22506 $str = $this->stringLeftTrim($str, $replace);
22507 $str = $this->stringRightTrim($str, $replace);
22508 return $str;
22509 }
22510
22511 /**
22512 * Return true if the current font is unicode type.
22513 * @return bool true for unicode font, false otherwise.
22514 * @author Nicola Asuni
22515 * @public
22516 * @since 5.8.002 (2010-08-14)
22517 */
22518 public function isUnicodeFont() {
22519 return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
22520 }
22521
22522 /**
22523 * Return normalized font name
22524 * @param string $fontfamily property string containing font family names
22525 * @return string normalized font name
22526 * @author Nicola Asuni
22527 * @public
22528 * @since 5.8.004 (2010-08-17)
22529 */
22530 public function getFontFamilyName($fontfamily) {
22531 // remove spaces and symbols
22532 $fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
22533 // extract all font names
22534 $fontslist = preg_split('/[,]/', $fontfamily);
22535 // find first valid font name
22536 foreach ($fontslist as $font) {
22537 // replace font variations
22538 $font = preg_replace('/regular$/', '', $font);
22539 $font = preg_replace('/italic$/', 'I', $font);
22540 $font = preg_replace('/oblique$/', 'I', $font);
22541 $font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
22542 // replace common family names and core fonts
22543 $pattern = array();
22544 $replacement = array();
22545 $pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
22546 $replacement[] = 'times';
22547 $pattern[] = '/^sansserif/';
22548 $replacement[] = 'helvetica';
22549 $pattern[] = '/^monospace/';
22550 $replacement[] = 'courier';
22551 $font = preg_replace($pattern, $replacement, $font);
22552 if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
22553 return $font;
22554 }
22555 }
22556 // return current font as default
22557 return $this->CurrentFont['fontkey'];
22558 }
22559
22560 /**
22561 * Start a new XObject Template.
22562 * 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).
22563 * 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.
22564 * Note: X,Y coordinates will be reset to 0,0.
22565 * @param int $w Template width in user units (empty string or zero = page width less margins).
22566 * @param int $h Template height in user units (empty string or zero = page height less margins).
22567 * @param mixed $group Set transparency group. Can be a boolean value or an array specifying optional parameters: 'CS' (solour space name), 'I' (boolean flag to indicate isolated group) and 'K' (boolean flag to indicate knockout group).
22568 * @return string|false the XObject Template ID in case of success or false in case of error.
22569 * @author Nicola Asuni
22570 * @public
22571 * @since 5.8.017 (2010-08-24)
22572 * @see endTemplate(), printTemplate()
22573 */
22574 public function startTemplate($w=0, $h=0, $group=false) {
22575 if ($this->inxobj) {
22576 // we are already inside an XObject template
22577 return false;
22578 }
22579 $this->inxobj = true;
22580 ++$this->n;
22581 // XObject ID
22582 $this->xobjid = 'XT'.$this->n;
22583 // object ID
22584 $this->xobjects[$this->xobjid] = array('n' => $this->n);
22585 // store current graphic state
22586 $this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
22587 // initialize data
22588 $this->xobjects[$this->xobjid]['intmrk'] = 0;
22589 $this->xobjects[$this->xobjid]['transfmrk'] = array();
22590 $this->xobjects[$this->xobjid]['outdata'] = '';
22591 $this->xobjects[$this->xobjid]['xobjects'] = array();
22592 $this->xobjects[$this->xobjid]['images'] = array();
22593 $this->xobjects[$this->xobjid]['fonts'] = array();
22594 $this->xobjects[$this->xobjid]['annotations'] = array();
22595 $this->xobjects[$this->xobjid]['extgstates'] = array();
22596 $this->xobjects[$this->xobjid]['gradients'] = array();
22597 $this->xobjects[$this->xobjid]['spot_colors'] = array();
22598 // set new environment
22599 $this->num_columns = 1;
22600 $this->current_column = 0;
22601 $this->setAutoPageBreak(false);
22602 if (($w === '') OR ($w <= 0)) {
22603 $w = $this->w - $this->lMargin - $this->rMargin;
22604 }
22605 if (($h === '') OR ($h <= 0)) {
22606 $h = $this->h - $this->tMargin - $this->bMargin;
22607 }
22608 $this->xobjects[$this->xobjid]['x'] = 0;
22609 $this->xobjects[$this->xobjid]['y'] = 0;
22610 $this->xobjects[$this->xobjid]['w'] = $w;
22611 $this->xobjects[$this->xobjid]['h'] = $h;
22612 $this->w = $w;
22613 $this->h = $h;
22614 $this->wPt = $this->w * $this->k;
22615 $this->hPt = $this->h * $this->k;
22616 $this->fwPt = $this->wPt;
22617 $this->fhPt = $this->hPt;
22618 $this->x = 0;
22619 $this->y = 0;
22620 $this->lMargin = 0;
22621 $this->rMargin = 0;
22622 $this->tMargin = 0;
22623 $this->bMargin = 0;
22624 // set group mode
22625 $this->xobjects[$this->xobjid]['group'] = $group;
22626 return $this->xobjid;
22627 }
22628
22629 /**
22630 * End the current XObject Template started with startTemplate() and restore the previous graphic state.
22631 * 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).
22632 * 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.
22633 * @return string|false the XObject Template ID in case of success or false in case of error.
22634 * @author Nicola Asuni
22635 * @public
22636 * @since 5.8.017 (2010-08-24)
22637 * @see startTemplate(), printTemplate()
22638 */
22639 public function endTemplate() {
22640 if (!$this->inxobj) {
22641 // we are not inside a template
22642 return false;
22643 }
22644 $this->inxobj = false;
22645 // restore previous graphic state
22646 $this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
22647 return $this->xobjid;
22648 }
22649
22650 /**
22651 * Print an XObject Template.
22652 * You can print an XObject Template inside the currently opened Template.
22653 * 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).
22654 * 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.
22655 * @param string $id The ID of XObject Template to print.
22656 * @param float|null $x X position in user units (empty string = current x position)
22657 * @param float|null $y Y position in user units (empty string = current y position)
22658 * @param float $w Width in user units (zero = remaining page width)
22659 * @param float $h Height in user units (zero = remaining page height)
22660 * @param string $align Indicates the alignment of the pointer next to template insertion relative to template height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
22661 * @param string $palign Allows to center or align the template on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
22662 * @param boolean $fitonpage If true the template is resized to not exceed page dimensions.
22663 * @author Nicola Asuni
22664 * @public
22665 * @since 5.8.017 (2010-08-24)
22666 * @see startTemplate(), endTemplate()
22667 */
22668 public function printTemplate($id, $x=null, $y=null, $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
22669 if ($this->state != 2) {
22670 return;
22671 }
22672 if (!isset($this->xobjects[$id])) {
22673 $this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
22674 }
22675 if ($this->inxobj) {
22676 if ($id == $this->xobjid) {
22677 // close current template
22678 $this->endTemplate();
22679 } else {
22680 // use the template as resource for the template currently opened
22681 $this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
22682 }
22683 }
22684 // set default values
22685 if (TCPDF_STATIC::empty_string($x)) {
22686 $x = $this->x;
22687 }
22688 if (TCPDF_STATIC::empty_string($y)) {
22689 $y = $this->y;
22690 }
22691 // check page for no-write regions and adapt page margins if necessary
22692 list($x, $y) = $this->checkPageRegions($h, $x, $y);
22693 $ow = $this->xobjects[$id]['w'];
22694 if ($ow <= 0) {
22695 $ow = 1;
22696 }
22697 $oh = $this->xobjects[$id]['h'];
22698 if ($oh <= 0) {
22699 $oh = 1;
22700 }
22701 // calculate template width and height on document
22702 if (($w <= 0) AND ($h <= 0)) {
22703 $w = $ow;
22704 $h = $oh;
22705 } elseif ($w <= 0) {
22706 $w = $h * $ow / $oh;
22707 } elseif ($h <= 0) {
22708 $h = $w * $oh / $ow;
22709 }
22710 // fit the template on available space
22711 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22712 // set page alignment
22713 $rb_y = $y + $h;
22714 // set alignment
22715 if ($this->rtl) {
22716 if ($palign == 'L') {
22717 $xt = $this->lMargin;
22718 } elseif ($palign == 'C') {
22719 $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22720 } elseif ($palign == 'R') {
22721 $xt = $this->w - $this->rMargin - $w;
22722 } else {
22723 $xt = $x - $w;
22724 }
22725 $rb_x = $xt;
22726 } else {
22727 if ($palign == 'L') {
22728 $xt = $this->lMargin;
22729 } elseif ($palign == 'C') {
22730 $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22731 } elseif ($palign == 'R') {
22732 $xt = $this->w - $this->rMargin - $w;
22733 } else {
22734 $xt = $x;
22735 }
22736 $rb_x = $xt + $w;
22737 }
22738 // print XObject Template + Transformation matrix
22739 $this->StartTransform();
22740 // translate and scale
22741 $sx = ($w / $ow);
22742 $sy = ($h / $oh);
22743 $tm = array();
22744 $tm[0] = $sx;
22745 $tm[1] = 0;
22746 $tm[2] = 0;
22747 $tm[3] = $sy;
22748 $tm[4] = $xt * $this->k;
22749 $tm[5] = ($this->h - $h - $y) * $this->k;
22750 $this->Transform($tm);
22751 // set object
22752 $this->_out('/'.$id.' Do');
22753 $this->StopTransform();
22754 // add annotations
22755 if (!empty($this->xobjects[$id]['annotations'])) {
22756 foreach ($this->xobjects[$id]['annotations'] as $annot) {
22757 // transform original coordinates
22758 $coordlt = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
22759 $ax = ($coordlt[4] / $this->k);
22760 $ay = ($this->h - $h - ($coordlt[5] / $this->k));
22761 $coordrb = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
22762 $aw = ($coordrb[4] / $this->k) - $ax;
22763 $ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
22764 $this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
22765 }
22766 }
22767 // set pointer to align the next text/objects
22768 switch($align) {
22769 case 'T': {
22770 $this->y = $y;
22771 $this->x = $rb_x;
22772 break;
22773 }
22774 case 'M': {
22775 $this->y = $y + round($h/2);
22776 $this->x = $rb_x;
22777 break;
22778 }
22779 case 'B': {
22780 $this->y = $rb_y;
22781 $this->x = $rb_x;
22782 break;
22783 }
22784 case 'N': {
22785 $this->setY($rb_y);
22786 break;
22787 }
22788 default:{
22789 break;
22790 }
22791 }
22792 }
22793
22794 /**
22795 * Set the percentage of character stretching.
22796 * @param int $perc percentage of stretching (100 = no stretching)
22797 * @author Nicola Asuni
22798 * @public
22799 * @since 5.9.000 (2010-09-29)
22800 */
22801 public function setFontStretching($perc=100) {
22802 $this->font_stretching = $perc;
22803 }
22804
22805 /**
22806 * Get the percentage of character stretching.
22807 * @return float stretching value
22808 * @author Nicola Asuni
22809 * @public
22810 * @since 5.9.000 (2010-09-29)
22811 */
22812 public function getFontStretching() {
22813 return $this->font_stretching;
22814 }
22815
22816 /**
22817 * Set the amount to increase or decrease the space between characters in a text.
22818 * @param float $spacing amount to increase or decrease the space between characters in a text (0 = default spacing)
22819 * @author Nicola Asuni
22820 * @public
22821 * @since 5.9.000 (2010-09-29)
22822 */
22823 public function setFontSpacing($spacing=0) {
22824 $this->font_spacing = $spacing;
22825 }
22826
22827 /**
22828 * Get the amount to increase or decrease the space between characters in a text.
22829 * @return int font spacing (tracking) value
22830 * @author Nicola Asuni
22831 * @public
22832 * @since 5.9.000 (2010-09-29)
22833 */
22834 public function getFontSpacing() {
22835 return $this->font_spacing;
22836 }
22837
22838 /**
22839 * Return an array of no-write page regions
22840 * @return array of no-write page regions
22841 * @author Nicola Asuni
22842 * @public
22843 * @since 5.9.003 (2010-10-13)
22844 * @see setPageRegions(), addPageRegion()
22845 */
22846 public function getPageRegions() {
22847 return $this->page_regions;
22848 }
22849
22850 /**
22851 * Set no-write regions on page.
22852 * 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.
22853 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22854 * You can set multiple regions for the same page.
22855 * @param array $regions array of no-write regions. For each region you can define an array as follow: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right). Omit this parameter to remove all regions.
22856 * @author Nicola Asuni
22857 * @public
22858 * @since 5.9.003 (2010-10-13)
22859 * @see addPageRegion(), getPageRegions()
22860 */
22861 public function setPageRegions($regions=array()) {
22862 // empty current regions array
22863 $this->page_regions = array();
22864 // add regions
22865 foreach ($regions as $data) {
22866 $this->addPageRegion($data);
22867 }
22868 }
22869
22870 /**
22871 * Add a single no-write region on selected page.
22872 * 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.
22873 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22874 * You can set multiple regions for the same page.
22875 * @param array $region array of a single no-write region array: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right).
22876 * @author Nicola Asuni
22877 * @public
22878 * @since 5.9.003 (2010-10-13)
22879 * @see setPageRegions(), getPageRegions()
22880 */
22881 public function addPageRegion($region) {
22882 if (!isset($region['page']) OR empty($region['page'])) {
22883 $region['page'] = $this->page;
22884 }
22885 if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
22886 AND isset($region['yt']) AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
22887 AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
22888 $this->page_regions[] = $region;
22889 }
22890 }
22891
22892 /**
22893 * Remove a single no-write region.
22894 * @param int $key region key
22895 * @author Nicola Asuni
22896 * @public
22897 * @since 5.9.003 (2010-10-13)
22898 * @see setPageRegions(), getPageRegions()
22899 */
22900 public function removePageRegion($key) {
22901 if (isset($this->page_regions[$key])) {
22902 unset($this->page_regions[$key]);
22903 }
22904 }
22905
22906 /**
22907 * Check page for no-write regions and adapt current coordinates and page margins if necessary.
22908 * 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.
22909 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22910 * @param float $h height of the text/image/object to print in user units
22911 * @param float $x current X coordinate in user units
22912 * @param float $y current Y coordinate in user units
22913 * @return float[] array($x, $y)
22914 * @author Nicola Asuni
22915 * @protected
22916 * @since 5.9.003 (2010-10-13)
22917 */
22918 protected function checkPageRegions($h, $x, $y) {
22919 // set default values
22920 if ($x === '') {
22921 $x = $this->x;
22922 }
22923 if ($y === '') {
22924 $y = $this->y;
22925 }
22926 if (!$this->check_page_regions OR empty($this->page_regions)) {
22927 // no page regions defined
22928 return array($x, $y);
22929 }
22930 if (empty($h)) {
22931 $h = $this->getCellHeight($this->FontSize);
22932 }
22933 // check for page break
22934 if ($this->checkPageBreak($h, $y)) {
22935 // the content will be printed on a new page
22936 $x = $this->x;
22937 $y = $this->y;
22938 }
22939 if ($this->num_columns > 1) {
22940 if ($this->rtl) {
22941 $this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22942 } else {
22943 $this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22944 }
22945 } else {
22946 if ($this->rtl) {
22947 $this->lMargin = max($this->clMargin, $this->original_lMargin);
22948 } else {
22949 $this->rMargin = max($this->crMargin, $this->original_rMargin);
22950 }
22951 }
22952 // adjust coordinates and page margins
22953 foreach ($this->page_regions as $regid => $regdata) {
22954 if ($regdata['page'] == $this->page) {
22955 // check region boundaries
22956 if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
22957 // Y is inside the region
22958 $minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
22959 $yt = max($y, $regdata['yt']);
22960 $yb = min(($yt + $h), $regdata['yb']);
22961 $xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
22962 $xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
22963 if ($regdata['side'] == 'L') { // left side
22964 $new_margin = max($xt, $xb);
22965 if ($this->lMargin < $new_margin) {
22966 if ($this->rtl) {
22967 // adjust left page margin
22968 $this->lMargin = max(0, $new_margin);
22969 }
22970 if ($x < $new_margin) {
22971 // adjust x position
22972 $x = $new_margin;
22973 if ($new_margin > ($this->w - $this->rMargin)) {
22974 // adjust y position
22975 $y = $regdata['yb'] - $h;
22976 }
22977 }
22978 }
22979 } elseif ($regdata['side'] == 'R') { // right side
22980 $new_margin = min($xt, $xb);
22981 if (($this->w - $this->rMargin) > $new_margin) {
22982 if (!$this->rtl) {
22983 // adjust right page margin
22984 $this->rMargin = max(0, ($this->w - $new_margin));
22985 }
22986 if ($x > $new_margin) {
22987 // adjust x position
22988 $x = $new_margin;
22989 if ($new_margin > $this->lMargin) {
22990 // adjust y position
22991 $y = $regdata['yb'] - $h;
22992 }
22993 }
22994 }
22995 }
22996 }
22997 }
22998 }
22999 return array($x, $y);
23000 }
23001
23002 // --- SVG METHODS ---------------------------------------------------------
23003
23004 /**
23005 * Embedd a Scalable Vector Graphics (SVG) image.
23006 * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
23007 * @param string $file Name of the SVG file or a '@' character followed by the SVG data string.
23008 * @param float|null $x Abscissa of the upper-left corner.
23009 * @param float|null $y Ordinate of the upper-left corner.
23010 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
23011 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
23012 * @param mixed $link URL or identifier returned by AddLink().
23013 * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul> If the alignment is an empty string, then the pointer will be restored on the starting SVG position.
23014 * @param string $palign Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
23015 * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
23016 * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
23017 * @author Nicola Asuni
23018 * @since 5.0.000 (2010-05-02)
23019 * @public
23020 */
23021 public function ImageSVG($file, $x=null, $y=null, $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
23022 if ($this->state != 2) {
23023 return;
23024 }
23025 // reset SVG vars
23026 $this->svggradients = array();
23027 $this->svggradientid = 0;
23028 $this->svgdefsmode = false;
23029 $this->svgdefs = array();
23030 $this->svgclipmode = false;
23031 $this->svgclippaths = array();
23032 $this->svgcliptm = array();
23033 $this->svgclipid = 0;
23034 $this->svgtext = '';
23035 $this->svgtextmode = array();
23036 if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
23037 // convert SVG to raster image using GD or ImageMagick libraries
23038 return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
23039 }
23040 if ($file[0] === '@') { // image from string
23041 $this->svgdir = '';
23042 $svgdata = substr($file, 1);
23043 } else { // SVG file
23044 $this->svgdir = dirname($file);
23045 $svgdata = $this->getCachedFileContents($file);
23046 }
23047 if ($svgdata === FALSE) {
23048 $this->Error('SVG file not found: '.$file);
23049 }
23050 if (TCPDF_STATIC::empty_string($x)) {
23051 $x = $this->x;
23052 }
23053 if (TCPDF_STATIC::empty_string($y)) {
23054 $y = $this->y;
23055 }
23056 // check page for no-write regions and adapt page margins if necessary
23057 list($x, $y) = $this->checkPageRegions($h, $x, $y);
23058 $k = $this->k;
23059 $ox = 0;
23060 $oy = 0;
23061 $ow = $w;
23062 $oh = $h;
23063 $aspect_ratio_align = 'xMidYMid';
23064 $aspect_ratio_ms = 'meet';
23065 $regs = array();
23066 // get original image width and height
23067 preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
23068 if (isset($regs[1]) AND !empty($regs[1])) {
23069 $tmp = array();
23070 if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
23071 $ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
23072 }
23073 $tmp = array();
23074 if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
23075 $oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
23076 }
23077 $tmp = array();
23078 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
23079 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
23080 }
23081 $tmp = array();
23082 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
23083 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
23084 }
23085 $tmp = array();
23086 $view_box = array();
23087 if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
23088 if (count($tmp) == 5) {
23089 array_shift($tmp);
23090 foreach ($tmp as $key => $val) {
23091 $view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
23092 }
23093 $ox = $view_box[0];
23094 $oy = $view_box[1];
23095 }
23096 // get aspect ratio
23097 $tmp = array();
23098 if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
23099 $aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
23100 switch (count($aspect_ratio)) {
23101 case 3: {
23102 $aspect_ratio_align = $aspect_ratio[1];
23103 $aspect_ratio_ms = $aspect_ratio[2];
23104 break;
23105 }
23106 case 2: {
23107 $aspect_ratio_align = $aspect_ratio[0];
23108 $aspect_ratio_ms = $aspect_ratio[1];
23109 break;
23110 }
23111 case 1: {
23112 $aspect_ratio_align = $aspect_ratio[0];
23113 $aspect_ratio_ms = 'meet';
23114 break;
23115 }
23116 }
23117 }
23118 }
23119 }
23120 if ($ow <= 0) {
23121 $ow = 1;
23122 }
23123 if ($oh <= 0) {
23124 $oh = 1;
23125 }
23126 // calculate image width and height on document
23127 if (($w <= 0) AND ($h <= 0)) {
23128 // convert image size to document unit
23129 $w = $ow;
23130 $h = $oh;
23131 } elseif ($w <= 0) {
23132 $w = $h * $ow / $oh;
23133 } elseif ($h <= 0) {
23134 $h = $w * $oh / $ow;
23135 }
23136 // fit the image on available space
23137 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
23138 if ($this->rasterize_vector_images) {
23139 // convert SVG to raster image using GD or ImageMagick libraries
23140 return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
23141 }
23142 // set alignment
23143 $this->img_rb_y = $y + $h;
23144 // set alignment
23145 if ($this->rtl) {
23146 if ($palign == 'L') {
23147 $ximg = $this->lMargin;
23148 } elseif ($palign == 'C') {
23149 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
23150 } elseif ($palign == 'R') {
23151 $ximg = $this->w - $this->rMargin - $w;
23152 } else {
23153 $ximg = $x - $w;
23154 }
23155 $this->img_rb_x = $ximg;
23156 } else {
23157 if ($palign == 'L') {
23158 $ximg = $this->lMargin;
23159 } elseif ($palign == 'C') {
23160 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
23161 } elseif ($palign == 'R') {
23162 $ximg = $this->w - $this->rMargin - $w;
23163 } else {
23164 $ximg = $x;
23165 }
23166 $this->img_rb_x = $ximg + $w;
23167 }
23168 // store current graphic vars
23169 $gvars = $this->getGraphicVars();
23170 // store SVG position and scale factors
23171 $svgoffset_x = ($ximg - $ox) * $this->k;
23172 $svgoffset_y = -($y - $oy) * $this->k;
23173 if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
23174 $ow = $view_box[2];
23175 $oh = $view_box[3];
23176 } else {
23177 if ($ow <= 0) {
23178 $ow = $w;
23179 }
23180 if ($oh <= 0) {
23181 $oh = $h;
23182 }
23183 }
23184 $svgscale_x = $w / $ow;
23185 $svgscale_y = $h / $oh;
23186 // scaling and alignment
23187 if ($aspect_ratio_align != 'none') {
23188 // store current scaling values
23189 $svgscale_old_x = $svgscale_x;
23190 $svgscale_old_y = $svgscale_y;
23191 // force uniform scaling
23192 if ($aspect_ratio_ms == 'slice') {
23193 // the entire viewport is covered by the viewBox
23194 if ($svgscale_x > $svgscale_y) {
23195 $svgscale_y = $svgscale_x;
23196 } elseif ($svgscale_x < $svgscale_y) {
23197 $svgscale_x = $svgscale_y;
23198 }
23199 } else { // meet
23200 // the entire viewBox is visible within the viewport
23201 if ($svgscale_x < $svgscale_y) {
23202 $svgscale_y = $svgscale_x;
23203 } elseif ($svgscale_x > $svgscale_y) {
23204 $svgscale_x = $svgscale_y;
23205 }
23206 }
23207 // correct X alignment
23208 switch (substr($aspect_ratio_align, 1, 3)) {
23209 case 'Min': {
23210 // do nothing
23211 break;
23212 }
23213 case 'Max': {
23214 $svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
23215 break;
23216 }
23217 default:
23218 case 'Mid': {
23219 $svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
23220 break;
23221 }
23222 }
23223 // correct Y alignment
23224 switch (substr($aspect_ratio_align, 5)) {
23225 case 'Min': {
23226 // do nothing
23227 break;
23228 }
23229 case 'Max': {
23230 $svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
23231 break;
23232 }
23233 default:
23234 case 'Mid': {
23235 $svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
23236 break;
23237 }
23238 }
23239 }
23240 // store current page break mode
23241 $page_break_mode = $this->AutoPageBreak;
23242 $page_break_margin = $this->getBreakMargin();
23243 $cell_padding = $this->cell_padding;
23244 $this->setCellPadding(0);
23245 $this->setAutoPageBreak(false);
23246 // save the current graphic state
23247 $this->_out('q'.$this->epsmarker);
23248 // set initial clipping mask
23249 $this->Rect($ximg, $y, $w, $h, 'CNZ', array(), array());
23250 // scale and translate
23251 $e = $ox * $this->k * (1 - $svgscale_x);
23252 $f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
23253 $this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
23254 // creates a new XML parser to be used by the other XML functions
23255 $parser = xml_parser_create('UTF-8');
23256 // disable case-folding for this XML parser
23257 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
23258 // sets the element handler functions for the XML parser
23259 xml_set_element_handler($parser, [$this, 'startSVGElementHandler'], [$this, 'endSVGElementHandler']);
23260 // sets the character data handler function for the XML parser
23261 xml_set_character_data_handler($parser, [$this, 'segSVGContentHandler']);
23262 // start parsing an XML document
23263 if (!xml_parse($parser, $svgdata)) {
23264 $error_message = sprintf('SVG Error: %s at line %d', xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser));
23265 $this->Error($error_message);
23266 }
23267
23268 // free this XML parser (does nothing in PHP >= 8.0)
23269 if (function_exists('xml_parser_free') && PHP_VERSION_ID < 80000) {
23270 xml_parser_free($parser);
23271 }
23272
23273 // >= PHP 7.0.0 "explicitly unset the reference to parser to avoid memory leaks"
23274 unset($parser);
23275
23276 // restore previous graphic state
23277 $this->_out($this->epsmarker.'Q');
23278 // restore graphic vars
23279 $this->setGraphicVars($gvars);
23280 $this->lasth = $gvars['lasth'];
23281 if (!empty($border)) {
23282 $bx = $this->x;
23283 $by = $this->y;
23284 $this->x = $ximg;
23285 if ($this->rtl) {
23286 $this->x += $w;
23287 }
23288 $this->y = $y;
23289 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
23290 $this->x = $bx;
23291 $this->y = $by;
23292 }
23293 if ($link) {
23294 $this->Link($ximg, $y, $w, $h, $link, 0);
23295 }
23296 // set pointer to align the next text/objects
23297 switch($align) {
23298 case 'T':{
23299 $this->y = $y;
23300 $this->x = $this->img_rb_x;
23301 break;
23302 }
23303 case 'M':{
23304 $this->y = $y + round($h/2);
23305 $this->x = $this->img_rb_x;
23306 break;
23307 }
23308 case 'B':{
23309 $this->y = $this->img_rb_y;
23310 $this->x = $this->img_rb_x;
23311 break;
23312 }
23313 case 'N':{
23314 $this->setY($this->img_rb_y);
23315 break;
23316 }
23317 default:{
23318 // restore pointer to starting position
23319 $this->x = $gvars['x'];
23320 $this->y = $gvars['y'];
23321 $this->page = $gvars['page'];
23322 $this->current_column = $gvars['current_column'];
23323 $this->tMargin = $gvars['tMargin'];
23324 $this->bMargin = $gvars['bMargin'];
23325 $this->w = $gvars['w'];
23326 $this->h = $gvars['h'];
23327 $this->wPt = $gvars['wPt'];
23328 $this->hPt = $gvars['hPt'];
23329 $this->fwPt = $gvars['fwPt'];
23330 $this->fhPt = $gvars['fhPt'];
23331 break;
23332 }
23333 }
23334 $this->endlinex = $this->img_rb_x;
23335 // restore page break
23336 $this->setAutoPageBreak($page_break_mode, $page_break_margin);
23337 $this->cell_padding = $cell_padding;
23338 }
23339
23340 /**
23341 * Convert SVG transformation matrix to PDF.
23342 * @param array $tm original SVG transformation matrix
23343 * @return array transformation matrix
23344 * @protected
23345 * @since 5.0.000 (2010-05-02)
23346 */
23347 protected function convertSVGtMatrix($tm) {
23348 $a = $tm[0];
23349 $b = -$tm[1];
23350 $c = -$tm[2];
23351 $d = $tm[3];
23352 $e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
23353 $f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
23354 $x = 0;
23355 $y = $this->h * $this->k;
23356 $e = ($x * (1 - $a)) - ($y * $c) + $e;
23357 $f = ($y * (1 - $d)) - ($x * $b) + $f;
23358 return array($a, $b, $c, $d, $e, $f);
23359 }
23360
23361 /**
23362 * Apply SVG graphic transformation matrix.
23363 * @param array $tm original SVG transformation matrix
23364 * @protected
23365 * @since 5.0.000 (2010-05-02)
23366 */
23367 protected function SVGTransform($tm) {
23368 $this->Transform($this->convertSVGtMatrix($tm));
23369 }
23370
23371 /**
23372 * Apply the requested SVG styles (*** TO BE COMPLETED ***)
23373 * @param array $svgstyle array of SVG styles to apply
23374 * @param array $prevsvgstyle array of previous SVG style
23375 * @param int $x X origin of the bounding box
23376 * @param int $y Y origin of the bounding box
23377 * @param int $w width of the bounding box
23378 * @param int $h height of the bounding box
23379 * @param string $clip_function clip function
23380 * @param array $clip_params array of parameters for clipping function
23381 * @return string style
23382 * @author Nicola Asuni
23383 * @since 5.0.000 (2010-05-02)
23384 * @protected
23385 */
23386 protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
23387 if ($this->state != 2) {
23388 return;
23389 }
23390 $objstyle = '';
23391 $minlen = (0.01 / $this->k); // minimum acceptable length
23392 if (!isset($svgstyle['opacity'])) {
23393 return $objstyle;
23394 }
23395 // clip-path
23396 $regs = array();
23397 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
23398 $clip_path = $this->svgclippaths[$regs[1]];
23399 foreach ($clip_path as $cp) {
23400 $this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
23401 }
23402 }
23403 // opacity
23404 if ($svgstyle['opacity'] != 1) {
23405 $this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
23406 }
23407 // color
23408 $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
23409 $this->setFillColorArray($fill_color);
23410 // text color
23411 $text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
23412 $this->setTextColorArray($text_color);
23413 // clip
23414 if (preg_match('/rect\(([a-z0-9\-\.]*+)[\s]*+([a-z0-9\-\.]*+)[\s]*+([a-z0-9\-\.]*+)[\s]*+([a-z0-9\-\.]*+)\)/si', $svgstyle['clip'], $regs)) {
23415 $top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
23416 $right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
23417 $bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
23418 $left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
23419 $cx = $x + $left;
23420 $cy = $y + $top;
23421 $cw = $w - $left - $right;
23422 $ch = $h - $top - $bottom;
23423 if ($svgstyle['clip-rule'] == 'evenodd') {
23424 $clip_rule = 'CNZ';
23425 } else {
23426 $clip_rule = 'CEO';
23427 }
23428 $this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
23429 }
23430 // fill
23431 $regs = array();
23432 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
23433 // gradient
23434 $gradient = $this->svggradients[$regs[1]];
23435 if (isset($gradient['xref'])) {
23436 // reference to another gradient definition
23437 $newgradient = $this->svggradients[$gradient['xref']];
23438 $newgradient['coords'] = $gradient['coords'];
23439 $newgradient['mode'] = $gradient['mode'];
23440 $newgradient['type'] = $gradient['type'];
23441 $newgradient['gradientUnits'] = $gradient['gradientUnits'];
23442 if (isset($gradient['gradientTransform'])) {
23443 $newgradient['gradientTransform'] = $gradient['gradientTransform'];
23444 }
23445 $gradient = $newgradient;
23446 }
23447 //save current Graphic State
23448 $this->_outSaveGraphicsState();
23449 //set clipping area
23450 if (!empty($clip_function) AND method_exists($this, $clip_function)) {
23451 $bbox = call_user_func_array(array($this, $clip_function), $clip_params);
23452 if ((!isset($gradient['type']) OR ($gradient['type'] != 3)) AND is_array($bbox) AND (count($bbox) == 4)) {
23453 list($x, $y, $w, $h) = $bbox;
23454 }
23455 }
23456 if ($gradient['mode'] == 'measure') {
23457 if (!isset($gradient['coords'][4])) {
23458 $gradient['coords'][4] = 0.5;
23459 }
23460 if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
23461 $gtm = $gradient['gradientTransform'];
23462 // apply transformation matrix
23463 $xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
23464 $ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
23465 $xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
23466 $yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
23467 $r = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
23468 $gradient['coords'][0] = $xa;
23469 $gradient['coords'][1] = $ya;
23470 $gradient['coords'][2] = $xb;
23471 $gradient['coords'][3] = $yb;
23472 $gradient['coords'][4] = $r;
23473 }
23474 // convert SVG coordinates to user units
23475 $gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
23476 $gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
23477 $gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
23478 $gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
23479 $gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
23480 if ($w <= $minlen) {
23481 $w = $minlen;
23482 }
23483 if ($h <= $minlen) {
23484 $h = $minlen;
23485 }
23486 // shift units
23487 if ($gradient['gradientUnits'] == 'objectBoundingBox') {
23488 // convert to SVG coordinate system
23489 $gradient['coords'][0] += $x;
23490 $gradient['coords'][1] += $y;
23491 $gradient['coords'][2] += $x;
23492 $gradient['coords'][3] += $y;
23493 }
23494 // calculate percentages
23495 $gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
23496 $gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
23497 $gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
23498 $gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
23499 $gradient['coords'][4] /= $w;
23500 } elseif ($gradient['mode'] == 'percentage') {
23501 foreach($gradient['coords'] as $key => $val) {
23502 $val = floatval($val) / 100;
23503 $gradient['coords'][$key] = $val;
23504 if ($val < 0) {
23505 $gradient['coords'][$key] = 0;
23506 } elseif ($val > 1) {
23507 $gradient['coords'][$key] = 1;
23508 }
23509 }
23510 }
23511 if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
23512 // single color (no shading)
23513 $gradient['coords'][0] = 1;
23514 $gradient['coords'][1] = 0;
23515 $gradient['coords'][2] = 0.999;
23516 $gradient['coords'][3] = 0;
23517 }
23518 // swap Y coordinates
23519 $tmp = $gradient['coords'][1];
23520 $gradient['coords'][1] = $gradient['coords'][3];
23521 $gradient['coords'][3] = $tmp;
23522 // set transformation map for gradient
23523 $cy = ($this->h - $y);
23524 if ($gradient['type'] == 3) {
23525 // circular gradient
23526 $cy -= ($gradient['coords'][1] * ($w + $h));
23527 $h = $w = max($w, $h);
23528 } else {
23529 $cy -= $h;
23530 }
23531 $this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), ($cy * $this->k)));
23532 if ((is_array($gradient['stops']) || $gradient['stops'] instanceof Countable) && count($gradient['stops']) > 1) {
23533 $this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops']);
23534 }
23535 } elseif ($svgstyle['fill'] != 'none') {
23536 $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
23537 if ($svgstyle['fill-opacity'] != 1) {
23538 $this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
23539 } elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['fill'], $rgba_matches)) {
23540 $this->setAlpha($this->alpha['CA'], 'Normal', $rgba_matches[1], false);
23541 }
23542 $this->setFillColorArray($fill_color);
23543 if ($svgstyle['fill-rule'] == 'evenodd') {
23544 $objstyle .= 'F*';
23545 } else {
23546 $objstyle .= 'F';
23547 }
23548 }
23549 // stroke
23550 if ($svgstyle['stroke'] != 'none') {
23551 if ($svgstyle['stroke-opacity'] != 1) {
23552 $this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
23553 } elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['stroke'], $rgba_matches)) {
23554 $this->setAlpha($rgba_matches[1], 'Normal', $this->alpha['ca'], false);
23555 }
23556 $stroke_style = array(
23557 'color' => TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors),
23558 'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
23559 'cap' => $svgstyle['stroke-linecap'],
23560 'join' => $svgstyle['stroke-linejoin']
23561 );
23562 if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
23563 $stroke_style['dash'] = $svgstyle['stroke-dasharray'];
23564 }
23565 $this->setLineStyle($stroke_style);
23566 $objstyle .= 'D';
23567 }
23568 // font
23569 $regs = array();
23570 if (!empty($svgstyle['font'])) {
23571 if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
23572 $font_family = $this->getFontFamilyName($regs[1]);
23573 } else {
23574 $font_family = $this->getFontFamilyName($svgstyle['font-family']);
23575 }
23576 if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23577 $font_size = trim($regs[1]);
23578 } else {
23579 $font_size = $svgstyle['font-size'];
23580 }
23581 if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23582 $font_style = trim($regs[1]);
23583 } else {
23584 $font_style = $svgstyle['font-style'];
23585 }
23586 if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23587 $font_weight = trim($regs[1]);
23588 } else {
23589 $font_weight = $svgstyle['font-weight'];
23590 }
23591 if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23592 $font_stretch = trim($regs[1]);
23593 } else {
23594 $font_stretch = $svgstyle['font-stretch'];
23595 }
23596 if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23597 $font_spacing = trim($regs[1]);
23598 } else {
23599 $font_spacing = $svgstyle['letter-spacing'];
23600 }
23601 } else {
23602 $font_family = $this->getFontFamilyName($svgstyle['font-family']);
23603 $font_size = $svgstyle['font-size'];
23604 $font_style = $svgstyle['font-style'];
23605 $font_weight = $svgstyle['font-weight'];
23606 $font_stretch = $svgstyle['font-stretch'];
23607 $font_spacing = $svgstyle['letter-spacing'];
23608 }
23609 $font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit);
23610 $font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
23611 $font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
23612 switch ($font_style) {
23613 case 'italic': {
23614 $font_style = 'I';
23615 break;
23616 }
23617 case 'oblique': {
23618 $font_style = 'I';
23619 break;
23620 }
23621 default:
23622 case 'normal': {
23623 $font_style = '';
23624 break;
23625 }
23626 }
23627 switch ($font_weight) {
23628 case 'bold':
23629 case 'bolder': {
23630 $font_style .= 'B';
23631 break;
23632 }
23633 case 'normal': {
23634 if ((substr($font_family, -1) == 'I') AND (substr($font_family, -2, 1) == 'B')) {
23635 $font_family = substr($font_family, 0, -2).'I';
23636 } elseif (substr($font_family, -1) == 'B') {
23637 $font_family = substr($font_family, 0, -1);
23638 }
23639 break;
23640 }
23641 }
23642 switch ($svgstyle['text-decoration']) {
23643 case 'underline': {
23644 $font_style .= 'U';
23645 break;
23646 }
23647 case 'overline': {
23648 $font_style .= 'O';
23649 break;
23650 }
23651 case 'line-through': {
23652 $font_style .= 'D';
23653 break;
23654 }
23655 default:
23656 case 'none': {
23657 break;
23658 }
23659 }
23660 $this->setFont($font_family, $font_style, $font_size);
23661 $this->setFontStretching($font_stretch);
23662 $this->setFontSpacing($font_spacing);
23663 return $objstyle;
23664 }
23665
23666 /**
23667 * Draws an SVG path
23668 * @param string $d attribute d of the path SVG element
23669 * @param string $style Style of rendering. Possible values are:
23670 * <ul>
23671 * <li>D or empty string: Draw (default).</li>
23672 * <li>F: Fill.</li>
23673 * <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23674 * <li>DF or FD: Draw and fill.</li>
23675 * <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23676 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
23677 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
23678 * </ul>
23679 * @return array of container box measures (x, y, w, h)
23680 * @author Nicola Asuni
23681 * @since 5.0.000 (2010-05-02)
23682 * @protected
23683 */
23684 protected function SVGPath($d, $style='') {
23685 if ($this->state != 2) {
23686 return;
23687 }
23688 // set fill/stroke style
23689 $op = TCPDF_STATIC::getPathPaintOperator($style, '');
23690 if (empty($op)) {
23691 return;
23692 }
23693 $paths = array();
23694 $d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
23695 $d = preg_replace('/(\.[0-9]+)(\.)/s', '\\1 \\2', $d);
23696 preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
23697 $x = 0;
23698 $y = 0;
23699 $x1 = 0;
23700 $y1 = 0;
23701 $x2 = 0;
23702 $y2 = 0;
23703 $xmin = 2147483647;
23704 $xmax = 0;
23705 $ymin = 2147483647;
23706 $ymax = 0;
23707 $xinitial = 0;
23708 $yinitial = 0;
23709 $relcoord = false;
23710 $minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
23711 $firstcmd = true; // used to print first point
23712 // draw curve pieces
23713 foreach ($paths as $key => $val) {
23714 // get curve type
23715 $cmd = trim($val[1]);
23716 if (strtolower($cmd) == $cmd) {
23717 // use relative coordinated instead of absolute
23718 $relcoord = true;
23719 $xoffset = $x;
23720 $yoffset = $y;
23721 } else {
23722 $relcoord = false;
23723 $xoffset = 0;
23724 $yoffset = 0;
23725 }
23726 $params = array();
23727 if (isset($val[2])) {
23728 // get curve parameters, see https://github.com/tecnickcom/TCPDF/issues/767
23729 $rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
23730 $rawparams = array_filter($rawparams, function($p) {
23731 return trim($p) != '';
23732 });
23733 $params = array();
23734 foreach ($rawparams as $ck => $cp) {
23735 $params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
23736 if (abs($params[$ck]) < $minlen) {
23737 // approximate little values to zero
23738 $params[$ck] = 0;
23739 }
23740 }
23741 }
23742 // store current origin point
23743 $x0 = $x;
23744 $y0 = $y;
23745 switch (strtoupper($cmd)) {
23746 case 'M': { // moveto
23747 foreach ($params as $ck => $cp) {
23748 if (($ck % 2) == 0) {
23749 $x = $cp + $xoffset;
23750 } else {
23751 $y = $cp + $yoffset;
23752 if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23753 if ($ck == 1) {
23754 $this->_outPoint($x, $y);
23755 $firstcmd = false;
23756 $xinitial = $x;
23757 $yinitial = $y;
23758 } else {
23759 $this->_outLine($x, $y);
23760 }
23761 $x0 = $x;
23762 $y0 = $y;
23763 }
23764 $xmin = min($xmin, $x);
23765 $ymin = min($ymin, $y);
23766 $xmax = max($xmax, $x);
23767 $ymax = max($ymax, $y);
23768 if ($relcoord) {
23769 $xoffset = $x;
23770 $yoffset = $y;
23771 }
23772 }
23773 }
23774 break;
23775 }
23776 case 'L': { // lineto
23777 foreach ($params as $ck => $cp) {
23778 if (($ck % 2) == 0) {
23779 $x = $cp + $xoffset;
23780 } else {
23781 $y = $cp + $yoffset;
23782 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23783 $this->_outLine($x, $y);
23784 $x0 = $x;
23785 $y0 = $y;
23786 }
23787 $xmin = min($xmin, $x);
23788 $ymin = min($ymin, $y);
23789 $xmax = max($xmax, $x);
23790 $ymax = max($ymax, $y);
23791 if ($relcoord) {
23792 $xoffset = $x;
23793 $yoffset = $y;
23794 }
23795 }
23796 }
23797 break;
23798 }
23799 case 'H': { // horizontal lineto
23800 foreach ($params as $ck => $cp) {
23801 $x = $cp + $xoffset;
23802 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23803 $this->_outLine($x, $y);
23804 $x0 = $x;
23805 $y0 = $y;
23806 }
23807 $xmin = min($xmin, $x);
23808 $xmax = max($xmax, $x);
23809 if ($relcoord) {
23810 $xoffset = $x;
23811 }
23812 }
23813 break;
23814 }
23815 case 'V': { // vertical lineto
23816 foreach ($params as $ck => $cp) {
23817 $y = $cp + $yoffset;
23818 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23819 $this->_outLine($x, $y);
23820 $x0 = $x;
23821 $y0 = $y;
23822 }
23823 $ymin = min($ymin, $y);
23824 $ymax = max($ymax, $y);
23825 if ($relcoord) {
23826 $yoffset = $y;
23827 }
23828 }
23829 break;
23830 }
23831 case 'C': { // curveto
23832 foreach ($params as $ck => $cp) {
23833 $params[$ck] = $cp;
23834 if ((($ck + 1) % 6) == 0) {
23835 $x1 = $params[($ck - 5)] + $xoffset;
23836 $y1 = $params[($ck - 4)] + $yoffset;
23837 $x2 = $params[($ck - 3)] + $xoffset;
23838 $y2 = $params[($ck - 2)] + $yoffset;
23839 $x = $params[($ck - 1)] + $xoffset;
23840 $y = $params[($ck)] + $yoffset;
23841 $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23842 $xmin = min($xmin, $x, $x1, $x2);
23843 $ymin = min($ymin, $y, $y1, $y2);
23844 $xmax = max($xmax, $x, $x1, $x2);
23845 $ymax = max($ymax, $y, $y1, $y2);
23846 if ($relcoord) {
23847 $xoffset = $x;
23848 $yoffset = $y;
23849 }
23850 }
23851 }
23852 break;
23853 }
23854 case 'S': { // shorthand/smooth curveto
23855 foreach ($params as $ck => $cp) {
23856 $params[$ck] = $cp;
23857 if ((($ck + 1) % 4) == 0) {
23858 if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
23859 $x1 = (2 * $x) - $x2;
23860 $y1 = (2 * $y) - $y2;
23861 } else {
23862 $x1 = $x;
23863 $y1 = $y;
23864 }
23865 $x2 = $params[($ck - 3)] + $xoffset;
23866 $y2 = $params[($ck - 2)] + $yoffset;
23867 $x = $params[($ck - 1)] + $xoffset;
23868 $y = $params[($ck)] + $yoffset;
23869 $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23870 $xmin = min($xmin, $x, $x1, $x2);
23871 $ymin = min($ymin, $y, $y1, $y2);
23872 $xmax = max($xmax, $x, $x1, $x2);
23873 $ymax = max($ymax, $y, $y1, $y2);
23874 if ($relcoord) {
23875 $xoffset = $x;
23876 $yoffset = $y;
23877 }
23878 }
23879 }
23880 break;
23881 }
23882 case 'Q': { // quadratic Bezier curveto
23883 foreach ($params as $ck => $cp) {
23884 $params[$ck] = $cp;
23885 if ((($ck + 1) % 4) == 0) {
23886 // convert quadratic points to cubic points
23887 $x1 = $params[($ck - 3)] + $xoffset;
23888 $y1 = $params[($ck - 2)] + $yoffset;
23889 $xa = ($x + (2 * $x1)) / 3;
23890 $ya = ($y + (2 * $y1)) / 3;
23891 $x = $params[($ck - 1)] + $xoffset;
23892 $y = $params[($ck)] + $yoffset;
23893 $xb = ($x + (2 * $x1)) / 3;
23894 $yb = ($y + (2 * $y1)) / 3;
23895 $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23896 $xmin = min($xmin, $x, $xa, $xb);
23897 $ymin = min($ymin, $y, $ya, $yb);
23898 $xmax = max($xmax, $x, $xa, $xb);
23899 $ymax = max($ymax, $y, $ya, $yb);
23900 if ($relcoord) {
23901 $xoffset = $x;
23902 $yoffset = $y;
23903 }
23904 }
23905 }
23906 break;
23907 }
23908 case 'T': { // shorthand/smooth quadratic Bezier curveto
23909 foreach ($params as $ck => $cp) {
23910 $params[$ck] = $cp;
23911 if (($ck % 2) != 0) {
23912 if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
23913 $x1 = (2 * $x) - $x1;
23914 $y1 = (2 * $y) - $y1;
23915 } else {
23916 $x1 = $x;
23917 $y1 = $y;
23918 }
23919 // convert quadratic points to cubic points
23920 $xa = ($x + (2 * $x1)) / 3;
23921 $ya = ($y + (2 * $y1)) / 3;
23922 $x = $params[($ck - 1)] + $xoffset;
23923 $y = $params[($ck)] + $yoffset;
23924 $xb = ($x + (2 * $x1)) / 3;
23925 $yb = ($y + (2 * $y1)) / 3;
23926 $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23927 $xmin = min($xmin, $x, $xa, $xb);
23928 $ymin = min($ymin, $y, $ya, $yb);
23929 $xmax = max($xmax, $x, $xa, $xb);
23930 $ymax = max($ymax, $y, $ya, $yb);
23931 if ($relcoord) {
23932 $xoffset = $x;
23933 $yoffset = $y;
23934 }
23935 }
23936 }
23937 break;
23938 }
23939 case 'A': { // elliptical arc
23940 foreach ($params as $ck => $cp) {
23941 $params[$ck] = $cp;
23942 if ((($ck + 1) % 7) == 0) {
23943 $x0 = $x;
23944 $y0 = $y;
23945 $rx = max(abs($params[($ck - 6)]), .000000001);
23946 $ry = max(abs($params[($ck - 5)]), .000000001);
23947 $ang = -$rawparams[($ck - 4)];
23948 $angle = deg2rad($ang);
23949 $fa = $rawparams[($ck - 3)]; // large-arc-flag
23950 $fs = $rawparams[($ck - 2)]; // sweep-flag
23951 $x = $params[($ck - 1)] + $xoffset;
23952 $y = $params[$ck] + $yoffset;
23953 if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
23954 // endpoints are almost identical
23955 $xmin = min($xmin, $x);
23956 $ymin = min($ymin, $y);
23957 $xmax = max($xmax, $x);
23958 $ymax = max($ymax, $y);
23959 } else {
23960 $cos_ang = cos($angle);
23961 $sin_ang = sin($angle);
23962 $a = (($x0 - $x) / 2);
23963 $b = (($y0 - $y) / 2);
23964 $xa = ($a * $cos_ang) - ($b * $sin_ang);
23965 $ya = ($a * $sin_ang) + ($b * $cos_ang);
23966 $rx2 = $rx * $rx;
23967 $ry2 = $ry * $ry;
23968 $xa2 = $xa * $xa;
23969 $ya2 = $ya * $ya;
23970 $delta = ($xa2 / $rx2) + ($ya2 / $ry2);
23971 if ($delta > 1) {
23972 $rx *= sqrt($delta);
23973 $ry *= sqrt($delta);
23974 $rx2 = $rx * $rx;
23975 $ry2 = $ry * $ry;
23976 }
23977 $numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
23978 if ($numerator < 0) {
23979 $root = 0;
23980 } else {
23981 $root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
23982 }
23983 if ($fa == $fs){
23984 $root *= -1;
23985 }
23986 $cax = $root * (($rx * $ya) / $ry);
23987 $cay = -$root * (($ry * $xa) / $rx);
23988 // coordinates of ellipse center
23989 $cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
23990 $cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
23991 // get angles
23992 $angs = TCPDF_STATIC::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
23993 $dang = TCPDF_STATIC::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
23994 if (($fs == 0) AND ($dang > 0)) {
23995 $dang -= (2 * M_PI);
23996 } elseif (($fs == 1) AND ($dang < 0)) {
23997 $dang += (2 * M_PI);
23998 }
23999 $angf = $angs - $dang;
24000 if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
24001 // reverse angles
24002 $tmp = $angs;
24003 $angs = $angf;
24004 $angf = $tmp;
24005 }
24006 $angs = round(rad2deg($angs), 6);
24007 $angf = round(rad2deg($angf), 6);
24008 // covent angles to positive values
24009 if (($angs < 0) AND ($angf < 0)) {
24010 $angs += 360;
24011 $angf += 360;
24012 }
24013 $pie = false;
24014 if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
24015 $pie = true;
24016 }
24017 list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
24018 $xmin = min($xmin, $x, $axmin);
24019 $ymin = min($ymin, $y, $aymin);
24020 $xmax = max($xmax, $x, $axmax);
24021 $ymax = max($ymax, $y, $aymax);
24022 }
24023 if ($relcoord) {
24024 $xoffset = $x;
24025 $yoffset = $y;
24026 }
24027 }
24028 }
24029 break;
24030 }
24031 case 'Z': {
24032 $this->_out('h');
24033 $x = $x0 = $xinitial;
24034 $y = $y0 = $yinitial;
24035 break;
24036 }
24037 }
24038 $firstcmd = false;
24039 } // end foreach
24040 $this->_out($op);
24041 return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
24042 }
24043
24044 /**
24045 * Return the tag name without the namespace
24046 * @param string $name Tag name
24047 * @protected
24048 */
24049 protected function removeTagNamespace($name) {
24050 if(strpos($name, ':') !== false) {
24051 $parts = explode(':', $name);
24052 return $parts[(sizeof($parts) - 1)];
24053 }
24054 return $name;
24055 }
24056
24057 /**
24058 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
24059 * @param resource|string $parser The first parameter, parser, is a reference to the XML parser calling the handler.
24060 * @param string $name The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
24061 * @param array $attribs The third parameter, attribs, contains an associative array with the element's attributes (if any). The keys of this array are the attribute names, the values are the attribute values. Attribute names are case-folded on the same criteria as element names. Attribute values are not case-folded. The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). The first key in the array was the first attribute, and so on.
24062 * @param array $ctm tranformation matrix for clipping mode (starting transformation matrix).
24063 * @author Nicola Asuni
24064 * @since 5.0.000 (2010-05-02)
24065 * @protected
24066 */
24067 protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
24068 $name = $this->removeTagNamespace($name);
24069 // check if we are in clip mode
24070 if ($this->svgclipmode) {
24071 $this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
24072 return;
24073 }
24074 if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
24075 if (isset($attribs['id'])) {
24076 $attribs['child_elements'] = array();
24077 $this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
24078 return;
24079 }
24080 if (end($this->svgdefs) !== FALSE) {
24081 $last_svgdefs_id = key($this->svgdefs);
24082 if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
24083 $attribs['id'] = 'DF_'.(count($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements']) + 1);
24084 $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
24085 return;
24086 }
24087 }
24088 return;
24089 }
24090 $clipping = false;
24091 if ($parser == 'clip-path') {
24092 // set clipping mode
24093 $clipping = true;
24094 }
24095 // get styling properties
24096 $prev_svgstyle = $this->svgstyles[max(0,(count($this->svgstyles) - 1))]; // previous style
24097 $svgstyle = $this->svgstyles[0]; // set default style
24098 if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
24099 // default fill attribute for clipping
24100 $attribs['fill'] = 'none';
24101 }
24102 if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
24103 // fix style for regular expression
24104 $attribs['style'] = ';'.$attribs['style'];
24105 }
24106 foreach ($prev_svgstyle as $key => $val) {
24107 if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
24108 // inherit previous value
24109 $svgstyle[$key] = $val;
24110 }
24111 if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
24112 // specific attribute settings
24113 if ($attribs[$key] == 'inherit') {
24114 $svgstyle[$key] = $val;
24115 } else {
24116 $svgstyle[$key] = $attribs[$key];
24117 }
24118 } elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
24119 // CSS style syntax
24120 $attrval = array();
24121 if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
24122 if ($attrval[1] == 'inherit') {
24123 $svgstyle[$key] = $val;
24124 } else {
24125 $svgstyle[$key] = $attrval[1];
24126 }
24127 }
24128 }
24129 }
24130 // transformation matrix
24131 if (!empty($ctm)) {
24132 $tm = $ctm;
24133 } else {
24134 $tm = array(1,0,0,1,0,0);
24135 }
24136 if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
24137 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
24138 }
24139 $svgstyle['transfmatrix'] = $tm;
24140 $invisible = false;
24141 if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
24142 // the current graphics element is invisible (nothing is painted)
24143 $invisible = true;
24144 }
24145 // process tag
24146 switch($name) {
24147 case 'defs': {
24148 $this->svgdefsmode = true;
24149 break;
24150 }
24151 // clipPath
24152 case 'clipPath': {
24153 if ($invisible) {
24154 break;
24155 }
24156 $this->svgclipmode = true;
24157 if (!isset($attribs['id'])) {
24158 $attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
24159 }
24160 $this->svgclipid = $attribs['id'];
24161 $this->svgclippaths[$this->svgclipid] = array();
24162 $this->svgcliptm[$this->svgclipid] = $tm;
24163 break;
24164 }
24165 case 'svg': {
24166 // start of SVG object
24167 if(++$this->svg_tag_depth <= 1) {
24168 break;
24169 }
24170 // inner SVG
24171 array_push($this->svgstyles, $svgstyle);
24172 $this->StartTransform();
24173 $svgX = (isset($attribs['x'])?$attribs['x']:0);
24174 $svgY = (isset($attribs['y'])?$attribs['y']:0);
24175 $svgW = (isset($attribs['width'])?$attribs['width']:0);
24176 $svgH = (isset($attribs['height'])?$attribs['height']:0);
24177 // set x, y position using transform matrix
24178 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array( 1, 0, 0, 1, $svgX, $svgY));
24179 $this->SVGTransform($tm);
24180 // set clipping for width and height
24181 $x = 0;
24182 $y = 0;
24183 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):$this->w);
24184 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):$this->h);
24185 // draw clipping rect
24186 $this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
24187 // parse viewbox, calculate extra transformation matrix
24188 if (isset($attribs['viewBox'])) {
24189 $tmp = array();
24190 preg_match_all("/[0-9]+/", $attribs['viewBox'], $tmp);
24191 $tmp = $tmp[0];
24192 if (sizeof($tmp) == 4) {
24193 $vx = $tmp[0];
24194 $vy = $tmp[1];
24195 $vw = $tmp[2];
24196 $vh = $tmp[3];
24197 // get aspect ratio
24198 $tmp = array();
24199 $aspectX = 'xMid';
24200 $aspectY = 'YMid';
24201 $fit = 'meet';
24202 if (isset($attribs['preserveAspectRatio'])) {
24203 if($attribs['preserveAspectRatio'] == 'none') {
24204 $fit = 'none';
24205 } else {
24206 preg_match_all('/[a-zA-Z]+/', $attribs['preserveAspectRatio'], $tmp);
24207 $tmp = $tmp[0];
24208 if ((sizeof($tmp) == 2) AND (strlen($tmp[0]) == 8) AND (in_array($tmp[1], array('meet', 'slice', 'none')))) {
24209 $aspectX = substr($tmp[0], 0, 4);
24210 $aspectY = substr($tmp[0], 4, 4);
24211 $fit = $tmp[1];
24212 }
24213 }
24214 }
24215 $wr = ($svgW / $vw);
24216 $hr = ($svgH / $vh);
24217 $ax = $ay = 0;
24218 if ((($fit == 'meet') AND ($hr < $wr)) OR (($fit == 'slice') AND ($hr > $wr))) {
24219 if ($aspectX == 'xMax') {
24220 $ax = (($vw * ($wr / $hr)) - $vw);
24221 }
24222 if ($aspectX == 'xMid') {
24223 $ax = ((($vw * ($wr / $hr)) - $vw) / 2);
24224 }
24225 $wr = $hr;
24226 } elseif ((($fit == 'meet') AND ($hr > $wr)) OR (($fit == 'slice') AND ($hr < $wr))) {
24227 if ($aspectY == 'YMax') {
24228 $ay = (($vh * ($hr / $wr)) - $vh);
24229 }
24230 if ($aspectY == 'YMid') {
24231 $ay = ((($vh * ($hr / $wr)) - $vh) / 2);
24232 }
24233 $hr = $wr;
24234 }
24235 $newtm = array($wr, 0, 0, $hr, (($wr * ($ax - $vx)) - $svgX), (($hr * ($ay - $vy)) - $svgY));
24236 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, $newtm);
24237 $this->SVGTransform($tm);
24238 }
24239 }
24240 $this->setSVGStyles($svgstyle, $prev_svgstyle);
24241 break;
24242 }
24243 case 'g': {
24244 // group together related graphics elements
24245 array_push($this->svgstyles, $svgstyle);
24246 $this->StartTransform();
24247 $x = (isset($attribs['x'])?$attribs['x']:0);
24248 $y = (isset($attribs['y'])?$attribs['y']:0);
24249 $w = 1;//(isset($attribs['width'])?$attribs['width']:1);
24250 $h = 1;//(isset($attribs['height'])?$attribs['height']:1);
24251 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24252 $this->SVGTransform($tm);
24253 $this->setSVGStyles($svgstyle, $prev_svgstyle);
24254 break;
24255 }
24256 case 'linearGradient': {
24257 if ($this->pdfa_mode && $this->pdfa_version < 2) {
24258 break;
24259 }
24260 if (!isset($attribs['id'])) {
24261 $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24262 }
24263 $this->svggradientid = $attribs['id'];
24264 $this->svggradients[$this->svggradientid] = array();
24265 $this->svggradients[$this->svggradientid]['type'] = 2;
24266 $this->svggradients[$this->svggradientid]['stops'] = array();
24267 if (isset($attribs['gradientUnits'])) {
24268 $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24269 } else {
24270 $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24271 }
24272 //$attribs['spreadMethod']
24273 if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
24274 OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
24275 OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
24276 OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
24277 OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
24278 $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24279 } else {
24280 $this->svggradients[$this->svggradientid]['mode'] = 'measure';
24281 }
24282 $x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
24283 $y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
24284 $x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
24285 $y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
24286 if (isset($attribs['gradientTransform'])) {
24287 $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24288 }
24289 $this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
24290 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24291 // gradient is defined on another place
24292 $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24293 }
24294 break;
24295 }
24296 case 'radialGradient': {
24297 if ($this->pdfa_mode && $this->pdfa_version < 2) {
24298 break;
24299 }
24300 if (!isset($attribs['id'])) {
24301 $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24302 }
24303 $this->svggradientid = $attribs['id'];
24304 $this->svggradients[$this->svggradientid] = array();
24305 $this->svggradients[$this->svggradientid]['type'] = 3;
24306 $this->svggradients[$this->svggradientid]['stops'] = array();
24307 if (isset($attribs['gradientUnits'])) {
24308 $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24309 } else {
24310 $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24311 }
24312 //$attribs['spreadMethod']
24313 if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
24314 OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
24315 OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')))) {
24316 $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24317 } elseif (isset($attribs['r']) AND is_numeric($attribs['r']) AND ($attribs['r']) <= 1) {
24318 $this->svggradients[$this->svggradientid]['mode'] = 'ratio';
24319 } else {
24320 $this->svggradients[$this->svggradientid]['mode'] = 'measure';
24321 }
24322 $cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
24323 $cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
24324 $fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
24325 $fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
24326 $r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
24327 if (isset($attribs['gradientTransform'])) {
24328 $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24329 }
24330 $this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
24331 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24332 // gradient is defined on another place
24333 $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24334 }
24335 break;
24336 }
24337 case 'stop': {
24338 // gradient stops
24339 if (substr($attribs['offset'], -1) == '%') {
24340 $offset = floatval(substr($attribs['offset'], 0, -1)) / 100;
24341 } else {
24342 $offset = floatval($attribs['offset']);
24343 if ($offset > 1) {
24344 $offset /= 100;
24345 }
24346 }
24347 $stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
24348 $opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
24349 $this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
24350 break;
24351 }
24352 // paths
24353 case 'path': {
24354 if ($invisible) {
24355 break;
24356 }
24357 if (isset($attribs['d'])) {
24358 $d = trim($attribs['d']);
24359 if (!empty($d)) {
24360 $x = (isset($attribs['x'])?$attribs['x']:0);
24361 $y = (isset($attribs['y'])?$attribs['y']:0);
24362 $w = (isset($attribs['width'])?$attribs['width']:1);
24363 $h = (isset($attribs['height'])?$attribs['height']:1);
24364 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24365 if ($clipping) {
24366 $this->SVGTransform($tm);
24367 $this->SVGPath($d, 'CNZ');
24368 } else {
24369 $this->StartTransform();
24370 $this->SVGTransform($tm);
24371 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
24372 if (!empty($obstyle)) {
24373 $this->SVGPath($d, $obstyle);
24374 }
24375 $this->StopTransform();
24376 }
24377 }
24378 }
24379 break;
24380 }
24381 // shapes
24382 case 'rect': {
24383 if ($invisible) {
24384 break;
24385 }
24386 $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24387 $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24388 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24389 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24390 $rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
24391 $ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
24392 if ($clipping) {
24393 $this->SVGTransform($tm);
24394 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
24395 } else {
24396 $this->StartTransform();
24397 $this->SVGTransform($tm);
24398 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
24399 if (!empty($obstyle)) {
24400 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
24401 }
24402 $this->StopTransform();
24403 }
24404 break;
24405 }
24406 case 'circle': {
24407 if ($invisible) {
24408 break;
24409 }
24410 $r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
24411 $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24412 $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24413 $x = ($cx - $r);
24414 $y = ($cy - $r);
24415 $w = (2 * $r);
24416 $h = $w;
24417 if ($clipping) {
24418 $this->SVGTransform($tm);
24419 $this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
24420 } else {
24421 $this->StartTransform();
24422 $this->SVGTransform($tm);
24423 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
24424 if (!empty($obstyle)) {
24425 $this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
24426 }
24427 $this->StopTransform();
24428 }
24429
24430 break;
24431 }
24432 case 'ellipse': {
24433 if ($invisible) {
24434 break;
24435 }
24436 $rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
24437 $ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
24438 $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24439 $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24440 $x = ($cx - $rx);
24441 $y = ($cy - $ry);
24442 $w = (2 * $rx);
24443 $h = (2 * $ry);
24444 if ($clipping) {
24445 $this->SVGTransform($tm);
24446 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
24447 } else {
24448 $this->StartTransform();
24449 $this->SVGTransform($tm);
24450 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
24451 if (!empty($obstyle)) {
24452 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
24453 }
24454 $this->StopTransform();
24455 }
24456 break;
24457 }
24458 case 'line': {
24459 if ($invisible) {
24460 break;
24461 }
24462 $x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
24463 $y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
24464 $x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
24465 $y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
24466 $x = $x1;
24467 $y = $y1;
24468 $w = abs($x2 - $x1);
24469 $h = abs($y2 - $y1);
24470 if (!$clipping) {
24471 $this->StartTransform();
24472 $this->SVGTransform($tm);
24473 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
24474 $this->Line($x1, $y1, $x2, $y2);
24475 $this->StopTransform();
24476 }
24477 break;
24478 }
24479 case 'polyline':
24480 case 'polygon': {
24481 if ($invisible) {
24482 break;
24483 }
24484 $points = (isset($attribs['points'])?$attribs['points']:'0 0');
24485 $points = trim($points);
24486 // note that point may use a complex syntax not covered here
24487 $points = preg_split('/[\,\s]+/si', $points);
24488 if (count($points) < 4) {
24489 break;
24490 }
24491 $p = array();
24492 $xmin = 2147483647;
24493 $xmax = 0;
24494 $ymin = 2147483647;
24495 $ymax = 0;
24496 foreach ($points as $key => $val) {
24497 $p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24498 if (($key % 2) == 0) {
24499 // X coordinate
24500 $xmin = min($xmin, $p[$key]);
24501 $xmax = max($xmax, $p[$key]);
24502 } else {
24503 // Y coordinate
24504 $ymin = min($ymin, $p[$key]);
24505 $ymax = max($ymax, $p[$key]);
24506 }
24507 }
24508 $x = $xmin;
24509 $y = $ymin;
24510 $w = ($xmax - $xmin);
24511 $h = ($ymax - $ymin);
24512 if ($name == 'polyline') {
24513 $this->StartTransform();
24514 $this->SVGTransform($tm);
24515 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
24516 if (!empty($obstyle)) {
24517 $this->PolyLine($p, $obstyle, array(), array());
24518 }
24519 $this->StopTransform();
24520 } else { // polygon
24521 if ($clipping) {
24522 $this->SVGTransform($tm);
24523 $this->Polygon($p, 'CNZ', array(), array(), true);
24524 } else {
24525 $this->StartTransform();
24526 $this->SVGTransform($tm);
24527 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
24528 if (!empty($obstyle)) {
24529 $this->Polygon($p, $obstyle, array(), array(), true);
24530 }
24531 $this->StopTransform();
24532 }
24533 }
24534 break;
24535 }
24536 // image
24537 case 'image': {
24538 if ($invisible) {
24539 break;
24540 }
24541 if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
24542 break;
24543 }
24544 $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24545 $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24546 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24547 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24548 $img = $attribs['xlink:href'];
24549 if (!$clipping) {
24550 $this->StartTransform();
24551 $this->SVGTransform($tm);
24552 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
24553 if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
24554 // embedded image encoded as base64
24555 $img = '@'.base64_decode(substr($img, strlen($m[0])));
24556 } else {
24557 // fix image path
24558 if ($this->isRelativePath($img) || $this->hasExtForbiddenProtocol($img)) {
24559 break;
24560 }
24561 if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
24562 // replace relative path with full server path
24563 $img = $this->svgdir.'/'.$img;
24564 }
24565 if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
24566 $findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
24567 if (($findroot === false) OR ($findroot > 1)) {
24568 if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
24569 $img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
24570 } else {
24571 $img = $_SERVER['DOCUMENT_ROOT'].$img;
24572 }
24573 }
24574 }
24575 $img = urldecode($img);
24576 $testscrtype = @parse_url($img);
24577 if (empty($testscrtype['query'])) {
24578 // convert URL to server path
24579 $img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
24580 } elseif (preg_match('|^https?://|', $img) !== 1) {
24581 // convert server path to URL
24582 $img = str_replace(K_PATH_MAIN, K_PATH_URL, $img);
24583 }
24584 }
24585 // get image type
24586 $imgtype = TCPDF_IMAGES::getImageFileType($img);
24587 if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
24588 $this->ImageEps($img, $x, $y, $w, $h);
24589 } elseif ($imgtype == 'svg') {
24590 // store SVG vars
24591 $svggradients = $this->svggradients;
24592 $svggradientid = $this->svggradientid;
24593 $svgdefsmode = $this->svgdefsmode;
24594 $svgdefs = $this->svgdefs;
24595 $svgclipmode = $this->svgclipmode;
24596 $svgclippaths = $this->svgclippaths;
24597 $svgcliptm = $this->svgcliptm;
24598 $svgclipid = $this->svgclipid;
24599 $svgtext = $this->svgtext;
24600 $svgtextmode = $this->svgtextmode;
24601 $this->ImageSVG($img, $x, $y, $w, $h);
24602 // restore SVG vars
24603 $this->svggradients = $svggradients;
24604 $this->svggradientid = $svggradientid;
24605 $this->svgdefsmode = $svgdefsmode;
24606 $this->svgdefs = $svgdefs;
24607 $this->svgclipmode = $svgclipmode;
24608 $this->svgclippaths = $svgclippaths;
24609 $this->svgcliptm = $svgcliptm;
24610 $this->svgclipid = $svgclipid;
24611 $this->svgtext = $svgtext;
24612 $this->svgtextmode = $svgtextmode;
24613 } else {
24614 $this->Image($img, $x, $y, $w, $h);
24615 }
24616 $this->StopTransform();
24617 }
24618 break;
24619 }
24620 // text
24621 case 'text':
24622 case 'tspan': {
24623 if (isset($this->svgtextmode['text-anchor']) AND !empty($this->svgtext)) {
24624 // @TODO: unsupported feature
24625 }
24626 // only basic support - advanced features must be implemented
24627 $this->svgtextmode['invisible'] = $invisible;
24628 if ($invisible) {
24629 break;
24630 }
24631 array_push($this->svgstyles, $svgstyle);
24632 if (isset($attribs['x'])) {
24633 $x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
24634 } elseif ($name == 'tspan') {
24635 $x = $this->x;
24636 } else {
24637 $x = 0;
24638 }
24639 if (isset($attribs['dx'])) {
24640 $x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
24641 }
24642 if (isset($attribs['y'])) {
24643 $y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
24644 } elseif ($name == 'tspan') {
24645 $y = $this->y;
24646 } else {
24647 $y = 0;
24648 }
24649 if (isset($attribs['dy'])) {
24650 $y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
24651 }
24652 $svgstyle['text-color'] = $svgstyle['fill'];
24653 $this->svgtext = '';
24654 if (isset($svgstyle['text-anchor'])) {
24655 $this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
24656 } else {
24657 $this->svgtextmode['text-anchor'] = 'start';
24658 }
24659 if (isset($svgstyle['direction'])) {
24660 if ($svgstyle['direction'] == 'rtl') {
24661 $this->svgtextmode['rtl'] = true;
24662 } else {
24663 $this->svgtextmode['rtl'] = false;
24664 }
24665 } else {
24666 $this->svgtextmode['rtl'] = false;
24667 }
24668 if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
24669 $this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
24670 } else {
24671 $this->svgtextmode['stroke'] = false;
24672 }
24673 $this->StartTransform();
24674 $this->SVGTransform($tm);
24675 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
24676 $this->x = $x;
24677 $this->y = $y;
24678 break;
24679 }
24680 // use
24681 case 'use': {
24682 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24683 $svgdefid = substr($attribs['xlink:href'], 1);
24684 if (isset($this->svgdefs[$svgdefid])) {
24685 $use = $this->svgdefs[$svgdefid];
24686 if (isset($attribs['xlink:href'])) {
24687 unset($attribs['xlink:href']);
24688 }
24689 if (isset($attribs['id'])) {
24690 unset($attribs['id']);
24691 }
24692 if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
24693 $attribs['x'] += $use['attribs']['x'];
24694 }
24695 if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
24696 $attribs['y'] += $use['attribs']['y'];
24697 }
24698 if (empty($attribs['style'])) {
24699 $attribs['style'] = '';
24700 }
24701 if (!empty($use['attribs']['style'])) {
24702 // merge styles
24703 $attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
24704 }
24705 $attribs = array_merge($use['attribs'], $attribs);
24706 $this->startSVGElementHandler($parser, $use['name'], $attribs);
24707 return;
24708 }
24709 }
24710 break;
24711 }
24712 default: {
24713 break;
24714 }
24715 } // end of switch
24716 // process child elements
24717 if (!empty($attribs['child_elements'])) {
24718 $child_elements = $attribs['child_elements'];
24719 unset($attribs['child_elements']);
24720 foreach($child_elements as $child_element) {
24721 if (empty($child_element['attribs']['closing_tag'])) {
24722 $this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
24723 } else {
24724 if (isset($child_element['attribs']['content'])) {
24725 $this->svgtext = $child_element['attribs']['content'];
24726 }
24727 $this->endSVGElementHandler('child-tag', $child_element['name']);
24728 }
24729 }
24730 }
24731 }
24732
24733 /**
24734 * Sets the closing SVG element handler function for the XML parser.
24735 * @param resource|string $parser The first parameter, parser, is a reference to the XML parser calling the handler.
24736 * @param string $name The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
24737 * @author Nicola Asuni
24738 * @since 5.0.000 (2010-05-02)
24739 * @protected
24740 */
24741 protected function endSVGElementHandler($parser, $name) {
24742 $name = $this->removeTagNamespace($name);
24743 if ($this->svgdefsmode AND !in_array($name, array('defs', 'clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
24744 if (end($this->svgdefs) !== FALSE) {
24745 $last_svgdefs_id = key($this->svgdefs);
24746 if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
24747 foreach($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'] as $child_element) {
24748 if (isset($child_element['attribs']['id']) AND ($child_element['name'] == $name)) {
24749 $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$child_element['attribs']['id'].'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24750 return;
24751 }
24752 }
24753 if ($this->svgdefs[$last_svgdefs_id]['name'] == $name) {
24754 $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$last_svgdefs_id.'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24755 return;
24756 }
24757 }
24758 }
24759 return;
24760 }
24761 switch($name) {
24762 case 'defs': {
24763 $this->svgdefsmode = false;
24764 break;
24765 }
24766 // clipPath
24767 case 'clipPath': {
24768 $this->svgclipmode = false;
24769 break;
24770 }
24771 case 'svg': {
24772 if (--$this->svg_tag_depth <= 0) {
24773 break;
24774 }
24775 }
24776 case 'g': {
24777 // ungroup: remove last style from array
24778 array_pop($this->svgstyles);
24779 $this->StopTransform();
24780 break;
24781 }
24782 case 'text':
24783 case 'tspan': {
24784 if ($this->svgtextmode['invisible']) {
24785 // This implementation must be fixed to following the rule:
24786 // 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.
24787 break;
24788 }
24789 // print text
24790 $text = $this->svgtext;
24791 //$text = $this->stringTrim($text);
24792 $textlen = $this->GetStringWidth($text);
24793 if ($this->svgtextmode['text-anchor'] != 'start') {
24794 // check if string is RTL text
24795 if ($this->svgtextmode['text-anchor'] == 'end') {
24796 if ($this->svgtextmode['rtl']) {
24797 $this->x += $textlen;
24798 } else {
24799 $this->x -= $textlen;
24800 }
24801 } elseif ($this->svgtextmode['text-anchor'] == 'middle') {
24802 if ($this->svgtextmode['rtl']) {
24803 $this->x += ($textlen / 2);
24804 } else {
24805 $this->x -= ($textlen / 2);
24806 }
24807 }
24808 }
24809 $textrendermode = $this->textrendermode;
24810 $textstrokewidth = $this->textstrokewidth;
24811 $this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
24812 if ($name == 'text') {
24813 // store current coordinates
24814 $tmpx = $this->x;
24815 $tmpy = $this->y;
24816 }
24817 // print the text
24818 $this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
24819 if ($name == 'text') {
24820 // restore coordinates
24821 $this->x = $tmpx;
24822 $this->y = $tmpy;
24823 }
24824 // restore previous rendering mode
24825 $this->textrendermode = $textrendermode;
24826 $this->textstrokewidth = $textstrokewidth;
24827 $this->svgtext = '';
24828 $this->StopTransform();
24829 if (!$this->svgdefsmode) {
24830 array_pop($this->svgstyles);
24831 }
24832 break;
24833 }
24834 default: {
24835 break;
24836 }
24837 }
24838 }
24839
24840 /**
24841 * Sets the character data handler function for the XML parser.
24842 * @param resource $parser The first parameter, parser, is a reference to the XML parser calling the handler.
24843 * @param string $data The second parameter, data, contains the character data as a string.
24844 * @author Nicola Asuni
24845 * @since 5.0.000 (2010-05-02)
24846 * @protected
24847 */
24848 protected function segSVGContentHandler($parser, $data) {
24849 $this->svgtext .= $data;
24850 }
24851
24852 // --- END SVG METHODS -----------------------------------------------------
24853
24854 /**
24855 * Keeps files in memory, so it doesn't need to downloaded everytime in a loop
24856 * @param string $file
24857 * @return string
24858 */
24859 protected function getCachedFileContents($file)
24860 {
24861 if (!isset($this->fileContentCache[$file])) {
24862 $this->fileContentCache[$file] = TCPDF_STATIC::fileGetContents($file);
24863 }
24864 return $this->fileContentCache[$file];
24865 }
24866
24867 /**
24868 * Avoid multiple calls to an external server to see if a file exists
24869 * @param string $file
24870 * @return bool
24871 */
24872 protected function fileExists($file)
24873 {
24874 if (isset($this->fileContentCache[$file]) || false !== $this->getImageBuffer($file)) {
24875 return true;
24876 }
24877
24878 return TCPDF_STATIC::file_exists($file);
24879 }
24880
24881 /**
24882 * Wrapper for unlink with disabled protocols.
24883 * @param string $file
24884 * @return bool
24885 */
24886 protected function _unlink($file)
24887 {
24888 if ((strpos($file, '://') !== false) && ((substr($file, 0, 7) !== 'file://') || (!$this->allowLocalFiles))) {
24889 // forbidden protocol
24890 return false;
24891 }
24892 return @unlink($file);
24893 }
24894
24895 } // END OF TCPDF CLASS
24896
24897 //============================================================+
24898 // END OF FILE
24899 //============================================================+
24900