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

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

29,152 lines 1.0 MB
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 : 5.9.153
5 // Begin : 2002-08-03
6 // Last Update : 2012-03-28
7 // Author : Nicola Asuni - Tecnick.com LTD - Manor Coach House, Church Hill, Aldershot, Hants, GU12 4RQ, UK - www.tecnick.com - info@tecnick.com
8 // License : http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT GNU-LGPLv3
9 // -------------------------------------------------------------------
10 // Copyright (C) 2002-2012 Nicola Asuni - Tecnick.com LTD
11 //
12 // This file is part of TCPDF software library.
13 //
14 // TCPDF is free software: you can redistribute it and/or modify it
15 // under the terms of the GNU Lesser General Public License as
16 // published by the Free Software Foundation, either version 3 of the
17 // License, or (at your option) any later version.
18 //
19 // TCPDF is distributed in the hope that it will be useful, but
20 // WITHOUT ANY WARRANTY; without even the implied warranty of
21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22 // See the GNU Lesser General Public License for more details.
23 //
24 // You should have received a copy of the License
25 // along with TCPDF. If not, see
26 // <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
27 //
28 // See LICENSE.TXT file for more information.
29 // -------------------------------------------------------------------
30 //
31 // Description :
32 // This is a PHP class for generating PDF documents without requiring external extensions.
33 //
34 // NOTE:
35 // This class was originally derived in 2002 from the Public
36 // Domain FPDF class by Olivier Plathey (http://www.fpdf.org),
37 // but now is almost entirely rewritten and contains thousands of
38 // new lines of code and hundreds new features.
39 //
40 // Main features:
41 // * no external libraries are required for the basic functions;
42 // * all standard page formats, custom page formats, custom margins and units of measure;
43 // * UTF-8 Unicode and Right-To-Left languages;
44 // * TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;
45 // * font subsetting;
46 // * methods to publish some XHTML + CSS code, Javascript and Forms;
47 // * images, graphic (geometric figures) and transformation methods;
48 // * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)
49 // * 1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extention, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;
50 // * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;
51 // * automatic page header and footer management;
52 // * document encryption up to 256 bit and digital signature certifications;
53 // * transactions to UNDO commands;
54 // * PDF annotations, including links, text and file attachments;
55 // * text rendering modes (fill, stroke and clipping);
56 // * multiple columns mode;
57 // * no-write page regions;
58 // * bookmarks, named destinations and table of content;
59 // * text hyphenation;
60 // * text stretching and spacing (tracking/kerning);
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 // THANKS TO:
71 //
72 // Olivier Plathey (http://www.fpdf.org) for original FPDF.
73 // Efthimios Mavrogeorgiadis (emavro@yahoo.com) for suggestions on RTL language support.
74 // Klemen Vodopivec (http://www.fpdf.de/downloads/addons/37/) for Encryption algorithm.
75 // Warren Sherliker (wsherliker@gmail.com) for better image handling.
76 // dullus for text Justification.
77 // Bob Vincent (pillarsdotnet@users.sourceforge.net) for <li> value attribute.
78 // Patrick Benny for text stretch suggestion on Cell().
79 // Johannes G�ntert for JavaScript support.
80 // Denis Van Nuffelen for Dynamic Form.
81 // Jacek Czekaj for multibyte justification
82 // Anthony Ferrara for the reintroduction of legacy image methods.
83 // Sourceforge user 1707880 (hucste) for line-trough mode.
84 // Larry Stanbery for page groups.
85 // Martin Hall-May for transparency.
86 // Aaron C. Spike for Polycurve method.
87 // Mohamad Ali Golkar, Saleh AlMatrafe, Charles Abbott for Arabic and Persian support.
88 // Moritz Wagner and Andreas Wurmser for graphic functions.
89 // Andrew Whitehead for core fonts support.
90 // Esteban Jo�l Mar�n for OpenType font conversion.
91 // Teus Hagen for several suggestions and fixes.
92 // Yukihiro Nakadaira for CID-0 CJK fonts fixes.
93 // Kosmas Papachristos for some CSS improvements.
94 // Marcel Partap for some fixes.
95 // Won Kyu Park for several suggestions, fixes and patches.
96 // Dominik Dzienia for QR-code support.
97 // Laurent Minguet for some suggestions.
98 // Christian Deligant for some suggestions and fixes.
99 // Travis Harris for crop mark suggestion.
100 // Anyone that has reported a bug or sent a suggestion.
101 //============================================================+
102
103 /**
104 * @file
105 * This is a PHP class for generating PDF documents without requiring external extensions.<br>
106 * 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>
107 * <h3>TCPDF main features are:</h3>
108 * <ul>
109 * <li>no external libraries are required for the basic functions;</li>
110 * <li>all standard page formats, custom page formats, custom margins and units of measure;</li>
111 * <li>UTF-8 Unicode and Right-To-Left languages;</li>
112 * <li>TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;</li>
113 * <li>font subsetting;</li>
114 * <li>methods to publish some XHTML + CSS code, Javascript and Forms;</li>
115 * <li>images, graphic (geometric figures) and transformation methods;
116 * <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)</li>
117 * <li>1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extention, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;</li>
118 * <li>JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;</li>
119 * <li>automatic page header and footer management;</li>
120 * <li>document encryption up to 256 bit and digital signature certifications;</li>
121 * <li>transactions to UNDO commands;</li>
122 * <li>PDF annotations, including links, text and file attachments;</li>
123 * <li>text rendering modes (fill, stroke and clipping);</li>
124 * <li>multiple columns mode;</li>
125 * <li>no-write page regions;</li>
126 * <li>bookmarks, named destinations and table of content;</li>
127 * <li>text hyphenation;</li>
128 * <li>text stretching and spacing (tracking/kerning);</li>
129 * <li>automatic page break, line break and text alignments including justification;</li>
130 * <li>automatic page numbering and page groups;</li>
131 * <li>move and delete pages;</li>
132 * <li>page compression (requires php-zlib extension);</li>
133 * <li>XOBject Templates;</li>
134 * <li>Layers and object visibility;</li>
135 * <li>PDF/A-1b support.</li>
136 * </ul>
137 * Tools to encode your unicode fonts are on fonts/utils directory.</p>
138 * @package com.tecnick.tcpdf
139 * @author Nicola Asuni
140 * @version 5.9.153
141 */
142
143 // Main configuration file. Define the K_TCPDF_EXTERNAL_CONFIG constant to skip this file.
144 require_once(dirname(__FILE__).'/config/tcpdf_config.php');
145
146 /**
147 * @class TCPDF
148 * PHP class for generating PDF documents without requiring external extensions.
149 * 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>
150 * @package com.tecnick.tcpdf
151 * @brief PHP class for generating PDF documents without requiring external extensions.
152 * @version 5.9.153
153 * @author Nicola Asuni - info@tecnick.com
154 */
155 class TCPDF {
156
157 // private properties
158
159 /**
160 * Current TCPDF version.
161 * @private
162 */
163 private $tcpdf_version = '5.9.153';
164
165 // Protected properties
166
167 /**
168 * Current page number.
169 * @protected
170 */
171 protected $page;
172
173 /**
174 * Current object number.
175 * @protected
176 */
177 protected $n;
178
179 /**
180 * Array of object offsets.
181 * @protected
182 */
183 protected $offsets;
184
185 /**
186 * Buffer holding in-memory PDF.
187 * @protected
188 */
189 protected $buffer;
190
191 /**
192 * Array containing pages.
193 * @protected
194 */
195 protected $pages = array();
196
197 /**
198 * Current document state.
199 * @protected
200 */
201 protected $state;
202
203 /**
204 * Compression flag.
205 * @protected
206 */
207 protected $compress;
208
209 /**
210 * Current page orientation (P = Portrait, L = Landscape).
211 * @protected
212 */
213 protected $CurOrientation;
214
215 /**
216 * Page dimensions.
217 * @protected
218 */
219 protected $pagedim = array();
220
221 /**
222 * Scale factor (number of points in user unit).
223 * @protected
224 */
225 protected $k;
226
227 /**
228 * Width of page format in points.
229 * @protected
230 */
231 protected $fwPt;
232
233 /**
234 * Height of page format in points.
235 * @protected
236 */
237 protected $fhPt;
238
239 /**
240 * Current width of page in points.
241 * @protected
242 */
243 protected $wPt;
244
245 /**
246 * Current height of page in points.
247 * @protected
248 */
249 protected $hPt;
250
251 /**
252 * Current width of page in user unit.
253 * @protected
254 */
255 protected $w;
256
257 /**
258 * Current height of page in user unit.
259 * @protected
260 */
261 protected $h;
262
263 /**
264 * Left margin.
265 * @protected
266 */
267 protected $lMargin;
268
269 /**
270 * Top margin.
271 * @protected
272 */
273 protected $tMargin;
274
275 /**
276 * Right margin.
277 * @protected
278 */
279 protected $rMargin;
280
281 /**
282 * Page break margin.
283 * @protected
284 */
285 protected $bMargin;
286
287 /**
288 * Array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
289 * @since 5.9.000 (2010-10-03)
290 * @protected
291 */
292 protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
293
294 /**
295 * Array of cell margins ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
296 * @since 5.9.000 (2010-10-04)
297 * @protected
298 */
299 protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
300
301 /**
302 * Current horizontal position in user unit for cell positioning.
303 * @protected
304 */
305 protected $x;
306
307 /**
308 * Current vertical position in user unit for cell positioning.
309 * @protected
310 */
311 protected $y;
312
313 /**
314 * Height of last cell printed.
315 * @protected
316 */
317 protected $lasth;
318
319 /**
320 * Line width in user unit.
321 * @protected
322 */
323 protected $LineWidth;
324
325 /**
326 * Array of standard font names.
327 * @protected
328 */
329 protected $CoreFonts;
330
331 /**
332 * Array of used fonts.
333 * @protected
334 */
335 protected $fonts = array();
336
337 /**
338 * Array of font files.
339 * @protected
340 */
341 protected $FontFiles = array();
342
343 /**
344 * Array of encoding differences.
345 * @protected
346 */
347 protected $diffs = array();
348
349 /**
350 * Array of used images.
351 * @protected
352 */
353 protected $images = array();
354
355 /**
356 * Array of Annotations in pages.
357 * @protected
358 */
359 protected $PageAnnots = array();
360
361 /**
362 * Array of internal links.
363 * @protected
364 */
365 protected $links = array();
366
367 /**
368 * Current font family.
369 * @protected
370 */
371 protected $FontFamily;
372
373 /**
374 * Current font style.
375 * @protected
376 */
377 protected $FontStyle;
378
379 /**
380 * Current font ascent (distance between font top and baseline).
381 * @protected
382 * @since 2.8.000 (2007-03-29)
383 */
384 protected $FontAscent;
385
386 /**
387 * Current font descent (distance between font bottom and baseline).
388 * @protected
389 * @since 2.8.000 (2007-03-29)
390 */
391 protected $FontDescent;
392
393 /**
394 * Underlining flag.
395 * @protected
396 */
397 protected $underline;
398
399 /**
400 * Overlining flag.
401 * @protected
402 */
403 protected $overline;
404
405 /**
406 * Current font info.
407 * @protected
408 */
409 protected $CurrentFont;
410
411 /**
412 * Current font size in points.
413 * @protected
414 */
415 protected $FontSizePt;
416
417 /**
418 * Current font size in user unit.
419 * @protected
420 */
421 protected $FontSize;
422
423 /**
424 * Commands for drawing color.
425 * @protected
426 */
427 protected $DrawColor;
428
429 /**
430 * Commands for filling color.
431 * @protected
432 */
433 protected $FillColor;
434
435 /**
436 * Commands for text color.
437 * @protected
438 */
439 protected $TextColor;
440
441 /**
442 * Indicates whether fill and text colors are different.
443 * @protected
444 */
445 protected $ColorFlag;
446
447 /**
448 * Automatic page breaking.
449 * @protected
450 */
451 protected $AutoPageBreak;
452
453 /**
454 * Threshold used to trigger page breaks.
455 * @protected
456 */
457 protected $PageBreakTrigger;
458
459 /**
460 * Flag set when processing page header.
461 * @protected
462 */
463 protected $InHeader = false;
464
465 /**
466 * Flag set when processing page footer.
467 * @protected
468 */
469 protected $InFooter = false;
470
471 /**
472 * Zoom display mode.
473 * @protected
474 */
475 protected $ZoomMode;
476
477 /**
478 * Layout display mode.
479 * @protected
480 */
481 protected $LayoutMode;
482
483 /**
484 * If true set the document information dictionary in Unicode.
485 * @protected
486 */
487 protected $docinfounicode = true;
488
489 /**
490 * Document title.
491 * @protected
492 */
493 protected $title = '';
494
495 /**
496 * Document subject.
497 * @protected
498 */
499 protected $subject = '';
500
501 /**
502 * Document author.
503 * @protected
504 */
505 protected $author = '';
506
507 /**
508 * Document keywords.
509 * @protected
510 */
511 protected $keywords = '';
512
513 /**
514 * Document creator.
515 * @protected
516 */
517 protected $creator = '';
518
519 /**
520 * Starting page number.
521 * @protected
522 */
523 protected $starting_page_number = 1;
524
525 /**
526 * String alias for total number of pages.
527 * @protected
528 */
529 protected $alias_tot_pages = '{:ptp:}';
530
531 /**
532 * String alias for page number.
533 * @protected
534 */
535 protected $alias_num_page = '{:pnp:}';
536
537 /**
538 * String alias for total number of pages in a single group.
539 * @protected
540 */
541 protected $alias_group_tot_pages = '{:ptg:}';
542
543 /**
544 * String alias for group page number.
545 * @protected
546 */
547 protected $alias_group_num_page = '{:png:}';
548
549 /**
550 * String alias for right shift compensation used to correctly align page numbers on the right.
551 * @protected
552 */
553 protected $alias_right_shift = '{rsc:';
554
555 /**
556 * The right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image.
557 * @since 2002-07-31
558 * @author Nicola Asuni
559 * @protected
560 */
561 protected $img_rb_x;
562
563 /**
564 * The right-bottom corner Y coordinate of last inserted image.
565 * @since 2002-07-31
566 * @author Nicola Asuni
567 * @protected
568 */
569 protected $img_rb_y;
570
571 /**
572 * Adjusting factor to convert pixels to user units.
573 * @since 2004-06-14
574 * @author Nicola Asuni
575 * @protected
576 */
577 protected $imgscale = 1;
578
579 /**
580 * Boolean flag set to true when the input text is unicode (require unicode fonts).
581 * @since 2005-01-02
582 * @author Nicola Asuni
583 * @protected
584 */
585 protected $isunicode = false;
586
587 /**
588 * Object containing unicode data.
589 * @since 5.9.004 (2010-10-18)
590 * @author Nicola Asuni
591 * @protected
592 */
593 protected $unicode;
594
595 /**
596 * Object containing font encoding maps.
597 * @since 5.9.123 (2011-10-01)
598 * @author Nicola Asuni
599 * @protected
600 */
601 protected $encmaps;
602
603 /**
604 * PDF version.
605 * @since 1.5.3
606 * @protected
607 */
608 protected $PDFVersion = '1.7';
609
610 /**
611 * ID of the stored default header template (-1 = not set).
612 * @protected
613 */
614 protected $header_xobjid = -1;
615
616 /**
617 * If true reset the Header Xobject template at each page
618 * @protected
619 */
620 protected $header_xobj_autoreset = false;
621
622 /**
623 * Minimum distance between header and top page margin.
624 * @protected
625 */
626 protected $header_margin;
627
628 /**
629 * Minimum distance between footer and bottom page margin.
630 * @protected
631 */
632 protected $footer_margin;
633
634 /**
635 * Original left margin value.
636 * @protected
637 * @since 1.53.0.TC013
638 */
639 protected $original_lMargin;
640
641 /**
642 * Original right margin value.
643 * @protected
644 * @since 1.53.0.TC013
645 */
646 protected $original_rMargin;
647
648 /**
649 * Default font used on page header.
650 * @protected
651 */
652 protected $header_font;
653
654 /**
655 * Default font used on page footer.
656 * @protected
657 */
658 protected $footer_font;
659
660 /**
661 * Language templates.
662 * @protected
663 */
664 protected $l;
665
666 /**
667 * Barcode to print on page footer (only if set).
668 * @protected
669 */
670 protected $barcode = false;
671
672 /**
673 * Boolean flag to print/hide page header.
674 * @protected
675 */
676 protected $print_header = true;
677
678 /**
679 * Boolean flag to print/hide page footer.
680 * @protected
681 */
682 protected $print_footer = true;
683
684 /**
685 * Header image logo.
686 * @protected
687 */
688 protected $header_logo = '';
689
690 /**
691 * Width of header image logo in user units.
692 * @protected
693 */
694 protected $header_logo_width = 30;
695
696 /**
697 * Title to be printed on default page header.
698 * @protected
699 */
700 protected $header_title = '';
701
702 /**
703 * String to pring on page header after title.
704 * @protected
705 */
706 protected $header_string = '';
707
708 /**
709 * Default number of columns for html table.
710 * @protected
711 */
712 protected $default_table_columns = 4;
713
714 // variables for html parser
715
716 /**
717 * HTML PARSER: array to store current link and rendering styles.
718 * @protected
719 */
720 protected $HREF = array();
721
722 /**
723 * List of available fonts on filesystem.
724 * @protected
725 */
726 protected $fontlist = array();
727
728 /**
729 * Current foreground color.
730 * @protected
731 */
732 protected $fgcolor;
733
734 /**
735 * HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
736 * @protected
737 */
738 protected $listordered = array();
739
740 /**
741 * HTML PARSER: array count list items on nested lists.
742 * @protected
743 */
744 protected $listcount = array();
745
746 /**
747 * HTML PARSER: current list nesting level.
748 * @protected
749 */
750 protected $listnum = 0;
751
752 /**
753 * HTML PARSER: indent amount for lists.
754 * @protected
755 */
756 protected $listindent = 0;
757
758 /**
759 * HTML PARSER: current list indententation level.
760 * @protected
761 */
762 protected $listindentlevel = 0;
763
764 /**
765 * Current background color.
766 * @protected
767 */
768 protected $bgcolor;
769
770 /**
771 * Temporary font size in points.
772 * @protected
773 */
774 protected $tempfontsize = 10;
775
776 /**
777 * Spacer string for LI tags.
778 * @protected
779 */
780 protected $lispacer = '';
781
782 /**
783 * Default encoding.
784 * @protected
785 * @since 1.53.0.TC010
786 */
787 protected $encoding = 'UTF-8';
788
789 /**
790 * PHP internal encoding.
791 * @protected
792 * @since 1.53.0.TC016
793 */
794 protected $internal_encoding;
795
796 /**
797 * Boolean flag to indicate if the document language is Right-To-Left.
798 * @protected
799 * @since 2.0.000
800 */
801 protected $rtl = false;
802
803 /**
804 * Boolean flag used to force RTL or LTR string direction.
805 * @protected
806 * @since 2.0.000
807 */
808 protected $tmprtl = false;
809
810 // --- Variables used for document encryption:
811
812 /**
813 * IBoolean flag indicating whether document is protected.
814 * @protected
815 * @since 2.0.000 (2008-01-02)
816 */
817 protected $encrypted;
818
819 /**
820 * Array containing encryption settings.
821 * @protected
822 * @since 5.0.005 (2010-05-11)
823 */
824 protected $encryptdata = array();
825
826 /**
827 * Last RC4 key encrypted (cached for optimisation).
828 * @protected
829 * @since 2.0.000 (2008-01-02)
830 */
831 protected $last_enc_key;
832
833 /**
834 * Last RC4 computed key.
835 * @protected
836 * @since 2.0.000 (2008-01-02)
837 */
838 protected $last_enc_key_c;
839
840 /**
841 * Encryption padding string.
842 * @protected
843 */
844 protected $enc_padding = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A";
845
846 /**
847 * File ID (used on document trailer).
848 * @protected
849 * @since 5.0.005 (2010-05-12)
850 */
851 protected $file_id;
852
853 // --- bookmark ---
854
855 /**
856 * Outlines for bookmark.
857 * @protected
858 * @since 2.1.002 (2008-02-12)
859 */
860 protected $outlines = array();
861
862 /**
863 * Outline root for bookmark.
864 * @protected
865 * @since 2.1.002 (2008-02-12)
866 */
867 protected $OutlineRoot;
868
869 // --- javascript and form ---
870
871 /**
872 * Javascript code.
873 * @protected
874 * @since 2.1.002 (2008-02-12)
875 */
876 protected $javascript = '';
877
878 /**
879 * Javascript counter.
880 * @protected
881 * @since 2.1.002 (2008-02-12)
882 */
883 protected $n_js;
884
885 /**
886 * line trough state
887 * @protected
888 * @since 2.8.000 (2008-03-19)
889 */
890 protected $linethrough;
891
892 /**
893 * Array with additional document-wide usage rights for the document.
894 * @protected
895 * @since 5.8.014 (2010-08-23)
896 */
897 protected $ur = array();
898
899 /**
900 * DPI (Dot Per Inch) Document Resolution (do not change).
901 * @protected
902 * @since 3.0.000 (2008-03-27)
903 */
904 protected $dpi = 72;
905
906 /**
907 * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
908 * @protected
909 * @since 3.0.000 (2008-03-27)
910 */
911 protected $newpagegroup = array();
912
913 /**
914 * Array that contains the number of pages in each page group.
915 * @protected
916 * @since 3.0.000 (2008-03-27)
917 */
918 protected $pagegroups = array();
919
920 /**
921 * Current page group number.
922 * @protected
923 * @since 3.0.000 (2008-03-27)
924 */
925 protected $currpagegroup = 0;
926
927 /**
928 * Array of transparency objects and parameters.
929 * @protected
930 * @since 3.0.000 (2008-03-27)
931 */
932 protected $extgstates;
933
934 /**
935 * Set the default JPEG compression quality (1-100).
936 * @protected
937 * @since 3.0.000 (2008-03-27)
938 */
939 protected $jpeg_quality;
940
941 /**
942 * Default cell height ratio.
943 * @protected
944 * @since 3.0.014 (2008-05-23)
945 */
946 protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
947
948 /**
949 * PDF viewer preferences.
950 * @protected
951 * @since 3.1.000 (2008-06-09)
952 */
953 protected $viewer_preferences;
954
955 /**
956 * A name object specifying how the document should be displayed when opened.
957 * @protected
958 * @since 3.1.000 (2008-06-09)
959 */
960 protected $PageMode;
961
962 /**
963 * Array for storing gradient information.
964 * @protected
965 * @since 3.1.000 (2008-06-09)
966 */
967 protected $gradients = array();
968
969 /**
970 * Array used to store positions inside the pages buffer (keys are the page numbers).
971 * @protected
972 * @since 3.2.000 (2008-06-26)
973 */
974 protected $intmrk = array();
975
976 /**
977 * Array used to store positions inside the pages buffer (keys are the page numbers).
978 * @protected
979 * @since 5.7.000 (2010-08-03)
980 */
981 protected $bordermrk = array();
982
983 /**
984 * Array used to store page positions to track empty pages (keys are the page numbers).
985 * @protected
986 * @since 5.8.007 (2010-08-18)
987 */
988 protected $emptypagemrk = array();
989
990 /**
991 * Array used to store content positions inside the pages buffer (keys are the page numbers).
992 * @protected
993 * @since 4.6.021 (2009-07-20)
994 */
995 protected $cntmrk = array();
996
997 /**
998 * Array used to store footer positions of each page.
999 * @protected
1000 * @since 3.2.000 (2008-07-01)
1001 */
1002 protected $footerpos = array();
1003
1004 /**
1005 * Array used to store footer length of each page.
1006 * @protected
1007 * @since 4.0.014 (2008-07-29)
1008 */
1009 protected $footerlen = array();
1010
1011 /**
1012 * Boolean flag to indicate if a new line is created.
1013 * @protected
1014 * @since 3.2.000 (2008-07-01)
1015 */
1016 protected $newline = true;
1017
1018 /**
1019 * End position of the latest inserted line.
1020 * @protected
1021 * @since 3.2.000 (2008-07-01)
1022 */
1023 protected $endlinex = 0;
1024
1025 /**
1026 * PDF string for width value of the last line.
1027 * @protected
1028 * @since 4.0.006 (2008-07-16)
1029 */
1030 protected $linestyleWidth = '';
1031
1032 /**
1033 * PDF string for CAP value of the last line.
1034 * @protected
1035 * @since 4.0.006 (2008-07-16)
1036 */
1037 protected $linestyleCap = '0 J';
1038
1039 /**
1040 * PDF string for join value of the last line.
1041 * @protected
1042 * @since 4.0.006 (2008-07-16)
1043 */
1044 protected $linestyleJoin = '0 j';
1045
1046 /**
1047 * PDF string for dash value of the last line.
1048 * @protected
1049 * @since 4.0.006 (2008-07-16)
1050 */
1051 protected $linestyleDash = '[] 0 d';
1052
1053 /**
1054 * Boolean flag to indicate if marked-content sequence is open.
1055 * @protected
1056 * @since 4.0.013 (2008-07-28)
1057 */
1058 protected $openMarkedContent = false;
1059
1060 /**
1061 * Count the latest inserted vertical spaces on HTML.
1062 * @protected
1063 * @since 4.0.021 (2008-08-24)
1064 */
1065 protected $htmlvspace = 0;
1066
1067 /**
1068 * Array of Spot colors.
1069 * @protected
1070 * @since 4.0.024 (2008-09-12)
1071 */
1072 protected $spot_colors = array();
1073
1074 /**
1075 * Symbol used for HTML unordered list items.
1076 * @protected
1077 * @since 4.0.028 (2008-09-26)
1078 */
1079 protected $lisymbol = '';
1080
1081 /**
1082 * String used to mark the beginning and end of EPS image blocks.
1083 * @protected
1084 * @since 4.1.000 (2008-10-18)
1085 */
1086 protected $epsmarker = 'x#!#EPS#!#x';
1087
1088 /**
1089 * Array of transformation matrix.
1090 * @protected
1091 * @since 4.2.000 (2008-10-29)
1092 */
1093 protected $transfmatrix = array();
1094
1095 /**
1096 * Current key for transformation matrix.
1097 * @protected
1098 * @since 4.8.005 (2009-09-17)
1099 */
1100 protected $transfmatrix_key = 0;
1101
1102 /**
1103 * Booklet mode for double-sided pages.
1104 * @protected
1105 * @since 4.2.000 (2008-10-29)
1106 */
1107 protected $booklet = false;
1108
1109 /**
1110 * Epsilon value used for float calculations.
1111 * @protected
1112 * @since 4.2.000 (2008-10-29)
1113 */
1114 protected $feps = 0.005;
1115
1116 /**
1117 * Array used for custom vertical spaces for HTML tags.
1118 * @protected
1119 * @since 4.2.001 (2008-10-30)
1120 */
1121 protected $tagvspaces = array();
1122
1123 /**
1124 * HTML PARSER: custom indent amount for lists. Negative value means disabled.
1125 * @protected
1126 * @since 4.2.007 (2008-11-12)
1127 */
1128 protected $customlistindent = -1;
1129
1130 /**
1131 * Boolean flag to indicate if the border of the cell sides that cross the page should be removed.
1132 * @protected
1133 * @since 4.2.010 (2008-11-14)
1134 */
1135 protected $opencell = true;
1136
1137 /**
1138 * Array of files to embedd.
1139 * @protected
1140 * @since 4.4.000 (2008-12-07)
1141 */
1142 protected $embeddedfiles = array();
1143
1144 /**
1145 * Boolean flag to indicate if we are inside a PRE tag.
1146 * @protected
1147 * @since 4.4.001 (2008-12-08)
1148 */
1149 protected $premode = false;
1150
1151 /**
1152 * Array used to store positions of graphics transformation blocks inside the page buffer.
1153 * keys are the page numbers
1154 * @protected
1155 * @since 4.4.002 (2008-12-09)
1156 */
1157 protected $transfmrk = array();
1158
1159 /**
1160 * Default color for html links.
1161 * @protected
1162 * @since 4.4.003 (2008-12-09)
1163 */
1164 protected $htmlLinkColorArray = array(0, 0, 255);
1165
1166 /**
1167 * Default font style to add to html links.
1168 * @protected
1169 * @since 4.4.003 (2008-12-09)
1170 */
1171 protected $htmlLinkFontStyle = 'U';
1172
1173 /**
1174 * Counts the number of pages.
1175 * @protected
1176 * @since 4.5.000 (2008-12-31)
1177 */
1178 protected $numpages = 0;
1179
1180 /**
1181 * Array containing page lengths in bytes.
1182 * @protected
1183 * @since 4.5.000 (2008-12-31)
1184 */
1185 protected $pagelen = array();
1186
1187 /**
1188 * Counts the number of pages.
1189 * @protected
1190 * @since 4.5.000 (2008-12-31)
1191 */
1192 protected $numimages = 0;
1193
1194 /**
1195 * Store the image keys.
1196 * @protected
1197 * @since 4.5.000 (2008-12-31)
1198 */
1199 protected $imagekeys = array();
1200
1201 /**
1202 * Length of the buffer in bytes.
1203 * @protected
1204 * @since 4.5.000 (2008-12-31)
1205 */
1206 protected $bufferlen = 0;
1207
1208 /**
1209 * If true enables disk caching.
1210 * @protected
1211 * @since 4.5.000 (2008-12-31)
1212 */
1213 protected $diskcache = false;
1214
1215 /**
1216 * Counts the number of fonts.
1217 * @protected
1218 * @since 4.5.000 (2009-01-02)
1219 */
1220 protected $numfonts = 0;
1221
1222 /**
1223 * Store the font keys.
1224 * @protected
1225 * @since 4.5.000 (2009-01-02)
1226 */
1227 protected $fontkeys = array();
1228
1229 /**
1230 * Store the font object IDs.
1231 * @protected
1232 * @since 4.8.001 (2009-09-09)
1233 */
1234 protected $font_obj_ids = array();
1235
1236 /**
1237 * Store the fage status (true when opened, false when closed).
1238 * @protected
1239 * @since 4.5.000 (2009-01-02)
1240 */
1241 protected $pageopen = array();
1242
1243 /**
1244 * Default monospace font.
1245 * @protected
1246 * @since 4.5.025 (2009-03-10)
1247 */
1248 protected $default_monospaced_font = 'courier';
1249
1250 /**
1251 * Cloned copy of the current class object.
1252 * @protected
1253 * @since 4.5.029 (2009-03-19)
1254 */
1255 protected $objcopy;
1256
1257 /**
1258 * Array used to store the lengths of cache files.
1259 * @protected
1260 * @since 4.5.029 (2009-03-19)
1261 */
1262 protected $cache_file_length = array();
1263
1264 /**
1265 * Table header content to be repeated on each new page.
1266 * @protected
1267 * @since 4.5.030 (2009-03-20)
1268 */
1269 protected $thead = '';
1270
1271 /**
1272 * Margins used for table header.
1273 * @protected
1274 * @since 4.5.030 (2009-03-20)
1275 */
1276 protected $theadMargins = array();
1277
1278 /**
1279 * Cache array for UTF8StringToArray() method.
1280 * @protected
1281 * @since 4.5.037 (2009-04-07)
1282 */
1283 protected $cache_UTF8StringToArray = array();
1284
1285 /**
1286 * Maximum size of cache array used for UTF8StringToArray() method.
1287 * @protected
1288 * @since 4.5.037 (2009-04-07)
1289 */
1290 protected $cache_maxsize_UTF8StringToArray = 8;
1291
1292 /**
1293 * Current size of cache array used for UTF8StringToArray() method.
1294 * @protected
1295 * @since 4.5.037 (2009-04-07)
1296 */
1297 protected $cache_size_UTF8StringToArray = 0;
1298
1299 /**
1300 * Boolean flag to enable document digital signature.
1301 * @protected
1302 * @since 4.6.005 (2009-04-24)
1303 */
1304 protected $sign = false;
1305
1306 /**
1307 * Digital signature data.
1308 * @protected
1309 * @since 4.6.005 (2009-04-24)
1310 */
1311 protected $signature_data = array();
1312
1313 /**
1314 * Digital signature max length.
1315 * @protected
1316 * @since 4.6.005 (2009-04-24)
1317 */
1318 protected $signature_max_length = 11742;
1319
1320 /**
1321 * Data for digital signature appearance.
1322 * @protected
1323 * @since 5.3.011 (2010-06-16)
1324 */
1325 protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1326
1327 /**
1328 * Array of empty digital signature appearances.
1329 * @protected
1330 * @since 5.9.101 (2011-07-06)
1331 */
1332 protected $empty_signature_appearance = array();
1333
1334 /**
1335 * Regular expression used to find blank characters (required for word-wrapping).
1336 * @protected
1337 * @since 4.6.006 (2009-04-28)
1338 */
1339 protected $re_spaces = '/[^\S\xa0]/';
1340
1341 /**
1342 * Array of $re_spaces parts.
1343 * @protected
1344 * @since 5.5.011 (2010-07-09)
1345 */
1346 protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1347
1348 /**
1349 * Digital signature object ID.
1350 * @protected
1351 * @since 4.6.022 (2009-06-23)
1352 */
1353 protected $sig_obj_id = 0;
1354
1355 /**
1356 * ByteRange placemark used during digital signature process.
1357 * @protected
1358 * @since 4.6.028 (2009-08-25)
1359 */
1360 protected $byterange_string = '/ByteRange[0 ********** ********** **********]';
1361
1362 /**
1363 * Placemark used during digital signature process.
1364 * @protected
1365 * @since 4.6.028 (2009-08-25)
1366 */
1367 protected $sig_annot_ref = '***SIGANNREF*** 0 R';
1368
1369 /**
1370 * ID of page objects.
1371 * @protected
1372 * @since 4.7.000 (2009-08-29)
1373 */
1374 protected $page_obj_id = array();
1375
1376 /**
1377 * List of form annotations IDs.
1378 * @protected
1379 * @since 4.8.000 (2009-09-07)
1380 */
1381 protected $form_obj_id = array();
1382
1383 /**
1384 * 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.
1385 * @protected
1386 * @since 4.8.000 (2009-09-07)
1387 */
1388 protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1389
1390 /**
1391 * Javascript objects array.
1392 * @protected
1393 * @since 4.8.000 (2009-09-07)
1394 */
1395 protected $js_objects = array();
1396
1397 /**
1398 * Current form action (used during XHTML rendering).
1399 * @protected
1400 * @since 4.8.000 (2009-09-07)
1401 */
1402 protected $form_action = '';
1403
1404 /**
1405 * Current form encryption type (used during XHTML rendering).
1406 * @protected
1407 * @since 4.8.000 (2009-09-07)
1408 */
1409 protected $form_enctype = 'application/x-www-form-urlencoded';
1410
1411 /**
1412 * Current method to submit forms.
1413 * @protected
1414 * @since 4.8.000 (2009-09-07)
1415 */
1416 protected $form_mode = 'post';
1417
1418 /**
1419 * List of fonts used on form fields (fontname => fontkey).
1420 * @protected
1421 * @since 4.8.001 (2009-09-09)
1422 */
1423 protected $annotation_fonts = array();
1424
1425 /**
1426 * List of radio buttons parent objects.
1427 * @protected
1428 * @since 4.8.001 (2009-09-09)
1429 */
1430 protected $radiobutton_groups = array();
1431
1432 /**
1433 * List of radio group objects IDs.
1434 * @protected
1435 * @since 4.8.001 (2009-09-09)
1436 */
1437 protected $radio_groups = array();
1438
1439 /**
1440 * Text indentation value (used for text-indent CSS attribute).
1441 * @protected
1442 * @since 4.8.006 (2009-09-23)
1443 */
1444 protected $textindent = 0;
1445
1446 /**
1447 * Store page number when startTransaction() is called.
1448 * @protected
1449 * @since 4.8.006 (2009-09-23)
1450 */
1451 protected $start_transaction_page = 0;
1452
1453 /**
1454 * Store Y position when startTransaction() is called.
1455 * @protected
1456 * @since 4.9.001 (2010-03-28)
1457 */
1458 protected $start_transaction_y = 0;
1459
1460 /**
1461 * True when we are printing the thead section on a new page.
1462 * @protected
1463 * @since 4.8.027 (2010-01-25)
1464 */
1465 protected $inthead = false;
1466
1467 /**
1468 * Array of column measures (width, space, starting Y position).
1469 * @protected
1470 * @since 4.9.001 (2010-03-28)
1471 */
1472 protected $columns = array();
1473
1474 /**
1475 * Number of colums.
1476 * @protected
1477 * @since 4.9.001 (2010-03-28)
1478 */
1479 protected $num_columns = 1;
1480
1481 /**
1482 * Current column number.
1483 * @protected
1484 * @since 4.9.001 (2010-03-28)
1485 */
1486 protected $current_column = 0;
1487
1488 /**
1489 * Starting page for columns.
1490 * @protected
1491 * @since 4.9.001 (2010-03-28)
1492 */
1493 protected $column_start_page = 0;
1494
1495 /**
1496 * Maximum page and column selected.
1497 * @protected
1498 * @since 5.8.000 (2010-08-11)
1499 */
1500 protected $maxselcol = array('page' => 0, 'column' => 0);
1501
1502 /**
1503 * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
1504 * @protected
1505 * @since 5.8.000 (2010-08-11)
1506 */
1507 protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1508
1509 /**
1510 * 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.
1511 * @protected
1512 * @since 4.9.008 (2010-04-03)
1513 */
1514 protected $textrendermode = 0;
1515
1516 /**
1517 * Text stroke width in doc units.
1518 * @protected
1519 * @since 4.9.008 (2010-04-03)
1520 */
1521 protected $textstrokewidth = 0;
1522
1523 /**
1524 * Current stroke color.
1525 * @protected
1526 * @since 4.9.008 (2010-04-03)
1527 */
1528 protected $strokecolor;
1529
1530 /**
1531 * Default unit of measure for document.
1532 * @protected
1533 * @since 5.0.000 (2010-04-22)
1534 */
1535 protected $pdfunit = 'mm';
1536
1537 /**
1538 * Boolean flag true when we are on TOC (Table Of Content) page.
1539 * @protected
1540 */
1541 protected $tocpage = false;
1542
1543 /**
1544 * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1545 * @protected
1546 * @since 5.0.000 (2010-04-26)
1547 */
1548 protected $rasterize_vector_images = false;
1549
1550 /**
1551 * Boolean flag: if true enables font subsetting by default.
1552 * @protected
1553 * @since 5.3.002 (2010-06-07)
1554 */
1555 protected $font_subsetting = true;
1556
1557 /**
1558 * Array of default graphic settings.
1559 * @protected
1560 * @since 5.5.008 (2010-07-02)
1561 */
1562 protected $default_graphic_vars = array();
1563
1564 /**
1565 * Array of XObjects.
1566 * @protected
1567 * @since 5.8.014 (2010-08-23)
1568 */
1569 protected $xobjects = array();
1570
1571 /**
1572 * Boolean value true when we are inside an XObject.
1573 * @protected
1574 * @since 5.8.017 (2010-08-24)
1575 */
1576 protected $inxobj = false;
1577
1578 /**
1579 * Current XObject ID.
1580 * @protected
1581 * @since 5.8.017 (2010-08-24)
1582 */
1583 protected $xobjid = '';
1584
1585 /**
1586 * Percentage of character stretching.
1587 * @protected
1588 * @since 5.9.000 (2010-09-29)
1589 */
1590 protected $font_stretching = 100;
1591
1592 /**
1593 * Increases or decreases the space between characters in a text by the specified amount (tracking/kerning).
1594 * @protected
1595 * @since 5.9.000 (2010-09-29)
1596 */
1597 protected $font_spacing = 0;
1598
1599 /**
1600 * Array of no-write regions.
1601 * ('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)
1602 * @protected
1603 * @since 5.9.003 (2010-10-14)
1604 */
1605 protected $page_regions = array();
1606
1607 /**
1608 * Array containing HTML color names and values.
1609 * @protected
1610 * @since 5.9.004 (2010-10-18)
1611 */
1612 protected $webcolor = array();
1613
1614 /**
1615 * Array containing spot color names and values.
1616 * @protected
1617 * @since 5.9.012 (2010-11-11)
1618 */
1619 protected $spotcolor = array();
1620
1621 /**
1622 * Array of PDF layers data.
1623 * @protected
1624 * @since 5.9.102 (2011-07-13)
1625 */
1626 protected $pdflayers = array();
1627
1628 /**
1629 * A dictionary of names and corresponding destinations (Dests key on document Catalog).
1630 * @protected
1631 * @since 5.9.097 (2011-06-23)
1632 */
1633 protected $dests = array();
1634
1635 /**
1636 * Object ID for Named Destinations
1637 * @protected
1638 * @since 5.9.097 (2011-06-23)
1639 */
1640 protected $n_dests;
1641
1642 /**
1643 * Directory used for the last SVG image.
1644 * @protected
1645 * @since 5.0.000 (2010-05-05)
1646 */
1647 protected $svgdir = '';
1648
1649 /**
1650 * Deafult unit of measure for SVG.
1651 * @protected
1652 * @since 5.0.000 (2010-05-02)
1653 */
1654 protected $svgunit = 'px';
1655
1656 /**
1657 * Array of SVG gradients.
1658 * @protected
1659 * @since 5.0.000 (2010-05-02)
1660 */
1661 protected $svggradients = array();
1662
1663 /**
1664 * ID of last SVG gradient.
1665 * @protected
1666 * @since 5.0.000 (2010-05-02)
1667 */
1668 protected $svggradientid = 0;
1669
1670 /**
1671 * Boolean value true when in SVG defs group.
1672 * @protected
1673 * @since 5.0.000 (2010-05-02)
1674 */
1675 protected $svgdefsmode = false;
1676
1677 /**
1678 * Array of SVG defs.
1679 * @protected
1680 * @since 5.0.000 (2010-05-02)
1681 */
1682 protected $svgdefs = array();
1683
1684 /**
1685 * Boolean value true when in SVG clipPath tag.
1686 * @protected
1687 * @since 5.0.000 (2010-04-26)
1688 */
1689 protected $svgclipmode = false;
1690
1691 /**
1692 * Array of SVG clipPath commands.
1693 * @protected
1694 * @since 5.0.000 (2010-05-02)
1695 */
1696 protected $svgclippaths = array();
1697
1698 /**
1699 * Array of SVG clipPath tranformation matrix.
1700 * @protected
1701 * @since 5.8.022 (2010-08-31)
1702 */
1703 protected $svgcliptm = array();
1704
1705 /**
1706 * ID of last SVG clipPath.
1707 * @protected
1708 * @since 5.0.000 (2010-05-02)
1709 */
1710 protected $svgclipid = 0;
1711
1712 /**
1713 * SVG text.
1714 * @protected
1715 * @since 5.0.000 (2010-05-02)
1716 */
1717 protected $svgtext = '';
1718
1719 /**
1720 * SVG text properties.
1721 * @protected
1722 * @since 5.8.013 (2010-08-23)
1723 */
1724 protected $svgtextmode = array();
1725
1726 /**
1727 * Array of hinheritable SVG properties.
1728 * @protected
1729 * @since 5.0.000 (2010-05-02)
1730 */
1731 protected $svginheritprop = array('clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'fill', 'fill-opacity', 'fill-rule', 'font', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'image-rendering', 'kerning', 'letter-spacing', 'marker', 'marker-end', 'marker-mid', 'marker-start', 'pointer-events', 'shape-rendering', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-rendering', 'visibility', 'word-spacing', 'writing-mode');
1732
1733 /**
1734 * Array of SVG properties.
1735 * @protected
1736 * @since 5.0.000 (2010-05-02)
1737 */
1738 protected $svgstyles = array(array(
1739 'alignment-baseline' => 'auto',
1740 'baseline-shift' => 'baseline',
1741 'clip' => 'auto',
1742 'clip-path' => 'none',
1743 'clip-rule' => 'nonzero',
1744 'color' => 'black',
1745 'color-interpolation' => 'sRGB',
1746 'color-interpolation-filters' => 'linearRGB',
1747 'color-profile' => 'auto',
1748 'color-rendering' => 'auto',
1749 'cursor' => 'auto',
1750 'direction' => 'ltr',
1751 'display' => 'inline',
1752 'dominant-baseline' => 'auto',
1753 'enable-background' => 'accumulate',
1754 'fill' => 'black',
1755 'fill-opacity' => 1,
1756 'fill-rule' => 'nonzero',
1757 'filter' => 'none',
1758 'flood-color' => 'black',
1759 'flood-opacity' => 1,
1760 'font' => '',
1761 'font-family' => 'helvetica',
1762 'font-size' => 'medium',
1763 'font-size-adjust' => 'none',
1764 'font-stretch' => 'normal',
1765 'font-style' => 'normal',
1766 'font-variant' => 'normal',
1767 'font-weight' => 'normal',
1768 'glyph-orientation-horizontal' => '0deg',
1769 'glyph-orientation-vertical' => 'auto',
1770 'image-rendering' => 'auto',
1771 'kerning' => 'auto',
1772 'letter-spacing' => 'normal',
1773 'lighting-color' => 'white',
1774 'marker' => '',
1775 'marker-end' => 'none',
1776 'marker-mid' => 'none',
1777 'marker-start' => 'none',
1778 'mask' => 'none',
1779 'opacity' => 1,
1780 'overflow' => 'auto',
1781 'pointer-events' => 'visiblePainted',
1782 'shape-rendering' => 'auto',
1783 'stop-color' => 'black',
1784 'stop-opacity' => 1,
1785 'stroke' => 'none',
1786 'stroke-dasharray' => 'none',
1787 'stroke-dashoffset' => 0,
1788 'stroke-linecap' => 'butt',
1789 'stroke-linejoin' => 'miter',
1790 'stroke-miterlimit' => 4,
1791 'stroke-opacity' => 1,
1792 'stroke-width' => 1,
1793 'text-anchor' => 'start',
1794 'text-decoration' => 'none',
1795 'text-rendering' => 'auto',
1796 'unicode-bidi' => 'normal',
1797 'visibility' => 'visible',
1798 'word-spacing' => 'normal',
1799 'writing-mode' => 'lr-tb',
1800 'text-color' => 'black',
1801 'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1802 ));
1803
1804 /**
1805 * If true force sRGB color profile for all document.
1806 * @protected
1807 * @since 5.9.121 (2011-09-28)
1808 */
1809 protected $force_srgb = false;
1810
1811 /**
1812 * If true set the document to PDF/A mode.
1813 * @protected
1814 * @since 5.9.121 (2011-09-27)
1815 */
1816 protected $pdfa_mode = false;
1817
1818 /**
1819 * Document creation date-time
1820 * @protected
1821 * @since 5.9.152 (2012-03-22)
1822 */
1823 protected $doc_creation_timestamp;
1824
1825 /**
1826 * Document modification date-time
1827 * @protected
1828 * @since 5.9.152 (2012-03-22)
1829 */
1830 protected $doc_modification_timestamp;
1831
1832 /**
1833 * Custom XMP data.
1834 * @protected
1835 * @since 5.9.128 (2011-10-06)
1836 */
1837 protected $custom_xmp = '';
1838
1839 /**
1840 * Overprint mode array.
1841 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1842 * @protected
1843 * @since 5.9.152 (2012-03-23)
1844 */
1845 protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1846
1847 /**
1848 * Alpha mode array.
1849 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1850 * @protected
1851 * @since 5.9.152 (2012-03-23)
1852 */
1853 protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1854
1855 /**
1856 * Define the page boundaries boxes to be set on document.
1857 * @protected
1858 * @since 5.9.152 (2012-03-23)
1859 */
1860 protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1861
1862 /**
1863 * Set the document producer metadata.
1864 * @protected
1865 * @since 5.9.152 (2012-03-23)
1866 */
1867 protected $pdfproducer;
1868
1869 /**
1870 * If true print TCPDF meta link.
1871 * @protected
1872 * @since 5.9.152 (2012-03-23)
1873 */
1874 protected $tcpdflink = true;
1875
1876 //------------------------------------------------------------
1877 // METHODS
1878 //------------------------------------------------------------
1879
1880 /**
1881 * This is the class constructor.
1882 * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1883 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
1884 * @param $unit (string) User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
1885 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
1886 * @param $unicode (boolean) TRUE means that the input text is unicode (default = true)
1887 * @param $encoding (string) Charset encoding; default is UTF-8.
1888 * @param $diskcache (boolean) If TRUE reduce the RAM memory usage by caching temporary data on filesystem (slower).
1889 * @param $pdfa (boolean) If TRUE set the document to PDF/A mode.
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 internal character encoding to ASCII */
1895 if (function_exists('mb_internal_encoding') AND mb_internal_encoding()) {
1896 $this->internal_encoding = mb_internal_encoding();
1897 mb_internal_encoding('ASCII');
1898 }
1899 // get array of HTML colors
1900 require(dirname(__FILE__).'/htmlcolors.php');
1901 $this->webcolor = $webcolor;
1902 // get array of custom spot colors
1903 if (file_exists(dirname(__FILE__).'/spotcolors.php')) {
1904 require(dirname(__FILE__).'/spotcolors.php');
1905 $this->spotcolor = $spotcolor;
1906 } else {
1907 $this->spotcolor = array();
1908 }
1909 require_once(dirname(__FILE__).'/unicode_data.php');
1910 $this->unicode = new TCPDF_UNICODE_DATA();
1911 require_once(dirname(__FILE__).'/encodings_maps.php');
1912 $this->encmaps = new TCPDF_ENCODING_MAPS();
1913 $this->font_obj_ids = array();
1914 $this->page_obj_id = array();
1915 $this->form_obj_id = array();
1916 // set pdf/a mode
1917 $this->pdfa_mode = $pdfa;
1918 $this->force_srgb = false;
1919 // set disk caching
1920 $this->diskcache = $diskcache ? true : false;
1921 // set language direction
1922 $this->rtl = false;
1923 $this->tmprtl = false;
1924 // some checks
1925 $this->_dochecks();
1926 // initialization of properties
1927 $this->isunicode = $unicode;
1928 $this->page = 0;
1929 $this->transfmrk[0] = array();
1930 $this->pagedim = array();
1931 $this->n = 2;
1932 $this->buffer = '';
1933 $this->pages = array();
1934 $this->state = 0;
1935 $this->fonts = array();
1936 $this->FontFiles = array();
1937 $this->diffs = array();
1938 $this->images = array();
1939 $this->links = array();
1940 $this->gradients = array();
1941 $this->InFooter = false;
1942 $this->lasth = 0;
1943 $this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
1944 $this->FontStyle = '';
1945 $this->FontSizePt = 12;
1946 $this->underline = false;
1947 $this->overline = false;
1948 $this->linethrough = false;
1949 $this->DrawColor = '0 G';
1950 $this->FillColor = '0 g';
1951 $this->TextColor = '0 g';
1952 $this->ColorFlag = false;
1953 $this->pdflayers = array();
1954 // encryption values
1955 $this->encrypted = false;
1956 $this->last_enc_key = '';
1957 // standard Unicode fonts
1958 $this->CoreFonts = array(
1959 'courier'=>'Courier',
1960 'courierB'=>'Courier-Bold',
1961 'courierI'=>'Courier-Oblique',
1962 'courierBI'=>'Courier-BoldOblique',
1963 'helvetica'=>'Helvetica',
1964 'helveticaB'=>'Helvetica-Bold',
1965 'helveticaI'=>'Helvetica-Oblique',
1966 'helveticaBI'=>'Helvetica-BoldOblique',
1967 'times'=>'Times-Roman',
1968 'timesB'=>'Times-Bold',
1969 'timesI'=>'Times-Italic',
1970 'timesBI'=>'Times-BoldItalic',
1971 'symbol'=>'Symbol',
1972 'zapfdingbats'=>'ZapfDingbats'
1973 );
1974 // set scale factor
1975 $this->setPageUnit($unit);
1976 // set page format and orientation
1977 $this->setPageFormat($format, $orientation);
1978 // page margins (1 cm)
1979 $margin = 28.35 / $this->k;
1980 $this->SetMargins($margin, $margin);
1981 // internal cell padding
1982 $cpadding = $margin / 10;
1983 $this->setCellPaddings($cpadding, 0, $cpadding, 0);
1984 // cell margins
1985 $this->setCellMargins(0, 0, 0, 0);
1986 // line width (0.2 mm)
1987 $this->LineWidth = 0.57 / $this->k;
1988 $this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
1989 $this->linestyleCap = '0 J';
1990 $this->linestyleJoin = '0 j';
1991 $this->linestyleDash = '[] 0 d';
1992 // automatic page break
1993 $this->SetAutoPageBreak(true, (2 * $margin));
1994 // full width display mode
1995 $this->SetDisplayMode('fullwidth');
1996 // compression
1997 $this->SetCompression();
1998 // set default PDF version number
1999 $this->setPDFVersion();
2000 $this->pdfproducer = "\x54\x43\x50\x44\x46\x20".$this->tcpdf_version."\x20\x28\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67\x29";
2001 $this->tcpdflink = true;
2002 $this->encoding = $encoding;
2003 $this->HREF = array();
2004 $this->getFontsList();
2005 $this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
2006 $this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
2007 $this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
2008 $this->extgstates = array();
2009 // user's rights
2010 $this->sign = false;
2011 $this->ur['enabled'] = false;
2012 $this->ur['document'] = '/FullSave';
2013 $this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
2014 $this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
2015 $this->ur['signature'] = '/Modify';
2016 $this->ur['ef'] = '/Create/Delete/Modify/Import';
2017 $this->ur['formex'] = '';
2018 $this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
2019 $this->empty_signature_appearance = array();
2020 // set default JPEG quality
2021 $this->jpeg_quality = 75;
2022 // initialize some settings
2023 $this->utf8Bidi(array(''), '');
2024 // set default font
2025 $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
2026 // check if PCRE Unicode support is enabled
2027 if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
2028 // PCRE unicode support is turned ON
2029 // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
2030 // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
2031 // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
2032 //$this->setSpacesRE('/[^\S\P{Z}\P{Lo}\xa0]/u');
2033 $this->setSpacesRE('/[^\S\P{Z}\xa0]/u');
2034 } else {
2035 // PCRE unicode support is turned OFF
2036 $this->setSpacesRE('/[^\S\xa0]/');
2037 }
2038 $this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
2039 // set file ID for trailer
2040 $this->file_id = md5($this->getRandomSeed('TCPDF'.$orientation.$unit.$format.$encoding));
2041 // set document creation and modification timestamp
2042 $this->doc_creation_timestamp = time();
2043 $this->doc_modification_timestamp = $this->doc_creation_timestamp;
2044 // get default graphic vars
2045 $this->default_graphic_vars = $this->getGraphicVars();
2046 $this->header_xobj_autoreset = false;
2047 $this->custom_xmp = '';
2048 }
2049
2050 /**
2051 * Default destructor.
2052 * @public
2053 * @since 1.53.0.TC016
2054 */
2055 public function __destruct() {
2056 // restore internal encoding
2057 if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) {
2058 mb_internal_encoding($this->internal_encoding);
2059 }
2060 // unset all class variables
2061 $this->_destroy(true);
2062 }
2063
2064 /**
2065 * Return the current TCPDF version.
2066 * @return TCPDF version string
2067 * @public
2068 * @since 5.9.012 (2010-11-10)
2069 */
2070 public function getTCPDFVersion() {
2071 return $this->tcpdf_version;
2072 }
2073
2074 /**
2075 * Set the units of measure for the document.
2076 * @param $unit (string) User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
2077 * @public
2078 * @since 3.0.015 (2008-06-06)
2079 */
2080 public function setPageUnit($unit) {
2081 $unit = strtolower($unit);
2082 //Set scale factor
2083 switch ($unit) {
2084 // points
2085 case 'px':
2086 case 'pt': {
2087 $this->k = 1;
2088 break;
2089 }
2090 // millimeters
2091 case 'mm': {
2092 $this->k = $this->dpi / 25.4;
2093 break;
2094 }
2095 // centimeters
2096 case 'cm': {
2097 $this->k = $this->dpi / 2.54;
2098 break;
2099 }
2100 // inches
2101 case 'in': {
2102 $this->k = $this->dpi;
2103 break;
2104 }
2105 // unsupported unit
2106 default : {
2107 $this->Error('Incorrect unit: '.$unit);
2108 break;
2109 }
2110 }
2111 $this->pdfunit = $unit;
2112 if (isset($this->CurOrientation)) {
2113 $this->setPageOrientation($this->CurOrientation);
2114 }
2115 }
2116
2117 /**
2118 * Get page dimensions from format name.
2119 * @param $format (mixed) The format name. It can be: <ul>
2120 * <li><b>ISO 216 A Series + 2 SIS 014711 extensions</b></li>
2121 * <li>A0 (841x1189 mm ; 33.11x46.81 in)</li>
2122 * <li>A1 (594x841 mm ; 23.39x33.11 in)</li>
2123 * <li>A2 (420x594 mm ; 16.54x23.39 in)</li>
2124 * <li>A3 (297x420 mm ; 11.69x16.54 in)</li>
2125 * <li>A4 (210x297 mm ; 8.27x11.69 in)</li>
2126 * <li>A5 (148x210 mm ; 5.83x8.27 in)</li>
2127 * <li>A6 (105x148 mm ; 4.13x5.83 in)</li>
2128 * <li>A7 (74x105 mm ; 2.91x4.13 in)</li>
2129 * <li>A8 (52x74 mm ; 2.05x2.91 in)</li>
2130 * <li>A9 (37x52 mm ; 1.46x2.05 in)</li>
2131 * <li>A10 (26x37 mm ; 1.02x1.46 in)</li>
2132 * <li>A11 (18x26 mm ; 0.71x1.02 in)</li>
2133 * <li>A12 (13x18 mm ; 0.51x0.71 in)</li>
2134 * <li><b>ISO 216 B Series + 2 SIS 014711 extensions</b></li>
2135 * <li>B0 (1000x1414 mm ; 39.37x55.67 in)</li>
2136 * <li>B1 (707x1000 mm ; 27.83x39.37 in)</li>
2137 * <li>B2 (500x707 mm ; 19.69x27.83 in)</li>
2138 * <li>B3 (353x500 mm ; 13.90x19.69 in)</li>
2139 * <li>B4 (250x353 mm ; 9.84x13.90 in)</li>
2140 * <li>B5 (176x250 mm ; 6.93x9.84 in)</li>
2141 * <li>B6 (125x176 mm ; 4.92x6.93 in)</li>
2142 * <li>B7 (88x125 mm ; 3.46x4.92 in)</li>
2143 * <li>B8 (62x88 mm ; 2.44x3.46 in)</li>
2144 * <li>B9 (44x62 mm ; 1.73x2.44 in)</li>
2145 * <li>B10 (31x44 mm ; 1.22x1.73 in)</li>
2146 * <li>B11 (22x31 mm ; 0.87x1.22 in)</li>
2147 * <li>B12 (15x22 mm ; 0.59x0.87 in)</li>
2148 * <li><b>ISO 216 C Series + 2 SIS 014711 extensions + 2 EXTENSION</b></li>
2149 * <li>C0 (917x1297 mm ; 36.10x51.06 in)</li>
2150 * <li>C1 (648x917 mm ; 25.51x36.10 in)</li>
2151 * <li>C2 (458x648 mm ; 18.03x25.51 in)</li>
2152 * <li>C3 (324x458 mm ; 12.76x18.03 in)</li>
2153 * <li>C4 (229x324 mm ; 9.02x12.76 in)</li>
2154 * <li>C5 (162x229 mm ; 6.38x9.02 in)</li>
2155 * <li>C6 (114x162 mm ; 4.49x6.38 in)</li>
2156 * <li>C7 (81x114 mm ; 3.19x4.49 in)</li>
2157 * <li>C8 (57x81 mm ; 2.24x3.19 in)</li>
2158 * <li>C9 (40x57 mm ; 1.57x2.24 in)</li>
2159 * <li>C10 (28x40 mm ; 1.10x1.57 in)</li>
2160 * <li>C11 (20x28 mm ; 0.79x1.10 in)</li>
2161 * <li>C12 (14x20 mm ; 0.55x0.79 in)</li>
2162 * <li>C76 (81x162 mm ; 3.19x6.38 in)</li>
2163 * <li>DL (110x220 mm ; 4.33x8.66 in)</li>
2164 * <li><b>SIS 014711 E Series</b></li>
2165 * <li>E0 (879x1241 mm ; 34.61x48.86 in)</li>
2166 * <li>E1 (620x879 mm ; 24.41x34.61 in)</li>
2167 * <li>E2 (440x620 mm ; 17.32x24.41 in)</li>
2168 * <li>E3 (310x440 mm ; 12.20x17.32 in)</li>
2169 * <li>E4 (220x310 mm ; 8.66x12.20 in)</li>
2170 * <li>E5 (155x220 mm ; 6.10x8.66 in)</li>
2171 * <li>E6 (110x155 mm ; 4.33x6.10 in)</li>
2172 * <li>E7 (78x110 mm ; 3.07x4.33 in)</li>
2173 * <li>E8 (55x78 mm ; 2.17x3.07 in)</li>
2174 * <li>E9 (39x55 mm ; 1.54x2.17 in)</li>
2175 * <li>E10 (27x39 mm ; 1.06x1.54 in)</li>
2176 * <li>E11 (19x27 mm ; 0.75x1.06 in)</li>
2177 * <li>E12 (13x19 mm ; 0.51x0.75 in)</li>
2178 * <li><b>SIS 014711 G Series</b></li>
2179 * <li>G0 (958x1354 mm ; 37.72x53.31 in)</li>
2180 * <li>G1 (677x958 mm ; 26.65x37.72 in)</li>
2181 * <li>G2 (479x677 mm ; 18.86x26.65 in)</li>
2182 * <li>G3 (338x479 mm ; 13.31x18.86 in)</li>
2183 * <li>G4 (239x338 mm ; 9.41x13.31 in)</li>
2184 * <li>G5 (169x239 mm ; 6.65x9.41 in)</li>
2185 * <li>G6 (119x169 mm ; 4.69x6.65 in)</li>
2186 * <li>G7 (84x119 mm ; 3.31x4.69 in)</li>
2187 * <li>G8 (59x84 mm ; 2.32x3.31 in)</li>
2188 * <li>G9 (42x59 mm ; 1.65x2.32 in)</li>
2189 * <li>G10 (29x42 mm ; 1.14x1.65 in)</li>
2190 * <li>G11 (21x29 mm ; 0.83x1.14 in)</li>
2191 * <li>G12 (14x21 mm ; 0.55x0.83 in)</li>
2192 * <li><b>ISO Press</b></li>
2193 * <li>RA0 (860x1220 mm ; 33.86x48.03 in)</li>
2194 * <li>RA1 (610x860 mm ; 24.02x33.86 in)</li>
2195 * <li>RA2 (430x610 mm ; 16.93x24.02 in)</li>
2196 * <li>RA3 (305x430 mm ; 12.01x16.93 in)</li>
2197 * <li>RA4 (215x305 mm ; 8.46x12.01 in)</li>
2198 * <li>SRA0 (900x1280 mm ; 35.43x50.39 in)</li>
2199 * <li>SRA1 (640x900 mm ; 25.20x35.43 in)</li>
2200 * <li>SRA2 (450x640 mm ; 17.72x25.20 in)</li>
2201 * <li>SRA3 (320x450 mm ; 12.60x17.72 in)</li>
2202 * <li>SRA4 (225x320 mm ; 8.86x12.60 in)</li>
2203 * <li><b>German DIN 476</b></li>
2204 * <li>4A0 (1682x2378 mm ; 66.22x93.62 in)</li>
2205 * <li>2A0 (1189x1682 mm ; 46.81x66.22 in)</li>
2206 * <li><b>Variations on the ISO Standard</b></li>
2207 * <li>A2_EXTRA (445x619 mm ; 17.52x24.37 in)</li>
2208 * <li>A3+ (329x483 mm ; 12.95x19.02 in)</li>
2209 * <li>A3_EXTRA (322x445 mm ; 12.68x17.52 in)</li>
2210 * <li>A3_SUPER (305x508 mm ; 12.01x20.00 in)</li>
2211 * <li>SUPER_A3 (305x487 mm ; 12.01x19.17 in)</li>
2212 * <li>A4_EXTRA (235x322 mm ; 9.25x12.68 in)</li>
2213 * <li>A4_SUPER (229x322 mm ; 9.02x12.68 in)</li>
2214 * <li>SUPER_A4 (227x356 mm ; 8.94x14.02 in)</li>
2215 * <li>A4_LONG (210x348 mm ; 8.27x13.70 in)</li>
2216 * <li>F4 (210x330 mm ; 8.27x12.99 in)</li>
2217 * <li>SO_B5_EXTRA (202x276 mm ; 7.95x10.87 in)</li>
2218 * <li>A5_EXTRA (173x235 mm ; 6.81x9.25 in)</li>
2219 * <li><b>ANSI Series</b></li>
2220 * <li>ANSI_E (864x1118 mm ; 34.00x44.00 in)</li>
2221 * <li>ANSI_D (559x864 mm ; 22.00x34.00 in)</li>
2222 * <li>ANSI_C (432x559 mm ; 17.00x22.00 in)</li>
2223 * <li>ANSI_B (279x432 mm ; 11.00x17.00 in)</li>
2224 * <li>ANSI_A (216x279 mm ; 8.50x11.00 in)</li>
2225 * <li><b>Traditional 'Loose' North American Paper Sizes</b></li>
2226 * <li>LEDGER, USLEDGER (432x279 mm ; 17.00x11.00 in)</li>
2227 * <li>TABLOID, USTABLOID, BIBLE, ORGANIZERK (279x432 mm ; 11.00x17.00 in)</li>
2228 * <li>LETTER, USLETTER, ORGANIZERM (216x279 mm ; 8.50x11.00 in)</li>
2229 * <li>LEGAL, USLEGAL (216x356 mm ; 8.50x14.00 in)</li>
2230 * <li>GLETTER, GOVERNMENTLETTER (203x267 mm ; 8.00x10.50 in)</li>
2231 * <li>JLEGAL, JUNIORLEGAL (203x127 mm ; 8.00x5.00 in)</li>
2232 * <li><b>Other North American Paper Sizes</b></li>
2233 * <li>QUADDEMY (889x1143 mm ; 35.00x45.00 in)</li>
2234 * <li>SUPER_B (330x483 mm ; 13.00x19.00 in)</li>
2235 * <li>QUARTO (229x279 mm ; 9.00x11.00 in)</li>
2236 * <li>FOLIO, GOVERNMENTLEGAL (216x330 mm ; 8.50x13.00 in)</li>
2237 * <li>EXECUTIVE, MONARCH (184x267 mm ; 7.25x10.50 in)</li>
2238 * <li>MEMO, STATEMENT, ORGANIZERL (140x216 mm ; 5.50x8.50 in)</li>
2239 * <li>FOOLSCAP (210x330 mm ; 8.27x13.00 in)</li>
2240 * <li>COMPACT (108x171 mm ; 4.25x6.75 in)</li>
2241 * <li>ORGANIZERJ (70x127 mm ; 2.75x5.00 in)</li>
2242 * <li><b>Canadian standard CAN 2-9.60M</b></li>
2243 * <li>P1 (560x860 mm ; 22.05x33.86 in)</li>
2244 * <li>P2 (430x560 mm ; 16.93x22.05 in)</li>
2245 * <li>P3 (280x430 mm ; 11.02x16.93 in)</li>
2246 * <li>P4 (215x280 mm ; 8.46x11.02 in)</li>
2247 * <li>P5 (140x215 mm ; 5.51x8.46 in)</li>
2248 * <li>P6 (107x140 mm ; 4.21x5.51 in)</li>
2249 * <li><b>North American Architectural Sizes</b></li>
2250 * <li>ARCH_E (914x1219 mm ; 36.00x48.00 in)</li>
2251 * <li>ARCH_E1 (762x1067 mm ; 30.00x42.00 in)</li>
2252 * <li>ARCH_D (610x914 mm ; 24.00x36.00 in)</li>
2253 * <li>ARCH_C, BROADSHEET (457x610 mm ; 18.00x24.00 in)</li>
2254 * <li>ARCH_B (305x457 mm ; 12.00x18.00 in)</li>
2255 * <li>ARCH_A (229x305 mm ; 9.00x12.00 in)</li>
2256 * <li><b>Announcement Envelopes</b></li>
2257 * <li>ANNENV_A2 (111x146 mm ; 4.37x5.75 in)</li>
2258 * <li>ANNENV_A6 (121x165 mm ; 4.75x6.50 in)</li>
2259 * <li>ANNENV_A7 (133x184 mm ; 5.25x7.25 in)</li>
2260 * <li>ANNENV_A8 (140x206 mm ; 5.50x8.12 in)</li>
2261 * <li>ANNENV_A10 (159x244 mm ; 6.25x9.62 in)</li>
2262 * <li>ANNENV_SLIM (98x225 mm ; 3.87x8.87 in)</li>
2263 * <li><b>Commercial Envelopes</b></li>
2264 * <li>COMMENV_N6_1/4 (89x152 mm ; 3.50x6.00 in)</li>
2265 * <li>COMMENV_N6_3/4 (92x165 mm ; 3.62x6.50 in)</li>
2266 * <li>COMMENV_N8 (98x191 mm ; 3.87x7.50 in)</li>
2267 * <li>COMMENV_N9 (98x225 mm ; 3.87x8.87 in)</li>
2268 * <li>COMMENV_N10 (105x241 mm ; 4.12x9.50 in)</li>
2269 * <li>COMMENV_N11 (114x263 mm ; 4.50x10.37 in)</li>
2270 * <li>COMMENV_N12 (121x279 mm ; 4.75x11.00 in)</li>
2271 * <li>COMMENV_N14 (127x292 mm ; 5.00x11.50 in)</li>
2272 * <li><b>Catalogue Envelopes</b></li>
2273 * <li>CATENV_N1 (152x229 mm ; 6.00x9.00 in)</li>
2274 * <li>CATENV_N1_3/4 (165x241 mm ; 6.50x9.50 in)</li>
2275 * <li>CATENV_N2 (165x254 mm ; 6.50x10.00 in)</li>
2276 * <li>CATENV_N3 (178x254 mm ; 7.00x10.00 in)</li>
2277 * <li>CATENV_N6 (191x267 mm ; 7.50x10.50 in)</li>
2278 * <li>CATENV_N7 (203x279 mm ; 8.00x11.00 in)</li>
2279 * <li>CATENV_N8 (210x286 mm ; 8.25x11.25 in)</li>
2280 * <li>CATENV_N9_1/2 (216x267 mm ; 8.50x10.50 in)</li>
2281 * <li>CATENV_N9_3/4 (222x286 mm ; 8.75x11.25 in)</li>
2282 * <li>CATENV_N10_1/2 (229x305 mm ; 9.00x12.00 in)</li>
2283 * <li>CATENV_N12_1/2 (241x318 mm ; 9.50x12.50 in)</li>
2284 * <li>CATENV_N13_1/2 (254x330 mm ; 10.00x13.00 in)</li>
2285 * <li>CATENV_N14_1/4 (286x311 mm ; 11.25x12.25 in)</li>
2286 * <li>CATENV_N14_1/2 (292x368 mm ; 11.50x14.50 in)</li>
2287 * <li><b>Japanese (JIS P 0138-61) Standard B-Series</b></li>
2288 * <li>JIS_B0 (1030x1456 mm ; 40.55x57.32 in)</li>
2289 * <li>JIS_B1 (728x1030 mm ; 28.66x40.55 in)</li>
2290 * <li>JIS_B2 (515x728 mm ; 20.28x28.66 in)</li>
2291 * <li>JIS_B3 (364x515 mm ; 14.33x20.28 in)</li>
2292 * <li>JIS_B4 (257x364 mm ; 10.12x14.33 in)</li>
2293 * <li>JIS_B5 (182x257 mm ; 7.17x10.12 in)</li>
2294 * <li>JIS_B6 (128x182 mm ; 5.04x7.17 in)</li>
2295 * <li>JIS_B7 (91x128 mm ; 3.58x5.04 in)</li>
2296 * <li>JIS_B8 (64x91 mm ; 2.52x3.58 in)</li>
2297 * <li>JIS_B9 (45x64 mm ; 1.77x2.52 in)</li>
2298 * <li>JIS_B10 (32x45 mm ; 1.26x1.77 in)</li>
2299 * <li>JIS_B11 (22x32 mm ; 0.87x1.26 in)</li>
2300 * <li>JIS_B12 (16x22 mm ; 0.63x0.87 in)</li>
2301 * <li><b>PA Series</b></li>
2302 * <li>PA0 (840x1120 mm ; 33.07x44.09 in)</li>
2303 * <li>PA1 (560x840 mm ; 22.05x33.07 in)</li>
2304 * <li>PA2 (420x560 mm ; 16.54x22.05 in)</li>
2305 * <li>PA3 (280x420 mm ; 11.02x16.54 in)</li>
2306 * <li>PA4 (210x280 mm ; 8.27x11.02 in)</li>
2307 * <li>PA5 (140x210 mm ; 5.51x8.27 in)</li>
2308 * <li>PA6 (105x140 mm ; 4.13x5.51 in)</li>
2309 * <li>PA7 (70x105 mm ; 2.76x4.13 in)</li>
2310 * <li>PA8 (52x70 mm ; 2.05x2.76 in)</li>
2311 * <li>PA9 (35x52 mm ; 1.38x2.05 in)</li>
2312 * <li>PA10 (26x35 mm ; 1.02x1.38 in)</li>
2313 * <li><b>Standard Photographic Print Sizes</b></li>
2314 * <li>PASSPORT_PHOTO (35x45 mm ; 1.38x1.77 in)</li>
2315 * <li>E (82x120 mm ; 3.25x4.72 in)</li>
2316 * <li>3R, L (89x127 mm ; 3.50x5.00 in)</li>
2317 * <li>4R, KG (102x152 mm ; 4.02x5.98 in)</li>
2318 * <li>4D (120x152 mm ; 4.72x5.98 in)</li>
2319 * <li>5R, 2L (127x178 mm ; 5.00x7.01 in)</li>
2320 * <li>6R, 8P (152x203 mm ; 5.98x7.99 in)</li>
2321 * <li>8R, 6P (203x254 mm ; 7.99x10.00 in)</li>
2322 * <li>S8R, 6PW (203x305 mm ; 7.99x12.01 in)</li>
2323 * <li>10R, 4P (254x305 mm ; 10.00x12.01 in)</li>
2324 * <li>S10R, 4PW (254x381 mm ; 10.00x15.00 in)</li>
2325 * <li>11R (279x356 mm ; 10.98x14.02 in)</li>
2326 * <li>S11R (279x432 mm ; 10.98x17.01 in)</li>
2327 * <li>12R (305x381 mm ; 12.01x15.00 in)</li>
2328 * <li>S12R (305x456 mm ; 12.01x17.95 in)</li>
2329 * <li><b>Common Newspaper Sizes</b></li>
2330 * <li>NEWSPAPER_BROADSHEET (750x600 mm ; 29.53x23.62 in)</li>
2331 * <li>NEWSPAPER_BERLINER (470x315 mm ; 18.50x12.40 in)</li>
2332 * <li>NEWSPAPER_COMPACT, NEWSPAPER_TABLOID (430x280 mm ; 16.93x11.02 in)</li>
2333 * <li><b>Business Cards</b></li>
2334 * <li>CREDIT_CARD, BUSINESS_CARD, BUSINESS_CARD_ISO7810 (54x86 mm ; 2.13x3.37 in)</li>
2335 * <li>BUSINESS_CARD_ISO216 (52x74 mm ; 2.05x2.91 in)</li>
2336 * <li>BUSINESS_CARD_IT, BUSINESS_CARD_UK, BUSINESS_CARD_FR, BUSINESS_CARD_DE, BUSINESS_CARD_ES (55x85 mm ; 2.17x3.35 in)</li>
2337 * <li>BUSINESS_CARD_US, BUSINESS_CARD_CA (51x89 mm ; 2.01x3.50 in)</li>
2338 * <li>BUSINESS_CARD_JP (55x91 mm ; 2.17x3.58 in)</li>
2339 * <li>BUSINESS_CARD_HK (54x90 mm ; 2.13x3.54 in)</li>
2340 * <li>BUSINESS_CARD_AU, BUSINESS_CARD_DK, BUSINESS_CARD_SE (55x90 mm ; 2.17x3.54 in)</li>
2341 * <li>BUSINESS_CARD_RU, BUSINESS_CARD_CZ, BUSINESS_CARD_FI, BUSINESS_CARD_HU, BUSINESS_CARD_IL (50x90 mm ; 1.97x3.54 in)</li>
2342 * <li><b>Billboards</b></li>
2343 * <li>4SHEET (1016x1524 mm ; 40.00x60.00 in)</li>
2344 * <li>6SHEET (1200x1800 mm ; 47.24x70.87 in)</li>
2345 * <li>12SHEET (3048x1524 mm ; 120.00x60.00 in)</li>
2346 * <li>16SHEET (2032x3048 mm ; 80.00x120.00 in)</li>
2347 * <li>32SHEET (4064x3048 mm ; 160.00x120.00 in)</li>
2348 * <li>48SHEET (6096x3048 mm ; 240.00x120.00 in)</li>
2349 * <li>64SHEET (8128x3048 mm ; 320.00x120.00 in)</li>
2350 * <li>96SHEET (12192x3048 mm ; 480.00x120.00 in)</li>
2351 * <li><b>Old Imperial English (some are still used in USA)</b></li>
2352 * <li>EN_EMPEROR (1219x1829 mm ; 48.00x72.00 in)</li>
2353 * <li>EN_ANTIQUARIAN (787x1346 mm ; 31.00x53.00 in)</li>
2354 * <li>EN_GRAND_EAGLE (730x1067 mm ; 28.75x42.00 in)</li>
2355 * <li>EN_DOUBLE_ELEPHANT (679x1016 mm ; 26.75x40.00 in)</li>
2356 * <li>EN_ATLAS (660x864 mm ; 26.00x34.00 in)</li>
2357 * <li>EN_COLOMBIER (597x876 mm ; 23.50x34.50 in)</li>
2358 * <li>EN_ELEPHANT (584x711 mm ; 23.00x28.00 in)</li>
2359 * <li>EN_DOUBLE_DEMY (572x902 mm ; 22.50x35.50 in)</li>
2360 * <li>EN_IMPERIAL (559x762 mm ; 22.00x30.00 in)</li>
2361 * <li>EN_PRINCESS (546x711 mm ; 21.50x28.00 in)</li>
2362 * <li>EN_CARTRIDGE (533x660 mm ; 21.00x26.00 in)</li>
2363 * <li>EN_DOUBLE_LARGE_POST (533x838 mm ; 21.00x33.00 in)</li>
2364 * <li>EN_ROYAL (508x635 mm ; 20.00x25.00 in)</li>
2365 * <li>EN_SHEET, EN_HALF_POST (495x597 mm ; 19.50x23.50 in)</li>
2366 * <li>EN_SUPER_ROYAL (483x686 mm ; 19.00x27.00 in)</li>
2367 * <li>EN_DOUBLE_POST (483x775 mm ; 19.00x30.50 in)</li>
2368 * <li>EN_MEDIUM (445x584 mm ; 17.50x23.00 in)</li>
2369 * <li>EN_DEMY (445x572 mm ; 17.50x22.50 in)</li>
2370 * <li>EN_LARGE_POST (419x533 mm ; 16.50x21.00 in)</li>
2371 * <li>EN_COPY_DRAUGHT (406x508 mm ; 16.00x20.00 in)</li>
2372 * <li>EN_POST (394x489 mm ; 15.50x19.25 in)</li>
2373 * <li>EN_CROWN (381x508 mm ; 15.00x20.00 in)</li>
2374 * <li>EN_PINCHED_POST (375x470 mm ; 14.75x18.50 in)</li>
2375 * <li>EN_BRIEF (343x406 mm ; 13.50x16.00 in)</li>
2376 * <li>EN_FOOLSCAP (343x432 mm ; 13.50x17.00 in)</li>
2377 * <li>EN_SMALL_FOOLSCAP (337x419 mm ; 13.25x16.50 in)</li>
2378 * <li>EN_POTT (318x381 mm ; 12.50x15.00 in)</li>
2379 * <li><b>Old Imperial Belgian</b></li>
2380 * <li>BE_GRAND_AIGLE (700x1040 mm ; 27.56x40.94 in)</li>
2381 * <li>BE_COLOMBIER (620x850 mm ; 24.41x33.46 in)</li>
2382 * <li>BE_DOUBLE_CARRE (620x920 mm ; 24.41x36.22 in)</li>
2383 * <li>BE_ELEPHANT (616x770 mm ; 24.25x30.31 in)</li>
2384 * <li>BE_PETIT_AIGLE (600x840 mm ; 23.62x33.07 in)</li>
2385 * <li>BE_GRAND_JESUS (550x730 mm ; 21.65x28.74 in)</li>
2386 * <li>BE_JESUS (540x730 mm ; 21.26x28.74 in)</li>
2387 * <li>BE_RAISIN (500x650 mm ; 19.69x25.59 in)</li>
2388 * <li>BE_GRAND_MEDIAN (460x605 mm ; 18.11x23.82 in)</li>
2389 * <li>BE_DOUBLE_POSTE (435x565 mm ; 17.13x22.24 in)</li>
2390 * <li>BE_COQUILLE (430x560 mm ; 16.93x22.05 in)</li>
2391 * <li>BE_PETIT_MEDIAN (415x530 mm ; 16.34x20.87 in)</li>
2392 * <li>BE_RUCHE (360x460 mm ; 14.17x18.11 in)</li>
2393 * <li>BE_PROPATRIA (345x430 mm ; 13.58x16.93 in)</li>
2394 * <li>BE_LYS (317x397 mm ; 12.48x15.63 in)</li>
2395 * <li>BE_POT (307x384 mm ; 12.09x15.12 in)</li>
2396 * <li>BE_ROSETTE (270x347 mm ; 10.63x13.66 in)</li>
2397 * <li><b>Old Imperial French</b></li>
2398 * <li>FR_UNIVERS (1000x1300 mm ; 39.37x51.18 in)</li>
2399 * <li>FR_DOUBLE_COLOMBIER (900x1260 mm ; 35.43x49.61 in)</li>
2400 * <li>FR_GRANDE_MONDE (900x1260 mm ; 35.43x49.61 in)</li>
2401 * <li>FR_DOUBLE_SOLEIL (800x1200 mm ; 31.50x47.24 in)</li>
2402 * <li>FR_DOUBLE_JESUS (760x1120 mm ; 29.92x44.09 in)</li>
2403 * <li>FR_GRAND_AIGLE (750x1060 mm ; 29.53x41.73 in)</li>
2404 * <li>FR_PETIT_AIGLE (700x940 mm ; 27.56x37.01 in)</li>
2405 * <li>FR_DOUBLE_RAISIN (650x1000 mm ; 25.59x39.37 in)</li>
2406 * <li>FR_JOURNAL (650x940 mm ; 25.59x37.01 in)</li>
2407 * <li>FR_COLOMBIER_AFFICHE (630x900 mm ; 24.80x35.43 in)</li>
2408 * <li>FR_DOUBLE_CAVALIER (620x920 mm ; 24.41x36.22 in)</li>
2409 * <li>FR_CLOCHE (600x800 mm ; 23.62x31.50 in)</li>
2410 * <li>FR_SOLEIL (600x800 mm ; 23.62x31.50 in)</li>
2411 * <li>FR_DOUBLE_CARRE (560x900 mm ; 22.05x35.43 in)</li>
2412 * <li>FR_DOUBLE_COQUILLE (560x880 mm ; 22.05x34.65 in)</li>
2413 * <li>FR_JESUS (560x760 mm ; 22.05x29.92 in)</li>
2414 * <li>FR_RAISIN (500x650 mm ; 19.69x25.59 in)</li>
2415 * <li>FR_CAVALIER (460x620 mm ; 18.11x24.41 in)</li>
2416 * <li>FR_DOUBLE_COURONNE (460x720 mm ; 18.11x28.35 in)</li>
2417 * <li>FR_CARRE (450x560 mm ; 17.72x22.05 in)</li>
2418 * <li>FR_COQUILLE (440x560 mm ; 17.32x22.05 in)</li>
2419 * <li>FR_DOUBLE_TELLIERE (440x680 mm ; 17.32x26.77 in)</li>
2420 * <li>FR_DOUBLE_CLOCHE (400x600 mm ; 15.75x23.62 in)</li>
2421 * <li>FR_DOUBLE_POT (400x620 mm ; 15.75x24.41 in)</li>
2422 * <li>FR_ECU (400x520 mm ; 15.75x20.47 in)</li>
2423 * <li>FR_COURONNE (360x460 mm ; 14.17x18.11 in)</li>
2424 * <li>FR_TELLIERE (340x440 mm ; 13.39x17.32 in)</li>
2425 * <li>FR_POT (310x400 mm ; 12.20x15.75 in)</li>
2426 * </ul>
2427 * @return array containing page width and height in points
2428 * @public
2429 * @since 5.0.010 (2010-05-17)
2430 */
2431 public function getPageSizeFromFormat($format) {
2432 // Paper cordinates are calculated in this way: (inches * 72) where (1 inch = 25.4 mm)
2433 switch (strtoupper($format)) {
2434 // ISO 216 A Series + 2 SIS 014711 extensions
2435 case 'A0' : {$pf = array( 2383.937, 3370.394); break;}
2436 case 'A1' : {$pf = array( 1683.780, 2383.937); break;}
2437 case 'A2' : {$pf = array( 1190.551, 1683.780); break;}
2438 case 'A3' : {$pf = array( 841.890, 1190.551); break;}
2439 case 'A4' : {$pf = array( 595.276, 841.890); break;}
2440 case 'A5' : {$pf = array( 419.528, 595.276); break;}
2441 case 'A6' : {$pf = array( 297.638, 419.528); break;}
2442 case 'A7' : {$pf = array( 209.764, 297.638); break;}
2443 case 'A8' : {$pf = array( 147.402, 209.764); break;}
2444 case 'A9' : {$pf = array( 104.882, 147.402); break;}
2445 case 'A10': {$pf = array( 73.701, 104.882); break;}
2446 case 'A11': {$pf = array( 51.024, 73.701); break;}
2447 case 'A12': {$pf = array( 36.850, 51.024); break;}
2448 // ISO 216 B Series + 2 SIS 014711 extensions
2449 case 'B0' : {$pf = array( 2834.646, 4008.189); break;}
2450 case 'B1' : {$pf = array( 2004.094, 2834.646); break;}
2451 case 'B2' : {$pf = array( 1417.323, 2004.094); break;}
2452 case 'B3' : {$pf = array( 1000.630, 1417.323); break;}
2453 case 'B4' : {$pf = array( 708.661, 1000.630); break;}
2454 case 'B5' : {$pf = array( 498.898, 708.661); break;}
2455 case 'B6' : {$pf = array( 354.331, 498.898); break;}
2456 case 'B7' : {$pf = array( 249.449, 354.331); break;}
2457 case 'B8' : {$pf = array( 175.748, 249.449); break;}
2458 case 'B9' : {$pf = array( 124.724, 175.748); break;}
2459 case 'B10': {$pf = array( 87.874, 124.724); break;}
2460 case 'B11': {$pf = array( 62.362, 87.874); break;}
2461 case 'B12': {$pf = array( 42.520, 62.362); break;}
2462 // ISO 216 C Series + 2 SIS 014711 extensions + 2 EXTENSION
2463 case 'C0' : {$pf = array( 2599.370, 3676.535); break;}
2464 case 'C1' : {$pf = array( 1836.850, 2599.370); break;}
2465 case 'C2' : {$pf = array( 1298.268, 1836.850); break;}
2466 case 'C3' : {$pf = array( 918.425, 1298.268); break;}
2467 case 'C4' : {$pf = array( 649.134, 918.425); break;}
2468 case 'C5' : {$pf = array( 459.213, 649.134); break;}
2469 case 'C6' : {$pf = array( 323.150, 459.213); break;}
2470 case 'C7' : {$pf = array( 229.606, 323.150); break;}
2471 case 'C8' : {$pf = array( 161.575, 229.606); break;}
2472 case 'C9' : {$pf = array( 113.386, 161.575); break;}
2473 case 'C10': {$pf = array( 79.370, 113.386); break;}
2474 case 'C11': {$pf = array( 56.693, 79.370); break;}
2475 case 'C12': {$pf = array( 39.685, 56.693); break;}
2476 case 'C76': {$pf = array( 229.606, 459.213); break;}
2477 case 'DL' : {$pf = array( 311.811, 623.622); break;}
2478 // SIS 014711 E Series
2479 case 'E0' : {$pf = array( 2491.654, 3517.795); break;}
2480 case 'E1' : {$pf = array( 1757.480, 2491.654); break;}
2481 case 'E2' : {$pf = array( 1247.244, 1757.480); break;}
2482 case 'E3' : {$pf = array( 878.740, 1247.244); break;}
2483 case 'E4' : {$pf = array( 623.622, 878.740); break;}
2484 case 'E5' : {$pf = array( 439.370, 623.622); break;}
2485 case 'E6' : {$pf = array( 311.811, 439.370); break;}
2486 case 'E7' : {$pf = array( 221.102, 311.811); break;}
2487 case 'E8' : {$pf = array( 155.906, 221.102); break;}
2488 case 'E9' : {$pf = array( 110.551, 155.906); break;}
2489 case 'E10': {$pf = array( 76.535, 110.551); break;}
2490 case 'E11': {$pf = array( 53.858, 76.535); break;}
2491 case 'E12': {$pf = array( 36.850, 53.858); break;}
2492 // SIS 014711 G Series
2493 case 'G0' : {$pf = array( 2715.591, 3838.110); break;}
2494 case 'G1' : {$pf = array( 1919.055, 2715.591); break;}
2495 case 'G2' : {$pf = array( 1357.795, 1919.055); break;}
2496 case 'G3' : {$pf = array( 958.110, 1357.795); break;}
2497 case 'G4' : {$pf = array( 677.480, 958.110); break;}
2498 case 'G5' : {$pf = array( 479.055, 677.480); break;}
2499 case 'G6' : {$pf = array( 337.323, 479.055); break;}
2500 case 'G7' : {$pf = array( 238.110, 337.323); break;}
2501 case 'G8' : {$pf = array( 167.244, 238.110); break;}
2502 case 'G9' : {$pf = array( 119.055, 167.244); break;}
2503 case 'G10': {$pf = array( 82.205, 119.055); break;}
2504 case 'G11': {$pf = array( 59.528, 82.205); break;}
2505 case 'G12': {$pf = array( 39.685, 59.528); break;}
2506 // ISO Press
2507 case 'RA0': {$pf = array( 2437.795, 3458.268); break;}
2508 case 'RA1': {$pf = array( 1729.134, 2437.795); break;}
2509 case 'RA2': {$pf = array( 1218.898, 1729.134); break;}
2510 case 'RA3': {$pf = array( 864.567, 1218.898); break;}
2511 case 'RA4': {$pf = array( 609.449, 864.567); break;}
2512 case 'SRA0': {$pf = array( 2551.181, 3628.346); break;}
2513 case 'SRA1': {$pf = array( 1814.173, 2551.181); break;}
2514 case 'SRA2': {$pf = array( 1275.591, 1814.173); break;}
2515 case 'SRA3': {$pf = array( 907.087, 1275.591); break;}
2516 case 'SRA4': {$pf = array( 637.795, 907.087); break;}
2517 // German DIN 476
2518 case '4A0': {$pf = array( 4767.874, 6740.787); break;}
2519 case '2A0': {$pf = array( 3370.394, 4767.874); break;}
2520 // Variations on the ISO Standard
2521 case 'A2_EXTRA' : {$pf = array( 1261.417, 1754.646); break;}
2522 case 'A3+' : {$pf = array( 932.598, 1369.134); break;}
2523 case 'A3_EXTRA' : {$pf = array( 912.756, 1261.417); break;}
2524 case 'A3_SUPER' : {$pf = array( 864.567, 1440.000); break;}
2525 case 'SUPER_A3' : {$pf = array( 864.567, 1380.472); break;}
2526 case 'A4_EXTRA' : {$pf = array( 666.142, 912.756); break;}
2527 case 'A4_SUPER' : {$pf = array( 649.134, 912.756); break;}
2528 case 'SUPER_A4' : {$pf = array( 643.465, 1009.134); break;}
2529 case 'A4_LONG' : {$pf = array( 595.276, 986.457); break;}
2530 case 'F4' : {$pf = array( 595.276, 935.433); break;}
2531 case 'SO_B5_EXTRA': {$pf = array( 572.598, 782.362); break;}
2532 case 'A5_EXTRA' : {$pf = array( 490.394, 666.142); break;}
2533 // ANSI Series
2534 case 'ANSI_E': {$pf = array( 2448.000, 3168.000); break;}
2535 case 'ANSI_D': {$pf = array( 1584.000, 2448.000); break;}
2536 case 'ANSI_C': {$pf = array( 1224.000, 1584.000); break;}
2537 case 'ANSI_B': {$pf = array( 792.000, 1224.000); break;}
2538 case 'ANSI_A': {$pf = array( 612.000, 792.000); break;}
2539 // Traditional 'Loose' North American Paper Sizes
2540 case 'USLEDGER':
2541 case 'LEDGER' : {$pf = array( 1224.000, 792.000); break;}
2542 case 'ORGANIZERK':
2543 case 'BIBLE':
2544 case 'USTABLOID':
2545 case 'TABLOID': {$pf = array( 792.000, 1224.000); break;}
2546 case 'ORGANIZERM':
2547 case 'USLETTER':
2548 case 'LETTER' : {$pf = array( 612.000, 792.000); break;}
2549 case 'USLEGAL':
2550 case 'LEGAL' : {$pf = array( 612.000, 1008.000); break;}
2551 case 'GOVERNMENTLETTER':
2552 case 'GLETTER': {$pf = array( 576.000, 756.000); break;}
2553 case 'JUNIORLEGAL':
2554 case 'JLEGAL' : {$pf = array( 576.000, 360.000); break;}
2555 // Other North American Paper Sizes
2556 case 'QUADDEMY': {$pf = array( 2520.000, 3240.000); break;}
2557 case 'SUPER_B': {$pf = array( 936.000, 1368.000); break;}
2558 case 'QUARTO': {$pf = array( 648.000, 792.000); break;}
2559 case 'GOVERNMENTLEGAL':
2560 case 'FOLIO': {$pf = array( 612.000, 936.000); break;}
2561 case 'MONARCH':
2562 case 'EXECUTIVE': {$pf = array( 522.000, 756.000); break;}
2563 case 'ORGANIZERL':
2564 case 'STATEMENT':
2565 case 'MEMO': {$pf = array( 396.000, 612.000); break;}
2566 case 'FOOLSCAP': {$pf = array( 595.440, 936.000); break;}
2567 case 'COMPACT': {$pf = array( 306.000, 486.000); break;}
2568 case 'ORGANIZERJ': {$pf = array( 198.000, 360.000); break;}
2569 // Canadian standard CAN 2-9.60M
2570 case 'P1': {$pf = array( 1587.402, 2437.795); break;}
2571 case 'P2': {$pf = array( 1218.898, 1587.402); break;}
2572 case 'P3': {$pf = array( 793.701, 1218.898); break;}
2573 case 'P4': {$pf = array( 609.449, 793.701); break;}
2574 case 'P5': {$pf = array( 396.850, 609.449); break;}
2575 case 'P6': {$pf = array( 303.307, 396.850); break;}
2576 // North American Architectural Sizes
2577 case 'ARCH_E' : {$pf = array( 2592.000, 3456.000); break;}
2578 case 'ARCH_E1': {$pf = array( 2160.000, 3024.000); break;}
2579 case 'ARCH_D' : {$pf = array( 1728.000, 2592.000); break;}
2580 case 'BROADSHEET':
2581 case 'ARCH_C' : {$pf = array( 1296.000, 1728.000); break;}
2582 case 'ARCH_B' : {$pf = array( 864.000, 1296.000); break;}
2583 case 'ARCH_A' : {$pf = array( 648.000, 864.000); break;}
2584 // --- North American Envelope Sizes ---
2585 // - Announcement Envelopes
2586 case 'ANNENV_A2' : {$pf = array( 314.640, 414.000); break;}
2587 case 'ANNENV_A6' : {$pf = array( 342.000, 468.000); break;}
2588 case 'ANNENV_A7' : {$pf = array( 378.000, 522.000); break;}
2589 case 'ANNENV_A8' : {$pf = array( 396.000, 584.640); break;}
2590 case 'ANNENV_A10' : {$pf = array( 450.000, 692.640); break;}
2591 case 'ANNENV_SLIM': {$pf = array( 278.640, 638.640); break;}
2592 // - Commercial Envelopes
2593 case 'COMMENV_N6_1/4': {$pf = array( 252.000, 432.000); break;}
2594 case 'COMMENV_N6_3/4': {$pf = array( 260.640, 468.000); break;}
2595 case 'COMMENV_N8' : {$pf = array( 278.640, 540.000); break;}
2596 case 'COMMENV_N9' : {$pf = array( 278.640, 638.640); break;}
2597 case 'COMMENV_N10' : {$pf = array( 296.640, 684.000); break;}
2598 case 'COMMENV_N11' : {$pf = array( 324.000, 746.640); break;}
2599 case 'COMMENV_N12' : {$pf = array( 342.000, 792.000); break;}
2600 case 'COMMENV_N14' : {$pf = array( 360.000, 828.000); break;}
2601 // - Catalogue Envelopes
2602 case 'CATENV_N1' : {$pf = array( 432.000, 648.000); break;}
2603 case 'CATENV_N1_3/4' : {$pf = array( 468.000, 684.000); break;}
2604 case 'CATENV_N2' : {$pf = array( 468.000, 720.000); break;}
2605 case 'CATENV_N3' : {$pf = array( 504.000, 720.000); break;}
2606 case 'CATENV_N6' : {$pf = array( 540.000, 756.000); break;}
2607 case 'CATENV_N7' : {$pf = array( 576.000, 792.000); break;}
2608 case 'CATENV_N8' : {$pf = array( 594.000, 810.000); break;}
2609 case 'CATENV_N9_1/2' : {$pf = array( 612.000, 756.000); break;}
2610 case 'CATENV_N9_3/4' : {$pf = array( 630.000, 810.000); break;}
2611 case 'CATENV_N10_1/2': {$pf = array( 648.000, 864.000); break;}
2612 case 'CATENV_N12_1/2': {$pf = array( 684.000, 900.000); break;}
2613 case 'CATENV_N13_1/2': {$pf = array( 720.000, 936.000); break;}
2614 case 'CATENV_N14_1/4': {$pf = array( 810.000, 882.000); break;}
2615 case 'CATENV_N14_1/2': {$pf = array( 828.000, 1044.000); break;}
2616 // Japanese (JIS P 0138-61) Standard B-Series
2617 case 'JIS_B0' : {$pf = array( 2919.685, 4127.244); break;}
2618 case 'JIS_B1' : {$pf = array( 2063.622, 2919.685); break;}
2619 case 'JIS_B2' : {$pf = array( 1459.843, 2063.622); break;}
2620 case 'JIS_B3' : {$pf = array( 1031.811, 1459.843); break;}
2621 case 'JIS_B4' : {$pf = array( 728.504, 1031.811); break;}
2622 case 'JIS_B5' : {$pf = array( 515.906, 728.504); break;}
2623 case 'JIS_B6' : {$pf = array( 362.835, 515.906); break;}
2624 case 'JIS_B7' : {$pf = array( 257.953, 362.835); break;}
2625 case 'JIS_B8' : {$pf = array( 181.417, 257.953); break;}
2626 case 'JIS_B9' : {$pf = array( 127.559, 181.417); break;}
2627 case 'JIS_B10': {$pf = array( 90.709, 127.559); break;}
2628 case 'JIS_B11': {$pf = array( 62.362, 90.709); break;}
2629 case 'JIS_B12': {$pf = array( 45.354, 62.362); break;}
2630 // PA Series
2631 case 'PA0' : {$pf = array( 2381.102, 3174.803,); break;}
2632 case 'PA1' : {$pf = array( 1587.402, 2381.102); break;}
2633 case 'PA2' : {$pf = array( 1190.551, 1587.402); break;}
2634 case 'PA3' : {$pf = array( 793.701, 1190.551); break;}
2635 case 'PA4' : {$pf = array( 595.276, 793.701); break;}
2636 case 'PA5' : {$pf = array( 396.850, 595.276); break;}
2637 case 'PA6' : {$pf = array( 297.638, 396.850); break;}
2638 case 'PA7' : {$pf = array( 198.425, 297.638); break;}
2639 case 'PA8' : {$pf = array( 147.402, 198.425); break;}
2640 case 'PA9' : {$pf = array( 99.213, 147.402); break;}
2641 case 'PA10': {$pf = array( 73.701, 99.213); break;}
2642 // Standard Photographic Print Sizes
2643 case 'PASSPORT_PHOTO': {$pf = array( 99.213, 127.559); break;}
2644 case 'E' : {$pf = array( 233.858, 340.157); break;}
2645 case 'L':
2646 case '3R' : {$pf = array( 252.283, 360.000); break;}
2647 case 'KG':
2648 case '4R' : {$pf = array( 289.134, 430.866); break;}
2649 case '4D' : {$pf = array( 340.157, 430.866); break;}
2650 case '2L':
2651 case '5R' : {$pf = array( 360.000, 504.567); break;}
2652 case '8P':
2653 case '6R' : {$pf = array( 430.866, 575.433); break;}
2654 case '6P':
2655 case '8R' : {$pf = array( 575.433, 720.000); break;}
2656 case '6PW':
2657 case 'S8R' : {$pf = array( 575.433, 864.567); break;}
2658 case '4P':
2659 case '10R' : {$pf = array( 720.000, 864.567); break;}
2660 case '4PW':
2661 case 'S10R': {$pf = array( 720.000, 1080.000); break;}
2662 case '11R' : {$pf = array( 790.866, 1009.134); break;}
2663 case 'S11R': {$pf = array( 790.866, 1224.567); break;}
2664 case '12R' : {$pf = array( 864.567, 1080.000); break;}
2665 case 'S12R': {$pf = array( 864.567, 1292.598); break;}
2666 // Common Newspaper Sizes
2667 case 'NEWSPAPER_BROADSHEET': {$pf = array( 2125.984, 1700.787); break;}
2668 case 'NEWSPAPER_BERLINER' : {$pf = array( 1332.283, 892.913); break;}
2669 case 'NEWSPAPER_TABLOID':
2670 case 'NEWSPAPER_COMPACT' : {$pf = array( 1218.898, 793.701); break;}
2671 // Business Cards
2672 case 'CREDIT_CARD':
2673 case 'BUSINESS_CARD':
2674 case 'BUSINESS_CARD_ISO7810': {$pf = array( 153.014, 242.646); break;}
2675 case 'BUSINESS_CARD_ISO216' : {$pf = array( 147.402, 209.764); break;}
2676 case 'BUSINESS_CARD_IT':
2677 case 'BUSINESS_CARD_UK':
2678 case 'BUSINESS_CARD_FR':
2679 case 'BUSINESS_CARD_DE':
2680 case 'BUSINESS_CARD_ES' : {$pf = array( 155.906, 240.945); break;}
2681 case 'BUSINESS_CARD_CA':
2682 case 'BUSINESS_CARD_US' : {$pf = array( 144.567, 252.283); break;}
2683 case 'BUSINESS_CARD_JP' : {$pf = array( 155.906, 257.953); break;}
2684 case 'BUSINESS_CARD_HK' : {$pf = array( 153.071, 255.118); break;}
2685 case 'BUSINESS_CARD_AU':
2686 case 'BUSINESS_CARD_DK':
2687 case 'BUSINESS_CARD_SE' : {$pf = array( 155.906, 255.118); break;}
2688 case 'BUSINESS_CARD_RU':
2689 case 'BUSINESS_CARD_CZ':
2690 case 'BUSINESS_CARD_FI':
2691 case 'BUSINESS_CARD_HU':
2692 case 'BUSINESS_CARD_IL' : {$pf = array( 141.732, 255.118); break;}
2693 // Billboards
2694 case '4SHEET' : {$pf = array( 2880.000, 4320.000); break;}
2695 case '6SHEET' : {$pf = array( 3401.575, 5102.362); break;}
2696 case '12SHEET': {$pf = array( 8640.000, 4320.000); break;}
2697 case '16SHEET': {$pf = array( 5760.000, 8640.000); break;}
2698 case '32SHEET': {$pf = array(11520.000, 8640.000); break;}
2699 case '48SHEET': {$pf = array(17280.000, 8640.000); break;}
2700 case '64SHEET': {$pf = array(23040.000, 8640.000); break;}
2701 case '96SHEET': {$pf = array(34560.000, 8640.000); break;}
2702 // Old European Sizes
2703 // - Old Imperial English Sizes
2704 case 'EN_EMPEROR' : {$pf = array( 3456.000, 5184.000); break;}
2705 case 'EN_ANTIQUARIAN' : {$pf = array( 2232.000, 3816.000); break;}
2706 case 'EN_GRAND_EAGLE' : {$pf = array( 2070.000, 3024.000); break;}
2707 case 'EN_DOUBLE_ELEPHANT' : {$pf = array( 1926.000, 2880.000); break;}
2708 case 'EN_ATLAS' : {$pf = array( 1872.000, 2448.000); break;}
2709 case 'EN_COLOMBIER' : {$pf = array( 1692.000, 2484.000); break;}
2710 case 'EN_ELEPHANT' : {$pf = array( 1656.000, 2016.000); break;}
2711 case 'EN_DOUBLE_DEMY' : {$pf = array( 1620.000, 2556.000); break;}
2712 case 'EN_IMPERIAL' : {$pf = array( 1584.000, 2160.000); break;}
2713 case 'EN_PRINCESS' : {$pf = array( 1548.000, 2016.000); break;}
2714 case 'EN_CARTRIDGE' : {$pf = array( 1512.000, 1872.000); break;}
2715 case 'EN_DOUBLE_LARGE_POST': {$pf = array( 1512.000, 2376.000); break;}
2716 case 'EN_ROYAL' : {$pf = array( 1440.000, 1800.000); break;}
2717 case 'EN_SHEET':
2718 case 'EN_HALF_POST' : {$pf = array( 1404.000, 1692.000); break;}
2719 case 'EN_SUPER_ROYAL' : {$pf = array( 1368.000, 1944.000); break;}
2720 case 'EN_DOUBLE_POST' : {$pf = array( 1368.000, 2196.000); break;}
2721 case 'EN_MEDIUM' : {$pf = array( 1260.000, 1656.000); break;}
2722 case 'EN_DEMY' : {$pf = array( 1260.000, 1620.000); break;}
2723 case 'EN_LARGE_POST' : {$pf = array( 1188.000, 1512.000); break;}
2724 case 'EN_COPY_DRAUGHT' : {$pf = array( 1152.000, 1440.000); break;}
2725 case 'EN_POST' : {$pf = array( 1116.000, 1386.000); break;}
2726 case 'EN_CROWN' : {$pf = array( 1080.000, 1440.000); break;}
2727 case 'EN_PINCHED_POST' : {$pf = array( 1062.000, 1332.000); break;}
2728 case 'EN_BRIEF' : {$pf = array( 972.000, 1152.000); break;}
2729 case 'EN_FOOLSCAP' : {$pf = array( 972.000, 1224.000); break;}
2730 case 'EN_SMALL_FOOLSCAP' : {$pf = array( 954.000, 1188.000); break;}
2731 case 'EN_POTT' : {$pf = array( 900.000, 1080.000); break;}
2732 // - Old Imperial Belgian Sizes
2733 case 'BE_GRAND_AIGLE' : {$pf = array( 1984.252, 2948.031); break;}
2734 case 'BE_COLOMBIER' : {$pf = array( 1757.480, 2409.449); break;}
2735 case 'BE_DOUBLE_CARRE': {$pf = array( 1757.480, 2607.874); break;}
2736 case 'BE_ELEPHANT' : {$pf = array( 1746.142, 2182.677); break;}
2737 case 'BE_PETIT_AIGLE' : {$pf = array( 1700.787, 2381.102); break;}
2738 case 'BE_GRAND_JESUS' : {$pf = array( 1559.055, 2069.291); break;}
2739 case 'BE_JESUS' : {$pf = array( 1530.709, 2069.291); break;}
2740 case 'BE_RAISIN' : {$pf = array( 1417.323, 1842.520); break;}
2741 case 'BE_GRAND_MEDIAN': {$pf = array( 1303.937, 1714.961); break;}
2742 case 'BE_DOUBLE_POSTE': {$pf = array( 1233.071, 1601.575); break;}
2743 case 'BE_COQUILLE' : {$pf = array( 1218.898, 1587.402); break;}
2744 case 'BE_PETIT_MEDIAN': {$pf = array( 1176.378, 1502.362); break;}
2745 case 'BE_RUCHE' : {$pf = array( 1020.472, 1303.937); break;}
2746 case 'BE_PROPATRIA' : {$pf = array( 977.953, 1218.898); break;}
2747 case 'BE_LYS' : {$pf = array( 898.583, 1125.354); break;}
2748 case 'BE_POT' : {$pf = array( 870.236, 1088.504); break;}
2749 case 'BE_ROSETTE' : {$pf = array( 765.354, 983.622); break;}
2750 // - Old Imperial French Sizes
2751 case 'FR_UNIVERS' : {$pf = array( 2834.646, 3685.039); break;}
2752 case 'FR_DOUBLE_COLOMBIER' : {$pf = array( 2551.181, 3571.654); break;}
2753 case 'FR_GRANDE_MONDE' : {$pf = array( 2551.181, 3571.654); break;}
2754 case 'FR_DOUBLE_SOLEIL' : {$pf = array( 2267.717, 3401.575); break;}
2755 case 'FR_DOUBLE_JESUS' : {$pf = array( 2154.331, 3174.803); break;}
2756 case 'FR_GRAND_AIGLE' : {$pf = array( 2125.984, 3004.724); break;}
2757 case 'FR_PETIT_AIGLE' : {$pf = array( 1984.252, 2664.567); break;}
2758 case 'FR_DOUBLE_RAISIN' : {$pf = array( 1842.520, 2834.646); break;}
2759 case 'FR_JOURNAL' : {$pf = array( 1842.520, 2664.567); break;}
2760 case 'FR_COLOMBIER_AFFICHE': {$pf = array( 1785.827, 2551.181); break;}
2761 case 'FR_DOUBLE_CAVALIER' : {$pf = array( 1757.480, 2607.874); break;}
2762 case 'FR_CLOCHE' : {$pf = array( 1700.787, 2267.717); break;}
2763 case 'FR_SOLEIL' : {$pf = array( 1700.787, 2267.717); break;}
2764 case 'FR_DOUBLE_CARRE' : {$pf = array( 1587.402, 2551.181); break;}
2765 case 'FR_DOUBLE_COQUILLE' : {$pf = array( 1587.402, 2494.488); break;}
2766 case 'FR_JESUS' : {$pf = array( 1587.402, 2154.331); break;}
2767 case 'FR_RAISIN' : {$pf = array( 1417.323, 1842.520); break;}
2768 case 'FR_CAVALIER' : {$pf = array( 1303.937, 1757.480); break;}
2769 case 'FR_DOUBLE_COURONNE' : {$pf = array( 1303.937, 2040.945); break;}
2770 case 'FR_CARRE' : {$pf = array( 1275.591, 1587.402); break;}
2771 case 'FR_COQUILLE' : {$pf = array( 1247.244, 1587.402); break;}
2772 case 'FR_DOUBLE_TELLIERE' : {$pf = array( 1247.244, 1927.559); break;}
2773 case 'FR_DOUBLE_CLOCHE' : {$pf = array( 1133.858, 1700.787); break;}
2774 case 'FR_DOUBLE_POT' : {$pf = array( 1133.858, 1757.480); break;}
2775 case 'FR_ECU' : {$pf = array( 1133.858, 1474.016); break;}
2776 case 'FR_COURONNE' : {$pf = array( 1020.472, 1303.937); break;}
2777 case 'FR_TELLIERE' : {$pf = array( 963.780, 1247.244); break;}
2778 case 'FR_POT' : {$pf = array( 878.740, 1133.858); break;}
2779 // DEFAULT ISO A4
2780 default: {$pf = array( 595.276, 841.890); break;}
2781 }
2782 return $pf;
2783 }
2784
2785 /**
2786 * Change the format of the current page
2787 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() documentation or an array of two numners (width, height) or an array containing the following measures and options:<ul>
2788 * <li>['format'] = page format name (one of the above);</li>
2789 * <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>
2790 * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2791 * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2792 * <li>['MediaBox']['llx'] : lower-left x coordinate in points</li>
2793 * <li>['MediaBox']['lly'] : lower-left y coordinate in points</li>
2794 * <li>['MediaBox']['urx'] : upper-right x coordinate in points</li>
2795 * <li>['MediaBox']['ury'] : upper-right y coordinate in points</li>
2796 * <li>['CropBox'] : the visible region of default user space:</li>
2797 * <li>['CropBox']['llx'] : lower-left x coordinate in points</li>
2798 * <li>['CropBox']['lly'] : lower-left y coordinate in points</li>
2799 * <li>['CropBox']['urx'] : upper-right x coordinate in points</li>
2800 * <li>['CropBox']['ury'] : upper-right y coordinate in points</li>
2801 * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2802 * <li>['BleedBox']['llx'] : lower-left x coordinate in points</li>
2803 * <li>['BleedBox']['lly'] : lower-left y coordinate in points</li>
2804 * <li>['BleedBox']['urx'] : upper-right x coordinate in points</li>
2805 * <li>['BleedBox']['ury'] : upper-right y coordinate in points</li>
2806 * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2807 * <li>['TrimBox']['llx'] : lower-left x coordinate in points</li>
2808 * <li>['TrimBox']['lly'] : lower-left y coordinate in points</li>
2809 * <li>['TrimBox']['urx'] : upper-right x coordinate in points</li>
2810 * <li>['TrimBox']['ury'] : upper-right y coordinate in points</li>
2811 * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2812 * <li>['ArtBox']['llx'] : lower-left x coordinate in points</li>
2813 * <li>['ArtBox']['lly'] : lower-left y coordinate in points</li>
2814 * <li>['ArtBox']['urx'] : upper-right x coordinate in points</li>
2815 * <li>['ArtBox']['ury'] : upper-right y coordinate in points</li>
2816 * <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>
2817 * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2818 * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2819 * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2820 * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2821 * <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>
2822 * <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>
2823 * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2824 * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2825 * <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>
2826 * <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>
2827 * <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>
2828 * <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>
2829 * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2830 * </ul>
2831 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul>
2832 * <li>P or Portrait (default)</li>
2833 * <li>L or Landscape</li>
2834 * <li>'' (empty string) for automatic orientation</li>
2835 * </ul>
2836 * @protected
2837 * @since 3.0.015 (2008-06-06)
2838 * @see getPageSizeFromFormat()
2839 */
2840 protected function setPageFormat($format, $orientation='P') {
2841 if (!empty($format) AND isset($this->pagedim[$this->page])) {
2842 // remove inherited values
2843 unset($this->pagedim[$this->page]);
2844 }
2845 if (is_string($format)) {
2846 // get page measures from format name
2847 $pf = $this->getPageSizeFromFormat($format);
2848 $this->fwPt = $pf[0];
2849 $this->fhPt = $pf[1];
2850 } else {
2851 // the boundaries of the physical medium on which the page shall be displayed or printed
2852 if (isset($format['MediaBox'])) {
2853 $this->setPageBoxes($this->page, 'MediaBox', $format['MediaBox']['llx'], $format['MediaBox']['lly'], $format['MediaBox']['urx'], $format['MediaBox']['ury'], false);
2854 $this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2855 $this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2856 } else {
2857 if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2858 $pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2859 } else {
2860 if (!isset($format['format'])) {
2861 // default value
2862 $format['format'] = 'A4';
2863 }
2864 $pf = $this->getPageSizeFromFormat($format['format']);
2865 }
2866 $this->fwPt = $pf[0];
2867 $this->fhPt = $pf[1];
2868 $this->setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true);
2869 }
2870 // the visible region of default user space
2871 if (isset($format['CropBox'])) {
2872 $this->setPageBoxes($this->page, 'CropBox', $format['CropBox']['llx'], $format['CropBox']['lly'], $format['CropBox']['urx'], $format['CropBox']['ury'], false);
2873 }
2874 // the region to which the contents of the page shall be clipped when output in a production environment
2875 if (isset($format['BleedBox'])) {
2876 $this->setPageBoxes($this->page, 'BleedBox', $format['BleedBox']['llx'], $format['BleedBox']['lly'], $format['BleedBox']['urx'], $format['BleedBox']['ury'], false);
2877 }
2878 // the intended dimensions of the finished page after trimming
2879 if (isset($format['TrimBox'])) {
2880 $this->setPageBoxes($this->page, 'TrimBox', $format['TrimBox']['llx'], $format['TrimBox']['lly'], $format['TrimBox']['urx'], $format['TrimBox']['ury'], false);
2881 }
2882 // the page's meaningful content (including potential white space)
2883 if (isset($format['ArtBox'])) {
2884 $this->setPageBoxes($this->page, 'ArtBox', $format['ArtBox']['llx'], $format['ArtBox']['lly'], $format['ArtBox']['urx'], $format['ArtBox']['ury'], false);
2885 }
2886 // specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2887 if (isset($format['BoxColorInfo'])) {
2888 $this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2889 }
2890 if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2891 // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2892 $this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2893 }
2894 if (isset($format['PZ'])) {
2895 // The page's preferred zoom (magnification) factor
2896 $this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2897 }
2898 if (isset($format['trans'])) {
2899 // The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2900 if (isset($format['trans']['Dur'])) {
2901 // The page's display duration
2902 $this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2903 }
2904 $stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2905 if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2906 // The transition style that shall be used when moving to this page from another during a presentation
2907 $this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2908 $valid_effect = array('Split', 'Blinds');
2909 $valid_vals = array('H', 'V');
2910 if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2911 $this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2912 }
2913 $valid_effect = array('Split', 'Box', 'Fly');
2914 $valid_vals = array('I', 'O');
2915 if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2916 $this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2917 }
2918 $valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2919 if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2920 if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2921 OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2922 OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2923 $this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2924 }
2925 }
2926 if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2927 $this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2928 }
2929 if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2930 $this->pagedim[$this->page]['trans']['B'] = 'true';
2931 }
2932 } else {
2933 $this->pagedim[$this->page]['trans']['S'] = 'R';
2934 }
2935 if (isset($format['trans']['D'])) {
2936 // The duration of the transition effect, in seconds
2937 $this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2938 } else {
2939 $this->pagedim[$this->page]['trans']['D'] = 1;
2940 }
2941 }
2942 }
2943 $this->setPageOrientation($orientation);
2944 }
2945
2946 /**
2947 * Set page boundaries.
2948 * @param $page (int) page number
2949 * @param $type (string) valid values are: <ul><li>'MediaBox' : the boundaries of the physical medium on which the page shall be displayed or printed;</li><li>'CropBox' : the visible region of default user space;</li><li>'BleedBox' : the region to which the contents of the page shall be clipped when output in a production environment;</li><li>'TrimBox' : the intended dimensions of the finished page after trimming;</li><li>'ArtBox' : the page's meaningful content (including potential white space).</li></ul>
2950 * @param $llx (float) lower-left x coordinate in user units
2951 * @param $lly (float) lower-left y coordinate in user units
2952 * @param $urx (float) upper-right x coordinate in user units
2953 * @param $ury (float) upper-right y coordinate in user units
2954 * @param $points (boolean) if true uses user units as unit of measure, otherwise uses PDF points
2955 * @public
2956 * @since 5.0.010 (2010-05-17)
2957 */
2958 public function setPageBoxes($page, $type, $llx, $lly, $urx, $ury, $points=false) {
2959 if (!isset($this->pagedim[$page])) {
2960 // initialize array
2961 $this->pagedim[$page] = array();
2962 }
2963 $pageboxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
2964 if (!in_array($type, $pageboxes)) {
2965 return;
2966 }
2967 if ($points) {
2968 $k = 1;
2969 } else {
2970 $k = $this->k;
2971 }
2972 $this->pagedim[$page][$type]['llx'] = ($llx * $k);
2973 $this->pagedim[$page][$type]['lly'] = ($lly * $k);
2974 $this->pagedim[$page][$type]['urx'] = ($urx * $k);
2975 $this->pagedim[$page][$type]['ury'] = ($ury * $k);
2976 }
2977
2978 /**
2979 * Swap X and Y coordinates of page boxes (change page boxes orientation).
2980 * @param $page (int) page number
2981 * @protected
2982 * @since 5.0.010 (2010-05-17)
2983 */
2984 protected function swapPageBoxCoordinates($page) {
2985 $pageboxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
2986 foreach ($pageboxes as $type) {
2987 // swap X and Y coordinates
2988 if (isset($this->pagedim[$page][$type])) {
2989 $tmp = $this->pagedim[$page][$type]['llx'];
2990 $this->pagedim[$page][$type]['llx'] = $this->pagedim[$page][$type]['lly'];
2991 $this->pagedim[$page][$type]['lly'] = $tmp;
2992 $tmp = $this->pagedim[$page][$type]['urx'];
2993 $this->pagedim[$page][$type]['urx'] = $this->pagedim[$page][$type]['ury'];
2994 $this->pagedim[$page][$type]['ury'] = $tmp;
2995 }
2996 }
2997 }
2998
2999 /**
3000 * Set page orientation.
3001 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
3002 * @param $autopagebreak (boolean) Boolean indicating if auto-page-break mode should be on or off.
3003 * @param $bottommargin (float) bottom margin of the page.
3004 * @public
3005 * @since 3.0.015 (2008-06-06)
3006 */
3007 public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
3008 if (!isset($this->pagedim[$this->page]['MediaBox'])) {
3009 // the boundaries of the physical medium on which the page shall be displayed or printed
3010 $this->setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true);
3011 }
3012 if (!isset($this->pagedim[$this->page]['CropBox'])) {
3013 // the visible region of default user space
3014 $this->setPageBoxes($this->page, 'CropBox', $this->pagedim[$this->page]['MediaBox']['llx'], $this->pagedim[$this->page]['MediaBox']['lly'], $this->pagedim[$this->page]['MediaBox']['urx'], $this->pagedim[$this->page]['MediaBox']['ury'], true);
3015 }
3016 if (!isset($this->pagedim[$this->page]['BleedBox'])) {
3017 // the region to which the contents of the page shall be clipped when output in a production environment
3018 $this->setPageBoxes($this->page, 'BleedBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true);
3019 }
3020 if (!isset($this->pagedim[$this->page]['TrimBox'])) {
3021 // the intended dimensions of the finished page after trimming
3022 $this->setPageBoxes($this->page, 'TrimBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true);
3023 }
3024 if (!isset($this->pagedim[$this->page]['ArtBox'])) {
3025 // the page's meaningful content (including potential white space)
3026 $this->setPageBoxes($this->page, 'ArtBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true);
3027 }
3028 if (!isset($this->pagedim[$this->page]['Rotate'])) {
3029 // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
3030 $this->pagedim[$this->page]['Rotate'] = 0;
3031 }
3032 if (!isset($this->pagedim[$this->page]['PZ'])) {
3033 // The page's preferred zoom (magnification) factor
3034 $this->pagedim[$this->page]['PZ'] = 1;
3035 }
3036 if ($this->fwPt > $this->fhPt) {
3037 // landscape
3038 $default_orientation = 'L';
3039 } else {
3040 // portrait
3041 $default_orientation = 'P';
3042 }
3043 $valid_orientations = array('P', 'L');
3044 if (empty($orientation)) {
3045 $orientation = $default_orientation;
3046 } else {
3047 $orientation = strtoupper($orientation{0});
3048 }
3049 if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
3050 $this->CurOrientation = $orientation;
3051 $this->wPt = $this->fhPt;
3052 $this->hPt = $this->fwPt;
3053 } else {
3054 $this->CurOrientation = $default_orientation;
3055 $this->wPt = $this->fwPt;
3056 $this->hPt = $this->fhPt;
3057 }
3058 if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
3059 // swap X and Y coordinates (change page orientation)
3060 $this->swapPageBoxCoordinates($this->page);
3061 }
3062 $this->w = $this->wPt / $this->k;
3063 $this->h = $this->hPt / $this->k;
3064 if ($this->empty_string($autopagebreak)) {
3065 if (isset($this->AutoPageBreak)) {
3066 $autopagebreak = $this->AutoPageBreak;
3067 } else {
3068 $autopagebreak = true;
3069 }
3070 }
3071 if ($this->empty_string($bottommargin)) {
3072 if (isset($this->bMargin)) {
3073 $bottommargin = $this->bMargin;
3074 } else {
3075 // default value = 2 cm
3076 $bottommargin = 2 * 28.35 / $this->k;
3077 }
3078 }
3079 $this->SetAutoPageBreak($autopagebreak, $bottommargin);
3080 // store page dimensions
3081 $this->pagedim[$this->page]['w'] = $this->wPt;
3082 $this->pagedim[$this->page]['h'] = $this->hPt;
3083 $this->pagedim[$this->page]['wk'] = $this->w;
3084 $this->pagedim[$this->page]['hk'] = $this->h;
3085 $this->pagedim[$this->page]['tm'] = $this->tMargin;
3086 $this->pagedim[$this->page]['bm'] = $bottommargin;
3087 $this->pagedim[$this->page]['lm'] = $this->lMargin;
3088 $this->pagedim[$this->page]['rm'] = $this->rMargin;
3089 $this->pagedim[$this->page]['pb'] = $autopagebreak;
3090 $this->pagedim[$this->page]['or'] = $this->CurOrientation;
3091 $this->pagedim[$this->page]['olm'] = $this->original_lMargin;
3092 $this->pagedim[$this->page]['orm'] = $this->original_rMargin;
3093 }
3094
3095 /**
3096 * Set regular expression to detect withespaces or word separators.
3097 * The pattern delimiter must be the forward-slash character "/".
3098 * Some example patterns are:
3099 * <pre>
3100 * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
3101 * Unicode and PCRE unicode support: "/[^\S\P{Z}\xa0]/u"
3102 * Unicode and PCRE unicode support in Chinese mode: "/[^\S\P{Z}\P{Lo}\xa0]/u"
3103 * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
3104 * "\p{Z}" or "\p{Separator}": any kind of Unicode whitespace or invisible separator.
3105 * "\p{Lo}" or "\p{Other_Letter}": a Unicode letter or ideograph that does not have lowercase and uppercase variants.
3106 * "\p{Lo}" is needed for Chinese characters because are packed next to each other without spaces in between.
3107 * </pre>
3108 * @param $re (string) regular expression (leave empty for default).
3109 * @public
3110 * @since 4.6.016 (2009-06-15)
3111 */
3112 public function setSpacesRE($re='/[^\S\xa0]/') {
3113 $this->re_spaces = $re;
3114 $re_parts = explode('/', $re);
3115 // get pattern parts
3116 $this->re_space = array();
3117 if (isset($re_parts[1]) AND !empty($re_parts[1])) {
3118 $this->re_space['p'] = $re_parts[1];
3119 } else {
3120 $this->re_space['p'] = '[\s]';
3121 }
3122 // set pattern modifiers
3123 if (isset($re_parts[2]) AND !empty($re_parts[2])) {
3124 $this->re_space['m'] = $re_parts[2];
3125 } else {
3126 $this->re_space['m'] = '';
3127 }
3128 }
3129
3130 /**
3131 * Enable or disable Right-To-Left language mode
3132 * @param $enable (Boolean) if true enable Right-To-Left language mode.
3133 * @param $resetx (Boolean) if true reset the X position on direction change.
3134 * @public
3135 * @since 2.0.000 (2008-01-03)
3136 */
3137 public function setRTL($enable, $resetx=true) {
3138 $enable = $enable ? true : false;
3139 $resetx = ($resetx AND ($enable != $this->rtl));
3140 $this->rtl = $enable;
3141 $this->tmprtl = false;
3142 if ($resetx) {
3143 $this->Ln(0);
3144 }
3145 }
3146
3147 /**
3148 * Return the RTL status
3149 * @return boolean
3150 * @public
3151 * @since 4.0.012 (2008-07-24)
3152 */
3153 public function getRTL() {
3154 return $this->rtl;
3155 }
3156
3157 /**
3158 * Force temporary RTL language direction
3159 * @param $mode (mixed) can be false, 'L' for LTR or 'R' for RTL
3160 * @public
3161 * @since 2.1.000 (2008-01-09)
3162 */
3163 public function setTempRTL($mode) {
3164 $newmode = false;
3165 switch (strtoupper($mode)) {
3166 case 'LTR':
3167 case 'L': {
3168 if ($this->rtl) {
3169 $newmode = 'L';
3170 }
3171 break;
3172 }
3173 case 'RTL':
3174 case 'R': {
3175 if (!$this->rtl) {
3176 $newmode = 'R';
3177 }
3178 break;
3179 }
3180 case false:
3181 default: {
3182 $newmode = false;
3183 break;
3184 }
3185 }
3186 $this->tmprtl = $newmode;
3187 }
3188
3189 /**
3190 * Return the current temporary RTL status
3191 * @return boolean
3192 * @public
3193 * @since 4.8.014 (2009-11-04)
3194 */
3195 public function isRTLTextDir() {
3196 return ($this->rtl OR ($this->tmprtl == 'R'));
3197 }
3198
3199 /**
3200 * Set the last cell height.
3201 * @param $h (float) cell height.
3202 * @author Nicola Asuni
3203 * @public
3204 * @since 1.53.0.TC034
3205 */
3206 public function setLastH($h) {
3207 $this->lasth = $h;
3208 }
3209
3210 /**
3211 * Reset the last cell height.
3212 * @public
3213 * @since 5.9.000 (2010-10-03)
3214 */
3215 public function resetLastH() {
3216 $this->lasth = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
3217 }
3218
3219 /**
3220 * Get the last cell height.
3221 * @return last cell height
3222 * @public
3223 * @since 4.0.017 (2008-08-05)
3224 */
3225 public function getLastH() {
3226 return $this->lasth;
3227 }
3228
3229 /**
3230 * Set the adjusting factor to convert pixels to user units.
3231 * @param $scale (float) adjusting factor to convert pixels to user units.
3232 * @author Nicola Asuni
3233 * @public
3234 * @since 1.5.2
3235 */
3236 public function setImageScale($scale) {
3237 $this->imgscale = $scale;
3238 }
3239
3240 /**
3241 * Returns the adjusting factor to convert pixels to user units.
3242 * @return float adjusting factor to convert pixels to user units.
3243 * @author Nicola Asuni
3244 * @public
3245 * @since 1.5.2
3246 */
3247 public function getImageScale() {
3248 return $this->imgscale;
3249 }
3250
3251 /**
3252 * Returns an array of page dimensions:
3253 * <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>
3254 * @param $pagenum (int) page number (empty = current page)
3255 * @return array of page dimensions.
3256 * @author Nicola Asuni
3257 * @public
3258 * @since 4.5.027 (2009-03-16)
3259 */
3260 public function getPageDimensions($pagenum='') {
3261 if (empty($pagenum)) {
3262 $pagenum = $this->page;
3263 }
3264 return $this->pagedim[$pagenum];
3265 }
3266
3267 /**
3268 * Returns the page width in units.
3269 * @param $pagenum (int) page number (empty = current page)
3270 * @return int page width.
3271 * @author Nicola Asuni
3272 * @public
3273 * @since 1.5.2
3274 * @see getPageDimensions()
3275 */
3276 public function getPageWidth($pagenum='') {
3277 if (empty($pagenum)) {
3278 return $this->w;
3279 }
3280 return $this->pagedim[$pagenum]['w'];
3281 }
3282
3283 /**
3284 * Returns the page height in units.
3285 * @param $pagenum (int) page number (empty = current page)
3286 * @return int page height.
3287 * @author Nicola Asuni
3288 * @public
3289 * @since 1.5.2
3290 * @see getPageDimensions()
3291 */
3292 public function getPageHeight($pagenum='') {
3293 if (empty($pagenum)) {
3294 return $this->h;
3295 }
3296 return $this->pagedim[$pagenum]['h'];
3297 }
3298
3299 /**
3300 * Returns the page break margin.
3301 * @param $pagenum (int) page number (empty = current page)
3302 * @return int page break margin.
3303 * @author Nicola Asuni
3304 * @public
3305 * @since 1.5.2
3306 * @see getPageDimensions()
3307 */
3308 public function getBreakMargin($pagenum='') {
3309 if (empty($pagenum)) {
3310 return $this->bMargin;
3311 }
3312 return $this->pagedim[$pagenum]['bm'];
3313 }
3314
3315 /**
3316 * Returns the scale factor (number of points in user unit).
3317 * @return int scale factor.
3318 * @author Nicola Asuni
3319 * @public
3320 * @since 1.5.2
3321 */
3322 public function getScaleFactor() {
3323 return $this->k;
3324 }
3325
3326 /**
3327 * Defines the left, top and right margins.
3328 * @param $left (float) Left margin.
3329 * @param $top (float) Top margin.
3330 * @param $right (float) Right margin. Default value is the left one.
3331 * @param $keepmargins (boolean) if true overwrites the default page margins
3332 * @public
3333 * @since 1.0
3334 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
3335 */
3336 public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
3337 //Set left, top and right margins
3338 $this->lMargin = $left;
3339 $this->tMargin = $top;
3340 if ($right == -1) {
3341 $right = $left;
3342 }
3343 $this->rMargin = $right;
3344 if ($keepmargins) {
3345 // overwrite original values
3346 $this->original_lMargin = $this->lMargin;
3347 $this->original_rMargin = $this->rMargin;
3348 }
3349 }
3350
3351 /**
3352 * 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.
3353 * @param $margin (float) The margin.
3354 * @public
3355 * @since 1.4
3356 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
3357 */
3358 public function SetLeftMargin($margin) {
3359 //Set left margin
3360 $this->lMargin = $margin;
3361 if (($this->page > 0) AND ($this->x < $margin)) {
3362 $this->x = $margin;
3363 }
3364 }
3365
3366 /**
3367 * Defines the top margin. The method can be called before creating the first page.
3368 * @param $margin (float) The margin.
3369 * @public
3370 * @since 1.5
3371 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
3372 */
3373 public function SetTopMargin($margin) {
3374 //Set top margin
3375 $this->tMargin = $margin;
3376 if (($this->page > 0) AND ($this->y < $margin)) {
3377 $this->y = $margin;
3378 }
3379 }
3380
3381 /**
3382 * Defines the right margin. The method can be called before creating the first page.
3383 * @param $margin (float) The margin.
3384 * @public
3385 * @since 1.5
3386 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
3387 */
3388 public function SetRightMargin($margin) {
3389 $this->rMargin = $margin;
3390 if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
3391 $this->x = $this->w - $margin;
3392 }
3393 }
3394
3395 /**
3396 * Set the same internal Cell padding for top, right, bottom, left-
3397 * @param $pad (float) internal padding.
3398 * @public
3399 * @since 2.1.000 (2008-01-09)
3400 * @see getCellPaddings(), setCellPaddings()
3401 */
3402 public function SetCellPadding($pad) {
3403 if ($pad >= 0) {
3404 $this->cell_padding['L'] = $pad;
3405 $this->cell_padding['T'] = $pad;
3406 $this->cell_padding['R'] = $pad;
3407 $this->cell_padding['B'] = $pad;
3408 }
3409 }
3410
3411 /**
3412 * Set the internal Cell paddings.
3413 * @param $left (float) left padding
3414 * @param $top (float) top padding
3415 * @param $right (float) right padding
3416 * @param $bottom (float) bottom padding
3417 * @public
3418 * @since 5.9.000 (2010-10-03)
3419 * @see getCellPaddings(), SetCellPadding()
3420 */
3421 public function setCellPaddings($left='', $top='', $right='', $bottom='') {
3422 if (($left !== '') AND ($left >= 0)) {
3423 $this->cell_padding['L'] = $left;
3424 }
3425 if (($top !== '') AND ($top >= 0)) {
3426 $this->cell_padding['T'] = $top;
3427 }
3428 if (($right !== '') AND ($right >= 0)) {
3429 $this->cell_padding['R'] = $right;
3430 }
3431 if (($bottom !== '') AND ($bottom >= 0)) {
3432 $this->cell_padding['B'] = $bottom;
3433 }
3434 }
3435
3436 /**
3437 * Get the internal Cell padding array.
3438 * @return array of padding values
3439 * @public
3440 * @since 5.9.000 (2010-10-03)
3441 * @see setCellPaddings(), SetCellPadding()
3442 */
3443 public function getCellPaddings() {
3444 return $this->cell_padding;
3445 }
3446
3447 /**
3448 * Set the internal Cell margins.
3449 * @param $left (float) left margin
3450 * @param $top (float) top margin
3451 * @param $right (float) right margin
3452 * @param $bottom (float) bottom margin
3453 * @public
3454 * @since 5.9.000 (2010-10-03)
3455 * @see getCellMargins()
3456 */
3457 public function setCellMargins($left='', $top='', $right='', $bottom='') {
3458 if (($left !== '') AND ($left >= 0)) {
3459 $this->cell_margin['L'] = $left;
3460 }
3461 if (($top !== '') AND ($top >= 0)) {
3462 $this->cell_margin['T'] = $top;
3463 }
3464 if (($right !== '') AND ($right >= 0)) {
3465 $this->cell_margin['R'] = $right;
3466 }
3467 if (($bottom !== '') AND ($bottom >= 0)) {
3468 $this->cell_margin['B'] = $bottom;
3469 }
3470 }
3471
3472 /**
3473 * Get the internal Cell margin array.
3474 * @return array of margin values
3475 * @public
3476 * @since 5.9.000 (2010-10-03)
3477 * @see setCellMargins()
3478 */
3479 public function getCellMargins() {
3480 return $this->cell_margin;
3481 }
3482
3483 /**
3484 * Adjust the internal Cell padding array to take account of the line width.
3485 * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
3486 * @return array of adjustments
3487 * @public
3488 * @since 5.9.000 (2010-10-03)
3489 */
3490 protected function adjustCellPadding($brd=0) {
3491 if (empty($brd)) {
3492 return;
3493 }
3494 if (is_string($brd)) {
3495 // convert string to array
3496 $slen = strlen($brd);
3497 $newbrd = array();
3498 for ($i = 0; $i < $slen; ++$i) {
3499 $newbrd[$brd[$i]] = true;
3500 }
3501 $brd = $newbrd;
3502 } elseif (($brd === 1) OR ($brd === true) OR (is_numeric($brd) AND (intval($brd) > 0))) {
3503 $brd = array('LRTB' => true);
3504 }
3505 if (!is_array($brd)) {
3506 return;
3507 }
3508 // store current cell padding
3509 $cp = $this->cell_padding;
3510 // select border mode
3511 if (isset($brd['mode'])) {
3512 $mode = $brd['mode'];
3513 unset($brd['mode']);
3514 } else {
3515 $mode = 'normal';
3516 }
3517 // process borders
3518 foreach ($brd as $border => $style) {
3519 $line_width = $this->LineWidth;
3520 if (is_array($style) AND isset($style['width'])) {
3521 // get border width
3522 $line_width = $style['width'];
3523 }
3524 $adj = 0; // line width inside the cell
3525 switch ($mode) {
3526 case 'ext': {
3527 $adj = 0;
3528 break;
3529 }
3530 case 'int': {
3531 $adj = $line_width;
3532 break;
3533 }
3534 case 'normal':
3535 default: {
3536 $adj = ($line_width / 2);
3537 break;
3538 }
3539 }
3540 // correct internal cell padding if required to avoid overlap between text and lines
3541 if ((strpos($border,'T') !== false) AND ($this->cell_padding['T'] < $adj)) {
3542 $this->cell_padding['T'] = $adj;
3543 }
3544 if ((strpos($border,'R') !== false) AND ($this->cell_padding['R'] < $adj)) {
3545 $this->cell_padding['R'] = $adj;
3546 }
3547 if ((strpos($border,'B') !== false) AND ($this->cell_padding['B'] < $adj)) {
3548 $this->cell_padding['B'] = $adj;
3549 }
3550 if ((strpos($border,'L') !== false) AND ($this->cell_padding['L'] < $adj)) {
3551 $this->cell_padding['L'] = $adj;
3552 }
3553 }
3554 return array('T' => ($this->cell_padding['T'] - $cp['T']), 'R' => ($this->cell_padding['R'] - $cp['R']), 'B' => ($this->cell_padding['B'] - $cp['B']), 'L' => ($this->cell_padding['L'] - $cp['L']));
3555 }
3556
3557 /**
3558 * 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.
3559 * @param $auto (boolean) Boolean indicating if mode should be on or off.
3560 * @param $margin (float) Distance from the bottom of the page.
3561 * @public
3562 * @since 1.0
3563 * @see Cell(), MultiCell(), AcceptPageBreak()
3564 */
3565 public function SetAutoPageBreak($auto, $margin=0) {
3566 $this->AutoPageBreak = $auto ? true : false;
3567 $this->bMargin = $margin;
3568 $this->PageBreakTrigger = $this->h - $margin;
3569 }
3570
3571 /**
3572 * Return the auto-page-break mode (true or false).
3573 * @return boolean auto-page-break mode
3574 * @public
3575 * @since 5.9.088
3576 */
3577 public function getAutoPageBreak() {
3578 return $this->AutoPageBreak;
3579 }
3580
3581 /**
3582 * Defines the way the document is to be displayed by the viewer.
3583 * @param $zoom (mixed) The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. <ul><li>fullpage: displays the entire page on screen </li><li>fullwidth: uses maximum width of window</li><li>real: uses real size (equivalent to 100% zoom)</li><li>default: uses viewer default mode</li></ul>
3584 * @param $layout (string) The page layout. Possible values are:<ul><li>SinglePage Display one page at a time</li><li>OneColumn Display the pages in one column</li><li>TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left</li><li>TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right</li><li>TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left</li><li>TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right</li></ul>
3585 * @param $mode (string) A name object specifying how the document should be displayed when opened:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible</li><li>UseOC (PDF 1.5) Optional content group panel visible</li><li>UseAttachments (PDF 1.6) Attachments panel visible</li></ul>
3586 * @public
3587 * @since 1.2
3588 */
3589 public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
3590 if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
3591 $this->ZoomMode = $zoom;
3592 } else {
3593 $this->Error('Incorrect zoom display mode: '.$zoom);
3594 }
3595 switch ($layout) {
3596 case 'default':
3597 case 'single':
3598 case 'SinglePage': {
3599 $this->LayoutMode = 'SinglePage';
3600 break;
3601 }
3602 case 'continuous':
3603 case 'OneColumn': {
3604 $this->LayoutMode = 'OneColumn';
3605 break;
3606 }
3607 case 'two':
3608 case 'TwoColumnLeft': {
3609 $this->LayoutMode = 'TwoColumnLeft';
3610 break;
3611 }
3612 case 'TwoColumnRight': {
3613 $this->LayoutMode = 'TwoColumnRight';
3614 break;
3615 }
3616 case 'TwoPageLeft': {
3617 $this->LayoutMode = 'TwoPageLeft';
3618 break;
3619 }
3620 case 'TwoPageRight': {
3621 $this->LayoutMode = 'TwoPageRight';
3622 break;
3623 }
3624 default: {
3625 $this->LayoutMode = 'SinglePage';
3626 }
3627 }
3628 switch ($mode) {
3629 case 'UseNone': {
3630 $this->PageMode = 'UseNone';
3631 break;
3632 }
3633 case 'UseOutlines': {
3634 $this->PageMode = 'UseOutlines';
3635 break;
3636 }
3637 case 'UseThumbs': {
3638 $this->PageMode = 'UseThumbs';
3639 break;
3640 }
3641 case 'FullScreen': {
3642 $this->PageMode = 'FullScreen';
3643 break;
3644 }
3645 case 'UseOC': {
3646 $this->PageMode = 'UseOC';
3647 break;
3648 }
3649 case '': {
3650 $this->PageMode = 'UseAttachments';
3651 break;
3652 }
3653 default: {
3654 $this->PageMode = 'UseNone';
3655 }
3656 }
3657 }
3658
3659 /**
3660 * 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.
3661 * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
3662 * @param $compress (boolean) Boolean indicating if compression must be enabled.
3663 * @public
3664 * @since 1.4
3665 */
3666 public function SetCompression($compress=true) {
3667 if (function_exists('gzcompress')) {
3668 $this->compress = $compress ? true : false;
3669 } else {
3670 $this->compress = false;
3671 }
3672 }
3673
3674 /**
3675 * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
3676 * @param $mode (boolean) If true force sRGB output intent.
3677 * @public
3678 * @since 5.9.121 (2011-09-28)
3679 */
3680 public function setSRGBmode($mode=false) {
3681 $this->force_srgb = $mode ? true : false;
3682 }
3683
3684 /**
3685 * Turn on/off Unicode mode for document information dictionary (meta tags).
3686 * This has effect only when unicode mode is set to false.
3687 * @param $unicode (boolean) if true set the meta information in Unicode
3688 * @since 5.9.027 (2010-12-01)
3689 * @public
3690 */
3691 public function SetDocInfoUnicode($unicode=true) {
3692 $this->docinfounicode = $unicode ? true : false;
3693 }
3694
3695 /**
3696 * Defines the title of the document.
3697 * @param $title (string) The title.
3698 * @public
3699 * @since 1.2
3700 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
3701 */
3702 public function SetTitle($title) {
3703 $this->title = $title;
3704 }
3705
3706 /**
3707 * Defines the subject of the document.
3708 * @param $subject (string) The subject.
3709 * @public
3710 * @since 1.2
3711 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
3712 */
3713 public function SetSubject($subject) {
3714 $this->subject = $subject;
3715 }
3716
3717 /**
3718 * Defines the author of the document.
3719 * @param $author (string) The name of the author.
3720 * @public
3721 * @since 1.2
3722 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
3723 */
3724 public function SetAuthor($author) {
3725 $this->author = $author;
3726 }
3727
3728 /**
3729 * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
3730 * @param $keywords (string) The list of keywords.
3731 * @public
3732 * @since 1.2
3733 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
3734 */
3735 public function SetKeywords($keywords) {
3736 $this->keywords = $keywords;
3737 }
3738
3739 /**
3740 * Defines the creator of the document. This is typically the name of the application that generates the PDF.
3741 * @param $creator (string) The name of the creator.
3742 * @public
3743 * @since 1.2
3744 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
3745 */
3746 public function SetCreator($creator) {
3747 $this->creator = $creator;
3748 }
3749
3750 /**
3751 * This method is automatically called in case of fatal error; it simply outputs the message and halts the execution. An inherited class may override it to customize the error handling but should always halt the script, or the resulting document would probably be invalid.
3752 * 2004-06-11 :: Nicola Asuni : changed bold tag with strong
3753 * @param $msg (string) The error message
3754 * @public
3755 * @since 1.0
3756 */
3757 public function Error($msg) {
3758 // unset all class variables
3759 $this->_destroy(true);
3760 // exit program and print error
3761 die('<strong>TCPDF ERROR: </strong>'.$msg);
3762 }
3763
3764 /**
3765 * This method begins the generation of the PDF document.
3766 * It is not necessary to call it explicitly because AddPage() does it automatically.
3767 * Note: no page is created by this method
3768 * @public
3769 * @since 1.0
3770 * @see AddPage(), Close()
3771 */
3772 public function Open() {
3773 $this->state = 1;
3774 }
3775
3776 /**
3777 * Terminates the PDF document.
3778 * It is not necessary to call this method explicitly because Output() does it automatically.
3779 * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
3780 * @public
3781 * @since 1.0
3782 * @see Open(), Output()
3783 */
3784 public function Close() {
3785 if ($this->state == 3) {
3786 return;
3787 }
3788 if ($this->page == 0) {
3789 $this->AddPage();
3790 }
3791 $this->endLayer();
3792 if ($this->tcpdflink) {
3793 // save current graphic settings
3794 $gvars = $this->getGraphicVars();
3795 $this->setEqualColumns();
3796 $this->lastpage(true);
3797 $this->SetAutoPageBreak(false);
3798 $this->x = 0;
3799 $this->y = $this->h - (1 / $this->k);
3800 $this->lMargin = 0;
3801 $this->_out('q');
3802 $font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
3803 $this->SetFont($font, '', 1);
3804 $this->setTextRenderingMode(0, false, false);
3805 $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";
3806 $lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
3807 $this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
3808 $this->_out('Q');
3809 // restore graphic settings
3810 $this->setGraphicVars($gvars);
3811 }
3812 // close page
3813 $this->endPage();
3814 // close document
3815 $this->_enddoc();
3816 // unset all class variables (except critical ones)
3817 $this->_destroy(false);
3818 }
3819
3820 /**
3821 * Move pointer at the specified document page and update page dimensions.
3822 * @param $pnum (int) page number (1 ... numpages)
3823 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
3824 * @public
3825 * @since 2.1.000 (2008-01-07)
3826 * @see getPage(), lastpage(), getNumPages()
3827 */
3828 public function setPage($pnum, $resetmargins=false) {
3829 if (($pnum == $this->page) AND ($this->state == 2)) {
3830 return;
3831 }
3832 if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3833 $this->state = 2;
3834 // save current graphic settings
3835 //$gvars = $this->getGraphicVars();
3836 $oldpage = $this->page;
3837 $this->page = $pnum;
3838 $this->wPt = $this->pagedim[$this->page]['w'];
3839 $this->hPt = $this->pagedim[$this->page]['h'];
3840 $this->w = $this->pagedim[$this->page]['wk'];
3841 $this->h = $this->pagedim[$this->page]['hk'];
3842 $this->tMargin = $this->pagedim[$this->page]['tm'];
3843 $this->bMargin = $this->pagedim[$this->page]['bm'];
3844 $this->original_lMargin = $this->pagedim[$this->page]['olm'];
3845 $this->original_rMargin = $this->pagedim[$this->page]['orm'];
3846 $this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3847 $this->CurOrientation = $this->pagedim[$this->page]['or'];
3848 $this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3849 // restore graphic settings
3850 //$this->setGraphicVars($gvars);
3851 if ($resetmargins) {
3852 $this->lMargin = $this->pagedim[$this->page]['olm'];
3853 $this->rMargin = $this->pagedim[$this->page]['orm'];
3854 $this->SetY($this->tMargin);
3855 } else {
3856 // account for booklet mode
3857 if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3858 $deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3859 $this->lMargin += $deltam;
3860 $this->rMargin -= $deltam;
3861 }
3862 }
3863 } else {
3864 $this->Error('Wrong page number on setPage() function: '.$pnum);
3865 }
3866 }
3867
3868 /**
3869 * Reset pointer to the last document page.
3870 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
3871 * @public
3872 * @since 2.0.000 (2008-01-04)
3873 * @see setPage(), getPage(), getNumPages()
3874 */
3875 public function lastPage($resetmargins=false) {
3876 $this->setPage($this->getNumPages(), $resetmargins);
3877 }
3878
3879 /**
3880 * Get current document page number.
3881 * @return int page number
3882 * @public
3883 * @since 2.1.000 (2008-01-07)
3884 * @see setPage(), lastpage(), getNumPages()
3885 */
3886 public function getPage() {
3887 return $this->page;
3888 }
3889
3890 /**
3891 * Get the total number of insered pages.
3892 * @return int number of pages
3893 * @public
3894 * @since 2.1.000 (2008-01-07)
3895 * @see setPage(), getPage(), lastpage()
3896 */
3897 public function getNumPages() {
3898 return $this->numpages;
3899 }
3900
3901 /**
3902 * Adds a new TOC (Table Of Content) page to the document.
3903 * @param $orientation (string) page orientation.
3904 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3905 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3906 * @public
3907 * @since 5.0.001 (2010-05-06)
3908 * @see AddPage(), startPage(), endPage(), endTOCPage()
3909 */
3910 public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3911 $this->AddPage($orientation, $format, $keepmargins, true);
3912 }
3913
3914 /**
3915 * Terminate the current TOC (Table Of Content) page
3916 * @public
3917 * @since 5.0.001 (2010-05-06)
3918 * @see AddPage(), startPage(), endPage(), addTOCPage()
3919 */
3920 public function endTOCPage() {
3921 $this->endPage(true);
3922 }
3923
3924 /**
3925 * 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).
3926 * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3927 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3928 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3929 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3930 * @param $tocpage (boolean) if true set the tocpage state to true (the added page will be used to display Table Of Content).
3931 * @public
3932 * @since 1.0
3933 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3934 */
3935 public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3936 if ($this->inxobj) {
3937 // we are inside an XObject template
3938 return;
3939 }
3940 if (!isset($this->original_lMargin) OR $keepmargins) {
3941 $this->original_lMargin = $this->lMargin;
3942 }
3943 if (!isset($this->original_rMargin) OR $keepmargins) {
3944 $this->original_rMargin = $this->rMargin;
3945 }
3946 // terminate previous page
3947 $this->endPage();
3948 // start new page
3949 $this->startPage($orientation, $format, $tocpage);
3950 }
3951
3952 /**
3953 * Terminate the current page
3954 * @param $tocpage (boolean) if true set the tocpage state to false (end the page used to display Table Of Content).
3955 * @public
3956 * @since 4.2.010 (2008-11-14)
3957 * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3958 */
3959 public function endPage($tocpage=false) {
3960 // check if page is already closed
3961 if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3962 return;
3963 }
3964 // print page footer
3965 $this->setFooter();
3966 // close page
3967 $this->_endpage();
3968 // mark page as closed
3969 $this->pageopen[$this->page] = false;
3970 if ($tocpage) {
3971 $this->tocpage = false;
3972 }
3973 }
3974
3975 /**
3976 * Starts a new page to the document. The page must be closed using the endPage() function.
3977 * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3978 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3979 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3980 * @param $tocpage (boolean) if true the page is designated to contain the Table-Of-Content.
3981 * @since 4.2.010 (2008-11-14)
3982 * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3983 * @public
3984 */
3985 public function startPage($orientation='', $format='', $tocpage=false) {
3986 if ($tocpage) {
3987 $this->tocpage = true;
3988 }
3989 // move page numbers of documents to be attached
3990 if ($this->tocpage) {
3991 // move reference to unexistent pages (used for page attachments)
3992 // adjust outlines
3993 $tmpoutlines = $this->outlines;
3994 foreach ($tmpoutlines as $key => $outline) {
3995 if ($outline['p'] > $this->numpages) {
3996 $this->outlines[$key]['p'] = ($outline['p'] + 1);
3997 }
3998 }
3999 // adjust dests
4000 $tmpdests = $this->dests;
4001 foreach ($tmpdests as $key => $dest) {
4002 if ($dest['p'] > $this->numpages) {
4003 $this->dests[$key]['p'] = ($dest['p'] + 1);
4004 }
4005 }
4006 // adjust links
4007 $tmplinks = $this->links;
4008 foreach ($tmplinks as $key => $link) {
4009 if ($link[0] > $this->numpages) {
4010 $this->links[$key][0] = ($link[0] + 1);
4011 }
4012 }
4013 }
4014 if ($this->numpages > $this->page) {
4015 // this page has been already added
4016 $this->setPage($this->page + 1);
4017 $this->SetY($this->tMargin);
4018 return;
4019 }
4020 // start a new page
4021 if ($this->state == 0) {
4022 $this->Open();
4023 }
4024 ++$this->numpages;
4025 $this->swapMargins($this->booklet);
4026 // save current graphic settings
4027 $gvars = $this->getGraphicVars();
4028 // start new page
4029 $this->_beginpage($orientation, $format);
4030 // mark page as open
4031 $this->pageopen[$this->page] = true;
4032 // restore graphic settings
4033 $this->setGraphicVars($gvars);
4034 // mark this point
4035 $this->setPageMark();
4036 // print page header
4037 $this->setHeader();
4038 // restore graphic settings
4039 $this->setGraphicVars($gvars);
4040 // mark this point
4041 $this->setPageMark();
4042 // print table header (if any)
4043 $this->setTableHeader();
4044 // set mark for empty page check
4045 $this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
4046 }
4047
4048 /**
4049 * Set start-writing mark on current page stream used to put borders and fills.
4050 * Borders and fills are always created after content and inserted on the position marked by this method.
4051 * This function must be called after calling Image() function for a background image.
4052 * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
4053 * @public
4054 * @since 4.0.016 (2008-07-30)
4055 */
4056 public function setPageMark() {
4057 $this->intmrk[$this->page] = $this->pagelen[$this->page];
4058 $this->bordermrk[$this->page] = $this->intmrk[$this->page];
4059 $this->setContentMark();
4060 }
4061
4062 /**
4063 * Set start-writing mark on selected page.
4064 * Borders and fills are always created after content and inserted on the position marked by this method.
4065 * @param $page (int) page number (default is the current page)
4066 * @protected
4067 * @since 4.6.021 (2009-07-20)
4068 */
4069 protected function setContentMark($page=0) {
4070 if ($page <= 0) {
4071 $page = $this->page;
4072 }
4073 if (isset($this->footerlen[$page])) {
4074 $this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
4075 } else {
4076 $this->cntmrk[$page] = $this->pagelen[$page];
4077 }
4078 }
4079
4080 /**
4081 * Set header data.
4082 * @param $ln (string) header image logo
4083 * @param $lw (string) header image logo width in mm
4084 * @param $ht (string) string to print as title on document header
4085 * @param $hs (string) string to print on document header
4086 * @public
4087 */
4088 public function setHeaderData($ln='', $lw=0, $ht='', $hs='') {
4089 $this->header_logo = $ln;
4090 $this->header_logo_width = $lw;
4091 $this->header_title = $ht;
4092 $this->header_string = $hs;
4093 }
4094
4095 /**
4096 * Returns header data:
4097 * <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>
4098 * @return array()
4099 * @public
4100 * @since 4.0.012 (2008-07-24)
4101 */
4102 public function getHeaderData() {
4103 $ret = array();
4104 $ret['logo'] = $this->header_logo;
4105 $ret['logo_width'] = $this->header_logo_width;
4106 $ret['title'] = $this->header_title;
4107 $ret['string'] = $this->header_string;
4108 return $ret;
4109 }
4110
4111 /**
4112 * Set header margin.
4113 * (minimum distance between header and top page margin)
4114 * @param $hm (int) distance in user units
4115 * @public
4116 */
4117 public function setHeaderMargin($hm=10) {
4118 $this->header_margin = $hm;
4119 }
4120
4121 /**
4122 * Returns header margin in user units.
4123 * @return float
4124 * @since 4.0.012 (2008-07-24)
4125 * @public
4126 */
4127 public function getHeaderMargin() {
4128 return $this->header_margin;
4129 }
4130
4131 /**
4132 * Set footer margin.
4133 * (minimum distance between footer and bottom page margin)
4134 * @param $fm (int) distance in user units
4135 * @public
4136 */
4137 public function setFooterMargin($fm=10) {
4138 $this->footer_margin = $fm;
4139 }
4140
4141 /**
4142 * Returns footer margin in user units.
4143 * @return float
4144 * @since 4.0.012 (2008-07-24)
4145 * @public
4146 */
4147 public function getFooterMargin() {
4148 return $this->footer_margin;
4149 }
4150 /**
4151 * Set a flag to print page header.
4152 * @param $val (boolean) set to true to print the page header (default), false otherwise.
4153 * @public
4154 */
4155 public function setPrintHeader($val=true) {
4156 $this->print_header = $val ? true : false;
4157 }
4158
4159 /**
4160 * Set a flag to print page footer.
4161 * @param $val (boolean) set to true to print the page footer (default), false otherwise.
4162 * @public
4163 */
4164 public function setPrintFooter($val=true) {
4165 $this->print_footer = $val ? true : false;
4166 }
4167
4168 /**
4169 * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
4170 * @return float
4171 * @public
4172 */
4173 public function getImageRBX() {
4174 return $this->img_rb_x;
4175 }
4176
4177 /**
4178 * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
4179 * @return float
4180 * @public
4181 */
4182 public function getImageRBY() {
4183 return $this->img_rb_y;
4184 }
4185
4186 /**
4187 * Reset the xobject template used by Header() method.
4188 * @public
4189 */
4190 public function resetHeaderTemplate() {
4191 $this->header_xobjid = -1;
4192 }
4193
4194 /**
4195 * Set a flag to automatically reset the xobject template used by Header() method at each page.
4196 * @param $val (boolean) set to true to reset Header xobject template at each page, false otherwise.
4197 * @public
4198 */
4199 public function setHeaderTemplateAutoreset($val=true) {
4200 $this->header_xobj_autoreset = $val ? true : false;
4201 }
4202
4203 /**
4204 * This method is used to render the page header.
4205 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
4206 * @public
4207 */
4208 public function Header() {
4209 if ($this->header_xobjid < 0) {
4210 // start a new XObject Template
4211 $this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
4212 $headerfont = $this->getHeaderFont();
4213 $headerdata = $this->getHeaderData();
4214 $this->y = $this->header_margin;
4215 if ($this->rtl) {
4216 $this->x = $this->w - $this->original_rMargin;
4217 } else {
4218 $this->x = $this->original_lMargin;
4219 }
4220 if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
4221 $imgtype = $this->getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
4222 if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
4223 $this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
4224 } elseif ($imgtype == 'svg') {
4225 $this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
4226 } else {
4227 $this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
4228 }
4229 $imgy = $this->getImageRBY();
4230 } else {
4231 $imgy = $this->y;
4232 }
4233 $cell_height = round(($this->cell_height_ratio * $headerfont[2]) / $this->k, 2);
4234 // set starting margin for text data cell
4235 if ($this->getRTL()) {
4236 $header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
4237 } else {
4238 $header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
4239 }
4240 $cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
4241 $this->SetTextColor(0, 0, 0);
4242 // header title
4243 $this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
4244 $this->SetX($header_x);
4245 $this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
4246 // header string
4247 $this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
4248 $this->SetX($header_x);
4249 $this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
4250 // print an ending header line
4251 $this->SetLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)));
4252 $this->SetY((2.835 / $this->k) + max($imgy, $this->y));
4253 if ($this->rtl) {
4254 $this->SetX($this->original_rMargin);
4255 } else {
4256 $this->SetX($this->original_lMargin);
4257 }
4258 $this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
4259 $this->endTemplate();
4260 }
4261 // print header template
4262 $x = 0;
4263 $dx = 0;
4264 if ($this->booklet AND (($this->page % 2) == 0)) {
4265 // adjust margins for booklet mode
4266 $dx = ($this->original_lMargin - $this->original_rMargin);
4267 }
4268 if ($this->rtl) {
4269 $x = $this->w + $dx;
4270 } else {
4271 $x = 0 + $dx;
4272 }
4273 $this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
4274 if ($this->header_xobj_autoreset) {
4275 // reset header xobject template at each page
4276 $this->header_xobjid = -1;
4277 }
4278 }
4279
4280 /**
4281 * This method is used to render the page footer.
4282 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
4283 * @public
4284 */
4285 public function Footer() {
4286 $cur_y = $this->y;
4287 $this->SetTextColor(0, 0, 0);
4288 //set style for cell border
4289 $line_width = 0.85 / $this->k;
4290 $this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)));
4291 //print document barcode
4292 $barcode = $this->getBarcode();
4293 if (!empty($barcode)) {
4294 $this->Ln($line_width);
4295 $barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
4296 $style = array(
4297 'position' => $this->rtl?'R':'L',
4298 'align' => $this->rtl?'R':'L',
4299 'stretch' => false,
4300 'fitwidth' => true,
4301 'cellfitalign' => '',
4302 'border' => false,
4303 'padding' => 0,
4304 'fgcolor' => array(0,0,0),
4305 'bgcolor' => false,
4306 'text' => false
4307 );
4308 $this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
4309 }
4310 if (empty($this->pagegroups)) {
4311 $pagenumtxt = $this->l['w_page'].' '.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
4312 } else {
4313 $pagenumtxt = $this->l['w_page'].' '.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
4314 }
4315 $this->SetY($cur_y);
4316 //Print page number
4317 if ($this->getRTL()) {
4318 $this->SetX($this->original_rMargin);
4319 $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
4320 } else {
4321 $this->SetX($this->original_lMargin);
4322 $this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
4323 }
4324 }
4325
4326 /**
4327 * This method is used to render the page header.
4328 * @protected
4329 * @since 4.0.012 (2008-07-24)
4330 */
4331 protected function setHeader() {
4332 if (!$this->print_header) {
4333 return;
4334 }
4335 $this->InHeader = true;
4336 $this->setGraphicVars($this->default_graphic_vars);
4337 $temp_thead = $this->thead;
4338 $temp_theadMargins = $this->theadMargins;
4339 $lasth = $this->lasth;
4340 $this->_out('q');
4341 $this->rMargin = $this->original_rMargin;
4342 $this->lMargin = $this->original_lMargin;
4343 $this->SetCellPadding(0);
4344 //set current position
4345 if ($this->rtl) {
4346 $this->SetXY($this->original_rMargin, $this->header_margin);
4347 } else {
4348 $this->SetXY($this->original_lMargin, $this->header_margin);
4349 }
4350 $this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
4351 $this->Header();
4352 //restore position
4353 if ($this->rtl) {
4354 $this->SetXY($this->original_rMargin, $this->tMargin);
4355 } else {
4356 $this->SetXY($this->original_lMargin, $this->tMargin);
4357 }
4358 $this->_out('Q');
4359 $this->lasth = $lasth;
4360 $this->thead = $temp_thead;
4361 $this->theadMargins = $temp_theadMargins;
4362 $this->newline = false;
4363 $this->InHeader = false;
4364 }
4365
4366 /**
4367 * This method is used to render the page footer.
4368 * @protected
4369 * @since 4.0.012 (2008-07-24)
4370 */
4371 protected function setFooter() {
4372 //Page footer
4373 $this->InFooter = true;
4374 // save current graphic settings
4375 $gvars = $this->getGraphicVars();
4376 // mark this point
4377 $this->footerpos[$this->page] = $this->pagelen[$this->page];
4378 $this->_out("\n");
4379 if ($this->print_footer) {
4380 $this->setGraphicVars($this->default_graphic_vars);
4381 $this->current_column = 0;
4382 $this->num_columns = 1;
4383 $temp_thead = $this->thead;
4384 $temp_theadMargins = $this->theadMargins;
4385 $lasth = $this->lasth;
4386 $this->_out('q');
4387 $this->rMargin = $this->original_rMargin;
4388 $this->lMargin = $this->original_lMargin;
4389 $this->SetCellPadding(0);
4390 //set current position
4391 $footer_y = $this->h - $this->footer_margin;
4392 if ($this->rtl) {
4393 $this->SetXY($this->original_rMargin, $footer_y);
4394 } else {
4395 $this->SetXY($this->original_lMargin, $footer_y);
4396 }
4397 $this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
4398 $this->Footer();
4399 //restore position
4400 if ($this->rtl) {
4401 $this->SetXY($this->original_rMargin, $this->tMargin);
4402 } else {
4403 $this->SetXY($this->original_lMargin, $this->tMargin);
4404 }
4405 $this->_out('Q');
4406 $this->lasth = $lasth;
4407 $this->thead = $temp_thead;
4408 $this->theadMargins = $temp_theadMargins;
4409 }
4410 // restore graphic settings
4411 $this->setGraphicVars($gvars);
4412 $this->current_column = $gvars['current_column'];
4413 $this->num_columns = $gvars['num_columns'];
4414 // calculate footer length
4415 $this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
4416 $this->InFooter = false;
4417 }
4418
4419 /**
4420 * Check if we are on the page body (excluding page header and footer).
4421 * @return true if we are not in page header nor in page footer, false otherwise.
4422 * @protected
4423 * @since 5.9.091 (2011-06-15)
4424 */
4425 protected function inPageBody() {
4426 return (($this->InHeader === false) AND ($this->InFooter === false));
4427 }
4428
4429 /**
4430 * This method is used to render the table header on new page (if any).
4431 * @protected
4432 * @since 4.5.030 (2009-03-25)
4433 */
4434 protected function setTableHeader() {
4435 if ($this->num_columns > 1) {
4436 // multi column mode
4437 return;
4438 }
4439 if (isset($this->theadMargins['top'])) {
4440 // restore the original top-margin
4441 $this->tMargin = $this->theadMargins['top'];
4442 $this->pagedim[$this->page]['tm'] = $this->tMargin;
4443 $this->y = $this->tMargin;
4444 }
4445 if (!$this->empty_string($this->thead) AND (!$this->inthead)) {
4446 // set margins
4447 $prev_lMargin = $this->lMargin;
4448 $prev_rMargin = $this->rMargin;
4449 $prev_cell_padding = $this->cell_padding;
4450 $this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
4451 $this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
4452 $this->cell_padding = $this->theadMargins['cell_padding'];
4453 if ($this->rtl) {
4454 $this->x = $this->w - $this->rMargin;
4455 } else {
4456 $this->x = $this->lMargin;
4457 }
4458 // account for special "cell" mode
4459 if ($this->theadMargins['cell']) {
4460 if ($this->rtl) {
4461 $this->x -= $this->cell_padding['R'];
4462 } else {
4463 $this->x += $this->cell_padding['L'];
4464 }
4465 }
4466 // print table header
4467 $this->writeHTML($this->thead, false, false, false, false, '');
4468 // set new top margin to skip the table headers
4469 if (!isset($this->theadMargins['top'])) {
4470 $this->theadMargins['top'] = $this->tMargin;
4471 }
4472 // store end of header position
4473 if (!isset($this->columns[0]['th'])) {
4474 $this->columns[0]['th'] = array();
4475 }
4476 $this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
4477 $this->tMargin = $this->y;
4478 $this->pagedim[$this->page]['tm'] = $this->tMargin;
4479 $this->lasth = 0;
4480 $this->lMargin = $prev_lMargin;
4481 $this->rMargin = $prev_rMargin;
4482 $this->cell_padding = $prev_cell_padding;
4483 }
4484 }
4485
4486 /**
4487 * Returns the current page number.
4488 * @return int page number
4489 * @public
4490 * @since 1.0
4491 * @see getAliasNbPages()
4492 */
4493 public function PageNo() {
4494 return $this->page;
4495 }
4496
4497 /**
4498 * Defines a new spot color.
4499 * It can be expressed in RGB components or gray scale.
4500 * The method can be called before the first page is created and the value is retained from page to page.
4501 * @param $name (string) Full name of the spot color.
4502 * @param $c (float) Cyan color for CMYK. Value between 0 and 100.
4503 * @param $m (float) Magenta color for CMYK. Value between 0 and 100.
4504 * @param $y (float) Yellow color for CMYK. Value between 0 and 100.
4505 * @param $k (float) Key (Black) color for CMYK. Value between 0 and 100.
4506 * @public
4507 * @since 4.0.024 (2008-09-12)
4508 * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
4509 */
4510 public function AddSpotColor($name, $c, $m, $y, $k) {
4511 if (!isset($this->spot_colors[$name])) {
4512 $i = (1 + count($this->spot_colors));
4513 $this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
4514 }
4515 }
4516
4517 /**
4518 * Return the Spot color array.
4519 * @param $name (string) Name of the spot color.
4520 * @return (array) Spot color array or false if not defined.
4521 * @public
4522 * @since 5.9.125 (2011-10-03)
4523 */
4524 public function getSpotColor($name) {
4525 if (isset($this->spot_colors[$name])) {
4526 return $this->spot_colors[$name];
4527 }
4528 $color = preg_replace('/[\s]*/', '', $name); // remove extra spaces
4529 $color = strtolower($color);
4530 if (isset($this->spotcolor[$color])) {
4531 $this->AddSpotColor($this->spotcolor[$color][4], $this->spotcolor[$color][0], $this->spotcolor[$color][1], $this->spotcolor[$color][2], $this->spotcolor[$color][3]);
4532 return $this->spot_colors[$this->spotcolor[$color][4]];
4533 }
4534 return false;
4535 }
4536
4537 /**
4538 * Set the spot color for the specified type ('draw', 'fill', 'text').
4539 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
4540 * @param $name (string) Name of the spot color.
4541 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
4542 * @return (string) PDF color command.
4543 * @public
4544 * @since 5.9.125 (2011-10-03)
4545 */
4546 public function setSpotColor($type, $name, $tint=100) {
4547 $spotcolor = $this->getSpotColor($name);
4548 if ($spotcolor === false) {
4549 $this->Error('Undefined spot color: '.$name.', you must add it on the spotcolors.php file.');
4550 }
4551 $tint = (max(0, min(100, $tint)) / 100);
4552 $pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
4553 switch ($type) {
4554 case 'draw': {
4555 $pdfcolor .= sprintf('CS %F SCN', $tint);
4556 $this->DrawColor = $pdfcolor;
4557 $this->strokecolor = $spotcolor;
4558 break;
4559 }
4560 case 'fill': {
4561 $pdfcolor .= sprintf('cs %F scn', $tint);
4562 $this->FillColor = $pdfcolor;
4563 $this->bgcolor = $spotcolor;
4564 break;
4565 }
4566 case 'text': {
4567 $pdfcolor .= sprintf('cs %F scn', $tint);
4568 $this->TextColor = $pdfcolor;
4569 $this->fgcolor = $spotcolor;
4570 break;
4571 }
4572 }
4573 $this->ColorFlag = ($this->FillColor != $this->TextColor);
4574 if ($this->page > 0) {
4575 $this->_out($pdfcolor);
4576 }
4577 if ($this->inxobj) {
4578 // we are inside an XObject template
4579 $this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
4580 }
4581 return $pdfcolor;
4582 }
4583
4584 /**
4585 * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
4586 * @param $name (string) Name of the spot color.
4587 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
4588 * @public
4589 * @since 4.0.024 (2008-09-12)
4590 * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
4591 */
4592 public function SetDrawSpotColor($name, $tint=100) {
4593 $this->setSpotColor('draw', $name, $tint);
4594 }
4595
4596 /**
4597 * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
4598 * @param $name (string) Name of the spot color.
4599 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
4600 * @public
4601 * @since 4.0.024 (2008-09-12)
4602 * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
4603 */
4604 public function SetFillSpotColor($name, $tint=100) {
4605 $this->setSpotColor('fill', $name, $tint);
4606 }
4607
4608 /**
4609 * Defines the spot color used for text.
4610 * @param $name (string) Name of the spot color.
4611 * @param $tint (int) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
4612 * @public
4613 * @since 4.0.024 (2008-09-12)
4614 * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
4615 */
4616 public function SetTextSpotColor($name, $tint=100) {
4617 $this->setSpotColor('text', $name, $tint);
4618 }
4619
4620 /**
4621 * Set the color array for the specified type ('draw', 'fill', 'text').
4622 * It can be expressed in RGB, CMYK or GRAY SCALE components.
4623 * The method can be called before the first page is created and the value is retained from page to page.
4624 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
4625 * @param $color (array) Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
4626 * @param $ret (boolean) If true do not send the PDF command.
4627 * @return (string) The PDF command or empty string.
4628 * @public
4629 * @since 3.1.000 (2008-06-11)
4630 */
4631 public function setColorArray($type, $color, $ret=false) {
4632 if (is_array($color)) {
4633 $color = array_values($color);
4634 // component: grey, RGB red or CMYK cyan
4635 $c = isset($color[0]) ? $color[0] : -1;
4636 // component: RGB green or CMYK magenta
4637 $m = isset($color[1]) ? $color[1] : -1;
4638 // component: RGB blue or CMYK yellow
4639 $y = isset($color[2]) ? $color[2] : -1;
4640 // component: CMYK black
4641 $k = isset($color[3]) ? $color[3] : -1;
4642 // color name
4643 $name = isset($color[4]) ? $color[4] : '';
4644 if ($c >= 0) {
4645 return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
4646 }
4647 }
4648 return '';
4649 }
4650
4651 /**
4652 * Defines the color used for all drawing operations (lines, rectangles and cell borders).
4653 * It can be expressed in RGB, CMYK or GRAY SCALE components.
4654 * The method can be called before the first page is created and the value is retained from page to page.
4655 * @param $color (array) Array of colors (1, 3 or 4 values).
4656 * @param $ret (boolean) If true do not send the PDF command.
4657 * @return string the PDF command
4658 * @public
4659 * @since 3.1.000 (2008-06-11)
4660 * @see SetDrawColor()
4661 */
4662 public function SetDrawColorArray($color, $ret=false) {
4663 return $this->setColorArray('draw', $color, $ret);
4664 }
4665
4666 /**
4667 * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
4668 * It can be expressed in RGB, CMYK or GRAY SCALE components.
4669 * The method can be called before the first page is created and the value is retained from page to page.
4670 * @param $color (array) Array of colors (1, 3 or 4 values).
4671 * @param $ret (boolean) If true do not send the PDF command.
4672 * @public
4673 * @since 3.1.000 (2008-6-11)
4674 * @see SetFillColor()
4675 */
4676 public function SetFillColorArray($color, $ret=false) {
4677 return $this->setColorArray('fill', $color, $ret);
4678 }
4679
4680 /**
4681 * Defines the color used for text. It can be expressed in RGB components or gray scale.
4682 * The method can be called before the first page is created and the value is retained from page to page.
4683 * @param $color (array) Array of colors (1, 3 or 4 values).
4684 * @param $ret (boolean) If true do not send the PDF command.
4685 * @public
4686 * @since 3.1.000 (2008-6-11)
4687 * @see SetFillColor()
4688 */
4689 public function SetTextColorArray($color, $ret=false) {
4690 return $this->setColorArray('text', $color, $ret);
4691 }
4692
4693 /**
4694 * Defines the color used by the specified type ('draw', 'fill', 'text').
4695 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
4696 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4697 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4698 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4699 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4700 * @param $ret (boolean) If true do not send the command.
4701 * @param $name (string) spot color name (if any)
4702 * @return (string) The PDF command or empty string.
4703 * @public
4704 * @since 5.9.125 (2011-10-03)
4705 */
4706 public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4707 // set default values
4708 if (!is_numeric($col1)) {
4709 $col1 = 0;
4710 }
4711 if (!is_numeric($col2)) {
4712 $col2 = -1;
4713 }
4714 if (!is_numeric($col3)) {
4715 $col3 = -1;
4716 }
4717 if (!is_numeric($col4)) {
4718 $col4 = -1;
4719 }
4720 // set color by case
4721 $suffix = '';
4722 if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
4723 // Grey scale
4724 $col1 = max(0, min(255, $col1));
4725 $intcolor = array('G' => $col1);
4726 $pdfcolor = sprintf('%F ', ($col1 / 255));
4727 $suffix = 'g';
4728 } elseif ($col4 == -1) {
4729 // RGB
4730 $col1 = max(0, min(255, $col1));
4731 $col2 = max(0, min(255, $col2));
4732 $col3 = max(0, min(255, $col3));
4733 $intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
4734 $pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
4735 $suffix = 'rg';
4736 } else {
4737 $col1 = max(0, min(100, $col1));
4738 $col2 = max(0, min(100, $col2));
4739 $col3 = max(0, min(100, $col3));
4740 $col4 = max(0, min(100, $col4));
4741 if (empty($name)) {
4742 // CMYK
4743 $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
4744 $pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
4745 $suffix = 'k';
4746 } else {
4747 // SPOT COLOR
4748 $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
4749 $this->AddSpotColor($name, $col1, $col2, $col3, $col4);
4750 $pdfcolor = $this->setSpotColor($type, $name, 100);
4751 }
4752 }
4753 switch ($type) {
4754 case 'draw': {
4755 $pdfcolor .= strtoupper($suffix);
4756 $this->DrawColor = $pdfcolor;
4757 $this->strokecolor = $intcolor;
4758 break;
4759 }
4760 case 'fill': {
4761 $pdfcolor .= $suffix;
4762 $this->FillColor = $pdfcolor;
4763 $this->bgcolor = $intcolor;
4764 break;
4765 }
4766 case 'text': {
4767 $pdfcolor .= $suffix;
4768 $this->TextColor = $pdfcolor;
4769 $this->fgcolor = $intcolor;
4770 break;
4771 }
4772 }
4773 $this->ColorFlag = ($this->FillColor != $this->TextColor);
4774 if (($type != 'text') AND ($this->page > 0)) {
4775 if (!$ret) {
4776 $this->_out($pdfcolor);
4777 }
4778 return $pdfcolor;
4779 }
4780 return '';
4781 }
4782
4783 /**
4784 * Convert a color array into a string representation.
4785 * @param $c (array) Array of colors.
4786 * @return (string) The color array representation.
4787 * @protected
4788 * @since 5.9.137 (2011-12-01)
4789 */
4790 protected function getColorStringFromArray($c) {
4791 $c = array_values($c);
4792 $color = '[';
4793 switch (count($c)) {
4794 case 4: {
4795 // CMYK
4796 $color .= sprintf('%F %F %F %F', (max(0, min(100, floatval($c[0]))) / 100), (max(0, min(100, floatval($c[1]))) / 100), (max(0, min(100, floatval($c[2]))) / 100), (max(0, min(100, floatval($c[3]))) / 100));
4797 break;
4798 }
4799 case 3: {
4800 // RGB
4801 $color .= sprintf('%F %F %F', (max(0, min(255, floatval($c[0]))) / 255), (max(0, min(255, floatval($c[1]))) / 255), (max(0, min(255, floatval($c[2]))) / 255));
4802 break;
4803 }
4804 case 1: {
4805 // grayscale
4806 $color .= sprintf('%F', (max(0, min(255, floatval($c[0]))) / 255));
4807 break;
4808 }
4809 }
4810 $color .= ']';
4811 return $color;
4812 }
4813
4814 /**
4815 * 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.
4816 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4817 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4818 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4819 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4820 * @param $ret (boolean) If true do not send the command.
4821 * @param $name (string) spot color name (if any)
4822 * @return string the PDF command
4823 * @public
4824 * @since 1.3
4825 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
4826 */
4827 public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4828 return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
4829 }
4830
4831 /**
4832 * 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.
4833 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4834 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4835 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4836 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4837 * @param $ret (boolean) If true do not send the command.
4838 * @param $name (string) Spot color name (if any).
4839 * @return (string) The PDF command.
4840 * @public
4841 * @since 1.3
4842 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
4843 */
4844 public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4845 return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
4846 }
4847
4848 /**
4849 * 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.
4850 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4851 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4852 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4853 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4854 * @param $ret (boolean) If true do not send the command.
4855 * @param $name (string) Spot color name (if any).
4856 * @return (string) Empty string.
4857 * @public
4858 * @since 1.3
4859 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4860 */
4861 public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4862 return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4863 }
4864
4865 /**
4866 * Returns the length of a string in user unit. A font must be selected.<br>
4867 * @param $s (string) The string whose length is to be computed
4868 * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4869 * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line-trough</li><li>O: overline</li></ul> or any combination. The default value is regular.
4870 * @param $fontsize (float) Font size in points. The default value is the current size.
4871 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4872 * @return mixed int total string length or array of characted widths
4873 * @author Nicola Asuni
4874 * @public
4875 * @since 1.2
4876 */
4877 public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4878 return $this->GetArrStringWidth($this->utf8Bidi($this->UTF8StringToArray($s), $s, $this->tmprtl), $fontname, $fontstyle, $fontsize, $getarray);
4879 }
4880
4881 /**
4882 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4883 * @param $sa (string) The array of chars whose total length is to be computed
4884 * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4885 * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line trough</li><li>O: overline</li></ul> or any combination. The default value is regular.
4886 * @param $fontsize (float) Font size in points. The default value is the current size.
4887 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4888 * @return mixed int total string length or array of characted widths
4889 * @author Nicola Asuni
4890 * @public
4891 * @since 2.4.000 (2008-03-06)
4892 */
4893 public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4894 // store current values
4895 if (!$this->empty_string($fontname)) {
4896 $prev_FontFamily = $this->FontFamily;
4897 $prev_FontStyle = $this->FontStyle;
4898 $prev_FontSizePt = $this->FontSizePt;
4899 $this->SetFont($fontname, $fontstyle, $fontsize, '', 'default', false);
4900 }
4901 // convert UTF-8 array to Latin1 if required
4902 $sa = $this->UTF8ArrToLatin1($sa);
4903 $w = 0; // total width
4904 $wa = array(); // array of characters widths
4905 foreach ($sa as $ck => $char) {
4906 // character width
4907 $cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4908 $wa[] = $cw;
4909 $w += $cw;
4910 }
4911 // restore previous values
4912 if (!$this->empty_string($fontname)) {
4913 $this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4914 }
4915 if ($getarray) {
4916 return $wa;
4917 }
4918 return $w;
4919 }
4920
4921 /**
4922 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking/kerning).
4923 * @param $char (int) The char code whose length is to be returned
4924 * @param $notlast (boolean) set to false for the latest character on string, true otherwise (default)
4925 * @return float char width
4926 * @author Nicola Asuni
4927 * @public
4928 * @since 2.4.000 (2008-03-06)
4929 */
4930 public function GetCharWidth($char, $notlast=true) {
4931 // get raw width
4932 $chw = $this->getRawCharWidth($char);
4933 if (($this->font_spacing != 0) AND $notlast) {
4934 // increase/decrease font spacing
4935 $chw += $this->font_spacing;
4936 }
4937 if ($this->font_stretching != 100) {
4938 // fixed stretching mode
4939 $chw *= ($this->font_stretching / 100);
4940 }
4941 return $chw;
4942 }
4943
4944 /**
4945 * Returns the length of the char in user unit for the current font.
4946 * @param $char (int) The char code whose length is to be returned
4947 * @return float char width
4948 * @author Nicola Asuni
4949 * @public
4950 * @since 5.9.000 (2010-09-28)
4951 */
4952 public function getRawCharWidth($char) {
4953 if ($char == 173) {
4954 // SHY character will not be printed
4955 return (0);
4956 }
4957 if (isset($this->CurrentFont['cw'][$char])) {
4958 $w = $this->CurrentFont['cw'][$char];
4959 } elseif (isset($this->CurrentFont['dw'])) {
4960 // default width
4961 $w = $this->CurrentFont['dw'];
4962 } elseif (isset($this->CurrentFont['cw'][32])) {
4963 // default width
4964 $w = $this->CurrentFont['cw'][32];
4965 } else {
4966 $w = 600;
4967 }
4968 return ($w * $this->FontSize / 1000);
4969 }
4970
4971 /**
4972 * Returns the numbero of characters in a string.
4973 * @param $s (string) The input string.
4974 * @return int number of characters
4975 * @public
4976 * @since 2.0.0001 (2008-01-07)
4977 */
4978 public function GetNumChars($s) {
4979 if ($this->isUnicodeFont()) {
4980 return count($this->UTF8StringToArray($s));
4981 }
4982 return strlen($s);
4983 }
4984
4985 /**
4986 * Fill the list of available fonts ($this->fontlist).
4987 * @protected
4988 * @since 4.0.013 (2008-07-28)
4989 */
4990 protected function getFontsList() {
4991 $fontsdir = opendir($this->_getfontpath());
4992 while (($file = readdir($fontsdir)) !== false) {
4993 if (substr($file, -4) == '.php') {
4994 array_push($this->fontlist, strtolower(basename($file, '.php')));
4995 }
4996 }
4997 closedir($fontsdir);
4998 }
4999
5000 /**
5001 * Imports a TrueType, Type1, core, or CID0 font and makes it available.
5002 * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
5003 * 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.
5004 * @param $family (string) Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
5005 * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular (default)</li><li>B: bold</li><li>I: italic</li><li>BI or IB: bold italic</li></ul>
5006 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
5007 * @return array containing the font data, or false in case of error.
5008 * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
5009 * @public
5010 * @since 1.5
5011 * @see SetFont(), setFontSubsetting()
5012 */
5013 public function AddFont($family, $style='', $fontfile='', $subset='default') {
5014 if ($subset === 'default') {
5015 $subset = $this->font_subsetting;
5016 }
5017 if ($this->pdfa_mode) {
5018 $subset = false;
5019 }
5020 if ($this->empty_string($family)) {
5021 if (!$this->empty_string($this->FontFamily)) {
5022 $family = $this->FontFamily;
5023 } else {
5024 $this->Error('Empty font family');
5025 }
5026 }
5027 // move embedded styles on $style
5028 if (substr($family, -1) == 'I') {
5029 $style .= 'I';
5030 $family = substr($family, 0, -1);
5031 }
5032 if (substr($family, -1) == 'B') {
5033 $style .= 'B';
5034 $family = substr($family, 0, -1);
5035 }
5036 // normalize family name
5037 $family = strtolower($family);
5038 if ((!$this->isunicode) AND ($family == 'arial')) {
5039 $family = 'helvetica';
5040 }
5041 if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
5042 $style = '';
5043 }
5044 if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
5045 // all fonts must be embedded
5046 $family = 'pdfa'.$family;
5047 }
5048 $tempstyle = strtoupper($style);
5049 $style = '';
5050 // underline
5051 if (strpos($tempstyle, 'U') !== false) {
5052 $this->underline = true;
5053 } else {
5054 $this->underline = false;
5055 }
5056 // line-through (deleted)
5057 if (strpos($tempstyle, 'D') !== false) {
5058 $this->linethrough = true;
5059 } else {
5060 $this->linethrough = false;
5061 }
5062 // overline
5063 if (strpos($tempstyle, 'O') !== false) {
5064 $this->overline = true;
5065 } else {
5066 $this->overline = false;
5067 }
5068 // bold
5069 if (strpos($tempstyle, 'B') !== false) {
5070 $style .= 'B';
5071 }
5072 // oblique
5073 if (strpos($tempstyle, 'I') !== false) {
5074 $style .= 'I';
5075 }
5076 $bistyle = $style;
5077 $fontkey = $family.$style;
5078 $font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
5079 $fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
5080 // check if the font has been already added
5081 $fb = $this->getFontBuffer($fontkey);
5082 if ($fb !== false) {
5083 if ($this->inxobj) {
5084 // we are inside an XObject template
5085 $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
5086 }
5087 return $fontdata;
5088 }
5089 if (isset($type)) {
5090 unset($type);
5091 }
5092 if (isset($cw)) {
5093 unset($cw);
5094 }
5095 // get specified font directory (if any)
5096 $fontdir = false;
5097 if (!$this->empty_string($fontfile)) {
5098 $fontdir = dirname($fontfile);
5099 if ($this->empty_string($fontdir) OR ($fontdir == '.')) {
5100 $fontdir = '';
5101 } else {
5102 $fontdir .= '/';
5103 }
5104 }
5105 $missing_style = false; // true when the font style variation is missing
5106 // search and include font file
5107 if ($this->empty_string($fontfile) OR (!file_exists($fontfile))) {
5108 // build a standard filenames for specified font
5109 $tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
5110 // search files on various directories
5111 if (($fontdir !== false) AND file_exists($fontdir.$tmp_fontfile)) {
5112 $fontfile = $fontdir.$tmp_fontfile;
5113 } elseif (file_exists($this->_getfontpath().$tmp_fontfile)) {
5114 $fontfile = $this->_getfontpath().$tmp_fontfile;
5115 } elseif (file_exists($tmp_fontfile)) {
5116 $fontfile = $tmp_fontfile;
5117 } elseif (!$this->empty_string($style)) {
5118 $missing_style = true;
5119 // try to remove the style part
5120 $tmp_fontfile = str_replace(' ', '', $family).'.php';
5121 if (($fontdir !== false) AND file_exists($fontdir.$tmp_fontfile)) {
5122 $fontfile = $fontdir.$tmp_fontfile;
5123 } elseif (file_exists($this->_getfontpath().$tmp_fontfile)) {
5124 $fontfile = $this->_getfontpath().$tmp_fontfile;
5125 } else {
5126 $fontfile = $tmp_fontfile;
5127 }
5128 }
5129 }
5130 // include font file
5131 if (file_exists($fontfile)) {
5132 include($fontfile);
5133 } else {
5134 $this->Error('Could not include font definition file: '.$family.'');
5135 }
5136 // check font parameters
5137 if ((!isset($type)) OR (!isset($cw))) {
5138 $this->Error('The font definition file has a bad format: '.$fontfile.'');
5139 }
5140 // SET default parameters
5141 if (!isset($file) OR $this->empty_string($file)) {
5142 $file = '';
5143 }
5144 if (!isset($enc) OR $this->empty_string($enc)) {
5145 $enc = '';
5146 }
5147 if (!isset($cidinfo) OR $this->empty_string($cidinfo)) {
5148 $cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
5149 $cidinfo['uni2cid'] = array();
5150 }
5151 if (!isset($ctg) OR $this->empty_string($ctg)) {
5152 $ctg = '';
5153 }
5154 if (!isset($desc) OR $this->empty_string($desc)) {
5155 $desc = array();
5156 }
5157 if (!isset($up) OR $this->empty_string($up)) {
5158 $up = -100;
5159 }
5160 if (!isset($ut) OR $this->empty_string($ut)) {
5161 $ut = 50;
5162 }
5163 if (!isset($cw) OR $this->empty_string($cw)) {
5164 $cw = array();
5165 }
5166 if (!isset($dw) OR $this->empty_string($dw)) {
5167 // set default width
5168 if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
5169 $dw = $desc['MissingWidth'];
5170 } elseif (isset($cw[32])) {
5171 $dw = $cw[32];
5172 } else {
5173 $dw = 600;
5174 }
5175 }
5176 ++$this->numfonts;
5177 if ($type == 'core') {
5178 $name = $this->CoreFonts[$fontkey];
5179 $subset = false;
5180 } elseif (($type == 'TrueType') OR ($type == 'Type1')) {
5181 $subset = false;
5182 } elseif ($type == 'TrueTypeUnicode') {
5183 $enc = 'Identity-H';
5184 } elseif ($type == 'cidfont0') {
5185 if ($this->pdfa_mode) {
5186 $this->Error('All fonts must be embedded in PDF/A mode!');
5187 }
5188 } else {
5189 $this->Error('Unknow font type: '.$type.'');
5190 }
5191 // set name if unset
5192 if (!isset($name) OR empty($name)) {
5193 $name = $fontkey;
5194 }
5195 // create artificial font style variations if missing (only works with non-embedded fonts)
5196 if (($type != 'core') AND $missing_style) {
5197 // style variations
5198 $styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
5199 $name .= $styles[$bistyle];
5200 // artificial bold
5201 if (strpos($bistyle, 'B') !== false) {
5202 if (isset($desc['StemV'])) {
5203 // from normal to bold
5204 $desc['StemV'] = round($desc['StemV'] * 1.75);
5205 } else {
5206 // bold
5207 $desc['StemV'] = 123;
5208 }
5209 }
5210 // artificial italic
5211 if (strpos($bistyle, 'I') !== false) {
5212 if (isset($desc['ItalicAngle'])) {
5213 $desc['ItalicAngle'] -= 11;
5214 } else {
5215 $desc['ItalicAngle'] = -11;
5216 }
5217 if (isset($desc['Flags'])) {
5218 $desc['Flags'] |= 64; //bit 7
5219 } else {
5220 $desc['Flags'] = 64;
5221 }
5222 }
5223 }
5224 // initialize subsetchars to contain default ASCII values (0-255)
5225 $subsetchars = array_fill(0, 256, true);
5226 $this->setFontBuffer($fontkey, array('fontkey' => $fontkey, 'i' => $this->numfonts, 'type' => $type, 'name' => $name, 'desc' => $desc, 'up' => $up, 'ut' => $ut, 'cw' => $cw, 'dw' => $dw, 'enc' => $enc, 'cidinfo' => $cidinfo, 'file' => $file, 'ctg' => $ctg, 'subset' => $subset, 'subsetchars' => $subsetchars));
5227 if ($this->inxobj) {
5228 // we are inside an XObject template
5229 $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
5230 }
5231 if (isset($diff) AND (!empty($diff))) {
5232 //Search existing encodings
5233 $d = 0;
5234 $nb = count($this->diffs);
5235 for ($i=1; $i <= $nb; ++$i) {
5236 if ($this->diffs[$i] == $diff) {
5237 $d = $i;
5238 break;
5239 }
5240 }
5241 if ($d == 0) {
5242 $d = $nb + 1;
5243 $this->diffs[$d] = $diff;
5244 }
5245 $this->setFontSubBuffer($fontkey, 'diff', $d);
5246 }
5247 if (!$this->empty_string($file)) {
5248 if (!isset($this->FontFiles[$file])) {
5249 if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
5250 $this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
5251 } elseif ($type != 'core') {
5252 $this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
5253 }
5254 } else {
5255 // update fontkeys that are sharing this font file
5256 $this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
5257 if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
5258 $this->FontFiles[$file]['fontkeys'][] = $fontkey;
5259 }
5260 }
5261 }
5262 return $fontdata;
5263 }
5264
5265 /**
5266 * Sets the font used to print character strings.
5267 * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
5268 * The method can be called before the first page is created and the font is retained from page to page.
5269 * If you just wish to change the current font size, it is simpler to call SetFontSize().
5270 * 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 />
5271 * @param $family (string) Family font. It can be either a name defined by AddFont() or one of the standard Type1 families (case insensitive):<ul><li>times (Times-Roman)</li><li>timesb (Times-Bold)</li><li>timesi (Times-Italic)</li><li>timesbi (Times-BoldItalic)</li><li>helvetica (Helvetica)</li><li>helveticab (Helvetica-Bold)</li><li>helveticai (Helvetica-Oblique)</li><li>helveticabi (Helvetica-BoldOblique)</li><li>courier (Courier)</li><li>courierb (Courier-Bold)</li><li>courieri (Courier-Oblique)</li><li>courierbi (Courier-BoldOblique)</li><li>symbol (Symbol)</li><li>zapfdingbats (ZapfDingbats)</li></ul> It is also possible to pass an empty string. In that case, the current family is retained.
5272 * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line trough</li><li>O: overline</li></ul> or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats basic fonts or other fonts when not defined.
5273 * @param $size (float) Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12
5274 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
5275 * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
5276 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
5277 * @author Nicola Asuni
5278 * @public
5279 * @since 1.0
5280 * @see AddFont(), SetFontSize()
5281 */
5282 public function SetFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
5283 //Select a font; size given in points
5284 if ($size === null) {
5285 $size = $this->FontSizePt;
5286 }
5287 if ($size < 0) {
5288 $size = 0;
5289 }
5290 // try to add font (if not already added)
5291 $fontdata = $this->AddFont($family, $style, $fontfile, $subset);
5292 $this->FontFamily = $fontdata['family'];
5293 $this->FontStyle = $fontdata['style'];
5294 $this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
5295 $this->SetFontSize($size, $out);
5296 }
5297
5298 /**
5299 * Defines the size of the current font.
5300 * @param $size (float) The font size in points.
5301 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
5302 * @public
5303 * @since 1.0
5304 * @see SetFont()
5305 */
5306 public function SetFontSize($size, $out=true) {
5307 // font size in points
5308 $this->FontSizePt = $size;
5309 // font size in user units
5310 $this->FontSize = $size / $this->k;
5311 // calculate some font metrics
5312 if (isset($this->CurrentFont['desc']['FontBBox'])) {
5313 $bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
5314 $font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
5315 } else {
5316 $font_height = $size * 1.219;
5317 }
5318 if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
5319 $font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
5320 }
5321 if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
5322 $font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
5323 }
5324 if (!isset($font_ascent) AND !isset($font_descent)) {
5325 // core font
5326 $font_ascent = 0.76 * $font_height;
5327 $font_descent = $font_height - $font_ascent;
5328 } elseif (!isset($font_descent)) {
5329 $font_descent = $font_height - $font_ascent;
5330 } elseif (!isset($font_ascent)) {
5331 $font_ascent = $font_height - $font_descent;
5332 }
5333 $this->FontAscent = ($font_ascent / $this->k);
5334 $this->FontDescent = ($font_descent / $this->k);
5335 if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i']))) {
5336 $this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
5337 }
5338 }
5339
5340 /**
5341 * Returns the bounding box of the current font in user units.
5342 * @return array
5343 * @public
5344 * @since 5.9.152 (2012-03-23)
5345 */
5346 public function getFontBBox() {
5347 $result = array();
5348 if (isset($this->CurrentFont['desc']['FontBBox'])) {
5349 $bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
5350 foreach ($bbox as $v) {
5351 $result[] = (intval($v) * $this->FontSize / 1000);
5352 }
5353 } else {
5354 // Find max width
5355 if (isset($this->CurrentFont['desc']['MaxWidth'])) {
5356 $maxw = (intval($this->CurrentFont['desc']['MaxWidth']) * $this->FontSize / 1000);
5357 } else {
5358 $maxw = 0;
5359 if (isset($this->CurrentFont['desc']['MissingWidth'])) {
5360 $maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
5361 }
5362 if (isset($this->CurrentFont['desc']['AvgWidth'])) {
5363 $maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
5364 }
5365 if (isset($this->CurrentFont['dw'])) {
5366 $maxw = max($maxw, $this->CurrentFont['dw']);
5367 }
5368 foreach ($this->CurrentFont['cw'] as $char => $w) {
5369 $maxw = max($maxw, $w);
5370 }
5371 if ($maxw == 0) {
5372 $maxw = 600;
5373 }
5374 $maxw = ($maxw * $this->FontSize / 1000);
5375 }
5376 $result = array(0, -$this->FontDescent, $maxw, $this->FontAscent);
5377 }
5378 return $result;
5379 }
5380
5381 /**
5382 * Return the font descent value
5383 * @param $font (string) font name
5384 * @param $style (string) font style
5385 * @param $size (float) The size (in points)
5386 * @return int font descent
5387 * @public
5388 * @author Nicola Asuni
5389 * @since 4.9.003 (2010-03-30)
5390 */
5391 public function getFontDescent($font, $style='', $size=0) {
5392 $fontdata = $this->AddFont($font, $style);
5393 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
5394 if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
5395 $descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
5396 } else {
5397 $descent = 1.219 * 0.24 * $size;
5398 }
5399 return ($descent / $this->k);
5400 }
5401
5402 /**
5403 * Return the font ascent value
5404 * @param $font (string) font name
5405 * @param $style (string) font style
5406 * @param $size (float) The size (in points)
5407 * @return int font ascent
5408 * @public
5409 * @author Nicola Asuni
5410 * @since 4.9.003 (2010-03-30)
5411 */
5412 public function getFontAscent($font, $style='', $size=0) {
5413 $fontdata = $this->AddFont($font, $style);
5414 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
5415 if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
5416 $ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
5417 } else {
5418 $ascent = 1.219 * 0.76 * $size;
5419 }
5420 return ($ascent / $this->k);
5421 }
5422
5423 /**
5424 * Return the font descent value
5425 * @param $char (mixed) Character to check (integer value or string)
5426 * @param $font (string) Font name (family name).
5427 * @param $style (string) Font style.
5428 * @return (boolean) true if the char is defined, false otherwise.
5429 * @public
5430 * @since 5.9.153 (2012-03-28)
5431 */
5432 public function isCharDefined($char, $font='', $style='') {
5433 if (is_string($char)) {
5434 // get character code
5435 $char = $this->UTF8StringToArray($char);
5436 $char = $char[0];
5437 }
5438 if ($this->empty_string($font)) {
5439 $font = $this->FontFamily;
5440 }
5441 $fontdata = $this->AddFont($font, $style);
5442 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
5443 return (isset($fontinfo['cw'][intval($char)]));
5444 }
5445
5446 /**
5447 * Replace missing font characters on selected font with specified substitutions.
5448 * @param $text (string) Text to process.
5449 * @param $font (string) Font name (family name).
5450 * @param $style (string) Font style.
5451 * @param $subs (array) Array of possible character substitutions. The key is the character to check (integer value) and the value is a single intege value or an array of possible substitutes.
5452 * @return (string) Processed text.
5453 * @public
5454 * @since 5.9.153 (2012-03-28)
5455 */
5456 public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
5457 if (empty($subs)) {
5458 return $text;
5459 }
5460 if ($this->empty_string($font)) {
5461 $font = $this->FontFamily;
5462 }
5463 $fontdata = $this->AddFont($font, $style);
5464 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
5465 $uniarr = $this->UTF8StringToArray($text);
5466 foreach ($uniarr as $k => $chr) {
5467 if (!isset($fontinfo['cw'][$chr])) {
5468 // this character is missing on the selected font
5469 if (isset($subs[$chr])) {
5470 // we have available substitutions
5471 if (is_array($subs[$chr])) {
5472 foreach($subs[$chr] as $s) {
5473 if (isset($fontinfo['cw'][$s])) {
5474 $uniarr[$k] = $s;
5475 break;
5476 }
5477 }
5478 } elseif (isset($fontinfo['cw'][$subs[$chr]])) {
5479 $uniarr[$k] = $subs[$chr];
5480 }
5481 }
5482 }
5483 }
5484 return $this->UniArrSubString($this->UTF8ArrayToUniArray($uniarr));
5485 }
5486
5487 /**
5488 * Defines the default monospaced font.
5489 * @param $font (string) Font name.
5490 * @public
5491 * @since 4.5.025
5492 */
5493 public function SetDefaultMonospacedFont($font) {
5494 $this->default_monospaced_font = $font;
5495 }
5496
5497 /**
5498 * 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 />
5499 * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
5500 * @public
5501 * @since 1.5
5502 * @see Cell(), Write(), Image(), Link(), SetLink()
5503 */
5504 public function AddLink() {
5505 //Create a new internal link
5506 $n = count($this->links) + 1;
5507 $this->links[$n] = array(0, 0);
5508 return $n;
5509 }
5510
5511 /**
5512 * Defines the page and position a link points to.
5513 * @param $link (int) The link identifier returned by AddLink()
5514 * @param $y (float) Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
5515 * @param $page (int) Number of target page; -1 indicates the current page. This is the default value
5516 * @public
5517 * @since 1.5
5518 * @see AddLink()
5519 */
5520 public function SetLink($link, $y=0, $page=-1) {
5521 if ($y == -1) {
5522 $y = $this->y;
5523 }
5524 if ($page == -1) {
5525 $page = $this->page;
5526 }
5527 $this->links[$link] = array($page, $y);
5528 }
5529
5530 /**
5531 * Puts a link on a rectangular area of the page.
5532 * 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.
5533 * @param $x (float) Abscissa of the upper-left corner of the rectangle
5534 * @param $y (float) Ordinate of the upper-left corner of the rectangle
5535 * @param $w (float) Width of the rectangle
5536 * @param $h (float) Height of the rectangle
5537 * @param $link (mixed) URL or identifier returned by AddLink()
5538 * @param $spaces (int) number of spaces on the text to link
5539 * @public
5540 * @since 1.5
5541 * @see AddLink(), Annotation(), Cell(), Write(), Image()
5542 */
5543 public function Link($x, $y, $w, $h, $link, $spaces=0) {
5544 $this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
5545 }
5546
5547 /**
5548 * Puts a markup annotation on a rectangular area of the page.
5549 * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
5550 * @param $x (float) Abscissa of the upper-left corner of the rectangle
5551 * @param $y (float) Ordinate of the upper-left corner of the rectangle
5552 * @param $w (float) Width of the rectangle
5553 * @param $h (float) Height of the rectangle
5554 * @param $text (string) annotation text or alternate content
5555 * @param $opt (array) array of options (see section 8.4 of PDF reference 1.7).
5556 * @param $spaces (int) number of spaces on the text to link
5557 * @public
5558 * @since 4.0.018 (2008-08-06)
5559 */
5560 public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
5561 if ($this->inxobj) {
5562 // store parameters for later use on template
5563 $this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
5564 return;
5565 }
5566 if ($x === '') {
5567 $x = $this->x;
5568 }
5569 if ($y === '') {
5570 $y = $this->y;
5571 }
5572 // check page for no-write regions and adapt page margins if necessary
5573 list($x, $y) = $this->checkPageRegions($h, $x, $y);
5574 // recalculate coordinates to account for graphic transformations
5575 if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
5576 for ($i=$this->transfmatrix_key; $i > 0; --$i) {
5577 $maxid = count($this->transfmatrix[$i]) - 1;
5578 for ($j=$maxid; $j >= 0; --$j) {
5579 $ctm = $this->transfmatrix[$i][$j];
5580 if (isset($ctm['a'])) {
5581 $x = $x * $this->k;
5582 $y = ($this->h - $y) * $this->k;
5583 $w = $w * $this->k;
5584 $h = $h * $this->k;
5585 // top left
5586 $xt = $x;
5587 $yt = $y;
5588 $x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
5589 $y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
5590 // top right
5591 $xt = $x + $w;
5592 $yt = $y;
5593 $x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
5594 $y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
5595 // bottom left
5596 $xt = $x;
5597 $yt = $y - $h;
5598 $x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
5599 $y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
5600 // bottom right
5601 $xt = $x + $w;
5602 $yt = $y - $h;
5603 $x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
5604 $y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
5605 // new coordinates (rectangle area)
5606 $x = min($x1, $x2, $x3, $x4);
5607 $y = max($y1, $y2, $y3, $y4);
5608 $w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
5609 $h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
5610 $x = $x / $this->k;
5611 $y = $this->h - ($y / $this->k);
5612 }
5613 }
5614 }
5615 }
5616 if ($this->page <= 0) {
5617 $page = 1;
5618 } else {
5619 $page = $this->page;
5620 }
5621 if (!isset($this->PageAnnots[$page])) {
5622 $this->PageAnnots[$page] = array();
5623 }
5624 ++$this->n;
5625 $this->PageAnnots[$page][] = array('n' => $this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
5626 if (!$this->pdfa_mode) {
5627 if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!$this->empty_string($opt['FS'])) AND file_exists($opt['FS']) AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
5628 ++$this->n;
5629 $this->embeddedfiles[basename($opt['FS'])] = array('n' => $this->n, 'file' => $opt['FS']);
5630 }
5631 }
5632 // Add widgets annotation's icons
5633 if (isset($opt['mk']['i']) AND file_exists($opt['mk']['i'])) {
5634 $this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
5635 }
5636 if (isset($opt['mk']['ri']) AND file_exists($opt['mk']['ri'])) {
5637 $this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
5638 }
5639 if (isset($opt['mk']['ix']) AND file_exists($opt['mk']['ix'])) {
5640 $this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
5641 }
5642 }
5643
5644 /**
5645 * Embedd the attached files.
5646 * @since 4.4.000 (2008-12-07)
5647 * @protected
5648 * @see Annotation()
5649 */
5650 protected function _putEmbeddedFiles() {
5651 if ($this->pdfa_mode) {
5652 // embedded files are not allowed in PDF/A mode
5653 return;
5654 }
5655 reset($this->embeddedfiles);
5656 foreach ($this->embeddedfiles as $filename => $filedata) {
5657 $data = file_get_contents($filedata['file']);
5658 $filter = '';
5659 if ($this->compress) {
5660 $data = gzcompress($data);
5661 $filter = ' /Filter /FlateDecode';
5662 }
5663 $stream = $this->_getrawstream($data, $filedata['n']);
5664 $out = $this->_getobj($filedata['n'])."\n";
5665 $out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' >>';
5666 $out .= ' stream'."\n".$stream."\n".'endstream';
5667 $out .= "\n".'endobj';
5668 $this->_out($out);
5669 }
5670 }
5671
5672 /**
5673 * Prints a text cell at the specified position.
5674 * This method allows to place a string precisely on the page.
5675 * @param $x (float) Abscissa of the cell origin
5676 * @param $y (float) Ordinate of the cell origin
5677 * @param $txt (string) String to print
5678 * @param $fstroke (int) outline size in user units (false = disable)
5679 * @param $fclip (boolean) if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
5680 * @param $ffill (boolean) if true fills the text
5681 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5682 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5683 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5684 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5685 * @param $link (mixed) URL or identifier returned by AddLink().
5686 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5687 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5688 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li><li>B : cell bottom</li></ul>
5689 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
5690 * @param $rtloff (boolean) if true uses the page top-left corner as origin of axis for $x and $y initial position.
5691 * @public
5692 * @since 1.0
5693 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
5694 */
5695 public function Text($x, $y, $txt, $fstroke=false, $fclip=false, $ffill=true, $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M', $rtloff=false) {
5696 $textrendermode = $this->textrendermode;
5697 $textstrokewidth = $this->textstrokewidth;
5698 $this->setTextRenderingMode($fstroke, $ffill, $fclip);
5699 $this->SetXY($x, $y, $rtloff);
5700 $this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
5701 // restore previous rendering mode
5702 $this->textrendermode = $textrendermode;
5703 $this->textstrokewidth = $textstrokewidth;
5704 }
5705
5706 /**
5707 * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
5708 * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
5709 * This method is called automatically and should not be called directly by the application.
5710 * @return boolean
5711 * @public
5712 * @since 1.4
5713 * @see SetAutoPageBreak()
5714 */
5715 public function AcceptPageBreak() {
5716 if ($this->num_columns > 1) {
5717 // multi column mode
5718 if ($this->current_column < ($this->num_columns - 1)) {
5719 // go to next column
5720 $this->selectColumn($this->current_column + 1);
5721 } else {
5722 // add a new page
5723 $this->AddPage();
5724 // set first column
5725 $this->selectColumn(0);
5726 }
5727 // avoid page breaking from checkPageBreak()
5728 return false;
5729 }
5730 return $this->AutoPageBreak;
5731 }
5732
5733 /**
5734 * Add page if needed.
5735 * @param $h (float) Cell height. Default value: 0.
5736 * @param $y (mixed) starting y position, leave empty for current position.
5737 * @param $addpage (boolean) if true add a page, otherwise only return the true/false state
5738 * @return boolean true in case of page break, false otherwise.
5739 * @since 3.2.000 (2008-07-01)
5740 * @protected
5741 */
5742 protected function checkPageBreak($h=0, $y='', $addpage=true) {
5743 if ($this->empty_string($y)) {
5744 $y = $this->y;
5745 }
5746 $current_page = $this->page;
5747 if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
5748 if ($addpage) {
5749 //Automatic page break
5750 $x = $this->x;
5751 $this->AddPage($this->CurOrientation);
5752 $this->y = $this->tMargin;
5753 $oldpage = $this->page - 1;
5754 if ($this->rtl) {
5755 if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
5756 $this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
5757 } else {
5758 $this->x = $x;
5759 }
5760 } else {
5761 if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
5762 $this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
5763 } else {
5764 $this->x = $x;
5765 }
5766 }
5767 }
5768 return true;
5769 }
5770 if ($current_page != $this->page) {
5771 // account for columns mode
5772 return true;
5773 }
5774 return false;
5775 }
5776
5777 /**
5778 * Removes SHY characters from text.
5779 * Unicode Data:<ul>
5780 * <li>Name : SOFT HYPHEN, commonly abbreviated as SHY</li>
5781 * <li>HTML Entity (decimal): "&amp;#173;"</li>
5782 * <li>HTML Entity (hex): "&amp;#xad;"</li>
5783 * <li>HTML Entity (named): "&amp;shy;"</li>
5784 * <li>How to type in Microsoft Windows: [Alt +00AD] or [Alt 0173]</li>
5785 * <li>UTF-8 (hex): 0xC2 0xAD (c2ad)</li>
5786 * <li>UTF-8 character: chr(194).chr(173)</li>
5787 * </ul>
5788 * @param $txt (string) input string
5789 * @return string without SHY characters.
5790 * @public
5791 * @since (4.5.019) 2009-02-28
5792 */
5793 public function removeSHY($txt='') {
5794 $txt = preg_replace('/([\\xc2]{1}[\\xad]{1})/', '', $txt);
5795 if (!$this->isunicode) {
5796 $txt = preg_replace('/([\\xad]{1})/', '', $txt);
5797 }
5798 return $txt;
5799 }
5800
5801 /**
5802 * 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 />
5803 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5804 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
5805 * @param $h (float) Cell height. Default value: 0.
5806 * @param $txt (string) String to print. Default value: empty string.
5807 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5808 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul> Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5809 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5810 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5811 * @param $link (mixed) URL or identifier returned by AddLink().
5812 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5813 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5814 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5815 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
5816 * @public
5817 * @since 1.0
5818 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
5819 */
5820 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') {
5821 $prev_cell_margin = $this->cell_margin;
5822 $prev_cell_padding = $this->cell_padding;
5823 $this->adjustCellPadding($border);
5824 if (!$ignore_min_height) {
5825 $min_cell_height = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
5826 if ($h < $min_cell_height) {
5827 $h = $min_cell_height;
5828 }
5829 }
5830 $this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5831 $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5832 $this->cell_padding = $prev_cell_padding;
5833 $this->cell_margin = $prev_cell_margin;
5834 }
5835
5836 /**
5837 * 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 />
5838 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5839 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
5840 * @param $h (float) Cell height. Default value: 0.
5841 * @param $txt (string) String to print. Default value: empty string.
5842 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5843 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5844 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5845 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5846 * @param $link (mixed) URL or identifier returned by AddLink().
5847 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5848 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5849 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5850 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>M : middle</li><li>B : bottom</li></ul>
5851 * @return string containing cell code
5852 * @protected
5853 * @since 1.0
5854 * @see Cell()
5855 */
5856 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') {
5857 // replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
5858 $txt = str_replace($this->unichr(160), ' ', $txt);
5859 $prev_cell_margin = $this->cell_margin;
5860 $prev_cell_padding = $this->cell_padding;
5861 $txt = $this->removeSHY($txt);
5862 $rs = ''; //string to be returned
5863 $this->adjustCellPadding($border);
5864 if (!$ignore_min_height) {
5865 $min_cell_height = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
5866 if ($h < $min_cell_height) {
5867 $h = $min_cell_height;
5868 }
5869 }
5870 $k = $this->k;
5871 // check page for no-write regions and adapt page margins if necessary
5872 list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
5873 if ($this->rtl) {
5874 $x = $this->x - $this->cell_margin['R'];
5875 } else {
5876 $x = $this->x + $this->cell_margin['L'];
5877 }
5878 $y = $this->y + $this->cell_margin['T'];
5879 $prev_font_stretching = $this->font_stretching;
5880 $prev_font_spacing = $this->font_spacing;
5881 // cell vertical alignment
5882 switch ($calign) {
5883 case 'A': {
5884 // font top
5885 switch ($valign) {
5886 case 'T': {
5887 // top
5888 $y -= $this->cell_padding['T'];
5889 break;
5890 }
5891 case 'B': {
5892 // bottom
5893 $y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5894 break;
5895 }
5896 default:
5897 case 'C':
5898 case 'M': {
5899 // center
5900 $y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5901 break;
5902 }
5903 }
5904 break;
5905 }
5906 case 'L': {
5907 // font baseline
5908 switch ($valign) {
5909 case 'T': {
5910 // top
5911 $y -= ($this->cell_padding['T'] + $this->FontAscent);
5912 break;
5913 }
5914 case 'B': {
5915 // bottom
5916 $y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5917 break;
5918 }
5919 default:
5920 case 'C':
5921 case 'M': {
5922 // center
5923 $y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5924 break;
5925 }
5926 }
5927 break;
5928 }
5929 case 'D': {
5930 // font bottom
5931 switch ($valign) {
5932 case 'T': {
5933 // top
5934 $y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5935 break;
5936 }
5937 case 'B': {
5938 // bottom
5939 $y -= ($h - $this->cell_padding['B']);
5940 break;
5941 }
5942 default:
5943 case 'C':
5944 case 'M': {
5945 // center
5946 $y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5947 break;
5948 }
5949 }
5950 break;
5951 }
5952 case 'B': {
5953 // cell bottom
5954 $y -= $h;
5955 break;
5956 }
5957 case 'C':
5958 case 'M': {
5959 // cell center
5960 $y -= ($h / 2);
5961 break;
5962 }
5963 default:
5964 case 'T': {
5965 // cell top
5966 break;
5967 }
5968 }
5969 // text vertical alignment
5970 switch ($valign) {
5971 case 'T': {
5972 // top
5973 $yt = $y + $this->cell_padding['T'];
5974 break;
5975 }
5976 case 'B': {
5977 // bottom
5978 $yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5979 break;
5980 }
5981 default:
5982 case 'C':
5983 case 'M': {
5984 // center
5985 $yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5986 break;
5987 }
5988 }
5989 $basefonty = $yt + $this->FontAscent;
5990 if ($this->empty_string($w) OR ($w <= 0)) {
5991 if ($this->rtl) {
5992 $w = $x - $this->lMargin;
5993 } else {
5994 $w = $this->w - $this->rMargin - $x;
5995 }
5996 }
5997 $s = '';
5998 // fill and borders
5999 if (is_string($border) AND (strlen($border) == 4)) {
6000 // full border
6001 $border = 1;
6002 }
6003 if ($fill OR ($border == 1)) {
6004 if ($fill) {
6005 $op = ($border == 1) ? 'B' : 'f';
6006 } else {
6007 $op = 'S';
6008 }
6009 if ($this->rtl) {
6010 $xk = (($x - $w) * $k);
6011 } else {
6012 $xk = ($x * $k);
6013 }
6014 $s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
6015 }
6016 // draw borders
6017 $s .= $this->getCellBorder($x, $y, $w, $h, $border);
6018 if ($txt != '') {
6019 $txt2 = $txt;
6020 if ($this->isunicode) {
6021 if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
6022 $txt2 = $this->UTF8ToLatin1($txt2);
6023 } else {
6024 $unicode = $this->UTF8StringToArray($txt); // array of UTF-8 unicode values
6025 $unicode = $this->utf8Bidi($unicode, '', $this->tmprtl);
6026 if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
6027 // ---- Fix for bug #2977340 "Incorrect Thai characters position arrangement" ----
6028 // NOTE: this doesn't work with HTML justification
6029 // Symbols that could overlap on the font top (only works in LTR)
6030 $topchar = array(3611, 3613, 3615, 3650, 3651, 3652); // chars that extends on top
6031 $topsym = array(3633, 3636, 3637, 3638, 3639, 3655, 3656, 3657, 3658, 3659, 3660, 3661, 3662); // symbols with top position
6032 $numchars = count($unicode); // number of chars
6033 $unik = 0;
6034 $uniblock = array();
6035 $uniblock[$unik] = array();
6036 $uniblock[$unik][] = $unicode[0];
6037 // resolve overlapping conflicts by splitting the string in several parts
6038 for ($i = 1; $i < $numchars; ++$i) {
6039 // check if symbols overlaps at top
6040 if (in_array($unicode[$i], $topsym) AND (in_array($unicode[($i - 1)], $topsym) OR in_array($unicode[($i - 1)], $topchar))) {
6041 // move symbols to another array
6042 ++$unik;
6043 $uniblock[$unik] = array();
6044 $uniblock[$unik][] = $unicode[$i];
6045 ++$unik;
6046 $uniblock[$unik] = array();
6047 $unicode[$i] = 0x200b; // Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B)
6048 } else {
6049 $uniblock[$unik][] = $unicode[$i];
6050 }
6051 }
6052 // ---- END OF Fix for bug #2977340
6053 }
6054 $txt2 = $this->arrUTF8ToUTF16BE($unicode, false);
6055 }
6056 }
6057 $txt2 = $this->_escape($txt2);
6058 // get current text width (considering general font stretching and spacing)
6059 $txwidth = $this->GetStringWidth($txt);
6060 $width = $txwidth;
6061 // check for stretch mode
6062 if ($stretch > 0) {
6063 // calculate ratio between cell width and text width
6064 if ($width <= 0) {
6065 $ratio = 1;
6066 } else {
6067 $ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
6068 }
6069 // check if stretching is required
6070 if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
6071 // the text will be stretched to fit cell width
6072 if ($stretch > 2) {
6073 // set new character spacing
6074 $this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
6075 } else {
6076 // set new horizontal stretching
6077 $this->font_stretching *= $ratio;
6078 }
6079 // recalculate text width (the text fills the entire cell)
6080 $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6081 // reset alignment
6082 $align = '';
6083 }
6084 }
6085 if ($this->font_stretching != 100) {
6086 // apply font stretching
6087 $rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
6088 }
6089 if ($this->font_spacing != 0) {
6090 // increase/decrease font spacing
6091 $rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
6092 }
6093 if ($this->ColorFlag AND ($this->textrendermode < 4)) {
6094 $s .= 'q '.$this->TextColor.' ';
6095 }
6096 // rendering mode
6097 $s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, $this->textstrokewidth);
6098 // count number of spaces
6099 $ns = substr_count($txt, chr(32));
6100 // Justification
6101 $spacewidth = 0;
6102 if (($align == 'J') AND ($ns > 0)) {
6103 if ($this->isUnicodeFont()) {
6104 // get string width without spaces
6105 $width = $this->GetStringWidth(str_replace(' ', '', $txt));
6106 // calculate average space width
6107 $spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / $this->FontSize;
6108 if ($this->font_stretching != 100) {
6109 // word spacing is affected by stretching
6110 $spacewidth /= ($this->font_stretching / 100);
6111 }
6112 // set word position to be used with TJ operator
6113 $txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
6114 $unicode_justification = true;
6115 } else {
6116 // get string width
6117 $width = $txwidth;
6118 // new space width
6119 $spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
6120 if ($this->font_stretching != 100) {
6121 // word spacing (Tw) is affected by stretching
6122 $spacewidth /= ($this->font_stretching / 100);
6123 }
6124 // set word spacing
6125 $rs .= sprintf('BT %F Tw ET ', $spacewidth);
6126 }
6127 $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6128 }
6129 // replace carriage return characters
6130 $txt2 = str_replace("\r", ' ', $txt2);
6131 switch ($align) {
6132 case 'C': {
6133 $dx = ($w - $width) / 2;
6134 break;
6135 }
6136 case 'R': {
6137 if ($this->rtl) {
6138 $dx = $this->cell_padding['R'];
6139 } else {
6140 $dx = $w - $width - $this->cell_padding['R'];
6141 }
6142 break;
6143 }
6144 case 'L': {
6145 if ($this->rtl) {
6146 $dx = $w - $width - $this->cell_padding['L'];
6147 } else {
6148 $dx = $this->cell_padding['L'];
6149 }
6150 break;
6151 }
6152 case 'J':
6153 default: {
6154 if ($this->rtl) {
6155 $dx = $this->cell_padding['R'];
6156 } else {
6157 $dx = $this->cell_padding['L'];
6158 }
6159 break;
6160 }
6161 }
6162 if ($this->rtl) {
6163 $xdx = $x - $dx - $width;
6164 } else {
6165 $xdx = $x + $dx;
6166 }
6167 $xdk = $xdx * $k;
6168 // print text
6169 $s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
6170 if (isset($uniblock)) {
6171 // print overlapping characters as separate string
6172 $xshift = 0; // horizontal shift
6173 $ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
6174 $spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
6175 foreach ($uniblock as $uk => $uniarr) {
6176 if (($uk % 2) == 0) {
6177 // x space to skip
6178 if ($spacewidth != 0) {
6179 // justification shift
6180 $xshift += (count(array_keys($uniarr, 32)) * $spw);
6181 }
6182 $xshift += $this->GetArrStringWidth($uniarr); // + shift justification
6183 } else {
6184 // character to print
6185 $topchr = $this->arrUTF8ToUTF16BE($uniarr, false);
6186 $topchr = $this->_escape($topchr);
6187 $s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
6188 }
6189 }
6190 }
6191 if ($this->underline) {
6192 $s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
6193 }
6194 if ($this->linethrough) {
6195 $s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
6196 }
6197 if ($this->overline) {
6198 $s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
6199 }
6200 if ($this->ColorFlag AND ($this->textrendermode < 4)) {
6201 $s .= ' Q';
6202 }
6203 if ($link) {
6204 $this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
6205 }
6206 }
6207 // output cell
6208 if ($s) {
6209 // output cell
6210 $rs .= $s;
6211 if ($this->font_spacing != 0) {
6212 // reset font spacing mode
6213 $rs .= ' BT 0 Tc ET';
6214 }
6215 if ($this->font_stretching != 100) {
6216 // reset font stretching mode
6217 $rs .= ' BT 100 Tz ET';
6218 }
6219 }
6220 // reset word spacing
6221 if (!$this->isUnicodeFont() AND ($align == 'J')) {
6222 $rs .= ' BT 0 Tw ET';
6223 }
6224 // reset stretching and spacing
6225 $this->font_stretching = $prev_font_stretching;
6226 $this->font_spacing = $prev_font_spacing;
6227 $this->lasth = $h;
6228 if ($ln > 0) {
6229 //Go to the beginning of the next line
6230 $this->y = $y + $h + $this->cell_margin['B'];
6231 if ($ln == 1) {
6232 if ($this->rtl) {
6233 $this->x = $this->w - $this->rMargin;
6234 } else {
6235 $this->x = $this->lMargin;
6236 }
6237 }
6238 } else {
6239 // go left or right by case
6240 if ($this->rtl) {
6241 $this->x = $x - $w - $this->cell_margin['L'];
6242 } else {
6243 $this->x = $x + $w + $this->cell_margin['R'];
6244 }
6245 }
6246 $gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
6247 $rs = $gstyles.$rs;
6248 $this->cell_padding = $prev_cell_padding;
6249 $this->cell_margin = $prev_cell_margin;
6250 return $rs;
6251 }
6252
6253 /**
6254 * Returns the code to draw the cell border
6255 * @param $x (float) X coordinate.
6256 * @param $y (float) Y coordinate.
6257 * @param $w (float) Cell width.
6258 * @param $h (float) Cell height.
6259 * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6260 * @return string containing cell border code
6261 * @protected
6262 * @see SetLineStyle()
6263 * @since 5.7.000 (2010-08-02)
6264 */
6265 protected function getCellBorder($x, $y, $w, $h, $brd) {
6266 $s = ''; // string to be returned
6267 if (empty($brd)) {
6268 return $s;
6269 }
6270 if ($brd == 1) {
6271 $brd = array('LRTB' => true);
6272 }
6273 // calculate coordinates for border
6274 $k = $this->k;
6275 if ($this->rtl) {
6276 $xeL = ($x - $w) * $k;
6277 $xeR = $x * $k;
6278 } else {
6279 $xeL = $x * $k;
6280 $xeR = ($x + $w) * $k;
6281 }
6282 $yeL = (($this->h - ($y + $h)) * $k);
6283 $yeT = (($this->h - $y) * $k);
6284 $xeT = $xeL;
6285 $xeB = $xeR;
6286 $yeR = $yeT;
6287 $yeB = $yeL;
6288 if (is_string($brd)) {
6289 // convert string to array
6290 $slen = strlen($brd);
6291 $newbrd = array();
6292 for ($i = 0; $i < $slen; ++$i) {
6293 $newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
6294 }
6295 $brd = $newbrd;
6296 }
6297 if (isset($brd['mode'])) {
6298 $mode = $brd['mode'];
6299 unset($brd['mode']);
6300 } else {
6301 $mode = 'normal';
6302 }
6303 foreach ($brd as $border => $style) {
6304 if (is_array($style) AND !empty($style)) {
6305 // apply border style
6306 $prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
6307 $s .= $this->SetLineStyle($style, true)."\n";
6308 }
6309 switch ($mode) {
6310 case 'ext': {
6311 $off = (($this->LineWidth / 2) * $k);
6312 $xL = $xeL - $off;
6313 $xR = $xeR + $off;
6314 $yT = $yeT + $off;
6315 $yL = $yeL - $off;
6316 $xT = $xL;
6317 $xB = $xR;
6318 $yR = $yT;
6319 $yB = $yL;
6320 $w += $this->LineWidth;
6321 $h += $this->LineWidth;
6322 break;
6323 }
6324 case 'int': {
6325 $off = ($this->LineWidth / 2) * $k;
6326 $xL = $xeL + $off;
6327 $xR = $xeR - $off;
6328 $yT = $yeT - $off;
6329 $yL = $yeL + $off;
6330 $xT = $xL;
6331 $xB = $xR;
6332 $yR = $yT;
6333 $yB = $yL;
6334 $w -= $this->LineWidth;
6335 $h -= $this->LineWidth;
6336 break;
6337 }
6338 case 'normal':
6339 default: {
6340 $xL = $xeL;
6341 $xT = $xeT;
6342 $xB = $xeB;
6343 $xR = $xeR;
6344 $yL = $yeL;
6345 $yT = $yeT;
6346 $yB = $yeB;
6347 $yR = $yeR;
6348 break;
6349 }
6350 }
6351 // draw borders by case
6352 if (strlen($border) == 4) {
6353 $s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
6354 } elseif (strlen($border) == 3) {
6355 if (strpos($border,'B') === false) { // LTR
6356 $s .= sprintf('%F %F m ', $xL, $yL);
6357 $s .= sprintf('%F %F l ', $xT, $yT);
6358 $s .= sprintf('%F %F l ', $xR, $yR);
6359 $s .= sprintf('%F %F l ', $xB, $yB);
6360 $s .= 'S ';
6361 } elseif (strpos($border,'L') === false) { // TRB
6362 $s .= sprintf('%F %F m ', $xT, $yT);
6363 $s .= sprintf('%F %F l ', $xR, $yR);
6364 $s .= sprintf('%F %F l ', $xB, $yB);
6365 $s .= sprintf('%F %F l ', $xL, $yL);
6366 $s .= 'S ';
6367 } elseif (strpos($border,'T') === false) { // RBL
6368 $s .= sprintf('%F %F m ', $xR, $yR);
6369 $s .= sprintf('%F %F l ', $xB, $yB);
6370 $s .= sprintf('%F %F l ', $xL, $yL);
6371 $s .= sprintf('%F %F l ', $xT, $yT);
6372 $s .= 'S ';
6373 } elseif (strpos($border,'R') === false) { // BLT
6374 $s .= sprintf('%F %F m ', $xB, $yB);
6375 $s .= sprintf('%F %F l ', $xL, $yL);
6376 $s .= sprintf('%F %F l ', $xT, $yT);
6377 $s .= sprintf('%F %F l ', $xR, $yR);
6378 $s .= 'S ';
6379 }
6380 } elseif (strlen($border) == 2) {
6381 if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
6382 $s .= sprintf('%F %F m ', $xL, $yL);
6383 $s .= sprintf('%F %F l ', $xT, $yT);
6384 $s .= sprintf('%F %F l ', $xR, $yR);
6385 $s .= 'S ';
6386 } elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
6387 $s .= sprintf('%F %F m ', $xT, $yT);
6388 $s .= sprintf('%F %F l ', $xR, $yR);
6389 $s .= sprintf('%F %F l ', $xB, $yB);
6390 $s .= 'S ';
6391 } elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
6392 $s .= sprintf('%F %F m ', $xR, $yR);
6393 $s .= sprintf('%F %F l ', $xB, $yB);
6394 $s .= sprintf('%F %F l ', $xL, $yL);
6395 $s .= 'S ';
6396 } elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
6397 $s .= sprintf('%F %F m ', $xB, $yB);
6398 $s .= sprintf('%F %F l ', $xL, $yL);
6399 $s .= sprintf('%F %F l ', $xT, $yT);
6400 $s .= 'S ';
6401 } elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
6402 $s .= sprintf('%F %F m ', $xL, $yL);
6403 $s .= sprintf('%F %F l ', $xT, $yT);
6404 $s .= 'S ';
6405 $s .= sprintf('%F %F m ', $xR, $yR);
6406 $s .= sprintf('%F %F l ', $xB, $yB);
6407 $s .= 'S ';
6408 } elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
6409 $s .= sprintf('%F %F m ', $xT, $yT);
6410 $s .= sprintf('%F %F l ', $xR, $yR);
6411 $s .= 'S ';
6412 $s .= sprintf('%F %F m ', $xB, $yB);
6413 $s .= sprintf('%F %F l ', $xL, $yL);
6414 $s .= 'S ';
6415 }
6416 } else { // strlen($border) == 1
6417 if (strpos($border,'L') !== false) { // L
6418 $s .= sprintf('%F %F m ', $xL, $yL);
6419 $s .= sprintf('%F %F l ', $xT, $yT);
6420 $s .= 'S ';
6421 } elseif (strpos($border,'T') !== false) { // T
6422 $s .= sprintf('%F %F m ', $xT, $yT);
6423 $s .= sprintf('%F %F l ', $xR, $yR);
6424 $s .= 'S ';
6425 } elseif (strpos($border,'R') !== false) { // R
6426 $s .= sprintf('%F %F m ', $xR, $yR);
6427 $s .= sprintf('%F %F l ', $xB, $yB);
6428 $s .= 'S ';
6429 } elseif (strpos($border,'B') !== false) { // B
6430 $s .= sprintf('%F %F m ', $xB, $yB);
6431 $s .= sprintf('%F %F l ', $xL, $yL);
6432 $s .= 'S ';
6433 }
6434 }
6435 if (is_array($style) AND !empty($style)) {
6436 // reset border style to previous value
6437 $s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
6438 }
6439 }
6440 return $s;
6441 }
6442
6443 /**
6444 * This method allows printing text with line breaks.
6445 * 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 />
6446 * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
6447 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6448 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
6449 * @param $txt (string) String to print
6450 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6451 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align</li><li>C: center</li><li>R: right align</li><li>J: justification (default value when $ishtml=false)</li></ul>
6452 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
6453 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line [DEFAULT]</li><li>2: below</li></ul>
6454 * @param $x (float) x position in user units
6455 * @param $y (float) y position in user units
6456 * @param $reseth (boolean) if true reset the last cell height (default true).
6457 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
6458 * @param $ishtml (boolean) INTERNAL USE ONLY -- set to true if $txt is HTML content (default = false). Never set this parameter to true, use instead writeHTMLCell() or writeHTML() methods.
6459 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
6460 * @param $maxh (float) maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. This feature works only when $ishtml=false.
6461 * @param $valign (string) Vertical alignment of text (requires $maxh = $h > 0). Possible values are:<ul><li>T: TOP</li><li>M: middle</li><li>B: bottom</li></ul>. This feature works only when $ishtml=false and the cell must fit in a single page.
6462 * @param $fitcell (boolean) if true attempt to fit all the text within the cell by reducing the font size (do not work in HTML mode).
6463 * @return int Return the number of cells or 1 for html mode.
6464 * @public
6465 * @since 1.3
6466 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
6467 */
6468 public function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0, $valign='T', $fitcell=false) {
6469 $prev_cell_margin = $this->cell_margin;
6470 $prev_cell_padding = $this->cell_padding;
6471 // adjust internal padding
6472 $this->adjustCellPadding($border);
6473 $mc_padding = $this->cell_padding;
6474 $mc_margin = $this->cell_margin;
6475 $this->cell_padding['T'] = 0;
6476 $this->cell_padding['B'] = 0;
6477 $this->setCellMargins(0, 0, 0, 0);
6478 if ($this->empty_string($this->lasth) OR $reseth) {
6479 // reset row height
6480 $this->resetLastH();
6481 }
6482 if (!$this->empty_string($y)) {
6483 $this->SetY($y);
6484 } else {
6485 $y = $this->GetY();
6486 }
6487 $resth = 0;
6488 if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
6489 // spit cell in more pages/columns
6490 $newh = ($this->PageBreakTrigger - $y);
6491 $resth = ($h - $newh); // cell to be printed on the next page/column
6492 $h = $newh;
6493 }
6494 // get current page number
6495 $startpage = $this->page;
6496 // get current column
6497 $startcolumn = $this->current_column;
6498 if (!$this->empty_string($x)) {
6499 $this->SetX($x);
6500 } else {
6501 $x = $this->GetX();
6502 }
6503 // check page for no-write regions and adapt page margins if necessary
6504 list($x, $y) = $this->checkPageRegions(0, $x, $y);
6505 // apply margins
6506 $oy = $y + $mc_margin['T'];
6507 if ($this->rtl) {
6508 $ox = $this->w - $x - $mc_margin['R'];
6509 } else {
6510 $ox = $x + $mc_margin['L'];
6511 }
6512 $this->x = $ox;
6513 $this->y = $oy;
6514 // set width
6515 if ($this->empty_string($w) OR ($w <= 0)) {
6516 if ($this->rtl) {
6517 $w = $this->x - $this->lMargin - $mc_margin['L'];
6518 } else {
6519 $w = $this->w - $this->x - $this->rMargin - $mc_margin['R'];
6520 }
6521 }
6522 // store original margin values
6523 $lMargin = $this->lMargin;
6524 $rMargin = $this->rMargin;
6525 if ($this->rtl) {
6526 $this->rMargin = $this->w - $this->x;
6527 $this->lMargin = $this->x - $w;
6528 } else {
6529 $this->lMargin = $this->x;
6530 $this->rMargin = $this->w - $this->x - $w;
6531 }
6532 if ($autopadding) {
6533 // add top padding
6534 $this->y += $mc_padding['T'];
6535 }
6536 if ($ishtml) { // ******* Write HTML text
6537 $this->writeHTML($txt, true, false, $reseth, true, $align);
6538 $nl = 1;
6539 } else { // ******* Write simple text
6540 $prev_FontSizePt = $this->FontSizePt;
6541 // vertical alignment
6542 if ($maxh > 0) {
6543 // get text height
6544 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
6545 if ($fitcell) {
6546 // try to reduce font size to fit text on cell (use a quick search algorithm)
6547 $fmin = 1;
6548 $fmax = $this->FontSizePt;
6549 $prev_text_height = $text_height;
6550 $maxit = 100; // max number of iterations
6551 while ($maxit > 0) {
6552 $fmid = (($fmax + $fmin) / 2);
6553 $this->SetFontSize($fmid, false);
6554 $this->resetLastH();
6555 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
6556 if (($text_height == $maxh) OR (($text_height < $maxh) AND ($fmin >= ($fmax - 0.01)))) {
6557 break;
6558 } elseif ($text_height < $maxh) {
6559 $fmin = $fmid;
6560 } else {
6561 $fmax = $fmid;
6562 }
6563 --$maxit;
6564 }
6565 $this->SetFontSize($this->FontSizePt);
6566 }
6567 if ($text_height < $maxh) {
6568 if ($valign == 'M') {
6569 // text vertically centered
6570 $this->y += (($maxh - $text_height) / 2);
6571 } elseif ($valign == 'B') {
6572 // text vertically aligned on bottom
6573 $this->y += ($maxh - $text_height);
6574 }
6575 }
6576 }
6577 $nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
6578 if ($fitcell) {
6579 // restore font size
6580 $this->SetFontSize($prev_FontSizePt);
6581 }
6582 }
6583 if ($autopadding) {
6584 // add bottom padding
6585 $this->y += $mc_padding['B'];
6586 }
6587 // Get end-of-text Y position
6588 $currentY = $this->y;
6589 // get latest page number
6590 $endpage = $this->page;
6591 if ($resth > 0) {
6592 $skip = ($endpage - $startpage);
6593 $tmpresth = $resth;
6594 while ($tmpresth > 0) {
6595 if ($skip <= 0) {
6596 // add a page (or trig AcceptPageBreak() for multicolumn mode)
6597 $this->checkPageBreak($this->PageBreakTrigger + 1);
6598 }
6599 if ($this->num_columns > 1) {
6600 $tmpresth -= ($this->h - $this->y - $this->bMargin);
6601 } else {
6602 $tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
6603 }
6604 --$skip;
6605 }
6606 $currentY = $this->y;
6607 $endpage = $this->page;
6608 }
6609 // get latest column
6610 $endcolumn = $this->current_column;
6611 if ($this->num_columns == 0) {
6612 $this->num_columns = 1;
6613 }
6614 // get border modes
6615 $border_start = $this->getBorderMode($border, $position='start');
6616 $border_end = $this->getBorderMode($border, $position='end');
6617 $border_middle = $this->getBorderMode($border, $position='middle');
6618 // design borders around HTML cells.
6619 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
6620 $ccode = '';
6621 $this->setPage($page);
6622 if ($this->num_columns < 2) {
6623 // single-column mode
6624 $this->SetX($x);
6625 $this->y = $this->tMargin;
6626 }
6627 // account for margin changes
6628 if ($page > $startpage) {
6629 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
6630 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
6631 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
6632 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
6633 }
6634 }
6635 if ($startpage == $endpage) {
6636 // single page
6637 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
6638 $this->selectColumn($column);
6639 if ($this->rtl) {
6640 $this->x -= $mc_margin['R'];
6641 } else {
6642 $this->x += $mc_margin['L'];
6643 }
6644 if ($startcolumn == $endcolumn) { // single column
6645 $cborder = $border;
6646 $h = max($h, ($currentY - $oy));
6647 $this->y = $oy;
6648 } elseif ($column == $startcolumn) { // first column
6649 $cborder = $border_start;
6650 $this->y = $oy;
6651 $h = $this->h - $this->y - $this->bMargin;
6652 } elseif ($column == $endcolumn) { // end column
6653 $cborder = $border_end;
6654 $h = $currentY - $this->y;
6655 if ($resth > $h) {
6656 $h = $resth;
6657 }
6658 } else { // middle column
6659 $cborder = $border_middle;
6660 $h = $this->h - $this->y - $this->bMargin;
6661 $resth -= $h;
6662 }
6663 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6664 } // end for each column
6665 } elseif ($page == $startpage) { // first page
6666 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
6667 $this->selectColumn($column);
6668 if ($this->rtl) {
6669 $this->x -= $mc_margin['R'];
6670 } else {
6671 $this->x += $mc_margin['L'];
6672 }
6673 if ($column == $startcolumn) { // first column
6674 $cborder = $border_start;
6675 $this->y = $oy;
6676 $h = $this->h - $this->y - $this->bMargin;
6677 } else { // middle column
6678 $cborder = $border_middle;
6679 $h = $this->h - $this->y - $this->bMargin;
6680 $resth -= $h;
6681 }
6682 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6683 } // end for each column
6684 } elseif ($page == $endpage) { // last page
6685 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
6686 $this->selectColumn($column);
6687 if ($this->rtl) {
6688 $this->x -= $mc_margin['R'];
6689 } else {
6690 $this->x += $mc_margin['L'];
6691 }
6692 if ($column == $endcolumn) {
6693 // end column
6694 $cborder = $border_end;
6695 $h = $currentY - $this->y;
6696 if ($resth > $h) {
6697 $h = $resth;
6698 }
6699 } else {
6700 // middle column
6701 $cborder = $border_middle;
6702 $h = $this->h - $this->y - $this->bMargin;
6703 $resth -= $h;
6704 }
6705 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6706 } // end for each column
6707 } else { // middle page
6708 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6709 $this->selectColumn($column);
6710 if ($this->rtl) {
6711 $this->x -= $mc_margin['R'];
6712 } else {
6713 $this->x += $mc_margin['L'];
6714 }
6715 $cborder = $border_middle;
6716 $h = $this->h - $this->y - $this->bMargin;
6717 $resth -= $h;
6718 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6719 } // end for each column
6720 }
6721 if ($cborder OR $fill) {
6722 $offsetlen = strlen($ccode);
6723 // draw border and fill
6724 if ($this->inxobj) {
6725 // we are inside an XObject template
6726 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6727 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6728 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6729 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
6730 } else {
6731 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
6732 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
6733 }
6734 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6735 $pstart = substr($pagebuff, 0, $pagemark);
6736 $pend = substr($pagebuff, $pagemark);
6737 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6738 } else {
6739 if (end($this->transfmrk[$this->page]) !== false) {
6740 $pagemarkkey = key($this->transfmrk[$this->page]);
6741 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
6742 $this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
6743 } elseif ($this->InFooter) {
6744 $pagemark = $this->footerpos[$this->page];
6745 $this->footerpos[$this->page] += $offsetlen;
6746 } else {
6747 $pagemark = $this->intmrk[$this->page];
6748 $this->intmrk[$this->page] += $offsetlen;
6749 }
6750 $pagebuff = $this->getPageBuffer($this->page);
6751 $pstart = substr($pagebuff, 0, $pagemark);
6752 $pend = substr($pagebuff, $pagemark);
6753 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6754 }
6755 }
6756 } // end for each page
6757 // Get end-of-cell Y position
6758 $currentY = $this->GetY();
6759 // restore previous values
6760 if ($this->num_columns > 1) {
6761 $this->selectColumn();
6762 } else {
6763 // restore original margins
6764 $this->lMargin = $lMargin;
6765 $this->rMargin = $rMargin;
6766 if ($this->page > $startpage) {
6767 // check for margin variations between pages (i.e. booklet mode)
6768 $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
6769 $dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
6770 if (($dl != 0) OR ($dr != 0)) {
6771 $this->lMargin += $dl;
6772 $this->rMargin += $dr;
6773 }
6774 }
6775 }
6776 if ($ln > 0) {
6777 //Go to the beginning of the next line
6778 $this->SetY($currentY + $mc_margin['B']);
6779 if ($ln == 2) {
6780 $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6781 }
6782 } else {
6783 // go left or right by case
6784 $this->setPage($startpage);
6785 $this->y = $y;
6786 $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6787 }
6788 $this->setContentMark();
6789 $this->cell_padding = $prev_cell_padding;
6790 $this->cell_margin = $prev_cell_margin;
6791 return $nl;
6792 }
6793
6794 /**
6795 * Get the border mode accounting for multicell position (opens bottom side of multicell crossing pages)
6796 * @param $brd (mixed) Indicates if borders must be drawn around the cell block. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul>or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6797 * @param $position (string) multicell position: 'start', 'middle', 'end'
6798 * @return border mode array
6799 * @protected
6800 * @since 4.4.002 (2008-12-09)
6801 */
6802 protected function getBorderMode($brd, $position='start') {
6803 if ((!$this->opencell) OR empty($brd)) {
6804 return $brd;
6805 }
6806 if ($brd == 1) {
6807 $brd = 'LTRB';
6808 }
6809 if (is_string($brd)) {
6810 // convert string to array
6811 $slen = strlen($brd);
6812 $newbrd = array();
6813 for ($i = 0; $i < $slen; ++$i) {
6814 $newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
6815 }
6816 $brd = $newbrd;
6817 }
6818 foreach ($brd as $border => $style) {
6819 switch ($position) {
6820 case 'start': {
6821 if (strpos($border, 'B') !== false) {
6822 // remove bottom line
6823 $newkey = str_replace('B', '', $border);
6824 if (strlen($newkey) > 0) {
6825 $brd[$newkey] = $style;
6826 }
6827 unset($brd[$border]);
6828 }
6829 break;
6830 }
6831 case 'middle': {
6832 if (strpos($border, 'B') !== false) {
6833 // remove bottom line
6834 $newkey = str_replace('B', '', $border);
6835 if (strlen($newkey) > 0) {
6836 $brd[$newkey] = $style;
6837 }
6838 unset($brd[$border]);
6839 $border = $newkey;
6840 }
6841 if (strpos($border, 'T') !== false) {
6842 // remove bottom line
6843 $newkey = str_replace('T', '', $border);
6844 if (strlen($newkey) > 0) {
6845 $brd[$newkey] = $style;
6846 }
6847 unset($brd[$border]);
6848 }
6849 break;
6850 }
6851 case 'end': {
6852 if (strpos($border, 'T') !== false) {
6853 // remove bottom line
6854 $newkey = str_replace('T', '', $border);
6855 if (strlen($newkey) > 0) {
6856 $brd[$newkey] = $style;
6857 }
6858 unset($brd[$border]);
6859 }
6860 break;
6861 }
6862 }
6863 }
6864 return $brd;
6865 }
6866
6867 /**
6868 * This method return the estimated number of lines for print a simple text string using Multicell() method.
6869 * @param $txt (string) String for calculating his height
6870 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6871 * @param $reseth (boolean) if true reset the last cell height (default false).
6872 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6873 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6874 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6875 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6876 * @author Alexander Escalona Fern�ndez, Nicola Asuni
6877 * @public
6878 * @since 4.5.011
6879 */
6880 public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6881 if ($txt === '') {
6882 // empty string
6883 return 1;
6884 }
6885 // adjust internal padding
6886 $prev_cell_padding = $this->cell_padding;
6887 $prev_lasth = $this->lasth;
6888 if (is_array($cellpadding)) {
6889 $this->cell_padding = $cellpadding;
6890 }
6891 $this->adjustCellPadding($border);
6892 if ($this->empty_string($w) OR ($w <= 0)) {
6893 if ($this->rtl) {
6894 $w = $this->x - $this->lMargin;
6895 } else {
6896 $w = $this->w - $this->rMargin - $this->x;
6897 }
6898 }
6899 $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6900 if ($reseth) {
6901 // reset row height
6902 $this->resetLastH();
6903 }
6904 $lines = 1;
6905 $sum = 0;
6906 $chars = $this->utf8Bidi($this->UTF8StringToArray($txt), $txt, $this->tmprtl);
6907 $charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6908 $length = count($chars);
6909 $lastSeparator = -1;
6910 for ($i = 0; $i < $length; ++$i) {
6911 $charWidth = $charsWidth[$i];
6912 if (preg_match($this->re_spaces, $this->unichr($chars[$i]))) {
6913 $lastSeparator = $i;
6914 }
6915 if ((($sum + $charWidth) > $wmax) OR ($chars[$i] == 10)) {
6916 ++$lines;
6917 if ($chars[$i] == 10) {
6918 $lastSeparator = -1;
6919 $sum = 0;
6920 } elseif ($lastSeparator != -1) {
6921 $i = $lastSeparator;
6922 $lastSeparator = -1;
6923 $sum = 0;
6924 } else {
6925 $sum = $charWidth;
6926 }
6927 } else {
6928 $sum += $charWidth;
6929 }
6930 }
6931 if ($chars[($length - 1)] == 10) {
6932 --$lines;
6933 }
6934 $this->cell_padding = $prev_cell_padding;
6935 $this->lasth = $prev_lasth;
6936 return $lines;
6937 }
6938
6939 /**
6940 * This method return the estimated height needed for printing a simple text string using the Multicell() method.
6941 * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6942 * @pre
6943 * // store current object
6944 * $pdf->startTransaction();
6945 * // store starting values
6946 * $start_y = $pdf->GetY();
6947 * $start_page = $pdf->getPage();
6948 * // call your printing functions with your parameters
6949 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6950 * $pdf->MultiCell($w=0, $h=0, $txt, $border=1, $align='L', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0);
6951 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6952 * // get the new Y
6953 * $end_y = $pdf->GetY();
6954 * $end_page = $pdf->getPage();
6955 * // calculate height
6956 * $height = 0;
6957 * if ($end_page == $start_page) {
6958 * $height = $end_y - $start_y;
6959 * } else {
6960 * for ($page=$start_page; $page <= $end_page; ++$page) {
6961 * $this->setPage($page);
6962 * if ($page == $start_page) {
6963 * // first page
6964 * $height = $this->h - $start_y - $this->bMargin;
6965 * } elseif ($page == $end_page) {
6966 * // last page
6967 * $height = $end_y - $this->tMargin;
6968 * } else {
6969 * $height = $this->h - $this->tMargin - $this->bMargin;
6970 * }
6971 * }
6972 * }
6973 * // restore previous object
6974 * $pdf = $pdf->rollbackTransaction();
6975 *
6976 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6977 * @param $txt (string) String for calculating his height
6978 * @param $reseth (boolean) if true reset the last cell height (default false).
6979 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6980 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6981 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6982 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6983 * @author Nicola Asuni, Alexander Escalona Fern�ndez
6984 * @public
6985 */
6986 public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6987 // adjust internal padding
6988 $prev_cell_padding = $this->cell_padding;
6989 $prev_lasth = $this->lasth;
6990 if (is_array($cellpadding)) {
6991 $this->cell_padding = $cellpadding;
6992 }
6993 $this->adjustCellPadding($border);
6994 $lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6995 $height = $lines * ($this->FontSize * $this->cell_height_ratio);
6996 if ($autopadding) {
6997 // add top and bottom padding
6998 $height += ($this->cell_padding['T'] + $this->cell_padding['B']);
6999 }
7000 $this->cell_padding = $prev_cell_padding;
7001 $this->lasth = $prev_lasth;
7002 return $height;
7003 }
7004
7005 /**
7006 * This method prints text from the current position.<br />
7007 * @param $h (float) Line height
7008 * @param $txt (string) String to print
7009 * @param $link (mixed) URL or identifier returned by AddLink()
7010 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
7011 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
7012 * @param $ln (boolean) if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
7013 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
7014 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
7015 * @param $firstblock (boolean) if true the string is the starting of a line.
7016 * @param $maxh (float) maximum height. The remaining unprinted text will be returned. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature.
7017 * @param $wadj (float) first line width will be reduced by this amount (used in HTML mode).
7018 * @param $margin (array) margin array of the parent container
7019 * @return mixed Return the number of cells or the remaining string if $firstline = true.
7020 * @public
7021 * @since 1.5
7022 */
7023 public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
7024 // check page for no-write regions and adapt page margins if necessary
7025 list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
7026 if (strlen($txt) == 0) {
7027 // fix empty text
7028 $txt = ' ';
7029 }
7030 if ($margin === '') {
7031 // set default margins
7032 $margin = $this->cell_margin;
7033 }
7034 // remove carriage returns
7035 $s = str_replace("\r", '', $txt);
7036 // check if string contains arabic text
7037 if (preg_match($this->unicode->uni_RE_PATTERN_ARABIC, $s)) {
7038 $arabic = true;
7039 } else {
7040 $arabic = false;
7041 }
7042 // check if string contains RTL text
7043 if ($arabic OR ($this->tmprtl == 'R') OR preg_match($this->unicode->uni_RE_PATTERN_RTL, $s)) {
7044 $rtlmode = true;
7045 } else {
7046 $rtlmode = false;
7047 }
7048 // get a char width
7049 $chrwidth = $this->GetCharWidth(46); // dot character
7050 // get array of unicode values
7051 $chars = $this->UTF8StringToArray($s);
7052 // get array of chars
7053 $uchars = $this->UTF8ArrayToUniArray($chars);
7054 // get the number of characters
7055 $nb = count($chars);
7056 // replacement for SHY character (minus symbol)
7057 $shy_replacement = 45;
7058 $shy_replacement_char = $this->unichr($shy_replacement);
7059 // widht for SHY replacement
7060 $shy_replacement_width = $this->GetCharWidth($shy_replacement);
7061 // max Y
7062 $maxy = $this->y + $maxh - $h - $this->cell_padding['T'] - $this->cell_padding['B'];
7063 // page width
7064 $pw = $w = $this->w - $this->lMargin - $this->rMargin;
7065 // calculate remaining line width ($w)
7066 if ($this->rtl) {
7067 $w = $this->x - $this->lMargin;
7068 } else {
7069 $w = $this->w - $this->rMargin - $this->x;
7070 }
7071 // max column width
7072 $wmax = $w - $wadj;
7073 if (!$firstline) {
7074 $wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
7075 }
7076 if ((!$firstline) AND (($chrwidth > $wmax) OR ($this->GetCharWidth($chars[0]) > $wmax))) {
7077 // a single character do not fit on column
7078 return '';
7079 }
7080 // minimum row height
7081 $row_height = max($h, $this->FontSize * $this->cell_height_ratio);
7082 $start_page = $this->page;
7083 $i = 0; // character position
7084 $j = 0; // current starting position
7085 $sep = -1; // position of the last blank space
7086 $shy = false; // true if the last blank is a soft hypen (SHY)
7087 $l = 0; // current string length
7088 $nl = 0; //number of lines
7089 $linebreak = false;
7090 $pc = 0; // previous character
7091 // for each character
7092 while ($i < $nb) {
7093 if (($maxh > 0) AND ($this->y >= $maxy) ) {
7094 break;
7095 }
7096 //Get the current character
7097 $c = $chars[$i];
7098 if ($c == 10) { // 10 = "\n" = new line
7099 //Explicit line break
7100 if ($align == 'J') {
7101 if ($this->rtl) {
7102 $talign = 'R';
7103 } else {
7104 $talign = 'L';
7105 }
7106 } else {
7107 $talign = $align;
7108 }
7109 $tmpstr = $this->UniArrSubString($uchars, $j, $i);
7110 if ($firstline) {
7111 $startx = $this->x;
7112 $tmparr = array_slice($chars, $j, ($i - $j));
7113 if ($rtlmode) {
7114 $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
7115 }
7116 $linew = $this->GetArrStringWidth($tmparr);
7117 unset($tmparr);
7118 if ($this->rtl) {
7119 $this->endlinex = $startx - $linew;
7120 } else {
7121 $this->endlinex = $startx + $linew;
7122 }
7123 $w = $linew;
7124 $tmpcellpadding = $this->cell_padding;
7125 if ($maxh == 0) {
7126 $this->SetCellPadding(0);
7127 }
7128 }
7129 if ($firstblock AND $this->isRTLTextDir()) {
7130 $tmpstr = $this->stringRightTrim($tmpstr);
7131 }
7132 // Skip newlines at the begining of a page or column
7133 if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
7134 $this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
7135 }
7136 unset($tmpstr);
7137 if ($firstline) {
7138 $this->cell_padding = $tmpcellpadding;
7139 return ($this->UniArrSubString($uchars, $i));
7140 }
7141 ++$nl;
7142 $j = $i + 1;
7143 $l = 0;
7144 $sep = -1;
7145 $shy = false;
7146 // account for margin changes
7147 if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
7148 $this->AcceptPageBreak();
7149 if ($this->rtl) {
7150 $this->x -= $margin['R'];
7151 } else {
7152 $this->x += $margin['L'];
7153 }
7154 $this->lMargin += $margin['L'];
7155 $this->rMargin += $margin['R'];
7156 }
7157 $w = $this->getRemainingWidth();
7158 $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
7159 } else {
7160 // 160 is the non-breaking space.
7161 // 173 is SHY (Soft Hypen).
7162 // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
7163 // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
7164 // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
7165 if (($c != 160) AND (($c == 173) OR preg_match($this->re_spaces, $this->unichr($c)))) {
7166 // update last blank space position
7167 $sep = $i;
7168 // check if is a SHY
7169 if ($c == 173) {
7170 $shy = true;
7171 if ($pc == 45) {
7172 $tmp_shy_replacement_width = 0;
7173 $tmp_shy_replacement_char = '';
7174 } else {
7175 $tmp_shy_replacement_width = $shy_replacement_width;
7176 $tmp_shy_replacement_char = $shy_replacement_char;
7177 }
7178 } else {
7179 $shy = false;
7180 }
7181 }
7182 // update string length
7183 if ($this->isUnicodeFont() AND ($arabic)) {
7184 // with bidirectional algorithm some chars may be changed affecting the line length
7185 // *** very slow ***
7186 $l = $this->GetArrStringWidth($this->utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl));
7187 } else {
7188 $l += $this->GetCharWidth($c);
7189 }
7190 if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) ) {
7191 // we have reached the end of column
7192 if ($sep == -1) {
7193 // check if the line was already started
7194 if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $chrwidth)))
7195 OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $chrwidth)))) {
7196 // print a void cell and go to next line
7197 $this->Cell($w, $h, '', 0, 1);
7198 $linebreak = true;
7199 if ($firstline) {
7200 return ($this->UniArrSubString($uchars, $j));
7201 }
7202 } else {
7203 // truncate the word because do not fit on column
7204 $tmpstr = $this->UniArrSubString($uchars, $j, $i);
7205 if ($firstline) {
7206 $startx = $this->x;
7207 $tmparr = array_slice($chars, $j, ($i - $j));
7208 if ($rtlmode) {
7209 $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
7210 }
7211 $linew = $this->GetArrStringWidth($tmparr);
7212 unset($tmparr);
7213 if ($this->rtl) {
7214 $this->endlinex = $startx - $linew;
7215 } else {
7216 $this->endlinex = $startx + $linew;
7217 }
7218 $w = $linew;
7219 $tmpcellpadding = $this->cell_padding;
7220 if ($maxh == 0) {
7221 $this->SetCellPadding(0);
7222 }
7223 }
7224 if ($firstblock AND $this->isRTLTextDir()) {
7225 $tmpstr = $this->stringRightTrim($tmpstr);
7226 }
7227 $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
7228 unset($tmpstr);
7229 if ($firstline) {
7230 $this->cell_padding = $tmpcellpadding;
7231 return ($this->UniArrSubString($uchars, $i));
7232 }
7233 $j = $i;
7234 --$i;
7235 }
7236 } else {
7237 // word wrapping
7238 if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
7239 $endspace = 1;
7240 } else {
7241 $endspace = 0;
7242 }
7243 // check the length of the next string
7244 $strrest = $this->UniArrSubString($uchars, ($sep + $endspace));
7245 $nextstr = preg_split('/'.$this->re_space['p'].'/'.$this->re_space['m'], $this->stringTrim($strrest));
7246 if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
7247 // truncate the word because do not fit on a full page width
7248 $tmpstr = $this->UniArrSubString($uchars, $j, $i);
7249 if ($firstline) {
7250 $startx = $this->x;
7251 $tmparr = array_slice($chars, $j, ($i - $j));
7252 if ($rtlmode) {
7253 $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
7254 }
7255 $linew = $this->GetArrStringWidth($tmparr);
7256 unset($tmparr);
7257 if ($this->rtl) {
7258 $this->endlinex = $startx - $linew;
7259 } else {
7260 $this->endlinex = $startx + $linew;
7261 }
7262 $w = $linew;
7263 $tmpcellpadding = $this->cell_padding;
7264 if ($maxh == 0) {
7265 $this->SetCellPadding(0);
7266 }
7267 }
7268 if ($firstblock AND $this->isRTLTextDir()) {
7269 $tmpstr = $this->stringRightTrim($tmpstr);
7270 }
7271 $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
7272 unset($tmpstr);
7273 if ($firstline) {
7274 $this->cell_padding = $tmpcellpadding;
7275 return ($this->UniArrSubString($uchars, $i));
7276 }
7277 $j = $i;
7278 --$i;
7279 } else {
7280 // word wrapping
7281 if ($shy) {
7282 // add hypen (minus symbol) at the end of the line
7283 $shy_width = $tmp_shy_replacement_width;
7284 if ($this->rtl) {
7285 $shy_char_left = $tmp_shy_replacement_char;
7286 $shy_char_right = '';
7287 } else {
7288 $shy_char_left = '';
7289 $shy_char_right = $tmp_shy_replacement_char;
7290 }
7291 } else {
7292 $shy_width = 0;
7293 $shy_char_left = '';
7294 $shy_char_right = '';
7295 }
7296 $tmpstr = $this->UniArrSubString($uchars, $j, ($sep + $endspace));
7297 if ($firstline) {
7298 $startx = $this->x;
7299 $tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
7300 if ($rtlmode) {
7301 $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
7302 }
7303 $linew = $this->GetArrStringWidth($tmparr);
7304 unset($tmparr);
7305 if ($this->rtl) {
7306 $this->endlinex = $startx - $linew - $shy_width;
7307 } else {
7308 $this->endlinex = $startx + $linew + $shy_width;
7309 }
7310 $w = $linew;
7311 $tmpcellpadding = $this->cell_padding;
7312 if ($maxh == 0) {
7313 $this->SetCellPadding(0);
7314 }
7315 }
7316 // print the line
7317 if ($firstblock AND $this->isRTLTextDir()) {
7318 $tmpstr = $this->stringRightTrim($tmpstr);
7319 }
7320 $this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
7321 unset($tmpstr);
7322 if ($firstline) {
7323 // return the remaining text
7324 $this->cell_padding = $tmpcellpadding;
7325 return ($this->UniArrSubString($uchars, ($sep + $endspace)));
7326 }
7327 $i = $sep;
7328 $sep = -1;
7329 $shy = false;
7330 $j = ($i+1);
7331 }
7332 }
7333 // account for margin changes
7334 if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
7335 $this->AcceptPageBreak();
7336 if ($this->rtl) {
7337 $this->x -= $margin['R'];
7338 } else {
7339 $this->x += $margin['L'];
7340 }
7341 $this->lMargin += $margin['L'];
7342 $this->rMargin += $margin['R'];
7343 }
7344 $w = $this->getRemainingWidth();
7345 $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
7346 if ($linebreak) {
7347 $linebreak = false;
7348 } else {
7349 ++$nl;
7350 $l = 0;
7351 }
7352 }
7353 }
7354 // save last character
7355 $pc = $c;
7356 ++$i;
7357 } // end while i < nb
7358 // print last substring (if any)
7359 if ($l > 0) {
7360 switch ($align) {
7361 case 'J':
7362 case 'C': {
7363 $w = $w;
7364 break;
7365 }
7366 case 'L': {
7367 if ($this->rtl) {
7368 $w = $w;
7369 } else {
7370 $w = $l;
7371 }
7372 break;
7373 }
7374 case 'R': {
7375 if ($this->rtl) {
7376 $w = $l;
7377 } else {
7378 $w = $w;
7379 }
7380 break;
7381 }
7382 default: {
7383 $w = $l;
7384 break;
7385 }
7386 }
7387 $tmpstr = $this->UniArrSubString($uchars, $j, $nb);
7388 if ($firstline) {
7389 $startx = $this->x;
7390 $tmparr = array_slice($chars, $j, ($nb - $j));
7391 if ($rtlmode) {
7392 $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
7393 }
7394 $linew = $this->GetArrStringWidth($tmparr);
7395 unset($tmparr);
7396 if ($this->rtl) {
7397 $this->endlinex = $startx - $linew;
7398 } else {
7399 $this->endlinex = $startx + $linew;
7400 }
7401 $w = $linew;
7402 $tmpcellpadding = $this->cell_padding;
7403 if ($maxh == 0) {
7404 $this->SetCellPadding(0);
7405 }
7406 }
7407 if ($firstblock AND $this->isRTLTextDir()) {
7408 $tmpstr = $this->stringRightTrim($tmpstr);
7409 }
7410 $this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
7411 unset($tmpstr);
7412 if ($firstline) {
7413 $this->cell_padding = $tmpcellpadding;
7414 return ($this->UniArrSubString($uchars, $nb));
7415 }
7416 ++$nl;
7417 }
7418 if ($firstline) {
7419 return '';
7420 }
7421 return $nl;
7422 }
7423
7424 /**
7425 * Returns the remaining width between the current position and margins.
7426 * @return int Return the remaining width
7427 * @protected
7428 */
7429 protected function getRemainingWidth() {
7430 list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
7431 if ($this->rtl) {
7432 return ($this->x - $this->lMargin);
7433 } else {
7434 return ($this->w - $this->rMargin - $this->x);
7435 }
7436 }
7437
7438 /**
7439 * Extract a slice of the $strarr array and return it as string.
7440 * @param $strarr (string) The input array of characters.
7441 * @param $start (int) the starting element of $strarr.
7442 * @param $end (int) first element that will not be returned.
7443 * @return Return part of a string
7444 * @public
7445 */
7446 public function UTF8ArrSubString($strarr, $start='', $end='') {
7447 if (strlen($start) == 0) {
7448 $start = 0;
7449 }
7450 if (strlen($end) == 0) {
7451 $end = count($strarr);
7452 }
7453 $string = '';
7454 for ($i=$start; $i < $end; ++$i) {
7455 $string .= $this->unichr($strarr[$i]);
7456 }
7457 return $string;
7458 }
7459
7460 /**
7461 * Extract a slice of the $uniarr array and return it as string.
7462 * @param $uniarr (string) The input array of characters.
7463 * @param $start (int) the starting element of $strarr.
7464 * @param $end (int) first element that will not be returned.
7465 * @return Return part of a string
7466 * @public
7467 * @since 4.5.037 (2009-04-07)
7468 */
7469 public function UniArrSubString($uniarr, $start='', $end='') {
7470 if (strlen($start) == 0) {
7471 $start = 0;
7472 }
7473 if (strlen($end) == 0) {
7474 $end = count($uniarr);
7475 }
7476 $string = '';
7477 for ($i=$start; $i < $end; ++$i) {
7478 $string .= $uniarr[$i];
7479 }
7480 return $string;
7481 }
7482
7483 /**
7484 * Convert an array of UTF8 values to array of unicode characters
7485 * @param $ta (string) The input array of UTF8 values.
7486 * @return Return array of unicode characters
7487 * @public
7488 * @since 4.5.037 (2009-04-07)
7489 */
7490 public function UTF8ArrayToUniArray($ta) {
7491 return array_map(array($this, 'unichr'), $ta);
7492 }
7493
7494 /**
7495 * Returns the unicode caracter specified by UTF-8 value
7496 * @param $c (int) UTF-8 value
7497 * @return Returns the specified character.
7498 * @author Miguel Perez, Nicola Asuni
7499 * @public
7500 * @since 2.3.000 (2008-03-05)
7501 */
7502 public function unichr($c) {
7503 if (!$this->isunicode) {
7504 return chr($c);
7505 } elseif ($c <= 0x7F) {
7506 // one byte
7507 return chr($c);
7508 } elseif ($c <= 0x7FF) {
7509 // two bytes
7510 return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F);
7511 } elseif ($c <= 0xFFFF) {
7512 // three bytes
7513 return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
7514 } elseif ($c <= 0x10FFFF) {
7515 // four bytes
7516 return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
7517 } else {
7518 return '';
7519 }
7520 }
7521
7522 /**
7523 * Return the image type given the file name or array returned by getimagesize() function.
7524 * @param $imgfile (string) image file name
7525 * @param $iminfo (array) array of image information returned by getimagesize() function.
7526 * @return string image type
7527 * @since 4.8.017 (2009-11-27)
7528 */
7529 public function getImageFileType($imgfile, $iminfo=array()) {
7530 $type = '';
7531 if (isset($iminfo['mime']) AND !empty($iminfo['mime'])) {
7532 $mime = explode('/', $iminfo['mime']);
7533 if ((count($mime) > 1) AND ($mime[0] == 'image') AND (!empty($mime[1]))) {
7534 $type = strtolower(trim($mime[1]));
7535 }
7536 }
7537 if (empty($type)) {
7538 $fileinfo = pathinfo($imgfile);
7539 if (isset($fileinfo['extension']) AND (!$this->empty_string($fileinfo['extension']))) {
7540 $type = strtolower(trim($fileinfo['extension']));
7541 }
7542 }
7543 if ($type == 'jpg') {
7544 $type = 'jpeg';
7545 }
7546 return $type;
7547 }
7548
7549 /**
7550 * Set the block dimensions accounting for page breaks and page/column fitting
7551 * @param $w (float) width
7552 * @param $h (float) height
7553 * @param $x (float) X coordinate
7554 * @param $y (float) Y coodiante
7555 * @param $fitonpage (boolean) if true the block is resized to not exceed page dimensions.
7556 * @return array($w, $h, $x, $y)
7557 * @protected
7558 * @since 5.5.009 (2010-07-05)
7559 */
7560 protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
7561 if ($w <= 0) {
7562 // set maximum width
7563 $w = ($this->w - $this->lMargin - $this->rMargin);
7564 }
7565 if ($h <= 0) {
7566 // set maximum height
7567 $h = ($this->PageBreakTrigger - $this->tMargin);
7568 }
7569 // resize the block to be vertically contained on a single page or single column
7570 if ($fitonpage OR $this->AutoPageBreak) {
7571 $ratio_wh = ($w / $h);
7572 if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
7573 $h = $this->PageBreakTrigger - $this->tMargin;
7574 $w = ($h * $ratio_wh);
7575 }
7576 // resize the block to be horizontally contained on a single page or single column
7577 if ($fitonpage) {
7578 $maxw = ($this->w - $this->lMargin - $this->rMargin);
7579 if ($w > $maxw) {
7580 $w = $maxw;
7581 $h = ($w / $ratio_wh);
7582 }
7583 }
7584 }
7585 // Check whether we need a new page or new column first as this does not fit
7586 $prev_x = $this->x;
7587 $prev_y = $this->y;
7588 if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
7589 $y = $this->y;
7590 if ($this->rtl) {
7591 $x += ($prev_x - $this->x);
7592 } else {
7593 $x += ($this->x - $prev_x);
7594 }
7595 $this->newline = true;
7596 }
7597 // resize the block to be contained on the remaining available page or column space
7598 if ($fitonpage) {
7599 $ratio_wh = ($w / $h);
7600 if (($y + $h) > $this->PageBreakTrigger) {
7601 $h = $this->PageBreakTrigger - $y;
7602 $w = ($h * $ratio_wh);
7603 }
7604 if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
7605 $w = $this->w - $this->rMargin - $x;
7606 $h = ($w / $ratio_wh);
7607 } elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
7608 $w = $x - $this->lMargin;
7609 $h = ($w / $ratio_wh);
7610 }
7611 }
7612 return array($w, $h, $x, $y);
7613 }
7614
7615 /**
7616 * Puts an image in the page.
7617 * The upper-left corner must be given.
7618 * The dimensions can be specified in different ways:<ul>
7619 * <li>explicit width and height (expressed in user unit)</li>
7620 * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
7621 * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
7622 * 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;
7623 * The format can be specified explicitly or inferred from the file extension.<br />
7624 * It is possible to put a link on the image.<br />
7625 * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
7626 * @param $file (string) Name of the file containing the image or a '@' character followed by the image data string. To link an image without embedding it on the document, set an asterisk character before the URL (i.e.: '*http://www.example.com/image.jpg').
7627 * @param $x (float) Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
7628 * @param $y (float) Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
7629 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7630 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7631 * @param $type (string) Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
7632 * @param $link (mixed) URL or identifier returned by AddLink().
7633 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
7634 * @param $resize (mixed) If true resize (reduce) the image to fit $w and $h (requires GD or ImageMagick library); if false do not resize; if 2 force resize in all cases (upscaling and downscaling).
7635 * @param $dpi (int) dot-per-inch resolution used on resize
7636 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
7637 * @param $ismask (boolean) true if this image is a mask, false otherwise
7638 * @param $imgmask (mixed) image object returned by this function or false
7639 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
7640 * @param $fitbox (mixed) If not false scale image dimensions proportionally to fit within the ($w, $h) box. $fitbox can be true or a 2 characters string indicating the image alignment inside the box. The first character indicate the horizontal alignment (L = left, C = center, R = right) the second character indicate the vertical algnment (T = top, M = middle, B = bottom).
7641 * @param $hidden (boolean) If true do not display the image.
7642 * @param $fitonpage (boolean) If true the image is resized to not exceed page dimensions.
7643 * @param $alt (boolean) If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
7644 * @param $altimgs (array) Array of alternate images IDs. Each alternative image must be an array with two values: an integer representing the image ID (the value returned by the Image method) and a boolean value to indicate if the image is the default for printing.
7645 * @return image information
7646 * @public
7647 * @since 1.1
7648 */
7649 public function Image($file, $x='', $y='', $w=0, $h=0, $type='', $link='', $align='', $resize=false, $dpi=300, $palign='', $ismask=false, $imgmask=false, $border=0, $fitbox=false, $hidden=false, $fitonpage=false, $alt=false, $altimgs=array()) {
7650 if ($x === '') {
7651 $x = $this->x;
7652 }
7653 if ($y === '') {
7654 $y = $this->y;
7655 }
7656 // check page for no-write regions and adapt page margins if necessary
7657 list($x, $y) = $this->checkPageRegions($h, $x, $y);
7658 $cached_file = false; // true when the file is cached
7659 $exurl = ''; // external streams
7660 // check if we are passing an image as file or string
7661 if ($file[0] === '@') {
7662 // image from string
7663 $imgdata = substr($file, 1);
7664 $file = K_PATH_CACHE.'img_'.md5($imgdata);
7665 $fp = fopen($file, 'w');
7666 fwrite($fp, $imgdata);
7667 fclose($fp);
7668 unset($imgdata);
7669 $cached_file = true;
7670 $imsize = @getimagesize($file);
7671 if ($imsize === FALSE) {
7672 unlink($file);
7673 $cached_file = false;
7674 }
7675 } else { // image file
7676 if ($file{0} === '*') {
7677 // image as external stream
7678 $file = substr($file, 1);
7679 $exurl = $file;
7680 }
7681 // check if is local file
7682 if (!@file_exists($file)) {
7683 // encode spaces on filename (file is probably an URL)
7684 $file = str_replace(' ', '%20', $file);
7685 }
7686 if (@file_exists($file)) {
7687 // get image dimensions
7688 $imsize = @getimagesize($file);
7689 } else {
7690 $imsize = false;
7691 }
7692 if ($imsize === FALSE) {
7693 if (function_exists('curl_init')) {
7694 // try to get remote file data using cURL
7695 $cs = curl_init(); // curl session
7696 curl_setopt($cs, CURLOPT_URL, $file);
7697 curl_setopt($cs, CURLOPT_BINARYTRANSFER, true);
7698 curl_setopt($cs, CURLOPT_FAILONERROR, true);
7699 curl_setopt($cs, CURLOPT_RETURNTRANSFER, true);
7700 curl_setopt($cs, CURLOPT_FOLLOWLOCATION, true);
7701 curl_setopt($cs, CURLOPT_CONNECTTIMEOUT, 5);
7702 curl_setopt($cs, CURLOPT_TIMEOUT, 30);
7703 curl_setopt($cs, CURLOPT_SSL_VERIFYPEER, false);
7704 curl_setopt($cs, CURLOPT_SSL_VERIFYHOST, false);
7705 curl_setopt($cs, CURLOPT_USERAGENT, 'TCPDF');
7706 $imgdata = curl_exec($cs);
7707 curl_close($cs);
7708 if ($imgdata !== FALSE) {
7709 // copy image to cache
7710 $file = K_PATH_CACHE.'img_'.md5($imgdata);
7711 $fp = fopen($file, 'w');
7712 fwrite($fp, $imgdata);
7713 fclose($fp);
7714 unset($imgdata);
7715 $cached_file = true;
7716 $imsize = @getimagesize($file);
7717 if ($imsize === FALSE) {
7718 unlink($file);
7719 $cached_file = false;
7720 }
7721 }
7722 } elseif (($w > 0) AND ($h > 0)) {
7723 // get measures from specified data
7724 $pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
7725 $ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
7726 $imsize = array($pw, $ph);
7727 }
7728 }
7729 }
7730 if ($imsize === FALSE) {
7731 if (substr($file, 0, -34) == K_PATH_CACHE.'msk') { // mask file
7732 // get measures from specified data
7733 $pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
7734 $ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
7735 $imsize = array($pw, $ph);
7736 } else {
7737 $this->Error('[Image] Unable to get image: '.$file);
7738 }
7739 }
7740 // file hash
7741 $filehash = md5($file);
7742 // get original image width and height in pixels
7743 list($pixw, $pixh) = $imsize;
7744 // calculate image width and height on document
7745 if (($w <= 0) AND ($h <= 0)) {
7746 // convert image size to document unit
7747 $w = $this->pixelsToUnits($pixw);
7748 $h = $this->pixelsToUnits($pixh);
7749 } elseif ($w <= 0) {
7750 $w = $h * $pixw / $pixh;
7751 } elseif ($h <= 0) {
7752 $h = $w * $pixh / $pixw;
7753 } elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
7754 if (strlen($fitbox) !== 2) {
7755 // set default alignment
7756 $fitbox = '--';
7757 }
7758 // scale image dimensions proportionally to fit within the ($w, $h) box
7759 if ((($w * $pixh) / ($h * $pixw)) < 1) {
7760 // store current height
7761 $oldh = $h;
7762 // calculate new height
7763 $h = $w * $pixh / $pixw;
7764 // height difference
7765 $hdiff = ($oldh - $h);
7766 // vertical alignment
7767 switch (strtoupper($fitbox{1})) {
7768 case 'T': {
7769 break;
7770 }
7771 case 'M': {
7772 $y += ($hdiff / 2);
7773 break;
7774 }
7775 case 'B': {
7776 $y += $hdiff;
7777 break;
7778 }
7779 }
7780 } else {
7781 // store current width
7782 $oldw = $w;
7783 // calculate new width
7784 $w = $h * $pixw / $pixh;
7785 // width difference
7786 $wdiff = ($oldw - $w);
7787 // horizontal alignment
7788 switch (strtoupper($fitbox{0})) {
7789 case 'L': {
7790 if ($this->rtl) {
7791 $x -= $wdiff;
7792 }
7793 break;
7794 }
7795 case 'C': {
7796 if ($this->rtl) {
7797 $x -= ($wdiff / 2);
7798 } else {
7799 $x += ($wdiff / 2);
7800 }
7801 break;
7802 }
7803 case 'R': {
7804 if (!$this->rtl) {
7805 $x += $wdiff;
7806 }
7807 break;
7808 }
7809 }
7810 }
7811 }
7812 // fit the image on available space
7813 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
7814 // calculate new minimum dimensions in pixels
7815 $neww = round($w * $this->k * $dpi / $this->dpi);
7816 $newh = round($h * $this->k * $dpi / $this->dpi);
7817 // check if resize is necessary (resize is used only to reduce the image)
7818 $newsize = ($neww * $newh);
7819 $pixsize = ($pixw * $pixh);
7820 if (intval($resize) == 2) {
7821 $resize = true;
7822 } elseif ($newsize >= $pixsize) {
7823 $resize = false;
7824 }
7825 // check if image has been already added on document
7826 $newimage = true;
7827 if (in_array($file, $this->imagekeys)) {
7828 $newimage = false;
7829 // get existing image data
7830 $info = $this->getImageBuffer($file);
7831 if (substr($file, 0, -34) != K_PATH_CACHE.'msk') {
7832 // check if the newer image is larger
7833 $oldsize = ($info['w'] * $info['h']);
7834 if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7835 $newimage = true;
7836 }
7837 }
7838 } elseif (substr($file, 0, -34) != K_PATH_CACHE.'msk') {
7839 // check for cached images with alpha channel
7840 $tempfile_plain = K_PATH_CACHE.'mskp_'.$filehash;
7841 $tempfile_alpha = K_PATH_CACHE.'mska_'.$filehash;
7842 if (in_array($tempfile_plain, $this->imagekeys)) {
7843 // get existing image data
7844 $info = $this->getImageBuffer($tempfile_plain);
7845 // check if the newer image is larger
7846 $oldsize = ($info['w'] * $info['h']);
7847 if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7848 $newimage = true;
7849 } else {
7850 $newimage = false;
7851 // embed mask image
7852 $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7853 // embed image, masked with previously embedded mask
7854 return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7855 }
7856 }
7857 }
7858 if ($newimage) {
7859 //First use of image, get info
7860 $type = strtolower($type);
7861 if ($type == '') {
7862 $type = $this->getImageFileType($file, $imsize);
7863 } elseif ($type == 'jpg') {
7864 $type = 'jpeg';
7865 }
7866 $mqr = $this->get_mqr();
7867 $this->set_mqr(false);
7868 // Specific image handlers
7869 $mtd = '_parse'.$type;
7870 // GD image handler function
7871 $gdfunction = 'imagecreatefrom'.$type;
7872 $info = false;
7873 if ((method_exists($this, $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
7874 // TCPDF image functions
7875 $info = $this->$mtd($file);
7876 if ($info == 'pngalpha') {
7877 return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
7878 }
7879 }
7880 if (!$info) {
7881 if (function_exists($gdfunction)) {
7882 // GD library
7883 $img = $gdfunction($file);
7884 if ($resize) {
7885 $imgr = imagecreatetruecolor($neww, $newh);
7886 if (($type == 'gif') OR ($type == 'png')) {
7887 $imgr = $this->_setGDImageTransparency($imgr, $img);
7888 }
7889 imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7890 if (($type == 'gif') OR ($type == 'png')) {
7891 $info = $this->_toPNG($imgr);
7892 } else {
7893 $info = $this->_toJPEG($imgr);
7894 }
7895 } else {
7896 if (($type == 'gif') OR ($type == 'png')) {
7897 $info = $this->_toPNG($img);
7898 } else {
7899 $info = $this->_toJPEG($img);
7900 }
7901 }
7902 } elseif (extension_loaded('imagick')) {
7903 // ImageMagick library
7904 $img = new Imagick();
7905 if ($type == 'SVG') {
7906 // get SVG file content
7907 $svgimg = file_get_contents($file);
7908 // get width and height
7909 $regs = array();
7910 if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7911 $svgtag = $regs[1];
7912 $tmp = array();
7913 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7914 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7915 $owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
7916 $svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7917 } else {
7918 $ow = $w;
7919 }
7920 $tmp = array();
7921 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7922 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7923 $ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
7924 $svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7925 } else {
7926 $oh = $h;
7927 }
7928 $tmp = array();
7929 if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7930 $vbw = ($ow * $this->imgscale * $this->k);
7931 $vbh = ($oh * $this->imgscale * $this->k);
7932 $vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
7933 $svgtag = $vbox.$svgtag;
7934 }
7935 $svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7936 }
7937 $img->readImageBlob($svgimg);
7938 } else {
7939 $img->readImage($file);
7940 }
7941 if ($resize) {
7942 $img->resizeImage($neww, $newh, 10, 1, false);
7943 }
7944 $img->setCompressionQuality($this->jpeg_quality);
7945 $img->setImageFormat('jpeg');
7946 $tempname = tempnam(K_PATH_CACHE, 'jpg_');
7947 $img->writeImage($tempname);
7948 $info = $this->_parsejpeg($tempname);
7949 unlink($tempname);
7950 $img->destroy();
7951 } else {
7952 return;
7953 }
7954 }
7955 if ($info === false) {
7956 //If false, we cannot process image
7957 return;
7958 }
7959 $this->set_mqr($mqr);
7960 if ($ismask) {
7961 // force grayscale
7962 $info['cs'] = 'DeviceGray';
7963 }
7964 $info['i'] = $this->numimages;
7965 if (!in_array($file, $this->imagekeys)) {
7966 ++$info['i'];
7967 }
7968 if ($imgmask !== false) {
7969 $info['masked'] = $imgmask;
7970 }
7971 if (!empty($exurl)) {
7972 $info['exurl'] = $exurl;
7973 }
7974 // array of alternative images
7975 $info['altimgs'] = $altimgs;
7976 // add image to document
7977 $this->setImageBuffer($file, $info);
7978 }
7979 if ($cached_file) {
7980 // remove cached file
7981 unlink($file);
7982 }
7983 // set alignment
7984 $this->img_rb_y = $y + $h;
7985 // set alignment
7986 if ($this->rtl) {
7987 if ($palign == 'L') {
7988 $ximg = $this->lMargin;
7989 } elseif ($palign == 'C') {
7990 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7991 } elseif ($palign == 'R') {
7992 $ximg = $this->w - $this->rMargin - $w;
7993 } else {
7994 $ximg = $x - $w;
7995 }
7996 $this->img_rb_x = $ximg;
7997 } else {
7998 if ($palign == 'L') {
7999 $ximg = $this->lMargin;
8000 } elseif ($palign == 'C') {
8001 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
8002 } elseif ($palign == 'R') {
8003 $ximg = $this->w - $this->rMargin - $w;
8004 } else {
8005 $ximg = $x;
8006 }
8007 $this->img_rb_x = $ximg + $w;
8008 }
8009 if ($ismask OR $hidden) {
8010 // image is not displayed
8011 return $info['i'];
8012 }
8013 $xkimg = $ximg * $this->k;
8014 if (!$alt) {
8015 // only non-alternative immages will be set
8016 $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']));
8017 }
8018 if (!empty($border)) {
8019 $bx = $this->x;
8020 $by = $this->y;
8021 $this->x = $ximg;
8022 if ($this->rtl) {
8023 $this->x += $w;
8024 }
8025 $this->y = $y;
8026 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
8027 $this->x = $bx;
8028 $this->y = $by;
8029 }
8030 if ($link) {
8031 $this->Link($ximg, $y, $w, $h, $link, 0);
8032 }
8033 // set pointer to align the next text/objects
8034 switch($align) {
8035 case 'T': {
8036 $this->y = $y;
8037 $this->x = $this->img_rb_x;
8038 break;
8039 }
8040 case 'M': {
8041 $this->y = $y + round($h/2);
8042 $this->x = $this->img_rb_x;
8043 break;
8044 }
8045 case 'B': {
8046 $this->y = $this->img_rb_y;
8047 $this->x = $this->img_rb_x;
8048 break;
8049 }
8050 case 'N': {
8051 $this->SetY($this->img_rb_y);
8052 break;
8053 }
8054 default:{
8055 break;
8056 }
8057 }
8058 $this->endlinex = $this->img_rb_x;
8059 if ($this->inxobj) {
8060 // we are inside an XObject template
8061 $this->xobjects[$this->xobjid]['images'][] = $info['i'];
8062 }
8063 return $info['i'];
8064 }
8065
8066 /**
8067 * Sets the current active configuration setting of magic_quotes_runtime (if the set_magic_quotes_runtime function exist)
8068 * @param $mqr (boolean) FALSE for off, TRUE for on.
8069 * @since 4.6.025 (2009-08-17)
8070 */
8071 public function set_mqr($mqr) {
8072 if (!defined('PHP_VERSION_ID')) {
8073 $version = PHP_VERSION;
8074 define('PHP_VERSION_ID', (($version{0} * 10000) + ($version{2} * 100) + $version{4}));
8075 }
8076 if (PHP_VERSION_ID < 50300) {
8077 @set_magic_quotes_runtime($mqr);
8078 }
8079 }
8080
8081 /**
8082 * Gets the current active configuration setting of magic_quotes_runtime (if the get_magic_quotes_runtime function exist)
8083 * @return Returns 0 if magic quotes runtime is off or get_magic_quotes_runtime doesn't exist, 1 otherwise.
8084 * @since 4.6.025 (2009-08-17)
8085 */
8086 public function get_mqr() {
8087 if (!defined('PHP_VERSION_ID')) {
8088 $version = PHP_VERSION;
8089 define('PHP_VERSION_ID', (($version{0} * 10000) + ($version{2} * 100) + $version{4}));
8090 }
8091 if (PHP_VERSION_ID < 50300) {
8092 return @get_magic_quotes_runtime();
8093 }
8094 return 0;
8095 }
8096
8097 /**
8098 * Convert the loaded image to a JPEG and then return a structure for the PDF creator.
8099 * This function requires GD library and write access to the directory defined on K_PATH_CACHE constant.
8100 * @param $image (image) Image object.
8101 * return image JPEG image object.
8102 * @protected
8103 */
8104 protected function _toJPEG($image) {
8105 $tempname = tempnam(K_PATH_CACHE, 'jpg_');
8106 imagejpeg($image, $tempname, $this->jpeg_quality);
8107 imagedestroy($image);
8108 $retvars = $this->_parsejpeg($tempname);
8109 // tidy up by removing temporary image
8110 unlink($tempname);
8111 return $retvars;
8112 }
8113
8114 /**
8115 * Convert the loaded image to a PNG and then return a structure for the PDF creator.
8116 * This function requires GD library and write access to the directory defined on K_PATH_CACHE constant.
8117 * @param $image (image) Image object.
8118 * return image PNG image object.
8119 * @protected
8120 * @since 4.9.016 (2010-04-20)
8121 */
8122 protected function _toPNG($image) {
8123 // set temporary image file name
8124 $tempname = tempnam(K_PATH_CACHE, 'jpg_');
8125 // turn off interlaced mode
8126 imageinterlace($image, 0);
8127 // create temporary PNG image
8128 imagepng($image, $tempname);
8129 // remove image from memory
8130 imagedestroy($image);
8131 // get PNG image data
8132 $retvars = $this->_parsepng($tempname);
8133 // tidy up by removing temporary image
8134 unlink($tempname);
8135 return $retvars;
8136 }
8137
8138 /**
8139 * Set the transparency for the given GD image.
8140 * @param $new_image (image) GD image object
8141 * @param $image (image) GD image object.
8142 * return GD image object.
8143 * @protected
8144 * @since 4.9.016 (2010-04-20)
8145 */
8146 protected function _setGDImageTransparency($new_image, $image) {
8147 // transparency index
8148 $tid = imagecolortransparent($image);
8149 // default transparency color
8150 $tcol = array('red' => 255, 'green' => 255, 'blue' => 255);
8151 if ($tid >= 0) {
8152 // get the colors for the transparency index
8153 $tcol = imagecolorsforindex($image, $tid);
8154 }
8155 $tid = imagecolorallocate($new_image, $tcol['red'], $tcol['green'], $tcol['blue']);
8156 imagefill($new_image, 0, 0, $tid);
8157 imagecolortransparent($new_image, $tid);
8158 return $new_image;
8159 }
8160
8161 /**
8162 * Extract info from a JPEG file without using the GD library.
8163 * @param $file (string) image file to parse
8164 * @return array structure containing the image data
8165 * @protected
8166 */
8167 protected function _parsejpeg($file) {
8168 $a = getimagesize($file);
8169 if (empty($a)) {
8170 $this->Error('Missing or incorrect image file: '.$file);
8171 }
8172 if ($a[2] != 2) {
8173 $this->Error('Not a JPEG file: '.$file);
8174 }
8175 // bits per pixel
8176 $bpc = isset($a['bits']) ? intval($a['bits']) : 8;
8177 // number of image channels
8178 if (!isset($a['channels'])) {
8179 $channels = 3;
8180 } else {
8181 $channels = intval($a['channels']);
8182 }
8183 // default colour space
8184 switch ($channels) {
8185 case 1: {
8186 $colspace = 'DeviceGray';
8187 break;
8188 }
8189 case 3: {
8190 $colspace = 'DeviceRGB';
8191 break;
8192 }
8193 case 4: {
8194 $colspace = 'DeviceCMYK';
8195 break;
8196 }
8197 default: {
8198 $channels = 3;
8199 $colspace = 'DeviceRGB';
8200 break;
8201 }
8202 }
8203 // get file content
8204 $data = file_get_contents($file);
8205 // check for embedded ICC profile
8206 $icc = array();
8207 $offset = 0;
8208 while (($pos = strpos($data, "ICC_PROFILE\0", $offset)) !== false) {
8209 // get ICC sequence length
8210 $length = ($this->_getUSHORT($data, ($pos - 2)) - 16);
8211 // marker sequence number
8212 $msn = max(1, ord($data[($pos + 12)]));
8213 // number of markers (total of APP2 used)
8214 $nom = max(1, ord($data[($pos + 13)]));
8215 // get sequence segment
8216 $icc[($msn - 1)] = substr($data, ($pos + 14), $length);
8217 // move forward to next sequence
8218 $offset = ($pos + 14 + $length);
8219 }
8220 // order and compact ICC segments
8221 if (count($icc) > 0) {
8222 ksort($icc);
8223 $icc = implode('', $icc);
8224 if ((ord($icc{36}) != 0x61) OR (ord($icc{37}) != 0x63) OR (ord($icc{38}) != 0x73) OR (ord($icc{39}) != 0x70)) {
8225 // invalid ICC profile
8226 $icc = false;
8227 }
8228 } else {
8229 $icc = false;
8230 }
8231 return array('w' => $a[0], 'h' => $a[1], 'ch' => $channels, 'icc' => $icc, 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'DCTDecode', 'data' => $data);
8232 }
8233
8234 /**
8235 * Extract info from a PNG file without using the GD library.
8236 * @param $file (string) image file to parse
8237 * @return array structure containing the image data
8238 * @protected
8239 */
8240 protected function _parsepng($file) {
8241 $f = fopen($file, 'rb');
8242 if ($f === false) {
8243 $this->Error('Can\'t open image file: '.$file);
8244 }
8245 //Check signature
8246 if (fread($f, 8) != chr(137).'PNG'.chr(13).chr(10).chr(26).chr(10)) {
8247 $this->Error('Not a PNG file: '.$file);
8248 }
8249 //Read header chunk
8250 fread($f, 4);
8251 if (fread($f, 4) != 'IHDR') {
8252 $this->Error('Incorrect PNG file: '.$file);
8253 }
8254 $w = $this->_freadint($f);
8255 $h = $this->_freadint($f);
8256 $bpc = ord(fread($f, 1));
8257 if ($bpc > 8) {
8258 //$this->Error('16-bit depth not supported: '.$file);
8259 fclose($f);
8260 return false;
8261 }
8262 $ct = ord(fread($f, 1));
8263 if ($ct == 0) {
8264 $colspace = 'DeviceGray';
8265 } elseif ($ct == 2) {
8266 $colspace = 'DeviceRGB';
8267 } elseif ($ct == 3) {
8268 $colspace = 'Indexed';
8269 } else {
8270 // alpha channel
8271 fclose($f);
8272 return 'pngalpha';
8273 }
8274 if (ord(fread($f, 1)) != 0) {
8275 //$this->Error('Unknown compression method: '.$file);
8276 fclose($f);
8277 return false;
8278 }
8279 if (ord(fread($f, 1)) != 0) {
8280 //$this->Error('Unknown filter method: '.$file);
8281 fclose($f);
8282 return false;
8283 }
8284 if (ord(fread($f, 1)) != 0) {
8285 //$this->Error('Interlacing not supported: '.$file);
8286 fclose($f);
8287 return false;
8288 }
8289 fread($f, 4);
8290 $channels = ($ct == 2 ? 3 : 1);
8291 $parms = '/DecodeParms << /Predictor 15 /Colors '.$channels.' /BitsPerComponent '.$bpc.' /Columns '.$w.' >>';
8292 //Scan chunks looking for palette, transparency and image data
8293 $pal = '';
8294 $trns = '';
8295 $data = '';
8296 $icc = false;
8297 do {
8298 $n = $this->_freadint($f);
8299 $type = fread($f, 4);
8300 if ($type == 'PLTE') {
8301 // read palette
8302 $pal = $this->rfread($f, $n);
8303 fread($f, 4);
8304 } elseif ($type == 'tRNS') {
8305 // read transparency info
8306 $t = $this->rfread($f, $n);
8307 if ($ct == 0) {
8308 $trns = array(ord($t{1}));
8309 } elseif ($ct == 2) {
8310 $trns = array(ord($t{1}), ord($t{3}), ord($t{5}));
8311 } else {
8312 $pos = strpos($t, chr(0));
8313 if ($pos !== false) {
8314 $trns = array($pos);
8315 }
8316 }
8317 fread($f, 4);
8318 } elseif ($type == 'IDAT') {
8319 // read image data block
8320 $data .= $this->rfread($f, $n);
8321 fread($f, 4);
8322 } elseif ($type == 'iCCP') {
8323 // skip profile name and null separator
8324 $len = 0;
8325 while ((ord(fread($f, 1)) > 0) AND ($len < 80)) {
8326 ++$len;
8327 }
8328 // get compression method
8329 if (ord(fread($f, 1)) != 0) {
8330 //$this->Error('Unknown filter method: '.$file);
8331 fclose($f);
8332 return false;
8333 }
8334 // read ICC Color Profile
8335 $icc = $this->rfread($f, ($n - $len - 2));
8336 // decompress profile
8337 $icc = gzuncompress($icc);
8338 fread($f, 4);
8339 } elseif ($type == 'IEND') {
8340 break;
8341 } else {
8342 $this->rfread($f, $n + 4);
8343 }
8344 } while ($n);
8345 if (($colspace == 'Indexed') AND (empty($pal))) {
8346 //$this->Error('Missing palette in '.$file);
8347 fclose($f);
8348 return false;
8349 }
8350 fclose($f);
8351 return array('w' => $w, 'h' => $h, 'ch' => $channels, 'icc' => $icc, 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'FlateDecode', 'parms' => $parms, 'pal' => $pal, 'trns' => $trns, 'data' => $data);
8352 }
8353
8354 /**
8355 * Binary-safe and URL-safe file read.
8356 * Reads up to length bytes from the file pointer referenced by handle. Reading stops as soon as one of the following conditions is met: length bytes have been read; EOF (end of file) is reached.
8357 * @param $handle (resource)
8358 * @param $length (int)
8359 * @return Returns the read string or FALSE in case of error.
8360 * @author Nicola Asuni
8361 * @protected
8362 * @since 4.5.027 (2009-03-16)
8363 */
8364 protected function rfread($handle, $length) {
8365 $data = fread($handle, $length);
8366 if ($data === false) {
8367 return false;
8368 }
8369 $rest = $length - strlen($data);
8370 if ($rest > 0) {
8371 $data .= $this->rfread($handle, $rest);
8372 }
8373 return $data;
8374 }
8375
8376 /**
8377 * Extract info from a PNG image with alpha channel using the GD library.
8378 * @param $file (string) Name of the file containing the image.
8379 * @param $x (float) Abscissa of the upper-left corner.
8380 * @param $y (float) Ordinate of the upper-left corner.
8381 * @param $wpx (float) Original width of the image in pixels.
8382 * @param $hpx (float) original height of the image in pixels.
8383 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
8384 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
8385 * @param $type (string) Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
8386 * @param $link (mixed) URL or identifier returned by AddLink().
8387 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
8388 * @param $resize (boolean) If true resize (reduce) the image to fit $w and $h (requires GD library).
8389 * @param $dpi (int) dot-per-inch resolution used on resize
8390 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
8391 * @param $filehash (string) File hash used to build unique file names.
8392 * @author Nicola Asuni
8393 * @protected
8394 * @since 4.3.007 (2008-12-04)
8395 * @see Image()
8396 */
8397 protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
8398 if (empty($filehash)) {
8399 $filehash = md5($file);
8400 }
8401 // create temp image file (without alpha channel)
8402 $tempfile_plain = K_PATH_CACHE.'mskp_'.$filehash;
8403 // create temp alpha file
8404 $tempfile_alpha = K_PATH_CACHE.'mska_'.$filehash;
8405 if (extension_loaded('imagick')) { // ImageMagick extension
8406 // ImageMagick library
8407 $img = new Imagick();
8408 $img->readImage($file);
8409 // clone image object
8410 $imga = $img->clone();
8411 // extract alpha channel
8412 $img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
8413 $img->negateImage(true);
8414 $img->setImageFormat('png');
8415 $img->writeImage($tempfile_alpha);
8416 // remove alpha channel
8417 $imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
8418 $imga->setImageFormat('png');
8419 $imga->writeImage($tempfile_plain);
8420 } elseif (function_exists('imagecreatefrompng')) { // GD extension
8421 // generate images
8422 $img = imagecreatefrompng($file);
8423 $imgalpha = imagecreate($wpx, $hpx);
8424 // generate gray scale palette (0 -> 255)
8425 for ($c = 0; $c < 256; ++$c) {
8426 ImageColorAllocate($imgalpha, $c, $c, $c);
8427 }
8428 // extract alpha channel
8429 for ($xpx = 0; $xpx < $wpx; ++$xpx) {
8430 for ($ypx = 0; $ypx < $hpx; ++$ypx) {
8431 $color = imagecolorat($img, $xpx, $ypx);
8432 $alpha = ($color >> 24); // shifts off the first 24 bits (where 8x3 are used for each color), and returns the remaining 7 allocated bits (commonly used for alpha)
8433 $alpha = (((127 - $alpha) / 127) * 255); // GD alpha is only 7 bit (0 -> 127)
8434 $alpha = $this->getGDgamma($alpha); // correct gamma
8435 imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
8436 }
8437 }
8438 imagepng($imgalpha, $tempfile_alpha);
8439 imagedestroy($imgalpha);
8440 // extract image without alpha channel
8441 $imgplain = imagecreatetruecolor($wpx, $hpx);
8442 imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
8443 imagepng($imgplain, $tempfile_plain);
8444 imagedestroy($imgplain);
8445 } else {
8446 $this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
8447 }
8448 // embed mask image
8449 $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
8450 // embed image, masked with previously embedded mask
8451 $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
8452 // remove temp files
8453 unlink($tempfile_alpha);
8454 unlink($tempfile_plain);
8455 }
8456
8457 /**
8458 * Correct the gamma value to be used with GD library
8459 * @param $v (float) the gamma value to be corrected
8460 * @protected
8461 * @since 4.3.007 (2008-12-04)
8462 */
8463 protected function getGDgamma($v) {
8464 return (pow(($v / 255), 2.2) * 255);
8465 }
8466
8467 /**
8468 * Performs a line break.
8469 * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
8470 * @param $h (float) The height of the break. By default, the value equals the height of the last printed cell.
8471 * @param $cell (boolean) if true add the current left (or right o for RTL) padding to the X coordinate
8472 * @public
8473 * @since 1.0
8474 * @see Cell()
8475 */
8476 public function Ln($h='', $cell=false) {
8477 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'])) {
8478 // revove vertical space from the top of the column
8479 return;
8480 }
8481 if ($cell) {
8482 if ($this->rtl) {
8483 $cellpadding = $this->cell_padding['R'];
8484 } else {
8485 $cellpadding = $this->cell_padding['L'];
8486 }
8487 } else {
8488 $cellpadding = 0;
8489 }
8490 if ($this->rtl) {
8491 $this->x = $this->w - $this->rMargin - $cellpadding;
8492 } else {
8493 $this->x = $this->lMargin + $cellpadding;
8494 }
8495 if (is_string($h)) {
8496 $this->y += $this->lasth;
8497 } else {
8498 $this->y += $h;
8499 }
8500 $this->newline = true;
8501 }
8502
8503 /**
8504 * Returns the relative X value of current position.
8505 * The value is relative to the left border for LTR languages and to the right border for RTL languages.
8506 * @return float
8507 * @public
8508 * @since 1.2
8509 * @see SetX(), GetY(), SetY()
8510 */
8511 public function GetX() {
8512 //Get x position
8513 if ($this->rtl) {
8514 return ($this->w - $this->x);
8515 } else {
8516 return $this->x;
8517 }
8518 }
8519
8520 /**
8521 * Returns the absolute X value of current position.
8522 * @return float
8523 * @public
8524 * @since 1.2
8525 * @see SetX(), GetY(), SetY()
8526 */
8527 public function GetAbsX() {
8528 return $this->x;
8529 }
8530
8531 /**
8532 * Returns the ordinate of the current position.
8533 * @return float
8534 * @public
8535 * @since 1.0
8536 * @see SetY(), GetX(), SetX()
8537 */
8538 public function GetY() {
8539 return $this->y;
8540 }
8541
8542 /**
8543 * Defines the abscissa of the current position.
8544 * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
8545 * @param $x (float) The value of the abscissa.
8546 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
8547 * @public
8548 * @since 1.2
8549 * @see GetX(), GetY(), SetY(), SetXY()
8550 */
8551 public function SetX($x, $rtloff=false) {
8552 if (!$rtloff AND $this->rtl) {
8553 if ($x >= 0) {
8554 $this->x = $this->w - $x;
8555 } else {
8556 $this->x = abs($x);
8557 }
8558 } else {
8559 if ($x >= 0) {
8560 $this->x = $x;
8561 } else {
8562 $this->x = $this->w + $x;
8563 }
8564 }
8565 if ($this->x < 0) {
8566 $this->x = 0;
8567 }
8568 if ($this->x > $this->w) {
8569 $this->x = $this->w;
8570 }
8571 }
8572
8573 /**
8574 * Moves the current abscissa back to the left margin and sets the ordinate.
8575 * If the passed value is negative, it is relative to the bottom of the page.
8576 * @param $y (float) The value of the ordinate.
8577 * @param $resetx (bool) if true (default) reset the X position.
8578 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
8579 * @public
8580 * @since 1.0
8581 * @see GetX(), GetY(), SetY(), SetXY()
8582 */
8583 public function SetY($y, $resetx=true, $rtloff=false) {
8584 if ($resetx) {
8585 //reset x
8586 if (!$rtloff AND $this->rtl) {
8587 $this->x = $this->w - $this->rMargin;
8588 } else {
8589 $this->x = $this->lMargin;
8590 }
8591 }
8592 if ($y >= 0) {
8593 $this->y = $y;
8594 } else {
8595 $this->y = $this->h + $y;
8596 }
8597 if ($this->y < 0) {
8598 $this->y = 0;
8599 }
8600 if ($this->y > $this->h) {
8601 $this->y = $this->h;
8602 }
8603 }
8604
8605 /**
8606 * Defines the abscissa and ordinate of the current position.
8607 * If the passed values are negative, they are relative respectively to the right and bottom of the page.
8608 * @param $x (float) The value of the abscissa.
8609 * @param $y (float) The value of the ordinate.
8610 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
8611 * @public
8612 * @since 1.2
8613 * @see SetX(), SetY()
8614 */
8615 public function SetXY($x, $y, $rtloff=false) {
8616 $this->SetY($y, false, $rtloff);
8617 $this->SetX($x, $rtloff);
8618 }
8619
8620 /**
8621 * Ouput input data and compress it if possible.
8622 * @param $data (string) Data to output.
8623 * @param $length (int) Data length in bytes.
8624 * @protected
8625 * @since 5.9.086
8626 */
8627 protected function sendOutputData($data, $length) {
8628 if (!isset($_SERVER['HTTP_ACCEPT_ENCODING']) OR empty($_SERVER['HTTP_ACCEPT_ENCODING'])) {
8629 // the content length may vary if the server is using compression
8630 header('Content-Length: '.$length);
8631 }
8632 echo $data;
8633 }
8634
8635 /**
8636 * Send the document to a given destination: string, local file or browser.
8637 * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
8638 * The method first calls Close() if necessary to terminate the document.
8639 * @param $name (string) The name of the file when saved. Note that special characters are removed and blanks characters are replaced with the underscore character.
8640 * @param $dest (string) Destination where to send the document. It can take one of the following values:<ul><li>I: send the file inline to the browser (default). The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.</li><li>D: send to the browser and force a file download with the name given by name.</li><li>F: save to a local server file with the name given by name.</li><li>S: return the document as a string (name is ignored).</li><li>FI: equivalent to F + I option</li><li>FD: equivalent to F + D option</li><li>E: return the document as base64 mime multi-part email attachment (RFC 2045)</li></ul>
8641 * @public
8642 * @since 1.0
8643 * @see Close()
8644 */
8645 public function Output($name='doc.pdf', $dest='I') {
8646 //Output PDF to some destination
8647 //Finish document if necessary
8648 if ($this->state < 3) {
8649 $this->Close();
8650 }
8651 //Normalize parameters
8652 if (is_bool($dest)) {
8653 $dest = $dest ? 'D' : 'F';
8654 }
8655 $dest = strtoupper($dest);
8656 if ($dest{0} != 'F') {
8657 $name = preg_replace('/[\s]+/', '_', $name);
8658 $name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
8659 }
8660 if ($this->sign) {
8661 // *** apply digital signature to the document ***
8662 // get the document content
8663 $pdfdoc = $this->getBuffer();
8664 // remove last newline
8665 $pdfdoc = substr($pdfdoc, 0, -1);
8666 // Remove the original buffer
8667 if (isset($this->diskcache) AND $this->diskcache) {
8668 // remove buffer file from cache
8669 unlink($this->buffer);
8670 }
8671 unset($this->buffer);
8672 // remove filler space
8673 $byterange_string_len = strlen($this->byterange_string);
8674 // define the ByteRange
8675 $byte_range = array();
8676 $byte_range[0] = 0;
8677 $byte_range[1] = strpos($pdfdoc, $this->byterange_string) + $byterange_string_len + 10;
8678 $byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
8679 $byte_range[3] = strlen($pdfdoc) - $byte_range[2];
8680 $pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
8681 // replace the ByteRange
8682 $byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
8683 $byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
8684 $pdfdoc = str_replace($this->byterange_string, $byterange, $pdfdoc);
8685 // write the document to a temporary folder
8686 $tempdoc = tempnam(K_PATH_CACHE, 'tmppdf_');
8687 $f = fopen($tempdoc, 'wb');
8688 if (!$f) {
8689 $this->Error('Unable to create temporary file: '.$tempdoc);
8690 }
8691 $pdfdoc_length = strlen($pdfdoc);
8692 fwrite($f, $pdfdoc, $pdfdoc_length);
8693 fclose($f);
8694 // get digital signature via openssl library
8695 $tempsign = tempnam(K_PATH_CACHE, 'tmpsig_');
8696 if (empty($this->signature_data['extracerts'])) {
8697 openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
8698 } else {
8699 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']);
8700 }
8701 unlink($tempdoc);
8702 // read signature
8703 $signature = file_get_contents($tempsign);
8704 unlink($tempsign);
8705 // extract signature
8706 $signature = substr($signature, $pdfdoc_length);
8707 $signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
8708 $tmparr = explode("\n\n", $signature);
8709 $signature = $tmparr[1];
8710 unset($tmparr);
8711 // decode signature
8712 $signature = base64_decode(trim($signature));
8713 // convert signature to hex
8714 $signature = current(unpack('H*', $signature));
8715 $signature = str_pad($signature, $this->signature_max_length, '0');
8716 // disable disk caching
8717 $this->diskcache = false;
8718 // Add signature to the document
8719 $this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
8720 $this->bufferlen = strlen($this->buffer);
8721 }
8722 switch($dest) {
8723 case 'I': {
8724 // Send PDF to the standard output
8725 if (ob_get_contents()) {
8726 $this->Error('Some data has already been output, can\'t send PDF file');
8727 }
8728 if (php_sapi_name() != 'cli') {
8729 // send output to a browser
8730 header('Content-Type: application/pdf');
8731 if (headers_sent()) {
8732 $this->Error('Some data has already been output to browser, can\'t send PDF file');
8733 }
8734 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
8735 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
8736 header('Pragma: public');
8737 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
8738 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
8739 header('Content-Disposition: inline; filename="'.basename($name).'";');
8740 $this->sendOutputData($this->getBuffer(), $this->bufferlen);
8741 } else {
8742 echo $this->getBuffer();
8743 }
8744 break;
8745 }
8746 case 'D': {
8747 // download PDF as file
8748 if (ob_get_contents()) {
8749 $this->Error('Some data has already been output, can\'t send PDF file');
8750 }
8751 header('Content-Description: File Transfer');
8752 if (headers_sent()) {
8753 $this->Error('Some data has already been output to browser, can\'t send PDF file');
8754 }
8755 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
8756 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
8757 header('Pragma: public');
8758 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
8759 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
8760 // force download dialog
8761 if (strpos(php_sapi_name(), 'cgi') === false) {
8762 header('Content-Type: application/force-download');
8763 header('Content-Type: application/octet-stream', false);
8764 header('Content-Type: application/download', false);
8765 header('Content-Type: application/pdf', false);
8766 } else {
8767 header('Content-Type: application/pdf');
8768 }
8769 // use the Content-Disposition header to supply a recommended filename
8770 header('Content-Disposition: attachment; filename="'.basename($name).'";');
8771 header('Content-Transfer-Encoding: binary');
8772 $this->sendOutputData($this->getBuffer(), $this->bufferlen);
8773 break;
8774 }
8775 case 'F':
8776 case 'FI':
8777 case 'FD': {
8778 // save PDF to a local file
8779 if ($this->diskcache) {
8780 copy($this->buffer, $name);
8781 } else {
8782 $f = fopen($name, 'wb');
8783 if (!$f) {
8784 $this->Error('Unable to create output file: '.$name);
8785 }
8786 fwrite($f, $this->getBuffer(), $this->bufferlen);
8787 fclose($f);
8788 }
8789 if ($dest == 'FI') {
8790 // send headers to browser
8791 header('Content-Type: application/pdf');
8792 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
8793 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
8794 header('Pragma: public');
8795 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
8796 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
8797 header('Content-Disposition: inline; filename="'.basename($name).'";');
8798 $this->sendOutputData(file_get_contents($name), filesize($name));
8799 } elseif ($dest == 'FD') {
8800 // send headers to browser
8801 if (ob_get_contents()) {
8802 $this->Error('Some data has already been output, can\'t send PDF file');
8803 }
8804 header('Content-Description: File Transfer');
8805 if (headers_sent()) {
8806 $this->Error('Some data has already been output to browser, can\'t send PDF file');
8807 }
8808 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
8809 header('Pragma: public');
8810 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
8811 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
8812 // force download dialog
8813 if (strpos(php_sapi_name(), 'cgi') === false) {
8814 header('Content-Type: application/force-download');
8815 header('Content-Type: application/octet-stream', false);
8816 header('Content-Type: application/download', false);
8817 header('Content-Type: application/pdf', false);
8818 } else {
8819 header('Content-Type: application/pdf');
8820 }
8821 // use the Content-Disposition header to supply a recommended filename
8822 header('Content-Disposition: attachment; filename="'.basename($name).'";');
8823 header('Content-Transfer-Encoding: binary');
8824 $this->sendOutputData(file_get_contents($name), filesize($name));
8825 }
8826 break;
8827 }
8828 case 'E': {
8829 // return PDF as base64 mime multi-part email attachment (RFC 2045)
8830 $retval = 'Content-Type: application/pdf;'."\r\n";
8831 $retval .= ' name="'.$name.'"'."\r\n";
8832 $retval .= 'Content-Transfer-Encoding: base64'."\r\n";
8833 $retval .= 'Content-Disposition: attachment;'."\r\n";
8834 $retval .= ' filename="'.$name.'"'."\r\n\r\n";
8835 $retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
8836 return $retval;
8837 }
8838 case 'S': {
8839 // returns PDF as a string
8840 return $this->getBuffer();
8841 }
8842 default: {
8843 $this->Error('Incorrect output destination: '.$dest);
8844 }
8845 }
8846 return '';
8847 }
8848
8849 /**
8850 * Unset all class variables except the following critical variables: internal_encoding, state, bufferlen, buffer and diskcache.
8851 * @param $destroyall (boolean) if true destroys all class variables, otherwise preserves critical variables.
8852 * @param $preserve_objcopy (boolean) if true preserves the objcopy variable
8853 * @public
8854 * @since 4.5.016 (2009-02-24)
8855 */
8856 public function _destroy($destroyall=false, $preserve_objcopy=false) {
8857 if ($destroyall AND isset($this->diskcache) AND $this->diskcache AND (!$preserve_objcopy) AND (!$this->empty_string($this->buffer))) {
8858 // remove buffer file from cache
8859 unlink($this->buffer);
8860 }
8861 foreach (array_keys(get_object_vars($this)) as $val) {
8862 if ($destroyall OR (
8863 ($val != 'internal_encoding')
8864 AND ($val != 'state')
8865 AND ($val != 'bufferlen')
8866 AND ($val != 'buffer')
8867 AND ($val != 'diskcache')
8868 AND ($val != 'sign')
8869 AND ($val != 'signature_data')
8870 AND ($val != 'signature_max_length')
8871 AND ($val != 'byterange_string')
8872 )) {
8873 if ((!$preserve_objcopy OR ($val != 'objcopy')) AND isset($this->$val)) {
8874 unset($this->$val);
8875 }
8876 }
8877 }
8878 }
8879
8880 /**
8881 * Check for locale-related bug
8882 * @protected
8883 */
8884 protected function _dochecks() {
8885 //Check for locale-related bug
8886 if (1.1 == 1) {
8887 $this->Error('Don\'t alter the locale before including class file');
8888 }
8889 //Check for decimal separator
8890 if (sprintf('%.1F', 1.0) != '1.0') {
8891 setlocale(LC_NUMERIC, 'C');
8892 }
8893 }
8894
8895 /**
8896 * Return fonts path
8897 * @return string
8898 * @protected
8899 */
8900 protected function _getfontpath() {
8901 if (!defined('K_PATH_FONTS') AND is_dir(dirname(__FILE__).'/fonts')) {
8902 define('K_PATH_FONTS', dirname(__FILE__).'/fonts/');
8903 }
8904 return defined('K_PATH_FONTS') ? K_PATH_FONTS : '';
8905 }
8906
8907 /**
8908 * Return an array containing variations for the basic page number alias.
8909 * @param $a (string) Base alias.
8910 * @return array of page number aliases
8911 * @protected
8912 */
8913 protected function getInternalPageNumberAliases($a= '') {
8914 $alias = array();
8915 // build array of Unicode + ASCII variants (the order is important)
8916 $alias = array('u' => array(), 'a' => array());
8917 $u = '{'.$a.'}';
8918 $alias['u'][] = $this->_escape($u);
8919 if ($this->isunicode) {
8920 $alias['u'][] = $this->_escape($this->UTF8ToLatin1($u));
8921 $alias['u'][] = $this->_escape($this->utf8StrRev($u, false, $this->tmprtl));
8922 $alias['a'][] = $this->_escape($this->UTF8ToLatin1($a));
8923 $alias['a'][] = $this->_escape($this->utf8StrRev($a, false, $this->tmprtl));
8924 }
8925 $alias['a'][] = $this->_escape($a);
8926 return $alias;
8927 }
8928
8929 /**
8930 * Return an array containing all internal page aliases.
8931 * @return array of page number aliases
8932 * @protected
8933 */
8934 protected function getAllInternalPageNumberAliases() {
8935 $basic_alias = array($this->alias_tot_pages, $this->alias_num_page, $this->alias_group_tot_pages, $this->alias_group_num_page, $this->alias_right_shift);
8936 $pnalias = array();
8937 foreach($basic_alias as $k => $a) {
8938 $pnalias[$k] = $this->getInternalPageNumberAliases($a);
8939 }
8940 return $pnalias;
8941 }
8942
8943 /**
8944 * Replace page number aliases with number.
8945 * @param $page (string) Page content.
8946 * @param $replace (array) Array of replacements (array keys are replacement strings, values are alias arrays).
8947 * @param $diff (int) If passed, this will be set to the total char number difference between alias and replacements.
8948 * @return replaced page content and updated $diff parameter as array.
8949 * @protected
8950 */
8951 protected function replacePageNumAliases($page, $replace, $diff=0) {
8952 foreach ($replace as $rep) {
8953 foreach ($rep[3] as $a) {
8954 if (strpos($page, $a) !== false) {
8955 $page = str_replace($a, $rep[0], $page);
8956 $diff += ($rep[2] - $rep[1]);
8957 }
8958 }
8959 }
8960 return array($page, $diff);
8961 }
8962
8963 /**
8964 * Replace right shift page number aliases with spaces to correct right alignment.
8965 * This works perfectly only when using monospaced fonts.
8966 * @param $page (string) Page content.
8967 * @param $aliases (array) Array of page aliases.
8968 * @param $diff (int) initial difference to add.
8969 * @return replaced page content.
8970 * @protected
8971 */
8972 protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
8973 foreach ($aliases as $type => $alias) {
8974 foreach ($alias as $a) {
8975 // find position of compensation factor
8976 $startnum = (strpos($a, ':') + 1);
8977 $a = substr($a, 0, $startnum);
8978 if (($pos = strpos($page, $a)) !== false) {
8979 // end of alias
8980 $endnum = strpos($page, '}', $pos);
8981 // string to be replaced
8982 $aa = substr($page, $pos, ($endnum - $pos + 1));
8983 // get compensation factor
8984 $ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
8985 $ratio = preg_replace('/[^0-9\.]/', '', $ratio);
8986 $ratio = floatval($ratio);
8987 if ($type == 'u') {
8988 $chrdiff = floor(($diff + 12) * $ratio);
8989 $shift = str_repeat(' ', $chrdiff);
8990 $shift = $this->UTF8ToUTF16BE($shift, false);
8991 } else {
8992 $chrdiff = floor(($diff + 11) * $ratio);
8993 $shift = str_repeat(' ', $chrdiff);
8994 }
8995 $page = str_replace($aa, $shift, $page);
8996 }
8997 }
8998 }
8999 return $page;
9000 }
9001
9002 /**
9003 * Set page boxes to be included on page descriptions.
9004 * @param $boxes (array) Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
9005 * @protected
9006 */
9007 protected function setPageBoxTypes($boxes) {
9008 $validboxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
9009 $this->page_boxes = array();
9010 foreach ($boxes as $box) {
9011 if (in_array($box, $validboxes)) {
9012 $this->page_boxes[] = $box;
9013 }
9014 }
9015 }
9016
9017 /**
9018 * Output pages (and replace page number aliases).
9019 * @protected
9020 */
9021 protected function _putpages() {
9022 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9023 // get internal aliases for page numbers
9024 $pnalias = $this->getAllInternalPageNumberAliases();
9025 $num_pages = $this->numpages;
9026 $ptpa = $this->formatPageNumber(($this->starting_page_number + $num_pages - 1));
9027 $ptpu = $this->UTF8ToUTF16BE($ptpa, false);
9028 $ptp_num_chars = $this->GetNumChars($ptpa);
9029 $pagegroupnum = 0;
9030 $groupnum = 0;
9031 $ptgu = 1;
9032 $ptga = 1;
9033 for ($n = 1; $n <= $num_pages; ++$n) {
9034 // get current page
9035 $temppage = $this->getPageBuffer($n);
9036 $pagelen = strlen($temppage);
9037 // set replacements for total pages number
9038 $pnpa = $this->formatPageNumber(($this->starting_page_number + $n - 1));
9039 $pnpu = $this->UTF8ToUTF16BE($pnpa, false);
9040 $pnp_num_chars = $this->GetNumChars($pnpa);
9041 $pdiff = 0; // difference used for right shift alignment of page numbers
9042 $gdiff = 0; // difference used for right shift alignment of page group numbers
9043 if (!empty($this->pagegroups)) {
9044 if (isset($this->newpagegroup[$n])) {
9045 $pagegroupnum = 0;
9046 ++$groupnum;
9047 $ptga = $this->formatPageNumber($this->pagegroups[$groupnum]);
9048 $ptgu = $this->UTF8ToUTF16BE($ptga, false);
9049 $ptg_num_chars = $this->GetNumChars($ptga);
9050 }
9051 ++$pagegroupnum;
9052 $pnga = $this->formatPageNumber($pagegroupnum);
9053 $pngu = $this->UTF8ToUTF16BE($pnga, false);
9054 $png_num_chars = $this->GetNumChars($pnga);
9055 // replace page numbers
9056 $replace = array();
9057 $replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
9058 $replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
9059 $replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
9060 $replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
9061 list($temppage, $gdiff) = $this->replacePageNumAliases($temppage, $replace, $gdiff);
9062 }
9063 // replace page numbers
9064 $replace = array();
9065 $replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
9066 $replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
9067 $replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
9068 $replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
9069 list($temppage, $pdiff) = $this->replacePageNumAliases($temppage, $replace, $pdiff);
9070 // replace right shift alias
9071 $temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
9072 // replace EPS marker
9073 $temppage = str_replace($this->epsmarker, '', $temppage);
9074 //Page
9075 $this->page_obj_id[$n] = $this->_newobj();
9076 $out = '<<';
9077 $out .= ' /Type /Page';
9078 $out .= ' /Parent 1 0 R';
9079 $out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
9080 $out .= ' /Resources 2 0 R';
9081 foreach ($this->page_boxes as $box) {
9082 $out .= ' /'.$box;
9083 $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']);
9084 }
9085 if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
9086 $out .= ' /BoxColorInfo <<';
9087 foreach ($this->page_boxes as $box) {
9088 if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
9089 $out .= ' /'.$box.' <<';
9090 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
9091 $color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
9092 $out .= ' /C [';
9093 $out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
9094 $out .= ' ]';
9095 }
9096 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
9097 $out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
9098 }
9099 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
9100 $out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
9101 }
9102 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
9103 $dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
9104 $out .= ' /D [';
9105 foreach ($dashes as $dash) {
9106 $out .= sprintf(' %F', ($dash * $this->k));
9107 }
9108 $out .= ' ]';
9109 }
9110 $out .= ' >>';
9111 }
9112 }
9113 $out .= ' >>';
9114 }
9115 $out .= ' /Contents '.($this->n + 1).' 0 R';
9116 $out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
9117 if (!$this->pdfa_mode) {
9118 $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
9119 }
9120 if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
9121 // page transitions
9122 if (isset($this->pagedim[$n]['trans']['Dur'])) {
9123 $out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
9124 }
9125 $out .= ' /Trans <<';
9126 $out .= ' /Type /Trans';
9127 if (isset($this->pagedim[$n]['trans']['S'])) {
9128 $out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
9129 }
9130 if (isset($this->pagedim[$n]['trans']['D'])) {
9131 $out .= ' /D '.$this->pagedim[$n]['trans']['D'];
9132 }
9133 if (isset($this->pagedim[$n]['trans']['Dm'])) {
9134 $out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
9135 }
9136 if (isset($this->pagedim[$n]['trans']['M'])) {
9137 $out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
9138 }
9139 if (isset($this->pagedim[$n]['trans']['Di'])) {
9140 $out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
9141 }
9142 if (isset($this->pagedim[$n]['trans']['SS'])) {
9143 $out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
9144 }
9145 if (isset($this->pagedim[$n]['trans']['B'])) {
9146 $out .= ' /B '.$this->pagedim[$n]['trans']['B'];
9147 }
9148 $out .= ' >>';
9149 }
9150 $out .= $this->_getannotsrefs($n);
9151 $out .= ' /PZ '.$this->pagedim[$n]['PZ'];
9152 $out .= ' >>';
9153 $out .= "\n".'endobj';
9154 $this->_out($out);
9155 //Page content
9156 $p = ($this->compress) ? gzcompress($temppage) : $temppage;
9157 $this->_newobj();
9158 $p = $this->_getrawstream($p);
9159 $this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
9160 if ($this->diskcache) {
9161 // remove temporary files
9162 unlink($this->pages[$n]);
9163 }
9164 }
9165 //Pages root
9166 $out = $this->_getobj(1)."\n";
9167 $out .= '<< /Type /Pages /Kids [';
9168 foreach($this->page_obj_id as $page_obj) {
9169 $out .= ' '.$page_obj.' 0 R';
9170 }
9171 $out .= ' ] /Count '.$num_pages.' >>';
9172 $out .= "\n".'endobj';
9173 $this->_out($out);
9174 }
9175
9176 /**
9177 * Output references to page annotations
9178 * @param $n (int) page number
9179 * @protected
9180 * @author Nicola Asuni
9181 * @since 4.7.000 (2008-08-29)
9182 * @deprecated
9183 */
9184 protected function _putannotsrefs($n) {
9185 $this->_out($this->_getannotsrefs($n));
9186 }
9187
9188 /**
9189 * Get references to page annotations.
9190 * @param $n (int) page number
9191 * @return string
9192 * @protected
9193 * @author Nicola Asuni
9194 * @since 5.0.010 (2010-05-17)
9195 */
9196 protected function _getannotsrefs($n) {
9197 if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
9198 return '';
9199 }
9200 $out = ' /Annots [';
9201 if (isset($this->PageAnnots[$n])) {
9202 foreach ($this->PageAnnots[$n] as $key => $val) {
9203 if (!in_array($val['n'], $this->radio_groups)) {
9204 $out .= ' '.$val['n'].' 0 R';
9205 }
9206 }
9207 // add radiobutton groups
9208 if (isset($this->radiobutton_groups[$n])) {
9209 foreach ($this->radiobutton_groups[$n] as $key => $data) {
9210 if (isset($data['n'])) {
9211 $out .= ' '.$data['n'].' 0 R';
9212 }
9213 }
9214 }
9215 }
9216 if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
9217 // set reference for signature object
9218 $out .= ' '.$this->sig_obj_id.' 0 R';
9219 }
9220 if (!empty($this->empty_signature_appearance)) {
9221 foreach ($this->empty_signature_appearance as $esa) {
9222 if ($esa['page'] == $n) {
9223 // set reference for empty signature objects
9224 $out .= ' '.$esa['objid'].' 0 R';
9225 }
9226 }
9227 }
9228 $out .= ' ]';
9229 return $out;
9230 }
9231
9232 /**
9233 * Output annotations objects for all pages.
9234 * !!! THIS METHOD IS NOT YET COMPLETED !!!
9235 * See section 12.5 of PDF 32000_2008 reference.
9236 * @protected
9237 * @author Nicola Asuni
9238 * @since 4.0.018 (2008-08-06)
9239 */
9240 protected function _putannotsobjs() {
9241 // reset object counter
9242 for ($n=1; $n <= $this->numpages; ++$n) {
9243 if (isset($this->PageAnnots[$n])) {
9244 // set page annotations
9245 foreach ($this->PageAnnots[$n] as $key => $pl) {
9246 $annot_obj_id = $this->PageAnnots[$n][$key]['n'];
9247 // create annotation object for grouping radiobuttons
9248 if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
9249 $radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
9250 $annots = '<<';
9251 $annots .= ' /Type /Annot';
9252 $annots .= ' /Subtype /Widget';
9253 $annots .= ' /Rect [0 0 0 0]';
9254 if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
9255 // read only
9256 $annots .= ' /F 68';
9257 $annots .= ' /Ff 49153';
9258 } else {
9259 $annots .= ' /F 4'; // default print for PDF/A
9260 $annots .= ' /Ff 49152';
9261 }
9262 $annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
9263 $annots .= ' /FT /Btn';
9264 $annots .= ' /Kids [';
9265 $defval = '';
9266 foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
9267 if (isset($data['kid'])) {
9268 $annots .= ' '.$data['kid'].' 0 R';
9269 if ($data['def'] !== 'Off') {
9270 $defval = $data['def'];
9271 }
9272 }
9273 }
9274 $annots .= ' ]';
9275 if (!empty($defval)) {
9276 $annots .= ' /V /'.$defval;
9277 }
9278 $annots .= ' >>';
9279 $this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
9280 $this->form_obj_id[] = $radio_button_obj_id;
9281 // store object id to be used on Parent entry of Kids
9282 $this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
9283 }
9284 $formfield = false;
9285 $pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
9286 $a = $pl['x'] * $this->k;
9287 $b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
9288 $c = $pl['w'] * $this->k;
9289 $d = $pl['h'] * $this->k;
9290 $rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
9291 // create new annotation object
9292 $annots = '<</Type /Annot';
9293 $annots .= ' /Subtype /'.$pl['opt']['subtype'];
9294 $annots .= ' /Rect ['.$rect.']';
9295 $ft = array('Btn', 'Tx', 'Ch', 'Sig');
9296 if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
9297 $annots .= ' /FT /'.$pl['opt']['ft'];
9298 $formfield = true;
9299 }
9300 $annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
9301 $annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
9302 $annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
9303 $annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
9304 if (isset($pl['opt']['f'])) {
9305 $fval = 0;
9306 if (is_array($pl['opt']['f'])) {
9307 foreach ($pl['opt']['f'] as $f) {
9308 switch (strtolower($f)) {
9309 case 'invisible': {
9310 $fval += 1 << 0;
9311 break;
9312 }
9313 case 'hidden': {
9314 $fval += 1 << 1;
9315 break;
9316 }
9317 case 'print': {
9318 $fval += 1 << 2;
9319 break;
9320 }
9321 case 'nozoom': {
9322 $fval += 1 << 3;
9323 break;
9324 }
9325 case 'norotate': {
9326 $fval += 1 << 4;
9327 break;
9328 }
9329 case 'noview': {
9330 $fval += 1 << 5;
9331 break;
9332 }
9333 case 'readonly': {
9334 $fval += 1 << 6;
9335 break;
9336 }
9337 case 'locked': {
9338 $fval += 1 << 8;
9339 break;
9340 }
9341 case 'togglenoview': {
9342 $fval += 1 << 9;
9343 break;
9344 }
9345 case 'lockedcontents': {
9346 $fval += 1 << 10;
9347 break;
9348 }
9349 default: {
9350 break;
9351 }
9352 }
9353 }
9354 } else {
9355 $fval = intval($pl['opt']['f']);
9356 }
9357 } else {
9358 $fval = 4;
9359 }
9360 if ($this->pdfa_mode) {
9361 // force print flag for PDF/A mode
9362 $fval |= 4;
9363 }
9364 $annots .= ' /F '.intval($fval);
9365 if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
9366 $annots .= ' /AS /'.$pl['opt']['as'];
9367 }
9368 if (isset($pl['opt']['ap'])) {
9369 // appearance stream
9370 $annots .= ' /AP <<';
9371 if (is_array($pl['opt']['ap'])) {
9372 foreach ($pl['opt']['ap'] as $apmode => $apdef) {
9373 // $apmode can be: n = normal; r = rollover; d = down;
9374 $annots .= ' /'.strtoupper($apmode);
9375 if (is_array($apdef)) {
9376 $annots .= ' <<';
9377 foreach ($apdef as $apstate => $stream) {
9378 // reference to XObject that define the appearance for this mode-state
9379 $apsobjid = $this->_putAPXObject($c, $d, $stream);
9380 $annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
9381 }
9382 $annots .= ' >>';
9383 } else {
9384 // reference to XObject that define the appearance for this mode
9385 $apsobjid = $this->_putAPXObject($c, $d, $apdef);
9386 $annots .= ' '.$apsobjid.' 0 R';
9387 }
9388 }
9389 } else {
9390 $annots .= $pl['opt']['ap'];
9391 }
9392 $annots .= ' >>';
9393 }
9394 if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
9395 $annots .= ' /BS <<';
9396 $annots .= ' /Type /Border';
9397 if (isset($pl['opt']['bs']['w'])) {
9398 $annots .= ' /W '.intval($pl['opt']['bs']['w']);
9399 }
9400 $bstyles = array('S', 'D', 'B', 'I', 'U');
9401 if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
9402 $annots .= ' /S /'.$pl['opt']['bs']['s'];
9403 }
9404 if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
9405 $annots .= ' /D [';
9406 foreach ($pl['opt']['bs']['d'] as $cord) {
9407 $annots .= ' '.intval($cord);
9408 }
9409 $annots .= ']';
9410 }
9411 $annots .= ' >>';
9412 } else {
9413 $annots .= ' /Border [';
9414 if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
9415 $annots .= intval($pl['opt']['border'][0]).' ';
9416 $annots .= intval($pl['opt']['border'][1]).' ';
9417 $annots .= intval($pl['opt']['border'][2]);
9418 if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
9419 $annots .= ' [';
9420 foreach ($pl['opt']['border'][3] as $dash) {
9421 $annots .= intval($dash).' ';
9422 }
9423 $annots .= ']';
9424 }
9425 } else {
9426 $annots .= '0 0 0';
9427 }
9428 $annots .= ']';
9429 }
9430 if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
9431 $annots .= ' /BE <<';
9432 $bstyles = array('S', 'C');
9433 if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $markups)) {
9434 $annots .= ' /S /'.$pl['opt']['bs']['s'];
9435 } else {
9436 $annots .= ' /S /S';
9437 }
9438 if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
9439 $annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
9440 }
9441 $annots .= '>>';
9442 }
9443 if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
9444 $annots .= ' /C '.$this->getColorStringFromArray($pl['opt']['c']);
9445 }
9446 //$annots .= ' /StructParent ';
9447 //$annots .= ' /OC ';
9448 $markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
9449 if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
9450 // this is a markup type
9451 if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
9452 $annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
9453 }
9454 //$annots .= ' /Popup ';
9455 if (isset($pl['opt']['ca'])) {
9456 $annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
9457 }
9458 if (isset($pl['opt']['rc'])) {
9459 $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
9460 }
9461 $annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
9462 //$annots .= ' /IRT ';
9463 if (isset($pl['opt']['subj'])) {
9464 $annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
9465 }
9466 //$annots .= ' /RT ';
9467 //$annots .= ' /IT ';
9468 //$annots .= ' /ExData ';
9469 }
9470 $lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
9471 // Annotation types
9472 switch (strtolower($pl['opt']['subtype'])) {
9473 case 'text': {
9474 if (isset($pl['opt']['open'])) {
9475 $annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
9476 }
9477 $iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
9478 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
9479 $annots .= ' /Name /'.$pl['opt']['name'];
9480 } else {
9481 $annots .= ' /Name /Note';
9482 }
9483 $statemodels = array('Marked', 'Review');
9484 if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
9485 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
9486 } else {
9487 $pl['opt']['statemodel'] = 'Marked';
9488 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
9489 }
9490 if ($pl['opt']['statemodel'] == 'Marked') {
9491 $states = array('Accepted', 'Unmarked');
9492 } else {
9493 $states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
9494 }
9495 if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
9496 $annots .= ' /State /'.$pl['opt']['state'];
9497 } else {
9498 if ($pl['opt']['statemodel'] == 'Marked') {
9499 $annots .= ' /State /Unmarked';
9500 } else {
9501 $annots .= ' /State /None';
9502 }
9503 }
9504 break;
9505 }
9506 case 'link': {
9507 if (is_string($pl['txt'])) {
9508 // external URI link
9509 $annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
9510 } else {
9511 // internal link
9512 $l = $this->links[$pl['txt']];
9513 if (isset($this->page_obj_id[($l[0])])) {
9514 $annots .= sprintf(' /Dest [%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($l[0])], ($this->pagedim[$l[0]]['h'] - ($l[1] * $this->k)));
9515 }
9516 }
9517 $hmodes = array('N', 'I', 'O', 'P');
9518 if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
9519 $annots .= ' /H /'.$pl['opt']['h'];
9520 } else {
9521 $annots .= ' /H /I';
9522 }
9523 //$annots .= ' /PA ';
9524 //$annots .= ' /Quadpoints ';
9525 break;
9526 }
9527 case 'freetext': {
9528 if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
9529 $annots .= ' /DA ('.$pl['opt']['da'].')';
9530 }
9531 if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
9532 $annots .= ' /Q '.intval($pl['opt']['q']);
9533 }
9534 if (isset($pl['opt']['rc'])) {
9535 $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
9536 }
9537 if (isset($pl['opt']['ds'])) {
9538 $annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
9539 }
9540 if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
9541 $annots .= ' /CL [';
9542 foreach ($pl['opt']['cl'] as $cl) {
9543 $annots .= sprintf('%F ', $cl * $this->k);
9544 }
9545 $annots .= ']';
9546 }
9547 $tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
9548 if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
9549 $annots .= ' /IT /'.$pl['opt']['it'];
9550 }
9551 if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
9552 $l = $pl['opt']['rd'][0] * $this->k;
9553 $r = $pl['opt']['rd'][1] * $this->k;
9554 $t = $pl['opt']['rd'][2] * $this->k;
9555 $b = $pl['opt']['rd'][3] * $this->k;
9556 $annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
9557 }
9558 if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
9559 $annots .= ' /LE /'.$pl['opt']['le'];
9560 }
9561 break;
9562 }
9563 case 'line': {
9564 break;
9565 }
9566 case 'square': {
9567 break;
9568 }
9569 case 'circle': {
9570 break;
9571 }
9572 case 'polygon': {
9573 break;
9574 }
9575 case 'polyline': {
9576 break;
9577 }
9578 case 'highlight': {
9579 break;
9580 }
9581 case 'underline': {
9582 break;
9583 }
9584 case 'squiggly': {
9585 break;
9586 }
9587 case 'strikeout': {
9588 break;
9589 }
9590 case 'stamp': {
9591 break;
9592 }
9593 case 'caret': {
9594 break;
9595 }
9596 case 'ink': {
9597 break;
9598 }
9599 case 'popup': {
9600 break;
9601 }
9602 case 'fileattachment': {
9603 if ($this->pdfa_mode) {
9604 // embedded files are not allowed in PDF/A mode
9605 break;
9606 }
9607 if (!isset($pl['opt']['fs'])) {
9608 break;
9609 }
9610 $filename = basename($pl['opt']['fs']);
9611 if (isset($this->embeddedfiles[$filename]['n'])) {
9612 $annots .= ' /FS <</Type /Filespec /F '.$this->_datastring($filename, $annot_obj_id).' /EF <</F '.$this->embeddedfiles[$filename]['n'].' 0 R>> >>';
9613 $iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
9614 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
9615 $annots .= ' /Name /'.$pl['opt']['name'];
9616 } else {
9617 $annots .= ' /Name /PushPin';
9618 }
9619 }
9620 break;
9621 }
9622 case 'sound': {
9623 if (!isset($pl['opt']['fs'])) {
9624 break;
9625 }
9626 $filename = basename($pl['opt']['fs']);
9627 if (isset($this->embeddedfiles[$filename]['n'])) {
9628 // ... TO BE COMPLETED ...
9629 // /R /C /B /E /CO /CP
9630 $annots .= ' /Sound <</Type /Filespec /F '.$this->_datastring($filename, $annot_obj_id).' /EF <</F '.$this->embeddedfiles[$filename]['n'].' 0 R>> >>';
9631 $iconsapp = array('Speaker', 'Mic');
9632 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
9633 $annots .= ' /Name /'.$pl['opt']['name'];
9634 } else {
9635 $annots .= ' /Name /Speaker';
9636 }
9637 }
9638 break;
9639 }
9640 case 'movie': {
9641 break;
9642 }
9643 case 'widget': {
9644 $hmode = array('N', 'I', 'O', 'P', 'T');
9645 if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
9646 $annots .= ' /H /'.$pl['opt']['h'];
9647 }
9648 if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
9649 $annots .= ' /MK <<';
9650 if (isset($pl['opt']['mk']['r'])) {
9651 $annots .= ' /R '.$pl['opt']['mk']['r'];
9652 }
9653 if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
9654 $annots .= ' /BC '.$this->getColorStringFromArray($pl['opt']['mk']['bc']);
9655 }
9656 if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
9657 $annots .= ' /BG '.$this->getColorStringFromArray($pl['opt']['mk']['bg']);
9658 }
9659 if (isset($pl['opt']['mk']['ca'])) {
9660 $annots .= ' /CA '.$pl['opt']['mk']['ca'];
9661 }
9662 if (isset($pl['opt']['mk']['rc'])) {
9663 $annots .= ' /RC '.$pl['opt']['mk']['rc'];
9664 }
9665 if (isset($pl['opt']['mk']['ac'])) {
9666 $annots .= ' /AC '.$pl['opt']['mk']['ac'];
9667 }
9668 if (isset($pl['opt']['mk']['i'])) {
9669 $info = $this->getImageBuffer($pl['opt']['mk']['i']);
9670 if ($info !== false) {
9671 $annots .= ' /I '.$info['n'].' 0 R';
9672 }
9673 }
9674 if (isset($pl['opt']['mk']['ri'])) {
9675 $info = $this->getImageBuffer($pl['opt']['mk']['ri']);
9676 if ($info !== false) {
9677 $annots .= ' /RI '.$info['n'].' 0 R';
9678 }
9679 }
9680 if (isset($pl['opt']['mk']['ix'])) {
9681 $info = $this->getImageBuffer($pl['opt']['mk']['ix']);
9682 if ($info !== false) {
9683 $annots .= ' /IX '.$info['n'].' 0 R';
9684 }
9685 }
9686 if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
9687 $annots .= ' /IF <<';
9688 $if_sw = array('A', 'B', 'S', 'N');
9689 if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
9690 $annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
9691 }
9692 $if_s = array('A', 'P');
9693 if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
9694 $annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
9695 }
9696 if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
9697 $annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
9698 }
9699 if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
9700 $annots .= ' /FB true';
9701 }
9702 $annots .= '>>';
9703 }
9704 if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
9705 $annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
9706 }
9707 $annots .= '>>';
9708 } // end MK
9709 // --- Entries for field dictionaries ---
9710 if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
9711 // set parent
9712 $annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
9713 }
9714 if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
9715 $annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
9716 }
9717 if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
9718 $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
9719 }
9720 if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
9721 $annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
9722 }
9723 if (isset($pl['opt']['ff'])) {
9724 if (is_array($pl['opt']['ff'])) {
9725 // array of bit settings
9726 $flag = 0;
9727 foreach($pl['opt']['ff'] as $val) {
9728 $flag += 1 << ($val - 1);
9729 }
9730 } else {
9731 $flag = intval($pl['opt']['ff']);
9732 }
9733 $annots .= ' /Ff '.$flag;
9734 }
9735 if (isset($pl['opt']['maxlen'])) {
9736 $annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
9737 }
9738 if (isset($pl['opt']['v'])) {
9739 $annots .= ' /V';
9740 if (is_array($pl['opt']['v'])) {
9741 foreach ($pl['opt']['v'] AS $optval) {
9742 if (is_float($optval)) {
9743 $optval = sprintf('%F', $optval);
9744 }
9745 $annots .= ' '.$optval;
9746 }
9747 } else {
9748 $annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
9749 }
9750 }
9751 if (isset($pl['opt']['dv'])) {
9752 $annots .= ' /DV';
9753 if (is_array($pl['opt']['dv'])) {
9754 foreach ($pl['opt']['dv'] AS $optval) {
9755 if (is_float($optval)) {
9756 $optval = sprintf('%F', $optval);
9757 }
9758 $annots .= ' '.$optval;
9759 }
9760 } else {
9761 $annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
9762 }
9763 }
9764 if (isset($pl['opt']['rv'])) {
9765 $annots .= ' /RV';
9766 if (is_array($pl['opt']['rv'])) {
9767 foreach ($pl['opt']['rv'] AS $optval) {
9768 if (is_float($optval)) {
9769 $optval = sprintf('%F', $optval);
9770 }
9771 $annots .= ' '.$optval;
9772 }
9773 } else {
9774 $annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
9775 }
9776 }
9777 if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
9778 $annots .= ' /A << '.$pl['opt']['a'].' >>';
9779 }
9780 if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
9781 $annots .= ' /AA << '.$pl['opt']['aa'].' >>';
9782 }
9783 if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
9784 $annots .= ' /DA ('.$pl['opt']['da'].')';
9785 }
9786 if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
9787 $annots .= ' /Q '.intval($pl['opt']['q']);
9788 }
9789 if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
9790 $annots .= ' /Opt [';
9791 foreach($pl['opt']['opt'] AS $copt) {
9792 if (is_array($copt)) {
9793 $annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
9794 } else {
9795 $annots .= ' '.$this->_textstring($copt, $annot_obj_id);
9796 }
9797 }
9798 $annots .= ']';
9799 }
9800 if (isset($pl['opt']['ti'])) {
9801 $annots .= ' /TI '.intval($pl['opt']['ti']);
9802 }
9803 if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
9804 $annots .= ' /I [';
9805 foreach($pl['opt']['i'] AS $copt) {
9806 $annots .= intval($copt).' ';
9807 }
9808 $annots .= ']';
9809 }
9810 break;
9811 }
9812 case 'screen': {
9813 break;
9814 }
9815 case 'printermark': {
9816 break;
9817 }
9818 case 'trapnet': {
9819 break;
9820 }
9821 case 'watermark': {
9822 break;
9823 }
9824 case '3d': {
9825 break;
9826 }
9827 default: {
9828 break;
9829 }
9830 }
9831 $annots .= '>>';
9832 // create new annotation object
9833 $this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
9834 if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
9835 // store reference of form object
9836 $this->form_obj_id[] = $annot_obj_id;
9837 }
9838 }
9839 }
9840 } // end for each page
9841 }
9842
9843 /**
9844 * Put appearance streams XObject used to define annotation's appearance states.
9845 * @param $w (int) annotation width
9846 * @param $h (int) annotation height
9847 * @param $stream (string) appearance stream
9848 * @return int object ID
9849 * @protected
9850 * @since 4.8.001 (2009-09-09)
9851 */
9852 protected function _putAPXObject($w=0, $h=0, $stream='') {
9853 $stream = trim($stream);
9854 $out = $this->_getobj()."\n";
9855 $this->xobjects['AX'.$this->n] = array('n' => $this->n);
9856 $out .= '<<';
9857 $out .= ' /Type /XObject';
9858 $out .= ' /Subtype /Form';
9859 $out .= ' /FormType 1';
9860 if ($this->compress) {
9861 $stream = gzcompress($stream);
9862 $out .= ' /Filter /FlateDecode';
9863 }
9864 $rect = sprintf('%F %F', $w, $h);
9865 $out .= ' /BBox [0 0 '.$rect.']';
9866 $out .= ' /Matrix [1 0 0 1 0 0]';
9867 $out .= ' /Resources 2 0 R';
9868 $stream = $this->_getrawstream($stream);
9869 $out .= ' /Length '.strlen($stream);
9870 $out .= ' >>';
9871 $out .= ' stream'."\n".$stream."\n".'endstream';
9872 $out .= "\n".'endobj';
9873 $this->_out($out);
9874 return $this->n;
9875 }
9876
9877 /**
9878 * Get ULONG from string (Big Endian 32-bit unsigned integer).
9879 * @param $str (string) string from where to extract value
9880 * @param $offset (int) point from where to read the data
9881 * @return int 32 bit value
9882 * @author Nicola Asuni
9883 * @protected
9884 * @since 5.2.000 (2010-06-02)
9885 */
9886 protected function _getULONG($str, $offset) {
9887 $v = unpack('Ni', substr($str, $offset, 4));
9888 return $v['i'];
9889 }
9890
9891 /**
9892 * Get USHORT from string (Big Endian 16-bit unsigned integer).
9893 * @param $str (string) string from where to extract value
9894 * @param $offset (int) point from where to read the data
9895 * @return int 16 bit value
9896 * @author Nicola Asuni
9897 * @protected
9898 * @since 5.2.000 (2010-06-02)
9899 */
9900 protected function _getUSHORT($str, $offset) {
9901 $v = unpack('ni', substr($str, $offset, 2));
9902 return $v['i'];
9903 }
9904
9905 /**
9906 * Get SHORT from string (Big Endian 16-bit signed integer).
9907 * @param $str (string) String from where to extract value.
9908 * @param $offset (int) Point from where to read the data.
9909 * @return int 16 bit value
9910 * @author Nicola Asuni
9911 * @protected
9912 * @since 5.2.000 (2010-06-02)
9913 */
9914 protected function _getSHORT($str, $offset) {
9915 $v = unpack('si', substr($str, $offset, 2));
9916 return $v['i'];
9917 }
9918
9919 /**
9920 * Get FWORD from string (Big Endian 16-bit signed integer).
9921 * @param $str (string) String from where to extract value.
9922 * @param $offset (int) Point from where to read the data.
9923 * @return int 16 bit value
9924 * @author Nicola Asuni
9925 * @protected
9926 * @since 5.9.123 (2011-09-30)
9927 */
9928 protected function _getFWORD($str, $offset) {
9929 $v = $this->_getUSHORT($str, $offset);
9930 if ($v > 0x7fff) {
9931 $v -= 0x10000;
9932 }
9933 return $v;
9934 }
9935
9936 /**
9937 * Get UFWORD from string (Big Endian 16-bit unsigned integer).
9938 * @param $str (string) string from where to extract value
9939 * @param $offset (int) point from where to read the data
9940 * @return int 16 bit value
9941 * @author Nicola Asuni
9942 * @protected
9943 * @since 5.9.123 (2011-09-30)
9944 */
9945 protected function _getUFWORD($str, $offset) {
9946 $v = $this->_getUSHORT($str, $offset);
9947 return $v;
9948 }
9949
9950 /**
9951 * Get FIXED from string (32-bit signed fixed-point number (16.16).
9952 * @param $str (string) string from where to extract value
9953 * @param $offset (int) point from where to read the data
9954 * @return int 16 bit value
9955 * @author Nicola Asuni
9956 * @protected
9957 * @since 5.9.123 (2011-09-30)
9958 */
9959 protected function _getFIXED($str, $offset) {
9960 // mantissa
9961 $m = $this->_getFWORD($str, $offset);
9962 // fraction
9963 $f = $this->_getUSHORT($str, ($offset + 2));
9964 $v = floatval(''.$m.'.'.$f.'');
9965 return $v;
9966 }
9967
9968 /**
9969 * Get BYTE from string (8-bit unsigned integer).
9970 * @param $str (string) String from where to extract value.
9971 * @param $offset (int) Point from where to read the data.
9972 * @return int 8 bit value
9973 * @author Nicola Asuni
9974 * @protected
9975 * @since 5.2.000 (2010-06-02)
9976 */
9977 protected function _getBYTE($str, $offset) {
9978 $v = unpack('Ci', substr($str, $offset, 1));
9979 return $v['i'];
9980 }
9981 /**
9982 * Update the CIDToGIDMap string with a new value.
9983 * @param $map (string) CIDToGIDMap.
9984 * @param $cid (int) CID value.
9985 * @param $gid (int) GID value.
9986 * @return (string) CIDToGIDMap.
9987 * @author Nicola Asuni
9988 * @protected
9989 * @since 5.9.123 (2011-09-29)
9990 */
9991 protected function updateCIDtoGIDmap($map, $cid, $gid) {
9992 if (($cid >= 0) AND ($cid <= 0xFFFF) AND ($gid >= 0)) {
9993 if ($gid > 0xFFFF) {
9994 $gid -= 0x10000;
9995 }
9996 $map[($cid * 2)] = chr($gid >> 8);
9997 $map[(($cid * 2) + 1)] = chr($gid & 0xFF);
9998 }
9999 return $map;
10000 }
10001
10002 /**
10003 * Convert and add the selected TrueType or Type1 font to the fonts folder (that must be writeable).
10004 * @param $fontfile (string) TrueType or Type1 font file (full path).
10005 * @param $fonttype (string) Font type. Leave empty for autodetect mode. Valid values are: TrueTypeUnicode, TrueType, Type1, CID0JP = CID-0 Japanese, CID0KR = CID-0 Korean, CID0CS = CID-0 Chinese Simplified, CID0CT = CID-0 Chinese Traditional.
10006 * @param $enc (string) Name of the encoding table to use. Leave empty for default mode. Omit this parameter for TrueType Unicode and symbolic fonts like Symbol or ZapfDingBats.
10007 * @param $flags (int) Unsigned 32-bit integer containing flags specifying various characteristics of the font (PDF32000:2008 - 9.8.2 Font Descriptor Flags): +1 for fixed font; +4 for symbol or +32 for non-symbol; +64 for italic. Fixed and Italic mode are generally autodetected so you have to set it to 32 = non-symbolic font (default) or 4 = symbolic font.
10008 * @param $outpath (string) Output path for generated font files (must be writeable by the web server). Leave empty for default font folder.
10009 * @return (string) TCPDF font name.
10010 * @author Nicola Asuni
10011 * @public
10012 * @since 5.9.123 (2010-09-30)
10013 */
10014 public function addTTFfont($fontfile, $fonttype='', $enc='', $flags=32, $outpath='') {
10015 if (!file_exists($fontfile)) {
10016 $this->Error('Could not find file: '.$fontfile.'');
10017 }
10018 // font metrics
10019 $fmetric = array();
10020 // build new font name for TCPDF compatibility
10021 $font_path_parts = pathinfo($fontfile);
10022 if (!isset($font_path_parts['filename'])) {
10023 $font_path_parts['filename'] = substr($font_path_parts['basename'], 0, -(strlen($font_path_parts['extension']) + 1));
10024 }
10025 $font_name = strtolower($font_path_parts['filename']);
10026 $font_name = preg_replace('/[^a-z0-9_]/', '', $font_name);
10027 $search = array('bold', 'oblique', 'italic', 'regular');
10028 $replace = array('b', 'i', 'i', '');
10029 $font_name = str_replace($search, $replace, $font_name);
10030 if (empty($font_name)) {
10031 // set generic name
10032 $font_name = 'tcpdffont';
10033 }
10034 // set output path
10035 if (empty($outpath)) {
10036 $outpath = $this->_getfontpath();
10037 }
10038 // check if this font already exist
10039 if (file_exists($outpath.$font_name.'.php')) {
10040 // this font already exist (delete it from fonts folder to rebuild it)
10041 return $font_name;
10042 }
10043 $fmetric['file'] = $font_name.'.z';
10044 $fmetric['ctg'] = $font_name.'.ctg.z';
10045 // get font data
10046 $font = file_get_contents($fontfile);
10047 $fmetric['originalsize'] = strlen($font);
10048 // autodetect font type
10049 if (empty($fonttype)) {
10050 if ($this->_getULONG($font, 0) == 0x10000) {
10051 // True Type (Unicode or not)
10052 $fonttype = 'TrueTypeUnicode';
10053 } elseif (substr($font, 0, 4) == 'OTTO') {
10054 // Open Type (Unicode or not)
10055 $this->Error('Unsupported font format: OpenType with CFF data.');
10056 } else {
10057 // Type 1
10058 $fonttype = 'Type1';
10059 }
10060 }
10061 // set font type
10062 switch ($fonttype) {
10063 case 'CID0CT':
10064 case 'CID0CS':
10065 case 'CID0KR':
10066 case 'CID0JP': {
10067 $fmetric['type'] = 'cidfont0';
10068 break;
10069 }
10070 case 'Type1': {
10071 $fmetric['type'] = 'Type1';
10072 if (empty($enc) AND (($flags & 4) == 0)) {
10073 $enc = 'cp1252';
10074 }
10075 break;
10076 }
10077 case 'TrueType': {
10078 $fmetric['type'] = 'TrueType';
10079 break;
10080 }
10081 case 'TrueTypeUnicode':
10082 default: {
10083 $fmetric['type'] = 'TrueTypeUnicode';
10084 break;
10085 }
10086 }
10087 // set encoding maps (if any)
10088 $fmetric['enc'] = preg_replace('/[^A-Za-z0-9_\-]/', '', $enc);
10089 $fmetric['diff'] = '';
10090 if (($fmetric['type'] == 'TrueType') OR ($fmetric['type'] == 'Type1')) {
10091 if (!empty($enc) AND ($enc != 'cp1252') AND isset($this->encmaps->encmap[$enc])) {
10092 // build differences from reference encoding
10093 $enc_ref = $this->encmaps->encmap['cp1252'];
10094 $enc_target = $this->encmaps->encmap[$enc];
10095 $last = 0;
10096 for ($i = 32; $i <= 255; ++$i) {
10097 if ($enc_target != $enc_ref[$i]) {
10098 if ($i != ($last + 1)) {
10099 $fmetric['diff'] .= $i.' ';
10100 }
10101 $last = $i;
10102 $fmetric['diff'] .= '/'.$enc_target[$i].' ';
10103 }
10104 }
10105 }
10106 }
10107 // parse the font by type
10108 if ($fmetric['type'] == 'Type1') {
10109 // ---------- TYPE 1 ----------
10110 // read first segment
10111 $a = unpack('Cmarker/Ctype/Vsize', substr($font, 0, 6));
10112 if ($a['marker'] != 128) {
10113 $this->Error('Font file is not a valid binary Type1');
10114 }
10115 $fmetric['size1'] = $a['size'];
10116 $data = substr($font, 6, $fmetric['size1']);
10117 // read second segment
10118 $a = unpack('Cmarker/Ctype/Vsize', substr($font, (6 + $fmetric['size1']), 6));
10119 if ($a['marker'] != 128) {
10120 $this->Error('Font file is not a valid binary Type1');
10121 }
10122 $fmetric['size2'] = $a['size'];
10123 $encrypted = substr($font, (12 + $fmetric['size1']), $fmetric['size2']);
10124 $data .= $encrypted;
10125 // store compressed font
10126 $fp = fopen($outpath.$fmetric['file'], 'wb');
10127 fwrite($fp, gzcompress($data));
10128 fclose($fp);
10129 // get font info
10130 $fmetric['Flags'] = $flags;
10131 preg_match ('#/FullName[\s]*\(([^\)]*)#', $font, $matches);
10132 $fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\-]/', '', $matches[1]);
10133 preg_match('#/FontBBox[\s]*{([^}]*)#', $font, $matches);
10134 $fmetric['bbox'] = trim($matches[1]);
10135 $bv = explode(' ', $fmetric['bbox']);
10136 $fmetric['Ascent'] = intval($bv[3]);
10137 $fmetric['Descent'] = intval($bv[1]);
10138 preg_match('#/ItalicAngle[\s]*([0-9\+\-]*)#', $font, $matches);
10139 $fmetric['italicAngle'] = intval($matches[1]);
10140 if ($fmetric['italicAngle'] != 0) {
10141 $fmetric['Flags'] |= 64;
10142 }
10143 preg_match('#/UnderlinePosition[\s]*([0-9\+\-]*)#', $font, $matches);
10144 $fmetric['underlinePosition'] = intval($matches[1]);
10145 preg_match('#/UnderlineThickness[\s]*([0-9\+\-]*)#', $font, $matches);
10146 $fmetric['underlineThickness'] = intval($matches[1]);
10147 preg_match('#/isFixedPitch[\s]*([^\s]*)#', $font, $matches);
10148 if ($matches[1] == 'true') {
10149 $fmetric['Flags'] |= 1;
10150 }
10151 // get internal map
10152 $imap = array();
10153 if (preg_match_all('#dup[\s]([0-9]+)[\s]*/([^\s]*)[\s]put#sU', $font, $fmap, PREG_SET_ORDER) > 0) {
10154 foreach ($fmap as $v) {
10155 $imap[$v[2]] = $v[1];
10156 }
10157 }
10158 // decrypt eexec encrypted part
10159 $r = 55665; // eexec encryption constant
10160 $c1 = 52845;
10161 $c2 = 22719;
10162 $elen = strlen($encrypted);
10163 $eplain = '';
10164 for ($i = 0; $i < $elen; ++$i) {
10165 $chr = ord($encrypted[$i]);
10166 $eplain .= chr($chr ^ ($r >> 8));
10167 $r = ((($chr + $r) * $c1 + $c2) % 65536);
10168 }
10169 if (preg_match('#/ForceBold[\s]*([^\s]*)#', $eplain, $matches) > 0) {
10170 if ($matches[1] == 'true') {
10171 $fmetric['Flags'] |= 0x40000;
10172 }
10173 }
10174 if (preg_match('#/StdVW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
10175 $fmetric['StemV'] = intval($matches[1]);
10176 } else {
10177 $fmetric['StemV'] = 70;
10178 }
10179 if (preg_match('#/StdHW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
10180 $fmetric['StemH'] = intval($matches[1]);
10181 } else {
10182 $fmetric['StemH'] = 30;
10183 }
10184 if (preg_match('#/BlueValues[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
10185 $bv = explode(' ', $matches[1]);
10186 if (count($bv) >= 6) {
10187 $v1 = intval($bv[2]);
10188 $v2 = intval($bv[4]);
10189 if ($v1 <= $v2) {
10190 $fmetric['XHeight'] = $v1;
10191 $fmetric['CapHeight'] = $v2;
10192 } else {
10193 $fmetric['XHeight'] = $v2;
10194 $fmetric['CapHeight'] = $v1;
10195 }
10196 } else {
10197 $fmetric['XHeight'] = 450;
10198 $fmetric['CapHeight'] = 700;
10199 }
10200 } else {
10201 $fmetric['XHeight'] = 450;
10202 $fmetric['CapHeight'] = 700;
10203 }
10204 // get the number of random bytes at the beginning of charstrings
10205 if (preg_match('#/lenIV[\s]*([0-9]*)#', $eplain, $matches) > 0) {
10206 $lenIV = intval($matches[1]);
10207 } else {
10208 $lenIV = 4;
10209 }
10210 $fmetric['Leading'] = 0;
10211 // get charstring data
10212 $eplain = substr($eplain, (strpos($eplain, '/CharStrings') + 1));
10213 preg_match_all('#/([A-Za-z0-9\.]*)[\s][0-9]+[\s]RD[\s](.*)[\s]ND#sU', $eplain, $matches, PREG_SET_ORDER);
10214 if (!empty($enc) AND isset($this->encmaps->encmap[$enc])) {
10215 $enc_map = $this->encmaps->encmap[$enc];
10216 } else {
10217 $enc_map = false;
10218 }
10219 $fmetric['cw'] = '';
10220 $fmetric['MaxWidth'] = 0;
10221 $cwidths = array();
10222 foreach ($matches as $k => $v) {
10223 $cid = 0;
10224 if (isset($imap[$v[1]])) {
10225 $cid = $imap[$v[1]];
10226 } elseif ($enc_map !== false) {
10227 $cid = array_search($v[1], $enc_map);
10228 if ($cid === false) {
10229 $cid = 0;
10230 } elseif ($cid > 1000) {
10231 $cid -= 1000;
10232 }
10233 }
10234 // decrypt charstring encrypted part
10235 $r = 4330; // charstring encryption constant
10236 $c1 = 52845;
10237 $c2 = 22719;
10238 $cd = $v[2];
10239 $clen = strlen($cd);
10240 $ccom = array();
10241 for ($i = 0; $i < $clen; ++$i) {
10242 $chr = ord($cd[$i]);
10243 $ccom[] = ($chr ^ ($r >> 8));
10244 $r = ((($chr + $r) * $c1 + $c2) % 65536);
10245 }
10246 // decode numbers
10247 $cdec = array();
10248 $ck = 0;
10249 $i = $lenIV;
10250 while ($i < $clen) {
10251 if ($ccom[$i] < 32) {
10252 $cdec[$ck] = $ccom[$i];
10253 if (($ck > 0) AND ($cdec[$ck] == 13)) {
10254 // hsbw command: update width
10255 $cwidths[$cid] = $cdec[($ck - 1)];
10256 }
10257 ++$i;
10258 } elseif (($ccom[$i] >= 32) AND ($ccom[$i] <= 246)) {
10259 $cdec[$ck] = ($ccom[$i] - 139);
10260 ++$i;
10261 } elseif (($ccom[$i] >= 247) AND ($ccom[$i] <= 250)) {
10262 $cdec[$ck] = ((($ccom[$i] - 247) * 256) + $ccom[($i + 1)] + 108);
10263 $i += 2;
10264 } elseif (($ccom[$i] >= 251) AND ($ccom[$i] <= 254)) {
10265 $cdec[$ck] = ((-($ccom[$i] - 251) * 256) - $ccom[($i + 1)] - 108);
10266 $i += 2;
10267 } elseif ($ccom[$i] == 255) {
10268 $sval = chr($ccom[($i + 1)]).chr($ccom[($i + 2)]).chr($ccom[($i + 3)]).chr($ccom[($i + 4)]);
10269 $vsval = unpack('li', $sval);
10270 $cdec[$ck] = $vsval['i'];
10271 $i += 5;
10272 }
10273 ++$ck;
10274 }
10275 } // end for each matches
10276 $fmetric['MissingWidth'] = $cwidths[0];
10277 $fmetric['MaxWidth'] = $fmetric['MissingWidth'];
10278 $fmetric['AvgWidth'] = 0;
10279 // set chars widths
10280 for ($cid = 0; $cid <= 255; ++$cid) {
10281 if (isset($cwidths[$cid])) {
10282 if ($cwidths[$cid] > $fmetric['MaxWidth']) {
10283 $fmetric['MaxWidth'] = $cwidths[$cid];
10284 }
10285 $fmetric['AvgWidth'] += $cwidths[$cid];
10286 $fmetric['cw'] .= ','.$cid.'=>'.$cwidths[$cid];
10287 } else {
10288 $fmetric['cw'] .= ','.$cid.'=>'.$fmetric['MissingWidth'];
10289 }
10290 }
10291 $fmetric['AvgWidth'] = round($fmetric['AvgWidth'] / count($cwidths));
10292 } else {
10293 // ---------- TRUE TYPE ----------
10294 if ($fmetric['type'] != 'cidfont0') {
10295 // store compressed font
10296 $fp = fopen($outpath.$fmetric['file'], 'wb');
10297 fwrite($fp, gzcompress($font));
10298 fclose($fp);
10299 }
10300 $offset = 0; // offset position of the font data
10301 if ($this->_getULONG($font, $offset) != 0x10000) { echo $this->_getULONG($font, $offset);
10302 // sfnt version must be 0x00010000 for TrueType version 1.0.
10303 return $font;
10304 }
10305 $offset += 4;
10306 // get number of tables
10307 $numTables = $this->_getUSHORT($font, $offset);
10308 $offset += 2;
10309 // skip searchRange, entrySelector and rangeShift
10310 $offset += 6;
10311 // tables array
10312 $table = array();
10313 // ---------- get tables ----------
10314 for ($i = 0; $i < $numTables; ++$i) {
10315 // get table info
10316 $tag = substr($font, $offset, 4);
10317 $offset += 4;
10318 $table[$tag] = array();
10319 $table[$tag]['checkSum'] = $this->_getULONG($font, $offset);
10320 $offset += 4;
10321 $table[$tag]['offset'] = $this->_getULONG($font, $offset);
10322 $offset += 4;
10323 $table[$tag]['length'] = $this->_getULONG($font, $offset);
10324 $offset += 4;
10325 }
10326 // check magicNumber
10327 $offset = $table['head']['offset'] + 12;
10328 if ($this->_getULONG($font, $offset) != 0x5F0F3CF5) {
10329 // magicNumber must be 0x5F0F3CF5
10330 return $font;
10331 }
10332 $offset += 4;
10333 $offset += 2; // skip flags
10334 // get FUnits
10335 $fmetric['unitsPerEm'] = $this->_getUSHORT($font, $offset);
10336 $offset += 2;
10337 // units ratio constant
10338 $urk = (1000 / $fmetric['unitsPerEm']);
10339 $offset += 16; // skip created, modified
10340 $xMin = round($this->_getFWORD($font, $offset) * $urk);
10341 $offset += 2;
10342 $yMin = round($this->_getFWORD($font, $offset) * $urk);
10343 $offset += 2;
10344 $xMax = round($this->_getFWORD($font, $offset) * $urk);
10345 $offset += 2;
10346 $yMax = round($this->_getFWORD($font, $offset) * $urk);
10347 $offset += 2;
10348 $fmetric['bbox'] = ''.$xMin.' '.$yMin.' '.$xMax.' '.$yMax.'';
10349 $macStyle = $this->_getUSHORT($font, $offset);
10350 $offset += 2;
10351 // PDF font flags
10352 $fmetric['Flags'] = $flags;
10353 if (($macStyle & 2) == 2) {
10354 // italic flag
10355 $fmetric['Flags'] |= 64;
10356 }
10357 // get offset mode (indexToLocFormat : 0 = short, 1 = long)
10358 $offset = $table['head']['offset'] + 50;
10359 $short_offset = ($this->_getSHORT($font, $offset) == 0);
10360 $offset += 2;
10361 // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
10362 $indexToLoc = array();
10363 $offset = $table['loca']['offset'];
10364 if ($short_offset) {
10365 // short version
10366 $tot_num_glyphs = ($table['loca']['length'] / 2); // numGlyphs + 1
10367 for ($i = 0; $i < $tot_num_glyphs; ++$i) {
10368 $indexToLoc[$i] = $this->_getUSHORT($font, $offset) * 2;
10369 $offset += 2;
10370 }
10371 } else {
10372 // long version
10373 $tot_num_glyphs = ($table['loca']['length'] / 4); // numGlyphs + 1
10374 for ($i = 0; $i < $tot_num_glyphs; ++$i) {
10375 $indexToLoc[$i] = $this->_getULONG($font, $offset);
10376 $offset += 4;
10377 }
10378 }
10379 // get glyphs indexes of chars from cmap table
10380 $offset = $table['cmap']['offset'] + 2;
10381 $numEncodingTables = $this->_getUSHORT($font, $offset);
10382 $offset += 2;
10383 $encodingTables = array();
10384 for ($i = 0; $i < $numEncodingTables; ++$i) {
10385 $encodingTables[$i]['platformID'] = $this->_getUSHORT($font, $offset);
10386 $offset += 2;
10387 $encodingTables[$i]['encodingID'] = $this->_getUSHORT($font, $offset);
10388 $offset += 2;
10389 $encodingTables[$i]['offset'] = $this->_getULONG($font, $offset);
10390 $offset += 4;
10391 }
10392 // ---------- get os/2 metrics ----------
10393 $offset = $table['OS/2']['offset'];
10394 $offset += 2; // skip version
10395 // xAvgCharWidth
10396 $fmetric['AvgWidth'] = round($this->_getFWORD($font, $offset) * $urk);
10397 $offset += 2;
10398 // usWeightClass
10399 $usWeightClass = round($this->_getUFWORD($font, $offset) * $urk);
10400 // estimate StemV and StemH (400 = usWeightClass for Normal - Regular font)
10401 $fmetric['StemV'] = round((70 * $usWeightClass) / 400);
10402 $fmetric['StemH'] = round((30 * $usWeightClass) / 400);
10403 $offset += 2;
10404 $offset += 2; // usWidthClass
10405 $fsType = $this->_getSHORT($font, $offset);
10406 $offset += 2;
10407 if ($fsType == 2) {
10408 $this->Error('This Font cannot be modified, embedded or exchanged in any manner without first obtaining permission of the legal owner.');
10409 }
10410 // ---------- get font name ----------
10411 $fmetric['name'] = '';
10412 $offset = $table['name']['offset'];
10413 $offset += 2; // skip Format selector (=0).
10414 // Number of NameRecords that follow n.
10415 $numNameRecords = $this->_getUSHORT($font, $offset);
10416 $offset += 2;
10417 // Offset to start of string storage (from start of table).
10418 $stringStorageOffset = $this->_getUSHORT($font, $offset);
10419 $offset += 2;
10420 for ($i = 0; $i < $numNameRecords; ++$i) {
10421 $offset += 6; // skip Platform ID, Platform-specific encoding ID, Language ID.
10422 // Name ID.
10423 $nameID = $this->_getUSHORT($font, $offset);
10424 $offset += 2;
10425 if ($nameID == 6) {
10426 // String length (in bytes).
10427 $stringLength = $this->_getUSHORT($font, $offset);
10428 $offset += 2;
10429 // String offset from start of storage area (in bytes).
10430 $stringOffset = $this->_getUSHORT($font, $offset);
10431 $offset += 2;
10432 $offset = ($table['name']['offset'] + $stringStorageOffset + $stringOffset);
10433 $fmetric['name'] = substr($font, $offset, $stringLength);
10434 $fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\-]/', '', $fmetric['name']);
10435 break;
10436 } else {
10437 $offset += 4; // skip String length, String offset
10438 }
10439 }
10440 if (empty($fmetric['name'])) {
10441 $fmetric['name'] = $font_name;
10442 }
10443 // ---------- get post data ----------
10444 $offset = $table['post']['offset'];
10445 $offset += 4; // skip Format Type
10446 $fmetric['italicAngle'] = $this->_getFIXED($font, $offset);
10447 $offset += 4;
10448 $fmetric['underlinePosition'] = round($this->_getFWORD($font, $offset) * $urk);
10449 $offset += 2;
10450 $fmetric['underlineThickness'] = round($this->_getFWORD($font, $offset) * $urk);
10451 $offset += 2;
10452 $isFixedPitch = ($this->_getULONG($font, $offset) == 0) ? false : true;
10453 $offset += 2;
10454 if ($isFixedPitch) {
10455 $fmetric['Flags'] |= 1;
10456 }
10457 // ---------- get hhea data ----------
10458 $offset = $table['hhea']['offset'];
10459 $offset += 4; // skip Table version number
10460 // Ascender
10461 $fmetric['Ascent'] = round($this->_getFWORD($font, $offset) * $urk);
10462 $offset += 2;
10463 // Descender
10464 $fmetric['Descent'] = round($this->_getFWORD($font, $offset) * $urk);
10465 $offset += 2;
10466 // LineGap
10467 $fmetric['Leading'] = round($this->_getFWORD($font, $offset) * $urk);
10468 $offset += 2;
10469 // advanceWidthMax
10470 $fmetric['MaxWidth'] = round($this->_getUFWORD($font, $offset) * $urk);
10471 $offset += 2;
10472 $offset += 22; // skip some values
10473 // get the number of hMetric entries in hmtx table
10474 $numberOfHMetrics = $this->_getUSHORT($font, $offset);
10475 // ---------- get maxp data ----------
10476 $offset = $table['maxp']['offset'];
10477 $offset += 4; // skip Table version number
10478 // get the the number of glyphs in the font.
10479 $numGlyphs = $this->_getUSHORT($font, $offset);
10480 // ---------- get CIDToGIDMap ----------
10481 $ctg = array();
10482 foreach ($encodingTables as $enctable) {
10483 if (($enctable['platformID'] == 3) AND ($enctable['encodingID'] == 0)) {
10484 $modesymbol = true;
10485 } else {
10486 $modesymbol = false;
10487 }
10488 $offset = $table['cmap']['offset'] + $enctable['offset'];
10489 $format = $this->_getUSHORT($font, $offset);
10490 $offset += 2;
10491 switch ($format) {
10492 case 0: { // Format 0: Byte encoding table
10493 $offset += 4; // skip length and version/language
10494 for ($c = 0; $c < 256; ++$c) {
10495 $g = $this->_getBYTE($font, $offset);
10496 $ctg[$c] = $g;
10497 ++$offset;
10498 }
10499 break;
10500 }
10501 case 2: { // Format 2: High-byte mapping through table
10502 $offset += 4; // skip length and version/language
10503 $numSubHeaders = 0;
10504 for ($i = 0; $i < 256; ++$i) {
10505 // Array that maps high bytes to subHeaders: value is subHeader index * 8.
10506 $subHeaderKeys[$i] = ($this->_getUSHORT($font, $offset) / 8);
10507 $offset += 2;
10508 if ($numSubHeaders < $subHeaderKeys[$i]) {
10509 $numSubHeaders = $subHeaderKeys[$i];
10510 }
10511 }
10512 // the number of subHeaders is equal to the max of subHeaderKeys + 1
10513 ++$numSubHeaders;
10514 // read subHeader structures
10515 $subHeaders = array();
10516 $numGlyphIndexArray = 0;
10517 for ($k = 0; $k < $numSubHeaders; ++$k) {
10518 $subHeaders[$k]['firstCode'] = $this->_getUSHORT($font, $offset);
10519 $offset += 2;
10520 $subHeaders[$k]['entryCount'] = $this->_getUSHORT($font, $offset);
10521 $offset += 2;
10522 $subHeaders[$k]['idDelta'] = $this->_getUSHORT($font, $offset);
10523 $offset += 2;
10524 $subHeaders[$k]['idRangeOffset'] = $this->_getUSHORT($font, $offset);
10525 $offset += 2;
10526 $subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8));
10527 $subHeaders[$k]['idRangeOffset'] /= 2;
10528 $numGlyphIndexArray += $subHeaders[$k]['entryCount'];
10529 }
10530 for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
10531 $glyphIndexArray[$k] = $this->_getUSHORT($font, $offset);
10532 $offset += 2;
10533 }
10534 for ($i = 0; $i < 256; ++$i) {
10535 $k = $subHeaderKeys[$i];
10536 if ($k == 0) {
10537 // one byte code
10538 $c = $i;
10539 $g = $glyphIndexArray[0];
10540 $ctg[$c] = $g;
10541 } else {
10542 // two bytes code
10543 $start_byte = $subHeaders[$k]['firstCode'];
10544 $end_byte = $start_byte + $subHeaders[$k]['entryCount'];
10545 for ($j = $start_byte; $j < $end_byte; ++$j) {
10546 // combine high and low bytes
10547 $c = (($i << 8) + $j);
10548 $idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']);
10549 $g = ($glyphIndexArray[$idRangeOffset] + $idDelta[$k]) % 65536;
10550 if ($g < 0) {
10551 $g = 0;
10552 }
10553 $ctg[$c] = $g;
10554 }
10555 }
10556 }
10557 break;
10558 }
10559 case 4: { // Format 4: Segment mapping to delta values
10560 $length = $this->_getUSHORT($font, $offset);
10561 $offset += 2;
10562 $offset += 2; // skip version/language
10563 $segCount = ($this->_getUSHORT($font, $offset) / 2);
10564 $offset += 2;
10565 $offset += 6; // skip searchRange, entrySelector, rangeShift
10566 $endCount = array(); // array of end character codes for each segment
10567 for ($k = 0; $k < $segCount; ++$k) {
10568 $endCount[$k] = $this->_getUSHORT($font, $offset);
10569 $offset += 2;
10570 }
10571 $offset += 2; // skip reservedPad
10572 $startCount = array(); // array of start character codes for each segment
10573 for ($k = 0; $k < $segCount; ++$k) {
10574 $startCount[$k] = $this->_getUSHORT($font, $offset);
10575 $offset += 2;
10576 }
10577 $idDelta = array(); // delta for all character codes in segment
10578 for ($k = 0; $k < $segCount; ++$k) {
10579 $idDelta[$k] = $this->_getUSHORT($font, $offset);
10580 $offset += 2;
10581 }
10582 $idRangeOffset = array(); // Offsets into glyphIdArray or 0
10583 for ($k = 0; $k < $segCount; ++$k) {
10584 $idRangeOffset[$k] = $this->_getUSHORT($font, $offset);
10585 $offset += 2;
10586 }
10587 $gidlen = ($length / 2) - 8 - (4 * $segCount);
10588 $glyphIdArray = array(); // glyph index array
10589 for ($k = 0; $k < $gidlen; ++$k) {
10590 $glyphIdArray[$k] = $this->_getUSHORT($font, $offset);
10591 $offset += 2;
10592 }
10593 for ($k = 0; $k < $segCount; ++$k) {
10594 for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
10595 if ($idRangeOffset[$k] == 0) {
10596 $g = ($idDelta[$k] + $c) % 65536;
10597 } else {
10598 $gid = (($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
10599 $g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
10600 }
10601 if ($g < 0) {
10602 $g = 0;
10603 }
10604 $ctg[$c] = $g;
10605 }
10606 }
10607 break;
10608 }
10609 case 6: { // Format 6: Trimmed table mapping
10610 $offset += 4; // skip length and version/language
10611 $firstCode = $this->_getUSHORT($font, $offset);
10612 $offset += 2;
10613 $entryCount = $this->_getUSHORT($font, $offset);
10614 $offset += 2;
10615 for ($k = 0; $k < $entryCount; ++$k) {
10616 $c = ($k + $firstCode);
10617 $g = $this->_getUSHORT($font, $offset);
10618 $ctg[$c] = $g;
10619 $offset += 2;
10620 }
10621 break;
10622 }
10623 case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
10624 $offset += 10; // skip reserved, length and version/language
10625 for ($k = 0; $k < 8192; ++$k) {
10626 $is32[$k] = $this->_getBYTE($font, $offset);
10627 ++$offset;
10628 }
10629 $nGroups = $this->_getULONG($font, $offset);
10630 $offset += 4;
10631 for ($i = 0; $i < $nGroups; ++$i) {
10632 $startCharCode = $this->_getULONG($font, $offset);
10633 $offset += 4;
10634 $endCharCode = $this->_getULONG($font, $offset);
10635 $offset += 4;
10636 $startGlyphID = $this->_getULONG($font, $offset);
10637 $offset += 4;
10638 for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
10639 $is32idx = floor($c / 8);
10640 if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) {
10641 $c = $k;
10642 } else {
10643 // 32 bit format
10644 // convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
10645 //LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
10646 //SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
10647 $c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888;
10648 }
10649 $ctg[$c] = 0;
10650 ++$startGlyphID;
10651 }
10652 }
10653 break;
10654 }
10655 case 10: { // Format 10: Trimmed array
10656 $offset += 10; // skip reserved, length and version/language
10657 $startCharCode = $this->_getULONG($font, $offset);
10658 $offset += 4;
10659 $numChars = $this->_getULONG($font, $offset);
10660 $offset += 4;
10661 for ($k = 0; $k < $numChars; ++$k) {
10662 $c = ($k + $startCharCode);
10663 $g = $this->_getUSHORT($font, $offset);
10664 $ctg[$c] = $g;
10665 $offset += 2;
10666 }
10667 break;
10668 }
10669 case 12: { // Format 12: Segmented coverage
10670 $offset += 10; // skip length and version/language
10671 $nGroups = $this->_getULONG($font, $offset);
10672 $offset += 4;
10673 for ($k = 0; $k < $nGroups; ++$k) {
10674 $startCharCode = $this->_getULONG($font, $offset);
10675 $offset += 4;
10676 $endCharCode = $this->_getULONG($font, $offset);
10677 $offset += 4;
10678 $startGlyphCode = $this->_getULONG($font, $offset);
10679 $offset += 4;
10680 for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
10681 $ctg[$c] = $startGlyphCode;
10682 ++$startGlyphCode;
10683 }
10684 }
10685 break;
10686 }
10687 case 13: { // Format 13: Many-to-one range mappings
10688 // to be implemented ...
10689 break;
10690 }
10691 case 14: { // Format 14: Unicode Variation Sequences
10692 // to be implemented ...
10693 break;
10694 }
10695 }
10696 }
10697 if (!isset($ctg[0])) {
10698 $ctg[0] = 0;
10699 }
10700 // get xHeight (height of x)
10701 $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[120]] + 4);
10702 $yMin = $this->_getFWORD($font, $offset);
10703 $offset += 4;
10704 $yMax = $this->_getFWORD($font, $offset);
10705 $offset += 2;
10706 $fmetric['XHeight'] = round(($yMax - $yMin) * $urk);
10707 // get CapHeight (height of H)
10708 $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[72]] + 4);
10709 $yMin = $this->_getFWORD($font, $offset);
10710 $offset += 4;
10711 $yMax = $this->_getFWORD($font, $offset);
10712 $offset += 2;
10713 $fmetric['CapHeight'] = round(($yMax - $yMin) * $urk);
10714 // ceate widths array
10715 $cw = array();
10716 $offset = $table['hmtx']['offset'];
10717 for ($i = 0 ; $i < $numberOfHMetrics; ++$i) {
10718 $cw[$i] = round($this->_getUFWORD($font, $offset) * $urk);
10719 $offset += 4; // skip lsb
10720 }
10721 if ($numberOfHMetrics < $numGlyphs) {
10722 // fill missing widths with the last value
10723 $cw = array_pad($cw, $numGlyphs, $cw[($numberOfHMetrics - 1)]);
10724 }
10725 $fmetric['MissingWidth'] = $cw[0];
10726 $fmetric['cw'] = '';
10727 for ($cid = 0; $cid <= 65535; ++$cid) {
10728 if (isset($ctg[$cid]) AND isset($cw[$ctg[$cid]])) {
10729 $fmetric['cw'] .= ','.$cid.'=>'.$cw[$ctg[$cid]];
10730 }
10731 }
10732 } // end of true type
10733 if (($fmetric['type'] == 'TrueTypeUnicode') AND (count($ctg) == 256)) {
10734 $fmetric['type'] == 'TrueType';
10735 }
10736 // ---------- create php font file ----------
10737 $pfile = '<'.'?'.'php'."\n";
10738 $pfile .= '// TCPDF FONT FILE DESCRIPTION'."\n";
10739 $pfile .= '$type=\''.$fmetric['type'].'\';'."\n";
10740 $pfile .= '$name=\''.$fmetric['name'].'\';'."\n";
10741 $pfile .= '$up='.$fmetric['underlinePosition'].';'."\n";
10742 $pfile .= '$ut='.$fmetric['underlineThickness'].';'."\n";
10743 if ($fmetric['MissingWidth'] > 0) {
10744 $pfile .= '$dw='.$fmetric['MissingWidth'].';'."\n";
10745 } else {
10746 $pfile .= '$dw='.$fmetric['AvgWidth'].';'."\n";
10747 }
10748 $pfile .= '$diff=\''.$fmetric['diff'].'\';'."\n";
10749 if ($fmetric['type'] == 'Type1') {
10750 // Type 1
10751 $pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n";
10752 $pfile .= '$file=\''.$fmetric['file'].'\';'."\n";
10753 $pfile .= '$size1='.$fmetric['size1'].';'."\n";
10754 $pfile .= '$size2='.$fmetric['size2'].';'."\n";
10755 } else {
10756 $pfile .= '$originalsize='.$fmetric['originalsize'].';'."\n";
10757 if ($fmetric['type'] == 'cidfont0') {
10758 // CID-0
10759 switch ($fonttype) {
10760 case 'CID0JP': {
10761 $pfile .= '// Japanese'."\n";
10762 $pfile .= '$enc=\'UniJIS-UTF16-H\';'."\n";
10763 $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Japan1\',\'Supplement\'=>5);'."\n";
10764 $pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');'."\n";
10765 break;
10766 }
10767 case 'CID0KR': {
10768 $pfile .= '// Korean'."\n";
10769 $pfile .= '$enc=\'UniKS-UTF16-H\';'."\n";
10770 $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Korea1\',\'Supplement\'=>0);'."\n";
10771 $pfile .= 'include(dirname(__FILE__).\'/uni2cid_ak12.php\');'."\n";
10772 break;
10773 }
10774 case 'CID0CS': {
10775 $pfile .= '// Chinese Simplified'."\n";
10776 $pfile .= '$enc=\'UniGB-UTF16-H\';'."\n";
10777 $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'GB1\',\'Supplement\'=>2);'."\n";
10778 $pfile .= 'include(dirname(__FILE__).\'/uni2cid_ag15.php\');'."\n";
10779 break;
10780 }
10781 case 'CID0CT':
10782 default: {
10783 $pfile .= '// Chinese Traditional'."\n";
10784 $pfile .= '$enc=\'UniCNS-UTF16-H\';'."\n";
10785 $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'CNS1\',\'Supplement\'=>0);'."\n";
10786 $pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');'."\n";
10787 break;
10788 }
10789 }
10790 } else {
10791 // TrueType
10792 $pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n";
10793 $pfile .= '$file=\''.$fmetric['file'].'\';'."\n";
10794 $pfile .= '$ctg=\''.$fmetric['ctg'].'\';'."\n";
10795 // create CIDToGIDMap
10796 $cidtogidmap = str_pad('', 131072, "\x00"); // (256 * 256 * 2) = 131072
10797 foreach ($ctg as $cid => $gid) {
10798 $cidtogidmap = $this->updateCIDtoGIDmap($cidtogidmap, $cid, $ctg[$cid]);
10799 }
10800 // store compressed CIDToGIDMap
10801 $fp = fopen($outpath.$fmetric['ctg'], 'wb');
10802 fwrite($fp, gzcompress($cidtogidmap));
10803 fclose($fp);
10804 }
10805 }
10806 $pfile .= '$desc=array(';
10807 $pfile .= '\'Flags\'=>'.$fmetric['Flags'].',';
10808 $pfile .= '\'FontBBox\'=>\'['.$fmetric['bbox'].']\',';
10809 $pfile .= '\'ItalicAngle\'=>'.$fmetric['italicAngle'].',';
10810 $pfile .= '\'Ascent\'=>'.$fmetric['Ascent'].',';
10811 $pfile .= '\'Descent\'=>'.$fmetric['Descent'].',';
10812 $pfile .= '\'Leading\'=>'.$fmetric['Leading'].',';
10813 $pfile .= '\'CapHeight\'=>'.$fmetric['CapHeight'].',';
10814 $pfile .= '\'XHeight\'=>'.$fmetric['XHeight'].',';
10815 $pfile .= '\'StemV\'=>'.$fmetric['StemV'].',';
10816 $pfile .= '\'StemH\'=>'.$fmetric['StemH'].',';
10817 $pfile .= '\'AvgWidth\'=>'.$fmetric['AvgWidth'].',';
10818 $pfile .= '\'MaxWidth\'=>'.$fmetric['MaxWidth'].',';
10819 $pfile .= '\'MissingWidth\'=>'.$fmetric['MissingWidth'].'';
10820 $pfile .= ');'."\n";
10821 $pfile .= '$cw=array('.substr($fmetric['cw'], 1).');'."\n";
10822 $pfile .= '// --- EOF ---'."\n";
10823 // store file
10824 $fp = fopen($outpath.$font_name.'.php', 'w');
10825 fwrite($fp, $pfile);
10826 fclose($fp);
10827 // return TCPDF font name
10828 return $font_name;
10829 }
10830
10831 /**
10832 * Returns a subset of the TrueType font data without the unused glyphs.
10833 * @param $font (string) TrueType font data.
10834 * @param $subsetchars (array) Array of used characters (the glyphs to keep).
10835 * @return (string) A subset of TrueType font data without the unused glyphs.
10836 * @author Nicola Asuni
10837 * @protected
10838 * @since 5.2.000 (2010-06-02)
10839 */
10840 protected function _getTrueTypeFontSubset($font, $subsetchars) {
10841 ksort($subsetchars);
10842 $offset = 0; // offset position of the font data
10843 if ($this->_getULONG($font, $offset) != 0x10000) {
10844 // sfnt version must be 0x00010000 for TrueType version 1.0.
10845 return $font;
10846 }
10847 $offset += 4;
10848 // get number of tables
10849 $numTables = $this->_getUSHORT($font, $offset);
10850 $offset += 2;
10851 // skip searchRange, entrySelector and rangeShift
10852 $offset += 6;
10853 // tables array
10854 $table = array();
10855 // for each table
10856 for ($i = 0; $i < $numTables; ++$i) {
10857 // get table info
10858 $tag = substr($font, $offset, 4);
10859 $offset += 4;
10860 $table[$tag] = array();
10861 $table[$tag]['checkSum'] = $this->_getULONG($font, $offset);
10862 $offset += 4;
10863 $table[$tag]['offset'] = $this->_getULONG($font, $offset);
10864 $offset += 4;
10865 $table[$tag]['length'] = $this->_getULONG($font, $offset);
10866 $offset += 4;
10867 }
10868 // check magicNumber
10869 $offset = $table['head']['offset'] + 12;
10870 if ($this->_getULONG($font, $offset) != 0x5F0F3CF5) {
10871 // magicNumber must be 0x5F0F3CF5
10872 return $font;
10873 }
10874 $offset += 4;
10875 // get offset mode (indexToLocFormat : 0 = short, 1 = long)
10876 $offset = $table['head']['offset'] + 50;
10877 $short_offset = ($this->_getSHORT($font, $offset) == 0);
10878 $offset += 2;
10879 // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
10880 $indexToLoc = array();
10881 $offset = $table['loca']['offset'];
10882 if ($short_offset) {
10883 // short version
10884 $tot_num_glyphs = ($table['loca']['length'] / 2); // numGlyphs + 1
10885 for ($i = 0; $i < $tot_num_glyphs; ++$i) {
10886 $indexToLoc[$i] = $this->_getUSHORT($font, $offset) * 2;
10887 $offset += 2;
10888 }
10889 } else {
10890 // long version
10891 $tot_num_glyphs = ($table['loca']['length'] / 4); // numGlyphs + 1
10892 for ($i = 0; $i < $tot_num_glyphs; ++$i) {
10893 $indexToLoc[$i] = $this->_getULONG($font, $offset);
10894 $offset += 4;
10895 }
10896 }
10897 // get glyphs indexes of chars from cmap table
10898 $subsetglyphs = array(); // glyph IDs on key
10899 $subsetglyphs[0] = true; // character codes that do not correspond to any glyph in the font should be mapped to glyph index 0
10900 $offset = $table['cmap']['offset'] + 2;
10901 $numEncodingTables = $this->_getUSHORT($font, $offset);
10902 $offset += 2;
10903 $encodingTables = array();
10904 for ($i = 0; $i < $numEncodingTables; ++$i) {
10905 $encodingTables[$i]['platformID'] = $this->_getUSHORT($font, $offset);
10906 $offset += 2;
10907 $encodingTables[$i]['encodingID'] = $this->_getUSHORT($font, $offset);
10908 $offset += 2;
10909 $encodingTables[$i]['offset'] = $this->_getULONG($font, $offset);
10910 $offset += 4;
10911 }
10912 foreach ($encodingTables as $enctable) {
10913 if (($enctable['platformID'] == 3) AND ($enctable['encodingID'] == 0)) {
10914 $modesymbol = true;
10915 } else {
10916 $modesymbol = false;
10917 }
10918 $offset = $table['cmap']['offset'] + $enctable['offset'];
10919 $format = $this->_getUSHORT($font, $offset);
10920 $offset += 2;
10921 switch ($format) {
10922 case 0: { // Format 0: Byte encoding table
10923 $offset += 4; // skip length and version/language
10924 for ($c = 0; $c < 256; ++$c) {
10925 if (isset($subsetchars[$c])) {
10926 $g = $this->_getBYTE($font, $offset);
10927 $subsetglyphs[$g] = true;
10928 }
10929 ++$offset;
10930 }
10931 break;
10932 }
10933 case 2: { // Format 2: High-byte mapping through table
10934 $offset += 4; // skip length and version/language
10935 $numSubHeaders = 0;
10936 for ($i = 0; $i < 256; ++$i) {
10937 // Array that maps high bytes to subHeaders: value is subHeader index * 8.
10938 $subHeaderKeys[$i] = ($this->_getUSHORT($font, $offset) / 8);
10939 $offset += 2;
10940 if ($numSubHeaders < $subHeaderKeys[$i]) {
10941 $numSubHeaders = $subHeaderKeys[$i];
10942 }
10943 }
10944 // the number of subHeaders is equal to the max of subHeaderKeys + 1
10945 ++$numSubHeaders;
10946 // read subHeader structures
10947 $subHeaders = array();
10948 $numGlyphIndexArray = 0;
10949 for ($k = 0; $k < $numSubHeaders; ++$k) {
10950 $subHeaders[$k]['firstCode'] = $this->_getUSHORT($font, $offset);
10951 $offset += 2;
10952 $subHeaders[$k]['entryCount'] = $this->_getUSHORT($font, $offset);
10953 $offset += 2;
10954 $subHeaders[$k]['idDelta'] = $this->_getUSHORT($font, $offset);
10955 $offset += 2;
10956 $subHeaders[$k]['idRangeOffset'] = $this->_getUSHORT($font, $offset);
10957 $offset += 2;
10958 $subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8));
10959 $subHeaders[$k]['idRangeOffset'] /= 2;
10960 $numGlyphIndexArray += $subHeaders[$k]['entryCount'];
10961 }
10962 for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
10963 $glyphIndexArray[$k] = $this->_getUSHORT($font, $offset);
10964 $offset += 2;
10965 }
10966 for ($i = 0; $i < 256; ++$i) {
10967 $k = $subHeaderKeys[$i];
10968 if ($k == 0) {
10969 // one byte code
10970 $c = $i;
10971 if (isset($subsetchars[$c])) {
10972 $g = $glyphIndexArray[0];
10973 $subsetglyphs[$g] = true;
10974 }
10975 } else {
10976 // two bytes code
10977 $start_byte = $subHeaders[$k]['firstCode'];
10978 $end_byte = $start_byte + $subHeaders[$k]['entryCount'];
10979 for ($j = $start_byte; $j < $end_byte; ++$j) {
10980 // combine high and low bytes
10981 $c = (($i << 8) + $j);
10982 if (isset($subsetchars[$c])) {
10983 $idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']);
10984 $g = ($glyphIndexArray[$idRangeOffset] + $idDelta[$k]) % 65536;
10985 if ($g < 0) {
10986 $g = 0;
10987 }
10988 $subsetglyphs[$g] = true;
10989 }
10990 }
10991 }
10992 }
10993 break;
10994 }
10995 case 4: { // Format 4: Segment mapping to delta values
10996 $length = $this->_getUSHORT($font, $offset);
10997 $offset += 2;
10998 $offset += 2; // skip version/language
10999 $segCount = ($this->_getUSHORT($font, $offset) / 2);
11000 $offset += 2;
11001 $offset += 6; // skip searchRange, entrySelector, rangeShift
11002 $endCount = array(); // array of end character codes for each segment
11003 for ($k = 0; $k < $segCount; ++$k) {
11004 $endCount[$k] = $this->_getUSHORT($font, $offset);
11005 $offset += 2;
11006 }
11007 $offset += 2; // skip reservedPad
11008 $startCount = array(); // array of start character codes for each segment
11009 for ($k = 0; $k < $segCount; ++$k) {
11010 $startCount[$k] = $this->_getUSHORT($font, $offset);
11011 $offset += 2;
11012 }
11013 $idDelta = array(); // delta for all character codes in segment
11014 for ($k = 0; $k < $segCount; ++$k) {
11015 $idDelta[$k] = $this->_getUSHORT($font, $offset);
11016 $offset += 2;
11017 }
11018 $idRangeOffset = array(); // Offsets into glyphIdArray or 0
11019 for ($k = 0; $k < $segCount; ++$k) {
11020 $idRangeOffset[$k] = $this->_getUSHORT($font, $offset);
11021 $offset += 2;
11022 }
11023 $gidlen = ($length / 2) - 8 - (4 * $segCount);
11024 $glyphIdArray = array(); // glyph index array
11025 for ($k = 0; $k < $gidlen; ++$k) {
11026 $glyphIdArray[$k] = $this->_getUSHORT($font, $offset);
11027 $offset += 2;
11028 }
11029 for ($k = 0; $k < $segCount; ++$k) {
11030 for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
11031 if (isset($subsetchars[$c])) {
11032 if ($idRangeOffset[$k] == 0) {
11033 $g = ($idDelta[$k] + $c) % 65536;
11034 } else {
11035 $gid = (($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
11036 $g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
11037 }
11038 if ($g < 0) {
11039 $g = 0;
11040 }
11041 $subsetglyphs[$g] = true;
11042 }
11043 }
11044 }
11045 break;
11046 }
11047 case 6: { // Format 6: Trimmed table mapping
11048 $offset += 4; // skip length and version/language
11049 $firstCode = $this->_getUSHORT($font, $offset);
11050 $offset += 2;
11051 $entryCount = $this->_getUSHORT($font, $offset);
11052 $offset += 2;
11053 for ($k = 0; $k < $entryCount; ++$k) {
11054 $c = ($k + $firstCode);
11055 if (isset($subsetchars[$c])) {
11056 $g = $this->_getUSHORT($font, $offset);
11057 $subsetglyphs[$g] = true;
11058 }
11059 $offset += 2;
11060 }
11061 break;
11062 }
11063 case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
11064 $offset += 10; // skip reserved, length and version/language
11065 for ($k = 0; $k < 8192; ++$k) {
11066 $is32[$k] = $this->_getBYTE($font, $offset);
11067 ++$offset;
11068 }
11069 $nGroups = $this->_getULONG($font, $offset);
11070 $offset += 4;
11071 for ($i = 0; $i < $nGroups; ++$i) {
11072 $startCharCode = $this->_getULONG($font, $offset);
11073 $offset += 4;
11074 $endCharCode = $this->_getULONG($font, $offset);
11075 $offset += 4;
11076 $startGlyphID = $this->_getULONG($font, $offset);
11077 $offset += 4;
11078 for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
11079 $is32idx = floor($c / 8);
11080 if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) {
11081 $c = $k;
11082 } else {
11083 // 32 bit format
11084 // convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
11085 //LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
11086 //SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
11087 $c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888;
11088 }
11089 if (isset($subsetchars[$c])) {
11090 $subsetglyphs[$startGlyphID] = true;
11091 }
11092 ++$startGlyphID;
11093 }
11094 }
11095 break;
11096 }
11097 case 10: { // Format 10: Trimmed array
11098 $offset += 10; // skip reserved, length and version/language
11099 $startCharCode = $this->_getULONG($font, $offset);
11100 $offset += 4;
11101 $numChars = $this->_getULONG($font, $offset);
11102 $offset += 4;
11103 for ($k = 0; $k < $numChars; ++$k) {
11104 $c = ($k + $startCharCode);
11105 if (isset($subsetchars[$c])) {
11106 $g = $this->_getUSHORT($font, $offset);
11107 $subsetglyphs[$g] = true;
11108 }
11109 $offset += 2;
11110 }
11111 break;
11112 }
11113 case 12: { // Format 12: Segmented coverage
11114 $offset += 10; // skip length and version/language
11115 $nGroups = $this->_getULONG($font, $offset);
11116 $offset += 4;
11117 for ($k = 0; $k < $nGroups; ++$k) {
11118 $startCharCode = $this->_getULONG($font, $offset);
11119 $offset += 4;
11120 $endCharCode = $this->_getULONG($font, $offset);
11121 $offset += 4;
11122 $startGlyphCode = $this->_getULONG($font, $offset);
11123 $offset += 4;
11124 for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
11125 if (isset($subsetchars[$c])) {
11126 $subsetglyphs[$startGlyphCode] = true;
11127 }
11128 ++$startGlyphCode;
11129 }
11130 }
11131 break;
11132 }
11133 case 13: { // Format 13: Many-to-one range mappings
11134 // to be implemented ...
11135 break;
11136 }
11137 case 14: { // Format 14: Unicode Variation Sequences
11138 // to be implemented ...
11139 break;
11140 }
11141 }
11142 }
11143 // include all parts of composite glyphs
11144 $new_sga = $subsetglyphs;
11145 while (!empty($new_sga)) {
11146 $sga = $new_sga;
11147 $new_sga = array();
11148 foreach ($sga as $key => $val) {
11149 if (isset($indexToLoc[$key])) {
11150 $offset = ($table['glyf']['offset'] + $indexToLoc[$key]);
11151 $numberOfContours = $this->_getSHORT($font, $offset);
11152 $offset += 2;
11153 if ($numberOfContours < 0) { // composite glyph
11154 $offset += 8; // skip xMin, yMin, xMax, yMax
11155 do {
11156 $flags = $this->_getUSHORT($font, $offset);
11157 $offset += 2;
11158 $glyphIndex = $this->_getUSHORT($font, $offset);
11159 $offset += 2;
11160 if (!isset($subsetglyphs[$glyphIndex])) {
11161 // add missing glyphs
11162 $new_sga[$glyphIndex] = true;
11163 }
11164 // skip some bytes by case
11165 if ($flags & 1) {
11166 $offset += 4;
11167 } else {
11168 $offset += 2;
11169 }
11170 if ($flags & 8) {
11171 $offset += 2;
11172 } elseif ($flags & 64) {
11173 $offset += 4;
11174 } elseif ($flags & 128) {
11175 $offset += 8;
11176 }
11177 } while ($flags & 32);
11178 }
11179 }
11180 }
11181 $subsetglyphs += $new_sga;
11182 }
11183 // sort glyphs by key (and remove duplicates)
11184 ksort($subsetglyphs);
11185 // build new glyf and loca tables
11186 $glyf = '';
11187 $loca = '';
11188 $offset = 0;
11189 $glyf_offset = $table['glyf']['offset'];
11190 for ($i = 0; $i < $tot_num_glyphs; ++$i) {
11191 if (isset($subsetglyphs[$i])) {
11192 $length = ($indexToLoc[($i + 1)] - $indexToLoc[$i]);
11193 $glyf .= substr($font, ($glyf_offset + $indexToLoc[$i]), $length);
11194 } else {
11195 $length = 0;
11196 }
11197 if ($short_offset) {
11198 $loca .= pack('n', ($offset / 2));
11199 } else {
11200 $loca .= pack('N', $offset);
11201 }
11202 $offset += $length;
11203 }
11204 // array of table names to preserve (loca and glyf tables will be added later)
11205 // the cmap table is not needed and shall not be present, since the mapping from character codes to glyph descriptions is provided separately
11206 $table_names = array ('head', 'hhea', 'hmtx', 'maxp', 'cvt ', 'fpgm', 'prep'); // minimum required table names
11207 // get the tables to preserve
11208 $offset = 12;
11209 foreach ($table as $tag => $val) {
11210 if (in_array($tag, $table_names)) {
11211 $table[$tag]['data'] = substr($font, $table[$tag]['offset'], $table[$tag]['length']);
11212 if ($tag == 'head') {
11213 // set the checkSumAdjustment to 0
11214 $table[$tag]['data'] = substr($table[$tag]['data'], 0, 8)."\x0\x0\x0\x0".substr($table[$tag]['data'], 12);
11215 }
11216 $pad = 4 - ($table[$tag]['length'] % 4);
11217 if ($pad != 4) {
11218 // the length of a table must be a multiple of four bytes
11219 $table[$tag]['length'] += $pad;
11220 $table[$tag]['data'] .= str_repeat("\x0", $pad);
11221 }
11222 $table[$tag]['offset'] = $offset;
11223 $offset += $table[$tag]['length'];
11224 // check sum is not changed (so keep the following line commented)
11225 //$table[$tag]['checkSum'] = $this->_getTTFtableChecksum($table[$tag]['data'], $table[$tag]['length']);
11226 } else {
11227 unset($table[$tag]);
11228 }
11229 }
11230 // add loca
11231 $table['loca']['data'] = $loca;
11232 $table['loca']['length'] = strlen($loca);
11233 $pad = 4 - ($table['loca']['length'] % 4);
11234 if ($pad != 4) {
11235 // the length of a table must be a multiple of four bytes
11236 $table['loca']['length'] += $pad;
11237 $table['loca']['data'] .= str_repeat("\x0", $pad);
11238 }
11239 $table['loca']['offset'] = $offset;
11240 $table['loca']['checkSum'] = $this->_getTTFtableChecksum($table['loca']['data'], $table['loca']['length']);
11241 $offset += $table['loca']['length'];
11242 // add glyf
11243 $table['glyf']['data'] = $glyf;
11244 $table['glyf']['length'] = strlen($glyf);
11245 $pad = 4 - ($table['glyf']['length'] % 4);
11246 if ($pad != 4) {
11247 // the length of a table must be a multiple of four bytes
11248 $table['glyf']['length'] += $pad;
11249 $table['glyf']['data'] .= str_repeat("\x0", $pad);
11250 }
11251 $table['glyf']['offset'] = $offset;
11252 $table['glyf']['checkSum'] = $this->_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length']);
11253 // rebuild font
11254 $font = '';
11255 $font .= pack('N', 0x10000); // sfnt version
11256 $numTables = count($table);
11257 $font .= pack('n', $numTables); // numTables
11258 $entrySelector = floor(log($numTables, 2));
11259 $searchRange = pow(2, $entrySelector) * 16;
11260 $rangeShift = ($numTables * 16) - $searchRange;
11261 $font .= pack('n', $searchRange); // searchRange
11262 $font .= pack('n', $entrySelector); // entrySelector
11263 $font .= pack('n', $rangeShift); // rangeShift
11264 $offset = ($numTables * 16);
11265 foreach ($table as $tag => $data) {
11266 $font .= $tag; // tag
11267 $font .= pack('N', $data['checkSum']); // checkSum
11268 $font .= pack('N', ($data['offset'] + $offset)); // offset
11269 $font .= pack('N', $data['length']); // length
11270 }
11271 foreach ($table as $data) {
11272 $font .= $data['data'];
11273 }
11274 // set checkSumAdjustment on head table
11275 $checkSumAdjustment = 0xB1B0AFBA - $this->_getTTFtableChecksum($font, strlen($font));
11276 $font = substr($font, 0, $table['head']['offset'] + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + 12);
11277 return $font;
11278 }
11279
11280 /**
11281 * Returs the checksum of a TTF table.
11282 * @param $table (string) table to check
11283 * @param $length (int) length of table in bytes
11284 * @return int checksum
11285 * @author Nicola Asuni
11286 * @protected
11287 * @since 5.2.000 (2010-06-02)
11288 */
11289 protected function _getTTFtableChecksum($table, $length) {
11290 $sum = 0;
11291 $tlen = ($length / 4);
11292 $offset = 0;
11293 for ($i = 0; $i < $tlen; ++$i) {
11294 $v = unpack('Ni', substr($table, $offset, 4));
11295 $sum += $v['i'];
11296 $offset += 4;
11297 }
11298 $sum = unpack('Ni', pack('N', $sum));
11299 return $sum['i'];
11300 }
11301
11302 /**
11303 * Outputs font widths
11304 * @param $font (array) font data
11305 * @param $cidoffset (int) offset for CID values
11306 * @return PDF command string for font widths
11307 * @author Nicola Asuni
11308 * @protected
11309 * @since 4.4.000 (2008-12-07)
11310 */
11311 protected function _putfontwidths($font, $cidoffset=0) {
11312 ksort($font['cw']);
11313 $rangeid = 0;
11314 $range = array();
11315 $prevcid = -2;
11316 $prevwidth = -1;
11317 $interval = false;
11318 // for each character
11319 foreach ($font['cw'] as $cid => $width) {
11320 $cid -= $cidoffset;
11321 if ($font['subset'] AND ($cid > 255) AND (!isset($font['subsetchars'][$cid]))) {
11322 // ignore the unused characters (font subsetting)
11323 continue;
11324 }
11325 if ($width != $font['dw']) {
11326 if ($cid == ($prevcid + 1)) {
11327 // consecutive CID
11328 if ($width == $prevwidth) {
11329 if ($width == $range[$rangeid][0]) {
11330 $range[$rangeid][] = $width;
11331 } else {
11332 array_pop($range[$rangeid]);
11333 // new range
11334 $rangeid = $prevcid;
11335 $range[$rangeid] = array();
11336 $range[$rangeid][] = $prevwidth;
11337 $range[$rangeid][] = $width;
11338 }
11339 $interval = true;
11340 $range[$rangeid]['interval'] = true;
11341 } else {
11342 if ($interval) {
11343 // new range
11344 $rangeid = $cid;
11345 $range[$rangeid] = array();
11346 $range[$rangeid][] = $width;
11347 } else {
11348 $range[$rangeid][] = $width;
11349 }
11350 $interval = false;
11351 }
11352 } else {
11353 // new range
11354 $rangeid = $cid;
11355 $range[$rangeid] = array();
11356 $range[$rangeid][] = $width;
11357 $interval = false;
11358 }
11359 $prevcid = $cid;
11360 $prevwidth = $width;
11361 }
11362 }
11363 // optimize ranges
11364 $prevk = -1;
11365 $nextk = -1;
11366 $prevint = false;
11367 foreach ($range as $k => $ws) {
11368 $cws = count($ws);
11369 if (($k == $nextk) AND (!$prevint) AND ((!isset($ws['interval'])) OR ($cws < 4))) {
11370 if (isset($range[$k]['interval'])) {
11371 unset($range[$k]['interval']);
11372 }
11373 $range[$prevk] = array_merge($range[$prevk], $range[$k]);
11374 unset($range[$k]);
11375 } else {
11376 $prevk = $k;
11377 }
11378 $nextk = $k + $cws;
11379 if (isset($ws['interval'])) {
11380 if ($cws > 3) {
11381 $prevint = true;
11382 } else {
11383 $prevint = false;
11384 }
11385 unset($range[$k]['interval']);
11386 --$nextk;
11387 } else {
11388 $prevint = false;
11389 }
11390 }
11391 // output data
11392 $w = '';
11393 foreach ($range as $k => $ws) {
11394 if (count(array_count_values($ws)) == 1) {
11395 // interval mode is more compact
11396 $w .= ' '.$k.' '.($k + count($ws) - 1).' '.$ws[0];
11397 } else {
11398 // range mode
11399 $w .= ' '.$k.' [ '.implode(' ', $ws).' ]';
11400 }
11401 }
11402 return '/W ['.$w.' ]';
11403 }
11404
11405 /**
11406 * Output fonts.
11407 * @author Nicola Asuni
11408 * @protected
11409 */
11410 protected function _putfonts() {
11411 $nf = $this->n;
11412 foreach ($this->diffs as $diff) {
11413 //Encodings
11414 $this->_newobj();
11415 $this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
11416 }
11417 $mqr = $this->get_mqr();
11418 $this->set_mqr(false);
11419 foreach ($this->FontFiles as $file => $info) {
11420 // search and get font file to embedd
11421 $fontdir = $info['fontdir'];
11422 $file = strtolower($file);
11423 $fontfile = '';
11424 // search files on various directories
11425 if (($fontdir !== false) AND file_exists($fontdir.$file)) {
11426 $fontfile = $fontdir.$file;
11427 } elseif (file_exists($this->_getfontpath().$file)) {
11428 $fontfile = $this->_getfontpath().$file;
11429 } elseif (file_exists($file)) {
11430 $fontfile = $file;
11431 }
11432 if (!$this->empty_string($fontfile)) {
11433 $font = file_get_contents($fontfile);
11434 $compressed = (substr($file, -2) == '.z');
11435 if ((!$compressed) AND (isset($info['length2']))) {
11436 $header = (ord($font{0}) == 128);
11437 if ($header) {
11438 // strip first binary header
11439 $font = substr($font, 6);
11440 }
11441 if ($header AND (ord($font[$info['length1']]) == 128)) {
11442 // strip second binary header
11443 $font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
11444 }
11445 } elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
11446 if ($compressed) {
11447 // uncompress font
11448 $font = gzuncompress($font);
11449 }
11450 // merge subset characters
11451 $subsetchars = array(); // used chars
11452 foreach ($info['fontkeys'] as $fontkey) {
11453 $fontinfo = $this->getFontBuffer($fontkey);
11454 $subsetchars += $fontinfo['subsetchars'];
11455 }
11456 // rebuild a font subset
11457 $font = $this->_getTrueTypeFontSubset($font, $subsetchars);
11458 // calculate new font length
11459 $info['length1'] = strlen($font);
11460 if ($compressed) {
11461 // recompress font
11462 $font = gzcompress($font);
11463 }
11464 }
11465 $this->_newobj();
11466 $this->FontFiles[$file]['n'] = $this->n;
11467 $stream = $this->_getrawstream($font);
11468 $out = '<< /Length '.strlen($stream);
11469 if ($compressed) {
11470 $out .= ' /Filter /FlateDecode';
11471 }
11472 $out .= ' /Length1 '.$info['length1'];
11473 if (isset($info['length2'])) {
11474 $out .= ' /Length2 '.$info['length2'].' /Length3 0';
11475 }
11476 $out .= ' >>';
11477 $out .= ' stream'."\n".$stream."\n".'endstream';
11478 $out .= "\n".'endobj';
11479 $this->_out($out);
11480 }
11481 }
11482 $this->set_mqr($mqr);
11483 foreach ($this->fontkeys as $k) {
11484 //Font objects
11485 $font = $this->getFontBuffer($k);
11486 $type = $font['type'];
11487 $name = $font['name'];
11488 if ($type == 'core') {
11489 // standard core font
11490 $out = $this->_getobj($this->font_obj_ids[$k])."\n";
11491 $out .= '<</Type /Font';
11492 $out .= ' /Subtype /Type1';
11493 $out .= ' /BaseFont /'.$name;
11494 $out .= ' /Name /F'.$font['i'];
11495 if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
11496 $out .= ' /Encoding /WinAnsiEncoding';
11497 }
11498 if ($k == 'helvetica') {
11499 // add default font for annotations
11500 $this->annotation_fonts[$k] = $font['i'];
11501 }
11502 $out .= ' >>';
11503 $out .= "\n".'endobj';
11504 $this->_out($out);
11505 } elseif (($type == 'Type1') OR ($type == 'TrueType')) {
11506 // additional Type1 or TrueType font
11507 $out = $this->_getobj($this->font_obj_ids[$k])."\n";
11508 $out .= '<</Type /Font';
11509 $out .= ' /Subtype /'.$type;
11510 $out .= ' /BaseFont /'.$name;
11511 $out .= ' /Name /F'.$font['i'];
11512 $out .= ' /FirstChar 32 /LastChar 255';
11513 $out .= ' /Widths '.($this->n + 1).' 0 R';
11514 $out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
11515 if ($font['enc']) {
11516 if (isset($font['diff'])) {
11517 $out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
11518 } else {
11519 $out .= ' /Encoding /WinAnsiEncoding';
11520 }
11521 }
11522 $out .= ' >>';
11523 $out .= "\n".'endobj';
11524 $this->_out($out);
11525 // Widths
11526 $this->_newobj();
11527 $s = '[';
11528 for ($i = 32; $i < 256; ++$i) {
11529 $s .= $font['cw'][$i].' ';
11530 }
11531 $s .= ']';
11532 $s .= "\n".'endobj';
11533 $this->_out($s);
11534 //Descriptor
11535 $this->_newobj();
11536 $s = '<</Type /FontDescriptor /FontName /'.$name;
11537 foreach ($font['desc'] as $fdk => $fdv) {
11538 if (is_float($fdv)) {
11539 $fdv = sprintf('%F', $fdv);
11540 }
11541 $s .= ' /'.$fdk.' '.$fdv.'';
11542 }
11543 if (!$this->empty_string($font['file'])) {
11544 $s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
11545 }
11546 $s .= '>>';
11547 $s .= "\n".'endobj';
11548 $this->_out($s);
11549 } else {
11550 // additional types
11551 $mtd = '_put'.strtolower($type);
11552 if (!method_exists($this, $mtd)) {
11553 $this->Error('Unsupported font type: '.$type);
11554 }
11555 $this->$mtd($font);
11556 }
11557 }
11558 }
11559
11560 /**
11561 * Adds unicode fonts.<br>
11562 * Based on PDF Reference 1.3 (section 5)
11563 * @param $font (array) font data
11564 * @protected
11565 * @author Nicola Asuni
11566 * @since 1.52.0.TC005 (2005-01-05)
11567 */
11568 protected function _puttruetypeunicode($font) {
11569 $fontname = '';
11570 if ($font['subset']) {
11571 // change name for font subsetting
11572 $subtag = sprintf('%06u', $font['i']);
11573 $subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
11574 $fontname .= $subtag.'+';
11575 }
11576 $fontname .= $font['name'];
11577 // Type0 Font
11578 // A composite font composed of other fonts, organized hierarchically
11579 $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
11580 $out .= '<< /Type /Font';
11581 $out .= ' /Subtype /Type0';
11582 $out .= ' /BaseFont /'.$fontname;
11583 $out .= ' /Name /F'.$font['i'];
11584 $out .= ' /Encoding /'.$font['enc'];
11585 $out .= ' /ToUnicode '.($this->n + 1).' 0 R';
11586 $out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
11587 $out .= ' >>';
11588 $out .= "\n".'endobj';
11589 $this->_out($out);
11590 // ToUnicode map for Identity-H
11591 $stream = "/CIDInit /ProcSet findresource begin\n";
11592 $stream .= "12 dict begin\n";
11593 $stream .= "begincmap\n";
11594 $stream .= "/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n";
11595 $stream .= "/CMapName /Adobe-Identity-UCS def\n";
11596 $stream .= "/CMapType 2 def\n";
11597 $stream .= "/WMode 0 def\n";
11598 $stream .= "1 begincodespacerange\n";
11599 $stream .= "<0000> <FFFF>\n";
11600 $stream .= "endcodespacerange\n";
11601 $stream .= "100 beginbfrange\n";
11602 $stream .= "<0000> <00ff> <0000>\n";
11603 $stream .= "<0100> <01ff> <0100>\n";
11604 $stream .= "<0200> <02ff> <0200>\n";
11605 $stream .= "<0300> <03ff> <0300>\n";
11606 $stream .= "<0400> <04ff> <0400>\n";
11607 $stream .= "<0500> <05ff> <0500>\n";
11608 $stream .= "<0600> <06ff> <0600>\n";
11609 $stream .= "<0700> <07ff> <0700>\n";
11610 $stream .= "<0800> <08ff> <0800>\n";
11611 $stream .= "<0900> <09ff> <0900>\n";
11612 $stream .= "<0a00> <0aff> <0a00>\n";
11613 $stream .= "<0b00> <0bff> <0b00>\n";
11614 $stream .= "<0c00> <0cff> <0c00>\n";
11615 $stream .= "<0d00> <0dff> <0d00>\n";
11616 $stream .= "<0e00> <0eff> <0e00>\n";
11617 $stream .= "<0f00> <0fff> <0f00>\n";
11618 $stream .= "<1000> <10ff> <1000>\n";
11619 $stream .= "<1100> <11ff> <1100>\n";
11620 $stream .= "<1200> <12ff> <1200>\n";
11621 $stream .= "<1300> <13ff> <1300>\n";
11622 $stream .= "<1400> <14ff> <1400>\n";
11623 $stream .= "<1500> <15ff> <1500>\n";
11624 $stream .= "<1600> <16ff> <1600>\n";
11625 $stream .= "<1700> <17ff> <1700>\n";
11626 $stream .= "<1800> <18ff> <1800>\n";
11627 $stream .= "<1900> <19ff> <1900>\n";
11628 $stream .= "<1a00> <1aff> <1a00>\n";
11629 $stream .= "<1b00> <1bff> <1b00>\n";
11630 $stream .= "<1c00> <1cff> <1c00>\n";
11631 $stream .= "<1d00> <1dff> <1d00>\n";
11632 $stream .= "<1e00> <1eff> <1e00>\n";
11633 $stream .= "<1f00> <1fff> <1f00>\n";
11634 $stream .= "<2000> <20ff> <2000>\n";
11635 $stream .= "<2100> <21ff> <2100>\n";
11636 $stream .= "<2200> <22ff> <2200>\n";
11637 $stream .= "<2300> <23ff> <2300>\n";
11638 $stream .= "<2400> <24ff> <2400>\n";
11639 $stream .= "<2500> <25ff> <2500>\n";
11640 $stream .= "<2600> <26ff> <2600>\n";
11641 $stream .= "<2700> <27ff> <2700>\n";
11642 $stream .= "<2800> <28ff> <2800>\n";
11643 $stream .= "<2900> <29ff> <2900>\n";
11644 $stream .= "<2a00> <2aff> <2a00>\n";
11645 $stream .= "<2b00> <2bff> <2b00>\n";
11646 $stream .= "<2c00> <2cff> <2c00>\n";
11647 $stream .= "<2d00> <2dff> <2d00>\n";
11648 $stream .= "<2e00> <2eff> <2e00>\n";
11649 $stream .= "<2f00> <2fff> <2f00>\n";
11650 $stream .= "<3000> <30ff> <3000>\n";
11651 $stream .= "<3100> <31ff> <3100>\n";
11652 $stream .= "<3200> <32ff> <3200>\n";
11653 $stream .= "<3300> <33ff> <3300>\n";
11654 $stream .= "<3400> <34ff> <3400>\n";
11655 $stream .= "<3500> <35ff> <3500>\n";
11656 $stream .= "<3600> <36ff> <3600>\n";
11657 $stream .= "<3700> <37ff> <3700>\n";
11658 $stream .= "<3800> <38ff> <3800>\n";
11659 $stream .= "<3900> <39ff> <3900>\n";
11660 $stream .= "<3a00> <3aff> <3a00>\n";
11661 $stream .= "<3b00> <3bff> <3b00>\n";
11662 $stream .= "<3c00> <3cff> <3c00>\n";
11663 $stream .= "<3d00> <3dff> <3d00>\n";
11664 $stream .= "<3e00> <3eff> <3e00>\n";
11665 $stream .= "<3f00> <3fff> <3f00>\n";
11666 $stream .= "<4000> <40ff> <4000>\n";
11667 $stream .= "<4100> <41ff> <4100>\n";
11668 $stream .= "<4200> <42ff> <4200>\n";
11669 $stream .= "<4300> <43ff> <4300>\n";
11670 $stream .= "<4400> <44ff> <4400>\n";
11671 $stream .= "<4500> <45ff> <4500>\n";
11672 $stream .= "<4600> <46ff> <4600>\n";
11673 $stream .= "<4700> <47ff> <4700>\n";
11674 $stream .= "<4800> <48ff> <4800>\n";
11675 $stream .= "<4900> <49ff> <4900>\n";
11676 $stream .= "<4a00> <4aff> <4a00>\n";
11677 $stream .= "<4b00> <4bff> <4b00>\n";
11678 $stream .= "<4c00> <4cff> <4c00>\n";
11679 $stream .= "<4d00> <4dff> <4d00>\n";
11680 $stream .= "<4e00> <4eff> <4e00>\n";
11681 $stream .= "<4f00> <4fff> <4f00>\n";
11682 $stream .= "<5000> <50ff> <5000>\n";
11683 $stream .= "<5100> <51ff> <5100>\n";
11684 $stream .= "<5200> <52ff> <5200>\n";
11685 $stream .= "<5300> <53ff> <5300>\n";
11686 $stream .= "<5400> <54ff> <5400>\n";
11687 $stream .= "<5500> <55ff> <5500>\n";
11688 $stream .= "<5600> <56ff> <5600>\n";
11689 $stream .= "<5700> <57ff> <5700>\n";
11690 $stream .= "<5800> <58ff> <5800>\n";
11691 $stream .= "<5900> <59ff> <5900>\n";
11692 $stream .= "<5a00> <5aff> <5a00>\n";
11693 $stream .= "<5b00> <5bff> <5b00>\n";
11694 $stream .= "<5c00> <5cff> <5c00>\n";
11695 $stream .= "<5d00> <5dff> <5d00>\n";
11696 $stream .= "<5e00> <5eff> <5e00>\n";
11697 $stream .= "<5f00> <5fff> <5f00>\n";
11698 $stream .= "<6000> <60ff> <6000>\n";
11699 $stream .= "<6100> <61ff> <6100>\n";
11700 $stream .= "<6200> <62ff> <6200>\n";
11701 $stream .= "<6300> <63ff> <6300>\n";
11702 $stream .= "endbfrange\n";
11703 $stream .= "100 beginbfrange\n";
11704 $stream .= "<6400> <64ff> <6400>\n";
11705 $stream .= "<6500> <65ff> <6500>\n";
11706 $stream .= "<6600> <66ff> <6600>\n";
11707 $stream .= "<6700> <67ff> <6700>\n";
11708 $stream .= "<6800> <68ff> <6800>\n";
11709 $stream .= "<6900> <69ff> <6900>\n";
11710 $stream .= "<6a00> <6aff> <6a00>\n";
11711 $stream .= "<6b00> <6bff> <6b00>\n";
11712 $stream .= "<6c00> <6cff> <6c00>\n";
11713 $stream .= "<6d00> <6dff> <6d00>\n";
11714 $stream .= "<6e00> <6eff> <6e00>\n";
11715 $stream .= "<6f00> <6fff> <6f00>\n";
11716 $stream .= "<7000> <70ff> <7000>\n";
11717 $stream .= "<7100> <71ff> <7100>\n";
11718 $stream .= "<7200> <72ff> <7200>\n";
11719 $stream .= "<7300> <73ff> <7300>\n";
11720 $stream .= "<7400> <74ff> <7400>\n";
11721 $stream .= "<7500> <75ff> <7500>\n";
11722 $stream .= "<7600> <76ff> <7600>\n";
11723 $stream .= "<7700> <77ff> <7700>\n";
11724 $stream .= "<7800> <78ff> <7800>\n";
11725 $stream .= "<7900> <79ff> <7900>\n";
11726 $stream .= "<7a00> <7aff> <7a00>\n";
11727 $stream .= "<7b00> <7bff> <7b00>\n";
11728 $stream .= "<7c00> <7cff> <7c00>\n";
11729 $stream .= "<7d00> <7dff> <7d00>\n";
11730 $stream .= "<7e00> <7eff> <7e00>\n";
11731 $stream .= "<7f00> <7fff> <7f00>\n";
11732 $stream .= "<8000> <80ff> <8000>\n";
11733 $stream .= "<8100> <81ff> <8100>\n";
11734 $stream .= "<8200> <82ff> <8200>\n";
11735 $stream .= "<8300> <83ff> <8300>\n";
11736 $stream .= "<8400> <84ff> <8400>\n";
11737 $stream .= "<8500> <85ff> <8500>\n";
11738 $stream .= "<8600> <86ff> <8600>\n";
11739 $stream .= "<8700> <87ff> <8700>\n";
11740 $stream .= "<8800> <88ff> <8800>\n";
11741 $stream .= "<8900> <89ff> <8900>\n";
11742 $stream .= "<8a00> <8aff> <8a00>\n";
11743 $stream .= "<8b00> <8bff> <8b00>\n";
11744 $stream .= "<8c00> <8cff> <8c00>\n";
11745 $stream .= "<8d00> <8dff> <8d00>\n";
11746 $stream .= "<8e00> <8eff> <8e00>\n";
11747 $stream .= "<8f00> <8fff> <8f00>\n";
11748 $stream .= "<9000> <90ff> <9000>\n";
11749 $stream .= "<9100> <91ff> <9100>\n";
11750 $stream .= "<9200> <92ff> <9200>\n";
11751 $stream .= "<9300> <93ff> <9300>\n";
11752 $stream .= "<9400> <94ff> <9400>\n";
11753 $stream .= "<9500> <95ff> <9500>\n";
11754 $stream .= "<9600> <96ff> <9600>\n";
11755 $stream .= "<9700> <97ff> <9700>\n";
11756 $stream .= "<9800> <98ff> <9800>\n";
11757 $stream .= "<9900> <99ff> <9900>\n";
11758 $stream .= "<9a00> <9aff> <9a00>\n";
11759 $stream .= "<9b00> <9bff> <9b00>\n";
11760 $stream .= "<9c00> <9cff> <9c00>\n";
11761 $stream .= "<9d00> <9dff> <9d00>\n";
11762 $stream .= "<9e00> <9eff> <9e00>\n";
11763 $stream .= "<9f00> <9fff> <9f00>\n";
11764 $stream .= "<a000> <a0ff> <a000>\n";
11765 $stream .= "<a100> <a1ff> <a100>\n";
11766 $stream .= "<a200> <a2ff> <a200>\n";
11767 $stream .= "<a300> <a3ff> <a300>\n";
11768 $stream .= "<a400> <a4ff> <a400>\n";
11769 $stream .= "<a500> <a5ff> <a500>\n";
11770 $stream .= "<a600> <a6ff> <a600>\n";
11771 $stream .= "<a700> <a7ff> <a700>\n";
11772 $stream .= "<a800> <a8ff> <a800>\n";
11773 $stream .= "<a900> <a9ff> <a900>\n";
11774 $stream .= "<aa00> <aaff> <aa00>\n";
11775 $stream .= "<ab00> <abff> <ab00>\n";
11776 $stream .= "<ac00> <acff> <ac00>\n";
11777 $stream .= "<ad00> <adff> <ad00>\n";
11778 $stream .= "<ae00> <aeff> <ae00>\n";
11779 $stream .= "<af00> <afff> <af00>\n";
11780 $stream .= "<b000> <b0ff> <b000>\n";
11781 $stream .= "<b100> <b1ff> <b100>\n";
11782 $stream .= "<b200> <b2ff> <b200>\n";
11783 $stream .= "<b300> <b3ff> <b300>\n";
11784 $stream .= "<b400> <b4ff> <b400>\n";
11785 $stream .= "<b500> <b5ff> <b500>\n";
11786 $stream .= "<b600> <b6ff> <b600>\n";
11787 $stream .= "<b700> <b7ff> <b700>\n";
11788 $stream .= "<b800> <b8ff> <b800>\n";
11789 $stream .= "<b900> <b9ff> <b900>\n";
11790 $stream .= "<ba00> <baff> <ba00>\n";
11791 $stream .= "<bb00> <bbff> <bb00>\n";
11792 $stream .= "<bc00> <bcff> <bc00>\n";
11793 $stream .= "<bd00> <bdff> <bd00>\n";
11794 $stream .= "<be00> <beff> <be00>\n";
11795 $stream .= "<bf00> <bfff> <bf00>\n";
11796 $stream .= "<c000> <c0ff> <c000>\n";
11797 $stream .= "<c100> <c1ff> <c100>\n";
11798 $stream .= "<c200> <c2ff> <c200>\n";
11799 $stream .= "<c300> <c3ff> <c300>\n";
11800 $stream .= "<c400> <c4ff> <c400>\n";
11801 $stream .= "<c500> <c5ff> <c500>\n";
11802 $stream .= "<c600> <c6ff> <c600>\n";
11803 $stream .= "<c700> <c7ff> <c700>\n";
11804 $stream .= "endbfrange\n";
11805 $stream .= "56 beginbfrange\n";
11806 $stream .= "<c800> <c8ff> <c800>\n";
11807 $stream .= "<c900> <c9ff> <c900>\n";
11808 $stream .= "<ca00> <caff> <ca00>\n";
11809 $stream .= "<cb00> <cbff> <cb00>\n";
11810 $stream .= "<cc00> <ccff> <cc00>\n";
11811 $stream .= "<cd00> <cdff> <cd00>\n";
11812 $stream .= "<ce00> <ceff> <ce00>\n";
11813 $stream .= "<cf00> <cfff> <cf00>\n";
11814 $stream .= "<d000> <d0ff> <d000>\n";
11815 $stream .= "<d100> <d1ff> <d100>\n";
11816 $stream .= "<d200> <d2ff> <d200>\n";
11817 $stream .= "<d300> <d3ff> <d300>\n";
11818 $stream .= "<d400> <d4ff> <d400>\n";
11819 $stream .= "<d500> <d5ff> <d500>\n";
11820 $stream .= "<d600> <d6ff> <d600>\n";
11821 $stream .= "<d700> <d7ff> <d700>\n";
11822 $stream .= "<d800> <d8ff> <d800>\n";
11823 $stream .= "<d900> <d9ff> <d900>\n";
11824 $stream .= "<da00> <daff> <da00>\n";
11825 $stream .= "<db00> <dbff> <db00>\n";
11826 $stream .= "<dc00> <dcff> <dc00>\n";
11827 $stream .= "<dd00> <ddff> <dd00>\n";
11828 $stream .= "<de00> <deff> <de00>\n";
11829 $stream .= "<df00> <dfff> <df00>\n";
11830 $stream .= "<e000> <e0ff> <e000>\n";
11831 $stream .= "<e100> <e1ff> <e100>\n";
11832 $stream .= "<e200> <e2ff> <e200>\n";
11833 $stream .= "<e300> <e3ff> <e300>\n";
11834 $stream .= "<e400> <e4ff> <e400>\n";
11835 $stream .= "<e500> <e5ff> <e500>\n";
11836 $stream .= "<e600> <e6ff> <e600>\n";
11837 $stream .= "<e700> <e7ff> <e700>\n";
11838 $stream .= "<e800> <e8ff> <e800>\n";
11839 $stream .= "<e900> <e9ff> <e900>\n";
11840 $stream .= "<ea00> <eaff> <ea00>\n";
11841 $stream .= "<eb00> <ebff> <eb00>\n";
11842 $stream .= "<ec00> <ecff> <ec00>\n";
11843 $stream .= "<ed00> <edff> <ed00>\n";
11844 $stream .= "<ee00> <eeff> <ee00>\n";
11845 $stream .= "<ef00> <efff> <ef00>\n";
11846 $stream .= "<f000> <f0ff> <f000>\n";
11847 $stream .= "<f100> <f1ff> <f100>\n";
11848 $stream .= "<f200> <f2ff> <f200>\n";
11849 $stream .= "<f300> <f3ff> <f300>\n";
11850 $stream .= "<f400> <f4ff> <f400>\n";
11851 $stream .= "<f500> <f5ff> <f500>\n";
11852 $stream .= "<f600> <f6ff> <f600>\n";
11853 $stream .= "<f700> <f7ff> <f700>\n";
11854 $stream .= "<f800> <f8ff> <f800>\n";
11855 $stream .= "<f900> <f9ff> <f900>\n";
11856 $stream .= "<fa00> <faff> <fa00>\n";
11857 $stream .= "<fb00> <fbff> <fb00>\n";
11858 $stream .= "<fc00> <fcff> <fc00>\n";
11859 $stream .= "<fd00> <fdff> <fd00>\n";
11860 $stream .= "<fe00> <feff> <fe00>\n";
11861 $stream .= "<ff00> <ffff> <ff00>\n";
11862 $stream .= "endbfrange\n";
11863 $stream .= "endcmap\n";
11864 $stream .= "CMapName currentdict /CMap defineresource pop\n";
11865 $stream .= "end\n";
11866 $stream .= "end";
11867 // ToUnicode Object
11868 $this->_newobj();
11869 $stream = ($this->compress) ? gzcompress($stream) : $stream;
11870 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
11871 $stream = $this->_getrawstream($stream);
11872 $this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
11873 // CIDFontType2
11874 // A CIDFont whose glyph descriptions are based on TrueType font technology
11875 $oid = $this->_newobj();
11876 $out = '<< /Type /Font';
11877 $out .= ' /Subtype /CIDFontType2';
11878 $out .= ' /BaseFont /'.$fontname;
11879 // A dictionary containing entries that define the character collection of the CIDFont.
11880 $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
11881 $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
11882 $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
11883 $out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
11884 $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
11885 $out .= ' /DW '.$font['dw']; // default width
11886 $out .= "\n".$this->_putfontwidths($font, 0);
11887 if (isset($font['ctg']) AND (!$this->empty_string($font['ctg']))) {
11888 $out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
11889 }
11890 $out .= ' >>';
11891 $out .= "\n".'endobj';
11892 $this->_out($out);
11893 // Font descriptor
11894 // A font descriptor describing the CIDFont default metrics other than its glyph widths
11895 $this->_newobj();
11896 $out = '<< /Type /FontDescriptor';
11897 $out .= ' /FontName /'.$fontname;
11898 foreach ($font['desc'] as $key => $value) {
11899 if (is_float($value)) {
11900 $value = sprintf('%F', $value);
11901 }
11902 $out .= ' /'.$key.' '.$value;
11903 }
11904 $fontdir = false;
11905 if (!$this->empty_string($font['file'])) {
11906 // A stream containing a TrueType font
11907 $out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
11908 $fontdir = $this->FontFiles[$font['file']]['fontdir'];
11909 }
11910 $out .= ' >>';
11911 $out .= "\n".'endobj';
11912 $this->_out($out);
11913 if (isset($font['ctg']) AND (!$this->empty_string($font['ctg']))) {
11914 $this->_newobj();
11915 // Embed CIDToGIDMap
11916 // A specification of the mapping from CIDs to glyph indices
11917 // search and get CTG font file to embedd
11918 $ctgfile = strtolower($font['ctg']);
11919 // search and get ctg font file to embedd
11920 $fontfile = '';
11921 // search files on various directories
11922 if (($fontdir !== false) AND file_exists($fontdir.$ctgfile)) {
11923 $fontfile = $fontdir.$ctgfile;
11924 } elseif (file_exists($this->_getfontpath().$ctgfile)) {
11925 $fontfile = $this->_getfontpath().$ctgfile;
11926 } elseif (file_exists($ctgfile)) {
11927 $fontfile = $ctgfile;
11928 }
11929 if ($this->empty_string($fontfile)) {
11930 $this->Error('Font file not found: '.$ctgfile);
11931 }
11932 $stream = $this->_getrawstream(file_get_contents($fontfile));
11933 $out = '<< /Length '.strlen($stream).'';
11934 if (substr($fontfile, -2) == '.z') { // check file extension
11935 // Decompresses data encoded using the public-domain
11936 // zlib/deflate compression method, reproducing the
11937 // original text or binary data
11938 $out .= ' /Filter /FlateDecode';
11939 }
11940 $out .= ' >>';
11941 $out .= ' stream'."\n".$stream."\n".'endstream';
11942 $out .= "\n".'endobj';
11943 $this->_out($out);
11944 }
11945 }
11946
11947 /**
11948 * Output CID-0 fonts.
11949 * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
11950 * @param $font (array) font data
11951 * @protected
11952 * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
11953 * @since 3.2.000 (2008-06-23)
11954 */
11955 protected function _putcidfont0($font) {
11956 $cidoffset = 0;
11957 if (!isset($font['cw'][1])) {
11958 $cidoffset = 31;
11959 }
11960 if (isset($font['cidinfo']['uni2cid'])) {
11961 // convert unicode to cid.
11962 $uni2cid = $font['cidinfo']['uni2cid'];
11963 $cw = array();
11964 foreach ($font['cw'] as $uni => $width) {
11965 if (isset($uni2cid[$uni])) {
11966 $cw[($uni2cid[$uni] + $cidoffset)] = $width;
11967 } elseif ($uni < 256) {
11968 $cw[$uni] = $width;
11969 } // else unknown character
11970 }
11971 $font = array_merge($font, array('cw' => $cw));
11972 }
11973 $name = $font['name'];
11974 $enc = $font['enc'];
11975 if ($enc) {
11976 $longname = $name.'-'.$enc;
11977 } else {
11978 $longname = $name;
11979 }
11980 $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
11981 $out .= '<</Type /Font';
11982 $out .= ' /Subtype /Type0';
11983 $out .= ' /BaseFont /'.$longname;
11984 $out .= ' /Name /F'.$font['i'];
11985 if ($enc) {
11986 $out .= ' /Encoding /'.$enc;
11987 }
11988 $out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
11989 $out .= ' >>';
11990 $out .= "\n".'endobj';
11991 $this->_out($out);
11992 $oid = $this->_newobj();
11993 $out = '<</Type /Font';
11994 $out .= ' /Subtype /CIDFontType0';
11995 $out .= ' /BaseFont /'.$name;
11996 $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
11997 $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
11998 $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
11999 $out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
12000 $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
12001 $out .= ' /DW '.$font['dw'];
12002 $out .= "\n".$this->_putfontwidths($font, $cidoffset);
12003 $out .= ' >>';
12004 $out .= "\n".'endobj';
12005 $this->_out($out);
12006 $this->_newobj();
12007 $s = '<</Type /FontDescriptor /FontName /'.$name;
12008 foreach ($font['desc'] as $k => $v) {
12009 if ($k != 'Style') {
12010 if (is_float($v)) {
12011 $v = sprintf('%F', $v);
12012 }
12013 $s .= ' /'.$k.' '.$v.'';
12014 }
12015 }
12016 $s .= '>>';
12017 $s .= "\n".'endobj';
12018 $this->_out($s);
12019 }
12020
12021 /**
12022 * Output images.
12023 * @protected
12024 */
12025 protected function _putimages() {
12026 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
12027 foreach ($this->imagekeys as $file) {
12028 $info = $this->getImageBuffer($file);
12029 // set object for alternate images array
12030 if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
12031 $altoid = $this->_newobj();
12032 $out = '[';
12033 foreach ($info['altimgs'] as $altimage) {
12034 if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
12035 $out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
12036 $out .= ' /DefaultForPrinting';
12037 if ($altimage[1] === true) {
12038 $out .= ' true';
12039 } else {
12040 $out .= ' false';
12041 }
12042 $out .= ' >>';
12043 }
12044 }
12045 $out .= ' ]';
12046 $out .= "\n".'endobj';
12047 $this->_out($out);
12048 }
12049 // set image object
12050 $oid = $this->_newobj();
12051 $this->xobjects['I'.$info['i']] = array('n' => $oid);
12052 $this->setImageSubBuffer($file, 'n', $this->n);
12053 $out = '<</Type /XObject';
12054 $out .= ' /Subtype /Image';
12055 $out .= ' /Width '.$info['w'];
12056 $out .= ' /Height '.$info['h'];
12057 if (array_key_exists('masked', $info)) {
12058 $out .= ' /SMask '.($this->n - 1).' 0 R';
12059 }
12060 // set color space
12061 $icc = false;
12062 if (isset($info['icc']) AND ($info['icc'] !== false)) {
12063 // ICC Colour Space
12064 $icc = true;
12065 $out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
12066 } elseif ($info['cs'] == 'Indexed') {
12067 // Indexed Colour Space
12068 $out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
12069 } else {
12070 // Device Colour Space
12071 $out .= ' /ColorSpace /'.$info['cs'];
12072 }
12073 if ($info['cs'] == 'DeviceCMYK') {
12074 $out .= ' /Decode [1 0 1 0 1 0 1 0]';
12075 }
12076 $out .= ' /BitsPerComponent '.$info['bpc'];
12077 if (isset($altoid) AND ($altoid > 0)) {
12078 // reference to alternate images dictionary
12079 $out .= ' /Alternates '.$altoid.' 0 R';
12080 }
12081 if (isset($info['exurl']) AND !empty($info['exurl'])) {
12082 // external stream
12083 $out .= ' /Length 0';
12084 $out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
12085 if (isset($info['f'])) {
12086 $out .= ' /FFilter /'.$info['f'];
12087 }
12088 $out .= ' >>';
12089 $out .= ' stream'."\n".'endstream';
12090 } else {
12091 if (isset($info['f'])) {
12092 $out .= ' /Filter /'.$info['f'];
12093 }
12094 if (isset($info['parms'])) {
12095 $out .= ' '.$info['parms'];
12096 }
12097 if (isset($info['trns']) AND is_array($info['trns'])) {
12098 $trns = '';
12099 $count_info = count($info['trns']);
12100 for ($i=0; $i < $count_info; ++$i) {
12101 $trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
12102 }
12103 $out .= ' /Mask ['.$trns.']';
12104 }
12105 $stream = $this->_getrawstream($info['data']);
12106 $out .= ' /Length '.strlen($stream).' >>';
12107 $out .= ' stream'."\n".$stream."\n".'endstream';
12108 }
12109 $out .= "\n".'endobj';
12110 $this->_out($out);
12111 if ($icc) {
12112 // ICC colour profile
12113 $this->_newobj();
12114 $icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
12115 $icc = $this->_getrawstream($icc);
12116 $this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
12117 } elseif ($info['cs'] == 'Indexed') {
12118 // colour palette
12119 $this->_newobj();
12120 $pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
12121 $pal = $this->_getrawstream($pal);
12122 $this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
12123 }
12124 }
12125 }
12126
12127 /**
12128 * Output Form XObjects Templates.
12129 * @author Nicola Asuni
12130 * @since 5.8.017 (2010-08-24)
12131 * @protected
12132 * @see startTemplate(), endTemplate(), printTemplate()
12133 */
12134 protected function _putxobjects() {
12135 foreach ($this->xobjects as $key => $data) {
12136 if (isset($data['outdata'])) {
12137 $stream = trim($data['outdata']);
12138 $out = $this->_getobj($data['n'])."\n";
12139 $out .= '<<';
12140 $out .= ' /Type /XObject';
12141 $out .= ' /Subtype /Form';
12142 $out .= ' /FormType 1';
12143 if ($this->compress) {
12144 $stream = gzcompress($stream);
12145 $out .= ' /Filter /FlateDecode';
12146 }
12147 $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));
12148 $out .= ' /Matrix [1 0 0 1 0 0]';
12149 $out .= ' /Resources <<';
12150 $out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
12151 if (!$this->pdfa_mode) {
12152 // transparency
12153 if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
12154 $out .= ' /ExtGState <<';
12155 foreach ($data['extgstates'] as $k => $extgstate) {
12156 if (isset($this->extgstates[$k]['name'])) {
12157 $out .= ' /'.$this->extgstates[$k]['name'];
12158 } else {
12159 $out .= ' /GS'.$k;
12160 }
12161 $out .= ' '.$this->extgstates[$k]['n'].' 0 R';
12162 }
12163 $out .= ' >>';
12164 }
12165 if (isset($data['gradients']) AND !empty($data['gradients'])) {
12166 $gp = '';
12167 $gs = '';
12168 foreach ($data['gradients'] as $id => $grad) {
12169 // gradient patterns
12170 $gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
12171 // gradient shadings
12172 $gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
12173 }
12174 $out .= ' /Pattern <<'.$gp.' >>';
12175 $out .= ' /Shading <<'.$gs.' >>';
12176 }
12177 }
12178 // spot colors
12179 if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
12180 $out .= ' /ColorSpace <<';
12181 foreach ($data['spot_colors'] as $name => $color) {
12182 $out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
12183 }
12184 $out .= ' >>';
12185 }
12186 // fonts
12187 if (!empty($data['fonts'])) {
12188 $out .= ' /Font <<';
12189 foreach ($data['fonts'] as $fontkey => $fontid) {
12190 $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
12191 }
12192 $out .= ' >>';
12193 }
12194 // images or nested xobjects
12195 if (!empty($data['images']) OR !empty($data['xobjects'])) {
12196 $out .= ' /XObject <<';
12197 foreach ($data['images'] as $imgid) {
12198 $out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
12199 }
12200 foreach ($data['xobjects'] as $sub_id => $sub_objid) {
12201 $out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
12202 }
12203 $out .= ' >>';
12204 }
12205 $out .= ' >>'; //end resources
12206 if (isset($data['group']) AND ($data['group'] !== false)) {
12207 // set transparency group
12208 $out .= ' /Group << /Type /Group /S /Transparency';
12209 if (is_array($data['group'])) {
12210 if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
12211 $out .= ' /CS /'.$data['group']['CS'];
12212 }
12213 if (isset($data['group']['I'])) {
12214 $out .= ' /I /'.($data['group']['I']===true?'true':'false');
12215 }
12216 if (isset($data['group']['K'])) {
12217 $out .= ' /K /'.($data['group']['K']===true?'true':'false');
12218 }
12219 }
12220 $out .= ' >>';
12221 }
12222 $stream = $this->_getrawstream($stream, $data['n']);
12223 $out .= ' /Length '.strlen($stream);
12224 $out .= ' >>';
12225 $out .= ' stream'."\n".$stream."\n".'endstream';
12226 $out .= "\n".'endobj';
12227 $this->_out($out);
12228 }
12229 }
12230 }
12231
12232 /**
12233 * Output Spot Colors Resources.
12234 * @protected
12235 * @since 4.0.024 (2008-09-12)
12236 */
12237 protected function _putspotcolors() {
12238 foreach ($this->spot_colors as $name => $color) {
12239 $this->_newobj();
12240 $this->spot_colors[$name]['n'] = $this->n;
12241 $out = '[/Separation /'.str_replace(' ', '#20', $name);
12242 $out .= ' /DeviceCMYK <<';
12243 $out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
12244 $out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
12245 $out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
12246 $out .= "\n".'endobj';
12247 $this->_out($out);
12248 }
12249 }
12250
12251 /**
12252 * Return XObjects Dictionary.
12253 * @return string XObjects dictionary
12254 * @protected
12255 * @since 5.8.014 (2010-08-23)
12256 */
12257 protected function _getxobjectdict() {
12258 $out = '';
12259 foreach ($this->xobjects as $id => $objid) {
12260 $out .= ' /'.$id.' '.$objid['n'].' 0 R';
12261 }
12262 return $out;
12263 }
12264
12265 /**
12266 * Output Resources Dictionary.
12267 * @protected
12268 */
12269 protected function _putresourcedict() {
12270 $out = $this->_getobj(2)."\n";
12271 $out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
12272 $out .= ' /Font <<';
12273 foreach ($this->fontkeys as $fontkey) {
12274 $font = $this->getFontBuffer($fontkey);
12275 $out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
12276 }
12277 $out .= ' >>';
12278 $out .= ' /XObject <<';
12279 $out .= $this->_getxobjectdict();
12280 $out .= ' >>';
12281 // layers
12282 if (!empty($this->pdflayers)) {
12283 $out .= ' /Properties <<';
12284 foreach ($this->pdflayers as $layer) {
12285 $out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
12286 }
12287 $out .= ' >>';
12288 }
12289 if (!$this->pdfa_mode) {
12290 // transparency
12291 if (isset($this->extgstates) AND !empty($this->extgstates)) {
12292 $out .= ' /ExtGState <<';
12293 foreach ($this->extgstates as $k => $extgstate) {
12294 if (isset($extgstate['name'])) {
12295 $out .= ' /'.$extgstate['name'];
12296 } else {
12297 $out .= ' /GS'.$k;
12298 }
12299 $out .= ' '.$extgstate['n'].' 0 R';
12300 }
12301 $out .= ' >>';
12302 }
12303 if (isset($this->gradients) AND !empty($this->gradients)) {
12304 $gp = '';
12305 $gs = '';
12306 foreach ($this->gradients as $id => $grad) {
12307 // gradient patterns
12308 $gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
12309 // gradient shadings
12310 $gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
12311 }
12312 $out .= ' /Pattern <<'.$gp.' >>';
12313 $out .= ' /Shading <<'.$gs.' >>';
12314 }
12315 }
12316 // spot colors
12317 if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
12318 $out .= ' /ColorSpace <<';
12319 foreach ($this->spot_colors as $color) {
12320 $out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
12321 }
12322 $out .= ' >>';
12323 }
12324 $out .= ' >>';
12325 $out .= "\n".'endobj';
12326 $this->_out($out);
12327 }
12328
12329 /**
12330 * Output Resources.
12331 * @protected
12332 */
12333 protected function _putresources() {
12334 $this->_putextgstates();
12335 $this->_putocg();
12336 $this->_putfonts();
12337 $this->_putimages();
12338 $this->_putspotcolors();
12339 $this->_putshaders();
12340 $this->_putxobjects();
12341 $this->_putresourcedict();
12342 $this->_putdests();
12343 $this->_putbookmarks();
12344 $this->_putEmbeddedFiles();
12345 $this->_putannotsobjs();
12346 $this->_putjavascript();
12347 $this->_putencryption();
12348 }
12349
12350 /**
12351 * Adds some Metadata information (Document Information Dictionary)
12352 * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
12353 * @return int object id
12354 * @protected
12355 */
12356 protected function _putinfo() {
12357 $oid = $this->_newobj();
12358 $out = '<<';
12359 // store current isunicode value
12360 $prev_isunicode = $this->isunicode;
12361 if ($this->docinfounicode) {
12362 $this->isunicode = true;
12363 }
12364 if (!$this->empty_string($this->title)) {
12365 // The document's title.
12366 $out .= ' /Title '.$this->_textstring($this->title, $oid);
12367 }
12368 if (!$this->empty_string($this->author)) {
12369 // The name of the person who created the document.
12370 $out .= ' /Author '.$this->_textstring($this->author, $oid);
12371 }
12372 if (!$this->empty_string($this->subject)) {
12373 // The subject of the document.
12374 $out .= ' /Subject '.$this->_textstring($this->subject, $oid);
12375 }
12376 if (!$this->empty_string($this->keywords)) {
12377 // Keywords associated with the document.
12378 $out .= ' /Keywords '.$this->_textstring($this->keywords.' TCPDF', $oid);
12379 }
12380 if (!$this->empty_string($this->creator)) {
12381 // 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.
12382 $out .= ' /Creator '.$this->_textstring($this->creator, $oid);
12383 }
12384 // restore previous isunicode value
12385 $this->isunicode = $prev_isunicode;
12386 // default producer
12387 $out .= ' /Producer '.$this->_textstring($this->pdfproducer, $oid);
12388 // The date and time the document was created, in human-readable form
12389 $out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
12390 // The date and time the document was most recently modified, in human-readable form
12391 $out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
12392 // A name object indicating whether the document has been modified to include trapping information
12393 $out .= ' /Trapped /False';
12394 $out .= ' >>';
12395 $out .= "\n".'endobj';
12396 $this->_out($out);
12397 return $oid;
12398 }
12399
12400 /**
12401 * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
12402 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
12403 * @param $xmp (string) Custom XMP data.
12404 * @since 5.9.128 (2011-10-06)
12405 * @public
12406 */
12407 public function setExtraXMP($xmp) {
12408 $this->custom_xmp = $xmp;
12409 }
12410
12411 /**
12412 * Put XMP data object and return ID.
12413 * @return (int) The object ID.
12414 * @since 5.9.121 (2011-09-28)
12415 * @protected
12416 */
12417 protected function _putXMP() {
12418 $oid = $this->_newobj();
12419 // store current isunicode value
12420 $prev_isunicode = $this->isunicode;
12421 $this->isunicode = true;
12422 $prev_encrypted = $this->encrypted;
12423 $this->encrypted = false;
12424 // set XMP data
12425 $xmp = '<?xpacket begin="'.$this->unichr(0xfeff).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
12426 $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";
12427 $xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
12428 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
12429 $xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
12430 $xmp .= "\t\t\t".'<dc:title>'."\n";
12431 $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
12432 $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.$this->_escapeXML($this->title).'</rdf:li>'."\n";
12433 $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
12434 $xmp .= "\t\t\t".'</dc:title>'."\n";
12435 $xmp .= "\t\t\t".'<dc:creator>'."\n";
12436 $xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
12437 $xmp .= "\t\t\t\t\t".'<rdf:li>'.$this->_escapeXML($this->author).'</rdf:li>'."\n";
12438 $xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
12439 $xmp .= "\t\t\t".'</dc:creator>'."\n";
12440 $xmp .= "\t\t\t".'<dc:description>'."\n";
12441 $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
12442 $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.$this->_escapeXML($this->subject).'</rdf:li>'."\n";
12443 $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
12444 $xmp .= "\t\t\t".'</dc:description>'."\n";
12445 $xmp .= "\t\t\t".'<dc:subject>'."\n";
12446 $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
12447 $xmp .= "\t\t\t\t\t".'<rdf:li>'.$this->_escapeXML($this->keywords).'</rdf:li>'."\n";
12448 $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
12449 $xmp .= "\t\t\t".'</dc:subject>'."\n";
12450 $xmp .= "\t\t".'</rdf:Description>'."\n";
12451 // convert doc creation date format
12452 $dcdate = $this->getFormattedDate($this->doc_creation_timestamp);
12453 $doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
12454 $doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
12455 $doccreationdate .= '+'.substr($dcdate, 15, 2).':'.substr($dcdate, 18, 2);
12456 $doccreationdate = $this->_escapeXML($doccreationdate);
12457 // convert doc modification date format
12458 $dmdate = $this->getFormattedDate($this->doc_modification_timestamp);
12459 $docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
12460 $docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
12461 $docmoddate .= '+'.substr($dmdate, 15, 2).':'.substr($dmdate, 18, 2);
12462 $docmoddate = $this->_escapeXML($docmoddate);
12463 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
12464 $xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
12465 $xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
12466 $xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
12467 $xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
12468 $xmp .= "\t\t".'</rdf:Description>'."\n";
12469 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
12470 $xmp .= "\t\t\t".'<pdf:Keywords>'.$this->_escapeXML($this->keywords).' TCPDF</pdf:Keywords>'."\n";
12471 $xmp .= "\t\t\t".'<pdf:Producer>'.$this->_escapeXML($this->pdfproducer).'</pdf:Producer>'."\n";
12472 $xmp .= "\t\t".'</rdf:Description>'."\n";
12473 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
12474 $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);
12475 $xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
12476 $xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
12477 $xmp .= "\t\t".'</rdf:Description>'."\n";
12478 if ($this->pdfa_mode) {
12479 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
12480 $xmp .= "\t\t\t".'<pdfaid:part>1</pdfaid:part>'."\n";
12481 $xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
12482 $xmp .= "\t\t".'</rdf:Description>'."\n";
12483 }
12484 // XMP extension schemas
12485 $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";
12486 $xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
12487 $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
12488 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
12489 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
12490 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
12491 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
12492 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
12493 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
12494 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
12495 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
12496 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
12497 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
12498 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
12499 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
12500 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
12501 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
12502 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
12503 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
12504 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
12505 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
12506 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
12507 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
12508 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
12509 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
12510 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
12511 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
12512 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
12513 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
12514 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
12515 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
12516 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
12517 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
12518 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
12519 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
12520 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
12521 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
12522 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
12523 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
12524 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
12525 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
12526 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
12527 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
12528 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
12529 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
12530 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
12531 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
12532 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
12533 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
12534 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
12535 $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
12536 $xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
12537 $xmp .= "\t\t".'</rdf:Description>'."\n";
12538 $xmp .= "\t".'</rdf:RDF>'."\n";
12539 $xmp .= $this->custom_xmp;
12540 $xmp .= '</x:xmpmeta>'."\n";
12541 $xmp .= '<?xpacket end="w"?>';
12542 $out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
12543 // restore previous isunicode value
12544 $this->isunicode = $prev_isunicode;
12545 $this->encrypted = $prev_encrypted;
12546 $this->_out($out);
12547 return $oid;
12548 }
12549
12550 /**
12551 * Output Catalog.
12552 * @return int object id
12553 * @protected
12554 */
12555 protected function _putcatalog() {
12556 // put XMP
12557 $xmpobj = $this->_putXMP();
12558 // if required, add standard sRGB_IEC61966-2.1 blackscaled ICC colour profile
12559 if ($this->pdfa_mode OR $this->force_srgb) {
12560 $iccobj = $this->_newobj();
12561 $icc = file_get_contents(dirname(__FILE__).'/sRGB.icc');
12562 $filter = '';
12563 if ($this->compress) {
12564 $filter = ' /Filter /FlateDecode';
12565 $icc = gzcompress($icc);
12566 }
12567 $icc = $this->_getrawstream($icc);
12568 $this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
12569 }
12570 // start catalog
12571 $oid = $this->_newobj();
12572 $out = '<< /Type /Catalog';
12573 $out .= ' /Version /'.$this->PDFVersion;
12574 //$out .= ' /Extensions <<>>';
12575 $out .= ' /Pages 1 0 R';
12576 //$out .= ' /PageLabels ' //...;
12577 $out .= ' /Names <<';
12578 if ((!$this->pdfa_mode) AND ((!empty($this->javascript)) OR (!empty($this->js_objects)))) {
12579 $out .= ' /JavaScript '.($this->n_js).' 0 R';
12580 }
12581 $out .= ' >>';
12582 if (!empty($this->dests)) {
12583 $out .= ' /Dests '.$this->n_dests.' 0 R';
12584 }
12585 $out .= $this->_putviewerpreferences();
12586 if (isset($this->LayoutMode) AND (!$this->empty_string($this->LayoutMode))) {
12587 $out .= ' /PageLayout /'.$this->LayoutMode;
12588 }
12589 if (isset($this->PageMode) AND (!$this->empty_string($this->PageMode))) {
12590 $out .= ' /PageMode /'.$this->PageMode;
12591 }
12592 if (count($this->outlines) > 0) {
12593 $out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
12594 $out .= ' /PageMode /UseOutlines';
12595 }
12596 //$out .= ' /Threads []';
12597 if ($this->ZoomMode == 'fullpage') {
12598 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
12599 } elseif ($this->ZoomMode == 'fullwidth') {
12600 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
12601 } elseif ($this->ZoomMode == 'real') {
12602 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
12603 } elseif (!is_string($this->ZoomMode)) {
12604 $out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
12605 }
12606 //$out .= ' /AA <<>>';
12607 //$out .= ' /URI <<>>';
12608 $out .= ' /Metadata '.$xmpobj.' 0 R';
12609 //$out .= ' /StructTreeRoot <<>>';
12610 //$out .= ' /MarkInfo <<>>';
12611 if (isset($this->l['a_meta_language'])) {
12612 $out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
12613 }
12614 //$out .= ' /SpiderInfo <<>>';
12615 // set OutputIntent to sRGB IEC61966-2.1 if required
12616 if ($this->pdfa_mode OR $this->force_srgb) {
12617 $out .= ' /OutputIntents [<<';
12618 $out .= ' /Type /OutputIntent';
12619 $out .= ' /S /GTS_PDFA1';
12620 $out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
12621 $out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
12622 $out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
12623 $out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
12624 $out .= ' /DestOutputProfile '.$iccobj.' 0 R';
12625 $out .= ' >>]';
12626 }
12627 //$out .= ' /PieceInfo <<>>';
12628 if (!empty($this->pdflayers)) {
12629 $lyrobjs = '';
12630 $lyrobjs_print = '';
12631 $lyrobjs_view = '';
12632 foreach ($this->pdflayers as $layer) {
12633 $lyrobjs .= ' '.$layer['objid'].' 0 R';
12634 if ($layer['print']) {
12635 $lyrobjs_print .= ' '.$layer['objid'].' 0 R';
12636 }
12637 if ($layer['view']) {
12638 $lyrobjs_view .= ' '.$layer['objid'].' 0 R';
12639 }
12640 }
12641 $out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
12642 $out .= ' /D <<';
12643 $out .= ' /Name '.$this->_textstring('Layers', $oid);
12644 $out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
12645 $out .= ' /BaseState /ON';
12646 $out .= ' /ON ['.$lyrobjs_print.']';
12647 $out .= ' /OFF ['.$lyrobjs_view.']';
12648 $out .= ' /Intent /View';
12649 $out .= ' /AS [';
12650 $out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
12651 $out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
12652 $out .= ' ]';
12653 $out .= ' /Order ['.$lyrobjs.']';
12654 $out .= ' /ListMode /AllPages';
12655 //$out .= ' /RBGroups ['..']';
12656 //$out .= ' /Locked ['..']';
12657 $out .= ' >>';
12658 $out .= ' >>';
12659 }
12660 // AcroForm
12661 if (!empty($this->form_obj_id) OR ($this->sign AND isset($this->signature_data['cert_type']))) {
12662 $out .= ' /AcroForm <<';
12663 $objrefs = '';
12664 if ($this->sign AND isset($this->signature_data['cert_type'])) {
12665 // set reference for signature object
12666 $objrefs .= $this->sig_obj_id.' 0 R';
12667 }
12668 if (!empty($this->empty_signature_appearance)) {
12669 foreach ($this->empty_signature_appearance as $esa) {
12670 // set reference for empty signature objects
12671 $objrefs .= ' '.$esa['objid'].' 0 R';
12672 }
12673 }
12674 if (!empty($this->form_obj_id)) {
12675 foreach($this->form_obj_id as $objid) {
12676 $objrefs .= ' '.$objid.' 0 R';
12677 }
12678 }
12679 $out .= ' /Fields ['.$objrefs.']';
12680 // It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
12681 $out .= ' /NeedAppearances false';
12682 if ($this->sign AND isset($this->signature_data['cert_type'])) {
12683 if ($this->signature_data['cert_type'] > 0) {
12684 $out .= ' /SigFlags 3';
12685 } else {
12686 $out .= ' /SigFlags 1';
12687 }
12688 }
12689 //$out .= ' /CO ';
12690 if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
12691 $out .= ' /DR <<';
12692 $out .= ' /Font <<';
12693 foreach ($this->annotation_fonts as $fontkey => $fontid) {
12694 $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
12695 }
12696 $out .= ' >> >>';
12697 }
12698 $font = $this->getFontBuffer('helvetica');
12699 $out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
12700 $out .= ' /Q '.(($this->rtl)?'2':'0');
12701 //$out .= ' /XFA ';
12702 $out .= ' >>';
12703 // signatures
12704 if ($this->sign AND isset($this->signature_data['cert_type'])) {
12705 if ($this->signature_data['cert_type'] > 0) {
12706 $out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
12707 } else {
12708 $out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
12709 }
12710 }
12711 }
12712 //$out .= ' /Legal <<>>';
12713 //$out .= ' /Requirements []';
12714 //$out .= ' /Collection <<>>';
12715 //$out .= ' /NeedsRendering true';
12716 $out .= ' >>';
12717 $out .= "\n".'endobj';
12718 $this->_out($out);
12719 return $oid;
12720 }
12721
12722 /**
12723 * Output viewer preferences.
12724 * @return string for viewer preferences
12725 * @author Nicola asuni
12726 * @since 3.1.000 (2008-06-09)
12727 * @protected
12728 */
12729 protected function _putviewerpreferences() {
12730 $out = ' /ViewerPreferences <<';
12731 if ($this->rtl) {
12732 $out .= ' /Direction /R2L';
12733 } else {
12734 $out .= ' /Direction /L2R';
12735 }
12736 if (isset($this->viewer_preferences['HideToolbar']) AND ($this->viewer_preferences['HideToolbar'])) {
12737 $out .= ' /HideToolbar true';
12738 }
12739 if (isset($this->viewer_preferences['HideMenubar']) AND ($this->viewer_preferences['HideMenubar'])) {
12740 $out .= ' /HideMenubar true';
12741 }
12742 if (isset($this->viewer_preferences['HideWindowUI']) AND ($this->viewer_preferences['HideWindowUI'])) {
12743 $out .= ' /HideWindowUI true';
12744 }
12745 if (isset($this->viewer_preferences['FitWindow']) AND ($this->viewer_preferences['FitWindow'])) {
12746 $out .= ' /FitWindow true';
12747 }
12748 if (isset($this->viewer_preferences['CenterWindow']) AND ($this->viewer_preferences['CenterWindow'])) {
12749 $out .= ' /CenterWindow true';
12750 }
12751 if (isset($this->viewer_preferences['DisplayDocTitle']) AND ($this->viewer_preferences['DisplayDocTitle'])) {
12752 $out .= ' /DisplayDocTitle true';
12753 }
12754 if (isset($this->viewer_preferences['NonFullScreenPageMode'])) {
12755 $out .= ' /NonFullScreenPageMode /'.$this->viewer_preferences['NonFullScreenPageMode'];
12756 }
12757 if (isset($this->viewer_preferences['ViewArea'])) {
12758 $out .= ' /ViewArea /'.$this->viewer_preferences['ViewArea'];
12759 }
12760 if (isset($this->viewer_preferences['ViewClip'])) {
12761 $out .= ' /ViewClip /'.$this->viewer_preferences['ViewClip'];
12762 }
12763 if (isset($this->viewer_preferences['PrintArea'])) {
12764 $out .= ' /PrintArea /'.$this->viewer_preferences['PrintArea'];
12765 }
12766 if (isset($this->viewer_preferences['PrintClip'])) {
12767 $out .= ' /PrintClip /'.$this->viewer_preferences['PrintClip'];
12768 }
12769 if (isset($this->viewer_preferences['PrintScaling'])) {
12770 $out .= ' /PrintScaling /'.$this->viewer_preferences['PrintScaling'];
12771 }
12772 if (isset($this->viewer_preferences['Duplex']) AND (!$this->empty_string($this->viewer_preferences['Duplex']))) {
12773 $out .= ' /Duplex /'.$this->viewer_preferences['Duplex'];
12774 }
12775 if (isset($this->viewer_preferences['PickTrayByPDFSize'])) {
12776 if ($this->viewer_preferences['PickTrayByPDFSize']) {
12777 $out .= ' /PickTrayByPDFSize true';
12778 } else {
12779 $out .= ' /PickTrayByPDFSize false';
12780 }
12781 }
12782 if (isset($this->viewer_preferences['PrintPageRange'])) {
12783 $PrintPageRangeNum = '';
12784 foreach ($this->viewer_preferences['PrintPageRange'] as $k => $v) {
12785 $PrintPageRangeNum .= ' '.($v - 1).'';
12786 }
12787 $out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
12788 }
12789 if (isset($this->viewer_preferences['NumCopies'])) {
12790 $out .= ' /NumCopies '.intval($this->viewer_preferences['NumCopies']);
12791 }
12792 $out .= ' >>';
12793 return $out;
12794 }
12795
12796 /**
12797 * Output PDF File Header (7.5.2).
12798 * @protected
12799 */
12800 protected function _putheader() {
12801 $this->_out('%PDF-'.$this->PDFVersion);
12802 $this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
12803 }
12804
12805 /**
12806 * Output end of document (EOF).
12807 * @protected
12808 */
12809 protected function _enddoc() {
12810 $this->state = 1;
12811 $this->_putheader();
12812 $this->_putpages();
12813 $this->_putresources();
12814 // empty signature fields
12815 if (!empty($this->empty_signature_appearance)) {
12816 foreach ($this->empty_signature_appearance as $key => $esa) {
12817 // widget annotation for empty signature
12818 $out = $this->_getobj($esa['objid'])."\n";
12819 $out .= '<< /Type /Annot';
12820 $out .= ' /Subtype /Widget';
12821 $out .= ' /Rect ['.$esa['rect'].']';
12822 $out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
12823 $out .= ' /F 4';
12824 $out .= ' /FT /Sig';
12825 $signame = sprintf('Signature_%03d', ($key + 1));
12826 $out .= ' /T '.$this->_textstring($signame, $esa['objid']);
12827 $out .= ' /Ff 0';
12828 $out .= ' >>';
12829 $out .= "\n".'endobj';
12830 $this->_out($out);
12831 }
12832 }
12833 // Signature
12834 if ($this->sign AND isset($this->signature_data['cert_type'])) {
12835 // widget annotation for signature
12836 $out = $this->_getobj($this->sig_obj_id)."\n";
12837 $out .= '<< /Type /Annot';
12838 $out .= ' /Subtype /Widget';
12839 $out .= ' /Rect ['.$this->signature_appearance['rect'].']';
12840 $out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
12841 $out .= ' /F 4';
12842 $out .= ' /FT /Sig';
12843 $out .= ' /T '.$this->_textstring('Signature_000', $this->sig_obj_id);
12844 $out .= ' /Ff 0';
12845 $out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
12846 $out .= ' >>';
12847 $out .= "\n".'endobj';
12848 $this->_out($out);
12849 // signature
12850 $this->_putsignature();
12851 }
12852 // Info
12853 $objid_info = $this->_putinfo();
12854 // Catalog
12855 $objid_catalog = $this->_putcatalog();
12856 // Cross-ref
12857 $o = $this->bufferlen;
12858 // XREF section
12859 $this->_out('xref');
12860 $this->_out('0 '.($this->n + 1));
12861 $this->_out('0000000000 65535 f ');
12862 for ($i=1; $i <= $this->n; ++$i) {
12863 if (!isset($this->offsets[$i]) AND ($i > 1)) {
12864 $this->offsets[$i] = $this->offsets[($i - 1)];
12865 }
12866 $this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
12867 }
12868 // TRAILER
12869 $out = 'trailer'."\n";
12870 $out .= '<<';
12871 $out .= ' /Size '.($this->n + 1);
12872 $out .= ' /Root '.$objid_catalog.' 0 R';
12873 $out .= ' /Info '.$objid_info.' 0 R';
12874 if ($this->encrypted) {
12875 $out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
12876 }
12877 $out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
12878 $out .= ' >>';
12879 $this->_out($out);
12880 $this->_out('startxref');
12881 $this->_out($o);
12882 $this->_out('%%EOF');
12883 $this->state = 3; // end-of-doc
12884 if ($this->diskcache) {
12885 // remove temporary files used for images
12886 foreach ($this->imagekeys as $key) {
12887 // remove temporary files
12888 unlink($this->images[$key]);
12889 }
12890 foreach ($this->fontkeys as $key) {
12891 // remove temporary files
12892 unlink($this->fonts[$key]);
12893 }
12894 }
12895 }
12896
12897 /**
12898 * Initialize a new page.
12899 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
12900 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
12901 * @protected
12902 * @see getPageSizeFromFormat(), setPageFormat()
12903 */
12904 protected function _beginpage($orientation='', $format='') {
12905 ++$this->page;
12906 $this->setPageBuffer($this->page, '');
12907 // initialize array for graphics tranformation positions inside a page buffer
12908 $this->transfmrk[$this->page] = array();
12909 $this->state = 2;
12910 if ($this->empty_string($orientation)) {
12911 if (isset($this->CurOrientation)) {
12912 $orientation = $this->CurOrientation;
12913 } elseif ($this->fwPt > $this->fhPt) {
12914 // landscape
12915 $orientation = 'L';
12916 } else {
12917 // portrait
12918 $orientation = 'P';
12919 }
12920 }
12921 if ($this->empty_string($format)) {
12922 $this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
12923 $this->setPageOrientation($orientation);
12924 } else {
12925 $this->setPageFormat($format, $orientation);
12926 }
12927 if ($this->rtl) {
12928 $this->x = $this->w - $this->rMargin;
12929 } else {
12930 $this->x = $this->lMargin;
12931 }
12932 $this->y = $this->tMargin;
12933 if (isset($this->newpagegroup[$this->page])) {
12934 // start a new group
12935 $this->currpagegroup = $this->newpagegroup[$this->page];
12936 $this->pagegroups[$this->currpagegroup] = 1;
12937 } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
12938 ++$this->pagegroups[$this->currpagegroup];
12939 }
12940 }
12941
12942 /**
12943 * Mark end of page.
12944 * @protected
12945 */
12946 protected function _endpage() {
12947 $this->setVisibility('all');
12948 $this->state = 1;
12949 }
12950
12951 /**
12952 * Begin a new object and return the object number.
12953 * @return int object number
12954 * @protected
12955 */
12956 protected function _newobj() {
12957 $this->_out($this->_getobj());
12958 return $this->n;
12959 }
12960
12961 /**
12962 * Return the starting object string for the selected object ID.
12963 * @param $objid (int) Object ID (leave empty to get a new ID).
12964 * @return string the starting object string
12965 * @protected
12966 * @since 5.8.009 (2010-08-20)
12967 */
12968 protected function _getobj($objid='') {
12969 if ($objid === '') {
12970 ++$this->n;
12971 $objid = $this->n;
12972 }
12973 $this->offsets[$objid] = $this->bufferlen;
12974 return $objid.' 0 obj';
12975 }
12976
12977 /**
12978 * Underline text.
12979 * @param $x (int) X coordinate
12980 * @param $y (int) Y coordinate
12981 * @param $txt (string) text to underline
12982 * @protected
12983 */
12984 protected function _dounderline($x, $y, $txt) {
12985 $w = $this->GetStringWidth($txt);
12986 return $this->_dounderlinew($x, $y, $w);
12987 }
12988
12989 /**
12990 * Underline for rectangular text area.
12991 * @param $x (int) X coordinate
12992 * @param $y (int) Y coordinate
12993 * @param $w (int) width to underline
12994 * @protected
12995 * @since 4.8.008 (2009-09-29)
12996 */
12997 protected function _dounderlinew($x, $y, $w) {
12998 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
12999 return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
13000 }
13001
13002 /**
13003 * Line through text.
13004 * @param $x (int) X coordinate
13005 * @param $y (int) Y coordinate
13006 * @param $txt (string) text to linethrough
13007 * @protected
13008 */
13009 protected function _dolinethrough($x, $y, $txt) {
13010 $w = $this->GetStringWidth($txt);
13011 return $this->_dolinethroughw($x, $y, $w);
13012 }
13013
13014 /**
13015 * Line through for rectangular text area.
13016 * @param $x (int) X coordinate
13017 * @param $y (int) Y coordinate
13018 * @param $w (int) line length (width)
13019 * @protected
13020 * @since 4.9.008 (2009-09-29)
13021 */
13022 protected function _dolinethroughw($x, $y, $w) {
13023 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
13024 return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
13025 }
13026
13027 /**
13028 * Overline text.
13029 * @param $x (int) X coordinate
13030 * @param $y (int) Y coordinate
13031 * @param $txt (string) text to overline
13032 * @protected
13033 * @since 4.9.015 (2010-04-19)
13034 */
13035 protected function _dooverline($x, $y, $txt) {
13036 $w = $this->GetStringWidth($txt);
13037 return $this->_dooverlinew($x, $y, $w);
13038 }
13039
13040 /**
13041 * Overline for rectangular text area.
13042 * @param $x (int) X coordinate
13043 * @param $y (int) Y coordinate
13044 * @param $w (int) width to overline
13045 * @protected
13046 * @since 4.9.015 (2010-04-19)
13047 */
13048 protected function _dooverlinew($x, $y, $w) {
13049 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
13050 return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
13051
13052 }
13053
13054 /**
13055 * Read a 4-byte (32 bit) integer from file.
13056 * @param $f (string) file name.
13057 * @return 4-byte integer
13058 * @protected
13059 */
13060 protected function _freadint($f) {
13061 $a = unpack('Ni', fread($f, 4));
13062 return $a['i'];
13063 }
13064
13065 /**
13066 * Add "\" before "\", "(" and ")"
13067 * @param $s (string) string to escape.
13068 * @return string escaped string.
13069 * @protected
13070 */
13071 protected function _escape($s) {
13072 // the chr(13) substitution fixes the Bugs item #1421290.
13073 return strtr($s, array(')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r'));
13074 }
13075
13076 /**
13077 * Format a data string for meta information
13078 * @param $s (string) data string to escape.
13079 * @param $n (int) object ID
13080 * @return string escaped string.
13081 * @protected
13082 */
13083 protected function _datastring($s, $n=0) {
13084 if ($n == 0) {
13085 $n = $this->n;
13086 }
13087 $s = $this->_encrypt_data($n, $s);
13088 return '('. $this->_escape($s).')';
13089 }
13090
13091 /**
13092 * Set the document creation timestamp
13093 * @param $time (mixed) Document creation timestamp in seconds or date-time string.
13094 * @public
13095 * @since 5.9.152 (2012-03-23)
13096 */
13097 public function setDocCreationTimestamp($time) {
13098 if (is_string($time)) {
13099 $time = getTimestamp($time);
13100 }
13101 $this->doc_creation_timestamp = intval($time);
13102 }
13103
13104 /**
13105 * Set the document modification timestamp
13106 * @param $time (mixed) Document modification timestamp in seconds or date-time string.
13107 * @public
13108 * @since 5.9.152 (2012-03-23)
13109 */
13110 public function setDocModificationTimestamp($time) {
13111 if (is_string($time)) {
13112 $time = getTimestamp($time);
13113 }
13114 $this->doc_modification_timestamp = intval($time);
13115 }
13116
13117 /**
13118 * Returns document creation timestamp in seconds.
13119 * @return (int) Creation timestamp in seconds.
13120 * @public
13121 * @since 5.9.152 (2012-03-23)
13122 */
13123 public function getDocCreationTimestamp() {
13124 return $this->doc_creation_timestamp;
13125 }
13126
13127 /**
13128 * Returns document modification timestamp in seconds.
13129 * @return (int) Modfication timestamp in seconds.
13130 * @public
13131 * @since 5.9.152 (2012-03-23)
13132 */
13133 public function getDocModificationTimestamp() {
13134 return $this->doc_modification_timestamp;
13135 }
13136
13137 /**
13138 * Returns timestamp in seconds from formatted date-time.
13139 * @param $date (string) Formatted date-time.
13140 * @return int seconds.
13141 * @public
13142 * @since 5.9.152 (2012-03-23)
13143 */
13144 public function getTimestamp($date) {
13145 if (($date[0] == 'D') AND ($date[1] == ':')) {
13146 // remove date prefix if present
13147 $date = substr($date, 2);
13148 }
13149 return strtotime($date);
13150 }
13151
13152 /**
13153 * Returns a formatted date-time.
13154 * @param $time (int) Time in seconds.
13155 * @return string escaped date string.
13156 * @public
13157 * @since 5.9.152 (2012-03-23)
13158 */
13159 public function getFormattedDate($time) {
13160 return substr_replace(date('YmdHisO', intval($time)), '\'', (0 - 2), 0).'\'';
13161 }
13162
13163 /**
13164 * Returns a formatted date for meta information
13165 * @param $n (int) Object ID.
13166 * @param $timestamp (int) Timestamp to convert.
13167 * @return string escaped date string.
13168 * @protected
13169 * @since 4.6.028 (2009-08-25)
13170 */
13171 protected function _datestring($n=0, $timestamp=0) {
13172 if ((empty($timestamp)) OR ($timestamp < 0)) {
13173 $timestamp = $this->doc_creation_timestamp;
13174 }
13175 return $this->_datastring('D:'.$this->getFormattedDate($timestamp), $n);
13176 }
13177
13178 /**
13179 * Format a text string for meta information
13180 * @param $s (string) string to escape.
13181 * @param $n (int) object ID
13182 * @return string escaped string.
13183 * @protected
13184 */
13185 protected function _textstring($s, $n=0) {
13186 if ($this->isunicode) {
13187 //Convert string to UTF-16BE
13188 $s = $this->UTF8ToUTF16BE($s, true);
13189 }
13190 return $this->_datastring($s, $n);
13191 }
13192
13193 /**
13194 * THIS METHOD IS DEPRECATED
13195 * Format a text string
13196 * @param $s (string) string to escape.
13197 * @return string escaped string.
13198 * @protected
13199 * @deprecated
13200 */
13201 protected function _escapetext($s) {
13202 if ($this->isunicode) {
13203 if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
13204 $s = $this->UTF8ToLatin1($s);
13205 } else {
13206 //Convert string to UTF-16BE and reverse RTL language
13207 $s = $this->utf8StrRev($s, false, $this->tmprtl);
13208 }
13209 }
13210 return $this->_escape($s);
13211 }
13212
13213 /**
13214 * Escape some special characters (&lt; &gt; &amp;) for XML output.
13215 * @param $str (string) Input string to convert.
13216 * @return converted string
13217 * @since 5.9.121 (2011-09-28)
13218 * @protected
13219 */
13220 protected function _escapeXML($str) {
13221 $replaceTable = array("\0" => '', '&' => '&amp;', '<' => '&lt;', '>' => '&gt;');
13222 $str = strtr($str, $replaceTable);
13223 return $str;
13224 }
13225
13226 /**
13227 * get raw output stream.
13228 * @param $s (string) string to output.
13229 * @param $n (int) object reference for encryption mode
13230 * @protected
13231 * @author Nicola Asuni
13232 * @since 5.5.000 (2010-06-22)
13233 */
13234 protected function _getrawstream($s, $n=0) {
13235 if ($n <= 0) {
13236 // default to current object
13237 $n = $this->n;
13238 }
13239 return $this->_encrypt_data($n, $s);
13240 }
13241
13242 /**
13243 * Format output stream (DEPRECATED).
13244 * @param $s (string) string to output.
13245 * @param $n (int) object reference for encryption mode
13246 * @protected
13247 * @deprecated
13248 */
13249 protected function _getstream($s, $n=0) {
13250 return 'stream'."\n".$this->_getrawstream($s, $n)."\n".'endstream';
13251 }
13252
13253 /**
13254 * Output a stream (DEPRECATED).
13255 * @param $s (string) string to output.
13256 * @param $n (int) object reference for encryption mode
13257 * @protected
13258 * @deprecated
13259 */
13260 protected function _putstream($s, $n=0) {
13261 $this->_out($this->_getstream($s, $n));
13262 }
13263
13264 /**
13265 * Output a string to the document.
13266 * @param $s (string) string to output.
13267 * @protected
13268 */
13269 protected function _out($s) {
13270 if ($this->state == 2) {
13271 if ($this->inxobj) {
13272 // we are inside an XObject template
13273 $this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
13274 } elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
13275 // puts data before page footer
13276 $pagebuff = $this->getPageBuffer($this->page);
13277 $page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
13278 $footer = substr($pagebuff, -$this->footerlen[$this->page]);
13279 $this->setPageBuffer($this->page, $page.$s."\n".$footer);
13280 // update footer position
13281 $this->footerpos[$this->page] += strlen($s."\n");
13282 } else {
13283 $this->setPageBuffer($this->page, $s."\n", true);
13284 }
13285 } else {
13286 $this->setBuffer($s."\n");
13287 }
13288 }
13289
13290 /**
13291 * Converts UTF-8 strings to codepoints array.<br>
13292 * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
13293 * Based on: http://www.faqs.org/rfcs/rfc3629.html
13294 * <pre>
13295 * Char. number range | UTF-8 octet sequence
13296 * (hexadecimal) | (binary)
13297 * --------------------+-----------------------------------------------
13298 * 0000 0000-0000 007F | 0xxxxxxx
13299 * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
13300 * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
13301 * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
13302 * ---------------------------------------------------------------------
13303 *
13304 * ABFN notation:
13305 * ---------------------------------------------------------------------
13306 * UTF8-octets = *( UTF8-char )
13307 * UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
13308 * UTF8-1 = %x00-7F
13309 * UTF8-2 = %xC2-DF UTF8-tail
13310 *
13311 * UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
13312 * %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
13313 * UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
13314 * %xF4 %x80-8F 2( UTF8-tail )
13315 * UTF8-tail = %x80-BF
13316 * ---------------------------------------------------------------------
13317 * </pre>
13318 * @param $str (string) string to process.
13319 * @return array containing codepoints (UTF-8 characters values)
13320 * @protected
13321 * @author Nicola Asuni
13322 * @since 1.53.0.TC005 (2005-01-05)
13323 */
13324 protected function UTF8StringToArray($str) {
13325 // build a unique string key
13326 $strkey = md5($str);
13327 if (isset($this->cache_UTF8StringToArray[$strkey])) {
13328 // return cached value
13329 $chrarray = $this->cache_UTF8StringToArray[$strkey]['s'];
13330 if (!isset($this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']])) {
13331 if ($this->isunicode) {
13332 foreach ($chrarray as $chr) {
13333 // store this char for font subsetting
13334 $this->CurrentFont['subsetchars'][$chr] = true;
13335 }
13336 // update font subsetchars
13337 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
13338 }
13339 $this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']] = true;
13340 }
13341 return $chrarray;
13342 }
13343 // check cache size
13344 if ($this->cache_size_UTF8StringToArray >= $this->cache_maxsize_UTF8StringToArray) {
13345 // remove first element
13346 array_shift($this->cache_UTF8StringToArray);
13347 }
13348 // new cache array for selected string
13349 $this->cache_UTF8StringToArray[$strkey] = array('s' => array(), 'f' => array());
13350 ++$this->cache_size_UTF8StringToArray;
13351 if (!$this->isunicode) {
13352 // split string into array of equivalent codes
13353 $strarr = array();
13354 $strlen = strlen($str);
13355 for ($i=0; $i < $strlen; ++$i) {
13356 $strarr[] = ord($str[$i]);
13357 }
13358 // insert new value on cache
13359 $this->cache_UTF8StringToArray[$strkey]['s'] = $strarr;
13360 $this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']] = true;
13361 return $strarr;
13362 }
13363 $unichar = -1; // last unicode char
13364 $unicode = array(); // array containing unicode values
13365 $bytes = array(); // array containing single character byte sequences
13366 $numbytes = 1; // number of octetc needed to represent the UTF-8 character
13367 $str .= ''; // force $str to be a string
13368 $length = strlen($str);
13369 for ($i = 0; $i < $length; ++$i) {
13370 $char = ord($str[$i]); // get one string character at time
13371 if (count($bytes) == 0) { // get starting octect
13372 if ($char <= 0x7F) {
13373 $unichar = $char; // use the character "as is" because is ASCII
13374 $numbytes = 1;
13375 } elseif (($char >> 0x05) == 0x06) { // 2 bytes character (0x06 = 110 BIN)
13376 $bytes[] = ($char - 0xC0) << 0x06;
13377 $numbytes = 2;
13378 } elseif (($char >> 0x04) == 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
13379 $bytes[] = ($char - 0xE0) << 0x0C;
13380 $numbytes = 3;
13381 } elseif (($char >> 0x03) == 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
13382 $bytes[] = ($char - 0xF0) << 0x12;
13383 $numbytes = 4;
13384 } else {
13385 // use replacement character for other invalid sequences
13386 $unichar = 0xFFFD;
13387 $bytes = array();
13388 $numbytes = 1;
13389 }
13390 } elseif (($char >> 0x06) == 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
13391 $bytes[] = $char - 0x80;
13392 if (count($bytes) == $numbytes) {
13393 // compose UTF-8 bytes to a single unicode value
13394 $char = $bytes[0];
13395 for ($j = 1; $j < $numbytes; ++$j) {
13396 $char += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
13397 }
13398 if ((($char >= 0xD800) AND ($char <= 0xDFFF)) OR ($char >= 0x10FFFF)) {
13399 /* The definition of UTF-8 prohibits encoding character numbers between
13400 U+D800 and U+DFFF, which are reserved for use with the UTF-16
13401 encoding form (as surrogate pairs) and do not directly represent
13402 characters. */
13403 $unichar = 0xFFFD; // use replacement character
13404 } else {
13405 $unichar = $char; // add char to array
13406 }
13407 // reset data for next char
13408 $bytes = array();
13409 $numbytes = 1;
13410 }
13411 } else {
13412 // use replacement character for other invalid sequences
13413 $unichar = 0xFFFD;
13414 $bytes = array();
13415 $numbytes = 1;
13416 }
13417 if ($unichar >= 0) {
13418 // insert unicode value into array
13419 $unicode[] = $unichar;
13420 // store this char for font subsetting
13421 $this->CurrentFont['subsetchars'][$unichar] = true;
13422 $unichar = -1;
13423 }
13424 }
13425 // update font subsetchars
13426 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
13427 // insert new value on cache
13428 $this->cache_UTF8StringToArray[$strkey]['s'] = $unicode;
13429 $this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']] = true;
13430 return $unicode;
13431 }
13432
13433 /**
13434 * Converts UTF-8 strings to UTF16-BE.<br>
13435 * @param $str (string) string to process.
13436 * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF)
13437 * @return string
13438 * @author Nicola Asuni
13439 * @since 1.53.0.TC005 (2005-01-05)
13440 * @see UTF8StringToArray(), arrUTF8ToUTF16BE()
13441 * @protected
13442 */
13443 protected function UTF8ToUTF16BE($str, $setbom=false) {
13444 if (!$this->isunicode) {
13445 return $str; // string is not in unicode
13446 }
13447 $unicode = $this->UTF8StringToArray($str); // array containing UTF-8 unicode values
13448 return $this->arrUTF8ToUTF16BE($unicode, $setbom);
13449 }
13450
13451 /**
13452 * Converts UTF-8 strings to Latin1 when using the standard 14 core fonts.<br>
13453 * @param $str (string) string to process.
13454 * @return string
13455 * @author Andrew Whitehead, Nicola Asuni
13456 * @protected
13457 * @since 3.2.000 (2008-06-23)
13458 */
13459 protected function UTF8ToLatin1($str) {
13460 if (!$this->isunicode) {
13461 return $str; // string is not in unicode
13462 }
13463 $outstr = ''; // string to be returned
13464 $unicode = $this->UTF8StringToArray($str); // array containing UTF-8 unicode values
13465 foreach ($unicode as $char) {
13466 if ($char < 256) {
13467 $outstr .= chr($char);
13468 } elseif (array_key_exists($char, $this->unicode->uni_utf8tolatin)) {
13469 // map from UTF-8
13470 $outstr .= chr($this->unicode->uni_utf8tolatin[$char]);
13471 } elseif ($char == 0xFFFD) {
13472 // skip
13473 } else {
13474 $outstr .= '?';
13475 }
13476 }
13477 return $outstr;
13478 }
13479
13480 /**
13481 * Converts UTF-8 characters array to array of Latin1 characters<br>
13482 * @param $unicode (array) array containing UTF-8 unicode values
13483 * @return array
13484 * @author Nicola Asuni
13485 * @protected
13486 * @since 4.8.023 (2010-01-15)
13487 */
13488 protected function UTF8ArrToLatin1($unicode) {
13489 if ((!$this->isunicode) OR $this->isUnicodeFont()) {
13490 return $unicode;
13491 }
13492 $outarr = array(); // array to be returned
13493 foreach ($unicode as $char) {
13494 if ($char < 256) {
13495 $outarr[] = $char;
13496 } elseif (array_key_exists($char, $this->unicode->uni_utf8tolatin)) {
13497 // map from UTF-8
13498 $outarr[] = $this->unicode->uni_utf8tolatin[$char];
13499 } elseif ($char == 0xFFFD) {
13500 // skip
13501 } else {
13502 $outarr[] = 63; // '?' character
13503 }
13504 }
13505 return $outarr;
13506 }
13507
13508 /**
13509 * Converts array of UTF-8 characters to UTF16-BE string.<br>
13510 * Based on: http://www.faqs.org/rfcs/rfc2781.html
13511 * <pre>
13512 * Encoding UTF-16:
13513 *
13514 * Encoding of a single character from an ISO 10646 character value to
13515 * UTF-16 proceeds as follows. Let U be the character number, no greater
13516 * than 0x10FFFF.
13517 *
13518 * 1) If U < 0x10000, encode U as a 16-bit unsigned integer and
13519 * terminate.
13520 *
13521 * 2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
13522 * U' must be less than or equal to 0xFFFFF. That is, U' can be
13523 * represented in 20 bits.
13524 *
13525 * 3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
13526 * 0xDC00, respectively. These integers each have 10 bits free to
13527 * encode the character value, for a total of 20 bits.
13528 *
13529 * 4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
13530 * bits of W1 and the 10 low-order bits of U' to the 10 low-order
13531 * bits of W2. Terminate.
13532 *
13533 * Graphically, steps 2 through 4 look like:
13534 * U' = yyyyyyyyyyxxxxxxxxxx
13535 * W1 = 110110yyyyyyyyyy
13536 * W2 = 110111xxxxxxxxxx
13537 * </pre>
13538 * @param $unicode (array) array containing UTF-8 unicode values
13539 * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF)
13540 * @return string
13541 * @protected
13542 * @author Nicola Asuni
13543 * @since 2.1.000 (2008-01-08)
13544 * @see UTF8ToUTF16BE()
13545 */
13546 protected function arrUTF8ToUTF16BE($unicode, $setbom=false) {
13547 $outstr = ''; // string to be returned
13548 if ($setbom) {
13549 $outstr .= "\xFE\xFF"; // Byte Order Mark (BOM)
13550 }
13551 foreach ($unicode as $char) {
13552 if ($char == 0x200b) {
13553 // skip Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B)
13554 } elseif ($char == 0xFFFD) {
13555 $outstr .= "\xFF\xFD"; // replacement character
13556 } elseif ($char < 0x10000) {
13557 $outstr .= chr($char >> 0x08);
13558 $outstr .= chr($char & 0xFF);
13559 } else {
13560 $char -= 0x10000;
13561 $w1 = 0xD800 | ($char >> 0x0a);
13562 $w2 = 0xDC00 | ($char & 0x3FF);
13563 $outstr .= chr($w1 >> 0x08);
13564 $outstr .= chr($w1 & 0xFF);
13565 $outstr .= chr($w2 >> 0x08);
13566 $outstr .= chr($w2 & 0xFF);
13567 }
13568 }
13569 return $outstr;
13570 }
13571 // ====================================================
13572
13573 /**
13574 * Set header font.
13575 * @param $font (array) font
13576 * @public
13577 * @since 1.1
13578 */
13579 public function setHeaderFont($font) {
13580 $this->header_font = $font;
13581 }
13582
13583 /**
13584 * Get header font.
13585 * @return array()
13586 * @public
13587 * @since 4.0.012 (2008-07-24)
13588 */
13589 public function getHeaderFont() {
13590 return $this->header_font;
13591 }
13592
13593 /**
13594 * Set footer font.
13595 * @param $font (array) font
13596 * @public
13597 * @since 1.1
13598 */
13599 public function setFooterFont($font) {
13600 $this->footer_font = $font;
13601 }
13602
13603 /**
13604 * Get Footer font.
13605 * @return array()
13606 * @public
13607 * @since 4.0.012 (2008-07-24)
13608 */
13609 public function getFooterFont() {
13610 return $this->footer_font;
13611 }
13612
13613 /**
13614 * Set language array.
13615 * @param $language (array)
13616 * @public
13617 * @since 1.1
13618 */
13619 public function setLanguageArray($language) {
13620 $this->l = $language;
13621 if (isset($this->l['a_meta_dir'])) {
13622 $this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
13623 } else {
13624 $this->rtl = false;
13625 }
13626 }
13627
13628 /**
13629 * Returns the PDF data.
13630 * @public
13631 */
13632 public function getPDFData() {
13633 if ($this->state < 3) {
13634 $this->Close();
13635 }
13636 return $this->buffer;
13637 }
13638
13639 /**
13640 * Output anchor link.
13641 * @param $url (string) link URL or internal link (i.e.: &lt;a href="#23,4.5"&gt;link to page 23 at 4.5 Y position&lt;/a&gt;)
13642 * @param $name (string) link name
13643 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
13644 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
13645 * @param $color (array) array of RGB text color
13646 * @param $style (string) font style (U, D, B, I)
13647 * @param $firstblock (boolean) if true the string is the starting of a line.
13648 * @return the number of cells used or the remaining text if $firstline = true;
13649 * @public
13650 */
13651 public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
13652 if (!$this->empty_string($url) AND ($url{0} == '#')) {
13653 // convert url to internal link
13654 $lnkdata = explode(',', $url);
13655 if (isset($lnkdata[0])) {
13656 $page = intval(substr($lnkdata[0], 1));
13657 if (empty($page) OR ($page <= 0)) {
13658 $page = $this->page;
13659 }
13660 if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
13661 $lnky = floatval($lnkdata[1]);
13662 } else {
13663 $lnky = 0;
13664 }
13665 $url = $this->AddLink();
13666 $this->SetLink($url, $lnky, $page);
13667 }
13668 }
13669 // store current settings
13670 $prevcolor = $this->fgcolor;
13671 $prevstyle = $this->FontStyle;
13672 if (empty($color)) {
13673 $this->SetTextColorArray($this->htmlLinkColorArray);
13674 } else {
13675 $this->SetTextColorArray($color);
13676 }
13677 if ($style == -1) {
13678 $this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
13679 } else {
13680 $this->SetFont('', $this->FontStyle.$style);
13681 }
13682 $ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
13683 // restore settings
13684 $this->SetFont('', $prevstyle);
13685 $this->SetTextColorArray($prevcolor);
13686 return $ret;
13687 }
13688
13689 /**
13690 * Returns an array (RGB or CMYK) from an html color name, or a six-digit (i.e. #3FE5AA), or three-digit (i.e. #7FF) hexadecimal color, or a javascript color array, or javascript color name.
13691 * @param $hcolor (string) HTML color.
13692 * @param $defcol (array) Color to return in case of error.
13693 * @return array RGB or CMYK color, or false in case of error.
13694 * @public
13695 */
13696 public function convertHTMLColorToDec($hcolor='#FFFFFF', $defcol=array('R'=>128,'G'=>128,'B'=>128)) {
13697 $color = preg_replace('/[\s]*/', '', $hcolor); // remove extra spaces
13698 $color = strtolower($color);
13699 // check for javascript color array syntax
13700 if (strpos($color, '[') !== false) {
13701 if (preg_match('/[\[][\"\'](t|g|rgb|cmyk)[\"\'][\,]?([0-9\.]*)[\,]?([0-9\.]*)[\,]?([0-9\.]*)[\,]?([0-9\.]*)[\]]/', $color, $m) > 0) {
13702 $returncolor = array();
13703 switch ($m[1]) {
13704 case 'cmyk': {
13705 // RGB
13706 $returncolor['C'] = max(0, min(100, (floatval($m[2]) * 100)));
13707 $returncolor['M'] = max(0, min(100, (floatval($m[3]) * 100)));
13708 $returncolor['Y'] = max(0, min(100, (floatval($m[4]) * 100)));
13709 $returncolor['K'] = max(0, min(100, (floatval($m[5]) * 100)));
13710 break;
13711 }
13712 case 'rgb': {
13713 // RGB
13714 $returncolor['R'] = max(0, min(255, (floatval($m[2]) * 255)));
13715 $returncolor['G'] = max(0, min(255, (floatval($m[3]) * 255)));
13716 $returncolor['B'] = max(0, min(255, (floatval($m[4]) * 255)));
13717 break;
13718 }
13719 case 'g': {
13720 // grayscale
13721 $returncolor['G'] = max(0, min(255, (floatval($m[2]) * 255)));
13722 break;
13723 }
13724 case 't':
13725 default: {
13726 // transparent (empty array)
13727 break;
13728 }
13729 }
13730 return $returncolor;
13731 }
13732 } elseif (($dotpos = strpos($color, '.')) !== false) {
13733 // remove class parent (i.e.: color.red)
13734 $color = substr($color, ($dotpos + 1));
13735 if ($color == 'transparent') {
13736 // transparent (empty array)
13737 return array();
13738 }
13739 }
13740 if (strlen($color) == 0) {
13741 return $defcol;
13742 }
13743 // RGB ARRAY
13744 if (substr($color, 0, 3) == 'rgb') {
13745 $codes = substr($color, 4);
13746 $codes = str_replace(')', '', $codes);
13747 $returncolor = explode(',', $codes);
13748 foreach ($returncolor as $key => $val) {
13749 if (strpos($val, '%') > 0) {
13750 // percentage
13751 $returncolor[$key] = (255 * intval($val) / 100);
13752 } else {
13753 $returncolor[$key] = intval($val);
13754 }
13755 // normalize value
13756 $returncolor[$key] = max(0, min(255, $returncolor[$key]));
13757 }
13758 return $returncolor;
13759 }
13760 // CMYK ARRAY
13761 if (substr($color, 0, 4) == 'cmyk') {
13762 $codes = substr($color, 5);
13763 $codes = str_replace(')', '', $codes);
13764 $returncolor = explode(',', $codes);
13765 foreach ($returncolor as $key => $val) {
13766 if (strpos($val, '%') !== false) {
13767 // percentage
13768 $returncolor[$key] = (100 * intval($val) / 100);
13769 } else {
13770 $returncolor[$key] = intval($val);
13771 }
13772 // normalize value
13773 $returncolor[$key] = max(0, min(100, $returncolor[$key]));
13774 }
13775 return $returncolor;
13776 }
13777 if ($color{0} != '#') {
13778 // COLOR NAME
13779 if (isset($this->webcolor[$color])) {
13780 // web color
13781 $color_code = $this->webcolor[$color];
13782 } else {
13783 // spot color
13784 $returncolor = $this->getSpotColor($color);
13785 if ($returncolor === false) {
13786 $returncolor = $defcol;
13787 }
13788 return $returncolor;
13789 }
13790 } else {
13791 $color_code = substr($color, 1);
13792 }
13793 // HEXADECIMAL REPRESENTATION
13794 switch (strlen($color_code)) {
13795 case 3: {
13796 // 3-digit RGB hexadecimal representation
13797 $r = substr($color_code, 0, 1);
13798 $g = substr($color_code, 1, 1);
13799 $b = substr($color_code, 2, 1);
13800 $returncolor = array();
13801 $returncolor['R'] = max(0, min(255, hexdec($r.$r)));
13802 $returncolor['G'] = max(0, min(255, hexdec($g.$g)));
13803 $returncolor['B'] = max(0, min(255, hexdec($b.$b)));
13804 break;
13805 }
13806 case 6: {
13807 // 6-digit RGB hexadecimal representation
13808 $returncolor = array();
13809 $returncolor['R'] = max(0, min(255, hexdec(substr($color_code, 0, 2))));
13810 $returncolor['G'] = max(0, min(255, hexdec(substr($color_code, 2, 2))));
13811 $returncolor['B'] = max(0, min(255, hexdec(substr($color_code, 4, 2))));
13812 break;
13813 }
13814 case 8: {
13815 // 8-digit CMYK hexadecimal representation
13816 $returncolor = array();
13817 $returncolor['C'] = max(0, min(100, round(hexdec(substr($color_code, 0, 2)) / 2.55)));
13818 $returncolor['M'] = max(0, min(100, round(hexdec(substr($color_code, 2, 2)) / 2.55)));
13819 $returncolor['Y'] = max(0, min(100, round(hexdec(substr($color_code, 4, 2)) / 2.55)));
13820 $returncolor['K'] = max(0, min(100, round(hexdec(substr($color_code, 6, 2)) / 2.55)));
13821 break;
13822 }
13823 default: {
13824 $returncolor = $defcol;
13825 break;
13826 }
13827 }
13828 return $returncolor;
13829 }
13830
13831 /**
13832 * Converts pixels to User's Units.
13833 * @param $px (int) pixels
13834 * @return float value in user's unit
13835 * @public
13836 * @see setImageScale(), getImageScale()
13837 */
13838 public function pixelsToUnits($px) {
13839 return ($px / ($this->imgscale * $this->k));
13840 }
13841
13842 /**
13843 * Reverse function for htmlentities.
13844 * Convert entities in UTF-8.
13845 * @param $text_to_convert (string) Text to convert.
13846 * @return string converted text string
13847 * @public
13848 */
13849 public function unhtmlentities($text_to_convert) {
13850 return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
13851 }
13852
13853 // ENCRYPTION METHODS ----------------------------------
13854
13855 /**
13856 * Returns a string containing random data to be used as a seed for encryption methods.
13857 * @param $seed (string) starting seed value
13858 * @return string containing random data
13859 * @author Nicola Asuni
13860 * @since 5.9.006 (2010-10-19)
13861 * @protected
13862 */
13863 protected function getRandomSeed($seed='') {
13864 $seed .= microtime();
13865 if (function_exists('openssl_random_pseudo_bytes') AND (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) {
13866 // this is not used on windows systems because it is very slow for a know bug
13867 $seed .= openssl_random_pseudo_bytes(512);
13868 } else {
13869 for ($i = 0; $i < 23; ++$i) {
13870 $seed .= uniqid('', true);
13871 }
13872 }
13873 $seed .= uniqid('', true);
13874 $seed .= rand();
13875 $seed .= getmypid();
13876 $seed .= __FILE__;
13877 $seed .= $this->bufferlen;
13878 if (isset($_SERVER['REMOTE_ADDR'])) {
13879 $seed .= $_SERVER['REMOTE_ADDR'];
13880 }
13881 if (isset($_SERVER['HTTP_USER_AGENT'])) {
13882 $seed .= $_SERVER['HTTP_USER_AGENT'];
13883 }
13884 if (isset($_SERVER['HTTP_ACCEPT'])) {
13885 $seed .= $_SERVER['HTTP_ACCEPT'];
13886 }
13887 if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
13888 $seed .= $_SERVER['HTTP_ACCEPT_ENCODING'];
13889 }
13890 if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
13891 $seed .= $_SERVER['HTTP_ACCEPT_LANGUAGE'];
13892 }
13893 if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) {
13894 $seed .= $_SERVER['HTTP_ACCEPT_CHARSET'];
13895 }
13896 $seed .= rand();
13897 $seed .= uniqid('', true);
13898 $seed .= microtime();
13899 return $seed;
13900 }
13901
13902 /**
13903 * Compute encryption key depending on object number where the encrypted data is stored.
13904 * This is used for all strings and streams without crypt filter specifier.
13905 * @param $n (int) object number
13906 * @return int object key
13907 * @protected
13908 * @author Nicola Asuni
13909 * @since 2.0.000 (2008-01-02)
13910 */
13911 protected function _objectkey($n) {
13912 $objkey = $this->encryptdata['key'].pack('VXxx', $n);
13913 if ($this->encryptdata['mode'] == 2) { // AES-128
13914 // AES padding
13915 $objkey .= "\x73\x41\x6C\x54"; // sAlT
13916 }
13917 $objkey = substr($this->_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
13918 $objkey = substr($objkey, 0, 16);
13919 return $objkey;
13920 }
13921
13922 /**
13923 * Encrypt the input string.
13924 * @param $n (int) object number
13925 * @param $s (string) data string to encrypt
13926 * @return encrypted string
13927 * @protected
13928 * @author Nicola Asuni
13929 * @since 5.0.005 (2010-05-11)
13930 */
13931 protected function _encrypt_data($n, $s) {
13932 if (!$this->encrypted) {
13933 return $s;
13934 }
13935 switch ($this->encryptdata['mode']) {
13936 case 0: // RC4-40
13937 case 1: { // RC4-128
13938 $s = $this->_RC4($this->_objectkey($n), $s);
13939 break;
13940 }
13941 case 2: { // AES-128
13942 $s = $this->_AES($this->_objectkey($n), $s);
13943 break;
13944 }
13945 case 3: { // AES-256
13946 $s = $this->_AES($this->encryptdata['key'], $s);
13947 break;
13948 }
13949 }
13950 return $s;
13951 }
13952
13953 /**
13954 * Put encryption on PDF document.
13955 * @protected
13956 * @author Nicola Asuni
13957 * @since 2.0.000 (2008-01-02)
13958 */
13959 protected function _putencryption() {
13960 if (!$this->encrypted) {
13961 return;
13962 }
13963 $this->encryptdata['objid'] = $this->_newobj();
13964 $out = '<<';
13965 if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
13966 $this->encryptdata['Filter'] = 'Standard';
13967 }
13968 $out .= ' /Filter /'.$this->encryptdata['Filter'];
13969 if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
13970 $out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
13971 }
13972 if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
13973 $this->encryptdata['V'] = 1;
13974 }
13975 // V is a code specifying the algorithm to be used in encrypting and decrypting the document
13976 $out .= ' /V '.$this->encryptdata['V'];
13977 if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
13978 // The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
13979 $out .= ' /Length '.$this->encryptdata['Length'];
13980 } else {
13981 $out .= ' /Length 40';
13982 }
13983 if ($this->encryptdata['V'] >= 4) {
13984 if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
13985 $this->encryptdata['StmF'] = 'Identity';
13986 }
13987 if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
13988 // The name of the crypt filter that shall be used when decrypting all strings in the document.
13989 $this->encryptdata['StrF'] = 'Identity';
13990 }
13991 // A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
13992 if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
13993 $out .= ' /CF <<';
13994 $out .= ' /'.$this->encryptdata['StmF'].' <<';
13995 $out .= ' /Type /CryptFilter';
13996 if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
13997 // The method used
13998 $out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
13999 if ($this->encryptdata['pubkey']) {
14000 $out .= ' /Recipients [';
14001 foreach ($this->encryptdata['Recipients'] as $rec) {
14002 $out .= ' <'.$rec.'>';
14003 }
14004 $out .= ' ]';
14005 if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
14006 $out .= ' /EncryptMetadata false';
14007 } else {
14008 $out .= ' /EncryptMetadata true';
14009 }
14010 }
14011 } else {
14012 $out .= ' /CFM /None';
14013 }
14014 if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
14015 // The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
14016 $out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
14017 } else {
14018 $out .= ' /AuthEvent /DocOpen';
14019 }
14020 if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
14021 // The bit length of the encryption key.
14022 $out .= ' /Length '.$this->encryptdata['CF']['Length'];
14023 }
14024 $out .= ' >> >>';
14025 }
14026 // The name of the crypt filter that shall be used by default when decrypting streams.
14027 $out .= ' /StmF /'.$this->encryptdata['StmF'];
14028 // The name of the crypt filter that shall be used when decrypting all strings in the document.
14029 $out .= ' /StrF /'.$this->encryptdata['StrF'];
14030 if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
14031 // The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
14032 $out .= ' /EFF /'.$this->encryptdata[''];
14033 }
14034 }
14035 // Additional encryption dictionary entries for the standard security handler
14036 if ($this->encryptdata['pubkey']) {
14037 if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
14038 $out .= ' /Recipients [';
14039 foreach ($this->encryptdata['Recipients'] as $rec) {
14040 $out .= ' <'.$rec.'>';
14041 }
14042 $out .= ' ]';
14043 }
14044 } else {
14045 $out .= ' /R';
14046 if ($this->encryptdata['V'] == 5) { // AES-256
14047 $out .= ' 5';
14048 $out .= ' /OE ('.$this->_escape($this->encryptdata['OE']).')';
14049 $out .= ' /UE ('.$this->_escape($this->encryptdata['UE']).')';
14050 $out .= ' /Perms ('.$this->_escape($this->encryptdata['perms']).')';
14051 } elseif ($this->encryptdata['V'] == 4) { // AES-128
14052 $out .= ' 4';
14053 } elseif ($this->encryptdata['V'] < 2) { // RC-40
14054 $out .= ' 2';
14055 } else { // RC-128
14056 $out .= ' 3';
14057 }
14058 $out .= ' /O ('.$this->_escape($this->encryptdata['O']).')';
14059 $out .= ' /U ('.$this->_escape($this->encryptdata['U']).')';
14060 $out .= ' /P '.$this->encryptdata['P'];
14061 if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
14062 $out .= ' /EncryptMetadata false';
14063 } else {
14064 $out .= ' /EncryptMetadata true';
14065 }
14066 }
14067 $out .= ' >>';
14068 $out .= "\n".'endobj';
14069 $this->_out($out);
14070 }
14071
14072 /**
14073 * Returns the input text encrypted using RC4 algorithm and the specified key.
14074 * RC4 is the standard encryption algorithm used in PDF format
14075 * @param $key (string) encryption key
14076 * @param $text (String) input text to be encrypted
14077 * @return String encrypted text
14078 * @protected
14079 * @since 2.0.000 (2008-01-02)
14080 * @author Klemen Vodopivec, Nicola Asuni
14081 */
14082 protected function _RC4($key, $text) {
14083 if (function_exists('mcrypt_decrypt') AND ($out = @mcrypt_decrypt(MCRYPT_ARCFOUR, $key, $text, MCRYPT_MODE_STREAM, ''))) {
14084 // try to use mcrypt function if exist
14085 return $out;
14086 }
14087 if ($this->last_enc_key != $key) {
14088 $k = str_repeat($key, ((256 / strlen($key)) + 1));
14089 $rc4 = range(0, 255);
14090 $j = 0;
14091 for ($i = 0; $i < 256; ++$i) {
14092 $t = $rc4[$i];
14093 $j = ($j + $t + ord($k[$i])) % 256;
14094 $rc4[$i] = $rc4[$j];
14095 $rc4[$j] = $t;
14096 }
14097 $this->last_enc_key = $key;
14098 $this->last_enc_key_c = $rc4;
14099 } else {
14100 $rc4 = $this->last_enc_key_c;
14101 }
14102 $len = strlen($text);
14103 $a = 0;
14104 $b = 0;
14105 $out = '';
14106 for ($i = 0; $i < $len; ++$i) {
14107 $a = ($a + 1) % 256;
14108 $t = $rc4[$a];
14109 $b = ($b + $t) % 256;
14110 $rc4[$a] = $rc4[$b];
14111 $rc4[$b] = $t;
14112 $k = $rc4[($rc4[$a] + $rc4[$b]) % 256];
14113 $out .= chr(ord($text[$i]) ^ $k);
14114 }
14115 return $out;
14116 }
14117
14118 /**
14119 * Returns the input text exrypted using AES algorithm and the specified key.
14120 * This method requires mcrypt.
14121 * @param $key (string) encryption key
14122 * @param $text (String) input text to be encrypted
14123 * @return String encrypted text
14124 * @protected
14125 * @author Nicola Asuni
14126 * @since 5.0.005 (2010-05-11)
14127 */
14128 protected function _AES($key, $text) {
14129 // padding (RFC 2898, PKCS #5: Password-Based Cryptography Specification Version 2.0)
14130 $padding = 16 - (strlen($text) % 16);
14131 $text .= str_repeat(chr($padding), $padding);
14132 $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
14133 $text = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);
14134 $text = $iv.$text;
14135 return $text;
14136 }
14137
14138 /**
14139 * Encrypts a string using MD5 and returns it's value as a binary string.
14140 * @param $str (string) input string
14141 * @return String MD5 encrypted binary string
14142 * @protected
14143 * @since 2.0.000 (2008-01-02)
14144 * @author Klemen Vodopivec
14145 */
14146 protected function _md5_16($str) {
14147 return pack('H*', md5($str));
14148 }
14149
14150 /**
14151 * Compute U value (used for encryption)
14152 * @return string U value
14153 * @protected
14154 * @since 2.0.000 (2008-01-02)
14155 * @author Nicola Asuni
14156 */
14157 protected function _Uvalue() {
14158 if ($this->encryptdata['mode'] == 0) { // RC4-40
14159 return $this->_RC4($this->encryptdata['key'], $this->enc_padding);
14160 } elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
14161 $tmp = $this->_md5_16($this->enc_padding.$this->encryptdata['fileid']);
14162 $enc = $this->_RC4($this->encryptdata['key'], $tmp);
14163 $len = strlen($tmp);
14164 for ($i = 1; $i <= 19; ++$i) {
14165 $ek = '';
14166 for ($j = 0; $j < $len; ++$j) {
14167 $ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
14168 }
14169 $enc = $this->_RC4($ek, $enc);
14170 }
14171 $enc .= str_repeat("\x00", 16);
14172 return substr($enc, 0, 32);
14173 } elseif ($this->encryptdata['mode'] == 3) { // AES-256
14174 $seed = $this->_md5_16($this->getRandomSeed());
14175 // User Validation Salt
14176 $this->encryptdata['UVS'] = substr($seed, 0, 8);
14177 // User Key Salt
14178 $this->encryptdata['UKS'] = substr($seed, 8, 16);
14179 return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
14180 }
14181 }
14182
14183 /**
14184 * Compute UE value (used for encryption)
14185 * @return string UE value
14186 * @protected
14187 * @since 5.9.006 (2010-10-19)
14188 * @author Nicola Asuni
14189 */
14190 protected function _UEvalue() {
14191 $hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
14192 $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
14193 return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
14194 }
14195
14196 /**
14197 * Compute O value (used for encryption)
14198 * @return string O value
14199 * @protected
14200 * @since 2.0.000 (2008-01-02)
14201 * @author Nicola Asuni
14202 */
14203 protected function _Ovalue() {
14204 if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
14205 $tmp = $this->_md5_16($this->encryptdata['owner_password']);
14206 if ($this->encryptdata['mode'] > 0) {
14207 for ($i = 0; $i < 50; ++$i) {
14208 $tmp = $this->_md5_16($tmp);
14209 }
14210 }
14211 $owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
14212 $enc = $this->_RC4($owner_key, $this->encryptdata['user_password']);
14213 if ($this->encryptdata['mode'] > 0) {
14214 $len = strlen($owner_key);
14215 for ($i = 1; $i <= 19; ++$i) {
14216 $ek = '';
14217 for ($j = 0; $j < $len; ++$j) {
14218 $ek .= chr(ord($owner_key[$j]) ^ $i);
14219 }
14220 $enc = $this->_RC4($ek, $enc);
14221 }
14222 }
14223 return $enc;
14224 } elseif ($this->encryptdata['mode'] == 3) { // AES-256
14225 $seed = $this->_md5_16($this->getRandomSeed());
14226 // Owner Validation Salt
14227 $this->encryptdata['OVS'] = substr($seed, 0, 8);
14228 // Owner Key Salt
14229 $this->encryptdata['OKS'] = substr($seed, 8, 16);
14230 return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
14231 }
14232 }
14233
14234 /**
14235 * Compute OE value (used for encryption)
14236 * @return string OE value
14237 * @protected
14238 * @since 5.9.006 (2010-10-19)
14239 * @author Nicola Asuni
14240 */
14241 protected function _OEvalue() {
14242 $hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
14243 $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
14244 return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
14245 }
14246
14247 /**
14248 * Convert password for AES-256 encryption mode
14249 * @param $password (string) password
14250 * @return string password
14251 * @protected
14252 * @since 5.9.006 (2010-10-19)
14253 * @author Nicola Asuni
14254 */
14255 protected function _fixAES256Password($password) {
14256 $psw = ''; // password to be returned
14257 $psw_array = $this->utf8Bidi($this->UTF8StringToArray($password), $password, $this->rtl);
14258 foreach ($psw_array as $c) {
14259 $psw .= $this->unichr($c);
14260 }
14261 return substr($psw, 0, 127);
14262 }
14263
14264 /**
14265 * Compute encryption key
14266 * @protected
14267 * @since 2.0.000 (2008-01-02)
14268 * @author Nicola Asuni
14269 */
14270 protected function _generateencryptionkey() {
14271 $keybytelen = ($this->encryptdata['Length'] / 8);
14272 if (!$this->encryptdata['pubkey']) { // standard mode
14273 if ($this->encryptdata['mode'] == 3) { // AES-256
14274 // generate 256 bit random key
14275 $this->encryptdata['key'] = substr(hash('sha256', $this->getRandomSeed(), true), 0, $keybytelen);
14276 // truncate passwords
14277 $this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
14278 $this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
14279 // Compute U value
14280 $this->encryptdata['U'] = $this->_Uvalue();
14281 // Compute UE value
14282 $this->encryptdata['UE'] = $this->_UEvalue();
14283 // Compute O value
14284 $this->encryptdata['O'] = $this->_Ovalue();
14285 // Compute OE value
14286 $this->encryptdata['OE'] = $this->_OEvalue();
14287 // Compute P value
14288 $this->encryptdata['P'] = $this->encryptdata['protection'];
14289 // Computing the encryption dictionary's Perms (permissions) value
14290 $perms = $this->getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
14291 $perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
14292 if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
14293 $perms .= 'F';
14294 } else {
14295 $perms .= 'T';
14296 }
14297 $perms .= 'adb'; // bytes 9-11
14298 $perms .= 'nick'; // bytes 12-15
14299 $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB));
14300 $this->encryptdata['perms'] = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->encryptdata['key'], $perms, MCRYPT_MODE_ECB, $iv);
14301 } else { // RC4-40, RC4-128, AES-128
14302 // Pad passwords
14303 $this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].$this->enc_padding, 0, 32);
14304 $this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].$this->enc_padding, 0, 32);
14305 // Compute O value
14306 $this->encryptdata['O'] = $this->_Ovalue();
14307 // get default permissions (reverse byte order)
14308 $permissions = $this->getEncPermissionsString($this->encryptdata['protection']);
14309 // Compute encryption key
14310 $tmp = $this->_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
14311 if ($this->encryptdata['mode'] > 0) {
14312 for ($i = 0; $i < 50; ++$i) {
14313 $tmp = $this->_md5_16(substr($tmp, 0, $keybytelen));
14314 }
14315 }
14316 $this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
14317 // Compute U value
14318 $this->encryptdata['U'] = $this->_Uvalue();
14319 // Compute P value
14320 $this->encryptdata['P'] = $this->encryptdata['protection'];
14321 }
14322 } else { // Public-Key mode
14323 // random 20-byte seed
14324 $seed = sha1($this->getRandomSeed(), true);
14325 $recipient_bytes = '';
14326 foreach ($this->encryptdata['pubkeys'] as $pubkey) {
14327 // for each public certificate
14328 if (isset($pubkey['p'])) {
14329 $pkprotection = $this->getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
14330 } else {
14331 $pkprotection = $this->encryptdata['protection'];
14332 }
14333 // get default permissions (reverse byte order)
14334 $pkpermissions = $this->getEncPermissionsString($pkprotection);
14335 // envelope data
14336 $envelope = $seed.$pkpermissions;
14337 // write the envelope data to a temporary file
14338 $tempkeyfile = tempnam(K_PATH_CACHE, 'tmpkey_');
14339 $f = fopen($tempkeyfile, 'wb');
14340 if (!$f) {
14341 $this->Error('Unable to create temporary key file: '.$tempkeyfile);
14342 }
14343 $envelope_length = strlen($envelope);
14344 fwrite($f, $envelope, $envelope_length);
14345 fclose($f);
14346 $tempencfile = tempnam(K_PATH_CACHE, 'tmpenc_');
14347 if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
14348 $this->Error('Unable to encrypt the file: '.$tempkeyfile);
14349 }
14350 unlink($tempkeyfile);
14351 // read encryption signature
14352 $signature = file_get_contents($tempencfile, false, null, $envelope_length);
14353 unlink($tempencfile);
14354 // extract signature
14355 $signature = substr($signature, strpos($signature, 'Content-Disposition'));
14356 $tmparr = explode("\n\n", $signature);
14357 $signature = trim($tmparr[1]);
14358 unset($tmparr);
14359 // decode signature
14360 $signature = base64_decode($signature);
14361 // convert signature to hex
14362 $hexsignature = current(unpack('H*', $signature));
14363 // store signature on recipients array
14364 $this->encryptdata['Recipients'][] = $hexsignature;
14365 // The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
14366 $recipient_bytes .= $signature;
14367 }
14368 // calculate encryption key
14369 if ($this->encryptdata['mode'] == 3) { // AES-256
14370 $this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
14371 } else { // RC4-40, RC4-128, AES-128
14372 $this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
14373 }
14374 }
14375 }
14376
14377 /**
14378 * Return the premission code used on encryption (P value).
14379 * @param $permissions (Array) the set of permissions (specify the ones you want to block).
14380 * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
14381 * @protected
14382 * @since 5.0.005 (2010-05-12)
14383 * @author Nicola Asuni
14384 */
14385 protected function getUserPermissionCode($permissions, $mode=0) {
14386 $options = array(
14387 'owner' => 2, // bit 2 -- inverted logic: cleared by default
14388 'print' => 4, // bit 3
14389 'modify' => 8, // bit 4
14390 'copy' => 16, // bit 5
14391 'annot-forms' => 32, // bit 6
14392 'fill-forms' => 256, // bit 9
14393 'extract' => 512, // bit 10
14394 'assemble' => 1024,// bit 11
14395 'print-high' => 2048 // bit 12
14396 );
14397 $protection = 2147422012; // 32 bit: (01111111 11111111 00001111 00111100)
14398 foreach ($permissions as $permission) {
14399 if (!isset($options[$permission])) {
14400 $this->Error('Incorrect permission: '.$permission);
14401 }
14402 if (($mode > 0) OR ($options[$permission] <= 32)) {
14403 // set only valid permissions
14404 if ($options[$permission] == 2) {
14405 // the logic for bit 2 is inverted (cleared by default)
14406 $protection += $options[$permission];
14407 } else {
14408 $protection -= $options[$permission];
14409 }
14410 }
14411 }
14412 return $protection;
14413 }
14414
14415 /**
14416 * Set document protection
14417 * Remark: the protection against modification is for people who have the full Acrobat product.
14418 * 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.
14419 * 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.
14420 * @param $permissions (Array) the set of permissions (specify the ones you want to block):<ul><li>print : Print the document;</li><li>modify : Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';</li><li>copy : Copy or otherwise extract text and graphics from the document;</li><li>annot-forms : Add or modify text annotations, fill in interactive form fields, and, if 'modify' is also set, create or modify interactive form fields (including signature fields);</li><li>fill-forms : Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified;</li><li>extract : Extract text and graphics (in support of accessibility to users with disabilities or for other purposes);</li><li>assemble : Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set;</li><li>print-high : Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.</li><li>owner : (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.</li></ul>
14421 * @param $user_pass (String) user password. Empty by default.
14422 * @param $owner_pass (String) owner password. If not specified, a random value is used.
14423 * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
14424 * @param $pubkeys (String) array of recipients containing public-key certificates ('c') and permissions ('p'). For example: array(array('c' => 'file://../tcpdf.crt', 'p' => array('print')))
14425 * @public
14426 * @since 2.0.000 (2008-01-02)
14427 * @author Nicola Asuni
14428 */
14429 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) {
14430 if ($this->pdfa_mode) {
14431 // encryption is not allowed in PDF/A mode
14432 return;
14433 }
14434 $this->encryptdata['protection'] = $this->getUserPermissionCode($permissions, $mode);
14435 if (($pubkeys !== null) AND (is_array($pubkeys))) {
14436 // public-key mode
14437 $this->encryptdata['pubkeys'] = $pubkeys;
14438 if ($mode == 0) {
14439 // public-Key Security requires at least 128 bit
14440 $mode = 1;
14441 }
14442 if (!function_exists('openssl_pkcs7_encrypt')) {
14443 $this->Error('Public-Key Security requires openssl library.');
14444 }
14445 // Set Public-Key filter (availabe are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
14446 $this->encryptdata['pubkey'] = true;
14447 $this->encryptdata['Filter'] = 'Adobe.PubSec';
14448 $this->encryptdata['StmF'] = 'DefaultCryptFilter';
14449 $this->encryptdata['StrF'] = 'DefaultCryptFilter';
14450 } else {
14451 // standard mode (password mode)
14452 $this->encryptdata['pubkey'] = false;
14453 $this->encryptdata['Filter'] = 'Standard';
14454 $this->encryptdata['StmF'] = 'StdCF';
14455 $this->encryptdata['StrF'] = 'StdCF';
14456 }
14457 if ($mode > 1) { // AES
14458 if (!extension_loaded('mcrypt')) {
14459 $this->Error('AES encryption requires mcrypt library (http://www.php.net/manual/en/mcrypt.requirements.php).');
14460 }
14461 if (mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
14462 $this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
14463 }
14464 if (($mode == 3) AND !function_exists('hash')) {
14465 // the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
14466 $this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
14467 }
14468 }
14469 if ($owner_pass === null) {
14470 $owner_pass = md5($this->getRandomSeed());
14471 }
14472 $this->encryptdata['user_password'] = $user_pass;
14473 $this->encryptdata['owner_password'] = $owner_pass;
14474 $this->encryptdata['mode'] = $mode;
14475 switch ($mode) {
14476 case 0: { // RC4 40 bit
14477 $this->encryptdata['V'] = 1;
14478 $this->encryptdata['Length'] = 40;
14479 $this->encryptdata['CF']['CFM'] = 'V2';
14480 break;
14481 }
14482 case 1: { // RC4 128 bit
14483 $this->encryptdata['V'] = 2;
14484 $this->encryptdata['Length'] = 128;
14485 $this->encryptdata['CF']['CFM'] = 'V2';
14486 if ($this->encryptdata['pubkey']) {
14487 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
14488 $this->encryptdata['Recipients'] = array();
14489 }
14490 break;
14491 }
14492 case 2: { // AES 128 bit
14493 $this->encryptdata['V'] = 4;
14494 $this->encryptdata['Length'] = 128;
14495 $this->encryptdata['CF']['CFM'] = 'AESV2';
14496 $this->encryptdata['CF']['Length'] = 128;
14497 if ($this->encryptdata['pubkey']) {
14498 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
14499 $this->encryptdata['Recipients'] = array();
14500 }
14501 break;
14502 }
14503 case 3: { // AES 256 bit
14504 $this->encryptdata['V'] = 5;
14505 $this->encryptdata['Length'] = 256;
14506 $this->encryptdata['CF']['CFM'] = 'AESV3';
14507 $this->encryptdata['CF']['Length'] = 256;
14508 if ($this->encryptdata['pubkey']) {
14509 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
14510 $this->encryptdata['Recipients'] = array();
14511 }
14512 break;
14513 }
14514 }
14515 $this->encrypted = true;
14516 $this->encryptdata['fileid'] = $this->convertHexStringToString($this->file_id);
14517 $this->_generateencryptionkey();
14518 }
14519
14520 /**
14521 * Convert hexadecimal string to string
14522 * @param $bs (string) byte-string to convert
14523 * @return String
14524 * @protected
14525 * @since 5.0.005 (2010-05-12)
14526 * @author Nicola Asuni
14527 */
14528 protected function convertHexStringToString($bs) {
14529 $string = ''; // string to be returned
14530 $bslength = strlen($bs);
14531 if (($bslength % 2) != 0) {
14532 // padding
14533 $bs .= '0';
14534 ++$bslength;
14535 }
14536 for ($i = 0; $i < $bslength; $i += 2) {
14537 $string .= chr(hexdec($bs[$i].$bs[($i + 1)]));
14538 }
14539 return $string;
14540 }
14541
14542 /**
14543 * Convert string to hexadecimal string (byte string)
14544 * @param $s (string) string to convert
14545 * @return byte string
14546 * @protected
14547 * @since 5.0.010 (2010-05-17)
14548 * @author Nicola Asuni
14549 */
14550 protected function convertStringToHexString($s) {
14551 $bs = '';
14552 $chars = preg_split('//', $s, -1, PREG_SPLIT_NO_EMPTY);
14553 foreach ($chars as $c) {
14554 $bs .= sprintf('%02s', dechex(ord($c)));
14555 }
14556 return $bs;
14557 }
14558
14559 /**
14560 * Convert encryption P value to a string of bytes, low-order byte first.
14561 * @param $protection (string) 32bit encryption permission value (P value)
14562 * @return String
14563 * @protected
14564 * @since 5.0.005 (2010-05-12)
14565 * @author Nicola Asuni
14566 */
14567 protected function getEncPermissionsString($protection) {
14568 $binprot = sprintf('%032b', $protection);
14569 $str = chr(bindec(substr($binprot, 24, 8)));
14570 $str .= chr(bindec(substr($binprot, 16, 8)));
14571 $str .= chr(bindec(substr($binprot, 8, 8)));
14572 $str .= chr(bindec(substr($binprot, 0, 8)));
14573 return $str;
14574 }
14575
14576 // END OF ENCRYPTION FUNCTIONS -------------------------
14577
14578 // START TRANSFORMATIONS SECTION -----------------------
14579
14580 /**
14581 * Starts a 2D tranformation saving current graphic state.
14582 * This function must be called before scaling, mirroring, translation, rotation and skewing.
14583 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
14584 * @public
14585 * @since 2.1.000 (2008-01-07)
14586 * @see StartTransform(), StopTransform()
14587 */
14588 public function StartTransform() {
14589 $this->_out('q');
14590 if ($this->inxobj) {
14591 // we are inside an XObject template
14592 $this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
14593 } else {
14594 $this->transfmrk[$this->page][] = $this->pagelen[$this->page];
14595 }
14596 ++$this->transfmatrix_key;
14597 $this->transfmatrix[$this->transfmatrix_key] = array();
14598 }
14599
14600 /**
14601 * Stops a 2D tranformation restoring previous graphic state.
14602 * This function must be called after scaling, mirroring, translation, rotation and skewing.
14603 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
14604 * @public
14605 * @since 2.1.000 (2008-01-07)
14606 * @see StartTransform(), StopTransform()
14607 */
14608 public function StopTransform() {
14609 $this->_out('Q');
14610 if (isset($this->transfmatrix[$this->transfmatrix_key])) {
14611 array_pop($this->transfmatrix[$this->transfmatrix_key]);
14612 --$this->transfmatrix_key;
14613 }
14614 if ($this->inxobj) {
14615 // we are inside an XObject template
14616 array_pop($this->xobjects[$this->xobjid]['transfmrk']);
14617 } else {
14618 array_pop($this->transfmrk[$this->page]);
14619 }
14620 }
14621 /**
14622 * Horizontal Scaling.
14623 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
14624 * @param $x (int) abscissa of the scaling center. Default is current x position
14625 * @param $y (int) ordinate of the scaling center. Default is current y position
14626 * @public
14627 * @since 2.1.000 (2008-01-07)
14628 * @see StartTransform(), StopTransform()
14629 */
14630 public function ScaleX($s_x, $x='', $y='') {
14631 $this->Scale($s_x, 100, $x, $y);
14632 }
14633
14634 /**
14635 * Vertical Scaling.
14636 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
14637 * @param $x (int) abscissa of the scaling center. Default is current x position
14638 * @param $y (int) ordinate of the scaling center. Default is current y position
14639 * @public
14640 * @since 2.1.000 (2008-01-07)
14641 * @see StartTransform(), StopTransform()
14642 */
14643 public function ScaleY($s_y, $x='', $y='') {
14644 $this->Scale(100, $s_y, $x, $y);
14645 }
14646
14647 /**
14648 * Vertical and horizontal proportional Scaling.
14649 * @param $s (float) scaling factor for width and height as percent. 0 is not allowed.
14650 * @param $x (int) abscissa of the scaling center. Default is current x position
14651 * @param $y (int) ordinate of the scaling center. Default is current y position
14652 * @public
14653 * @since 2.1.000 (2008-01-07)
14654 * @see StartTransform(), StopTransform()
14655 */
14656 public function ScaleXY($s, $x='', $y='') {
14657 $this->Scale($s, $s, $x, $y);
14658 }
14659
14660 /**
14661 * Vertical and horizontal non-proportional Scaling.
14662 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
14663 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
14664 * @param $x (int) abscissa of the scaling center. Default is current x position
14665 * @param $y (int) ordinate of the scaling center. Default is current y position
14666 * @public
14667 * @since 2.1.000 (2008-01-07)
14668 * @see StartTransform(), StopTransform()
14669 */
14670 public function Scale($s_x, $s_y, $x='', $y='') {
14671 if ($x === '') {
14672 $x = $this->x;
14673 }
14674 if ($y === '') {
14675 $y = $this->y;
14676 }
14677 if (($s_x == 0) OR ($s_y == 0)) {
14678 $this->Error('Please do not use values equal to zero for scaling');
14679 }
14680 $y = ($this->h - $y) * $this->k;
14681 $x *= $this->k;
14682 //calculate elements of transformation matrix
14683 $s_x /= 100;
14684 $s_y /= 100;
14685 $tm = array();
14686 $tm[0] = $s_x;
14687 $tm[1] = 0;
14688 $tm[2] = 0;
14689 $tm[3] = $s_y;
14690 $tm[4] = $x * (1 - $s_x);
14691 $tm[5] = $y * (1 - $s_y);
14692 //scale the coordinate system
14693 $this->Transform($tm);
14694 }
14695
14696 /**
14697 * Horizontal Mirroring.
14698 * @param $x (int) abscissa of the point. Default is current x position
14699 * @public
14700 * @since 2.1.000 (2008-01-07)
14701 * @see StartTransform(), StopTransform()
14702 */
14703 public function MirrorH($x='') {
14704 $this->Scale(-100, 100, $x);
14705 }
14706
14707 /**
14708 * Verical Mirroring.
14709 * @param $y (int) ordinate of the point. Default is current y position
14710 * @public
14711 * @since 2.1.000 (2008-01-07)
14712 * @see StartTransform(), StopTransform()
14713 */
14714 public function MirrorV($y='') {
14715 $this->Scale(100, -100, '', $y);
14716 }
14717
14718 /**
14719 * Point reflection mirroring.
14720 * @param $x (int) abscissa of the point. Default is current x position
14721 * @param $y (int) ordinate of the point. Default is current y position
14722 * @public
14723 * @since 2.1.000 (2008-01-07)
14724 * @see StartTransform(), StopTransform()
14725 */
14726 public function MirrorP($x='',$y='') {
14727 $this->Scale(-100, -100, $x, $y);
14728 }
14729
14730 /**
14731 * Reflection against a straight line through point (x, y) with the gradient angle (angle).
14732 * @param $angle (float) gradient angle of the straight line. Default is 0 (horizontal line).
14733 * @param $x (int) abscissa of the point. Default is current x position
14734 * @param $y (int) ordinate of the point. Default is current y position
14735 * @public
14736 * @since 2.1.000 (2008-01-07)
14737 * @see StartTransform(), StopTransform()
14738 */
14739 public function MirrorL($angle=0, $x='',$y='') {
14740 $this->Scale(-100, 100, $x, $y);
14741 $this->Rotate(-2*($angle-90), $x, $y);
14742 }
14743
14744 /**
14745 * Translate graphic object horizontally.
14746 * @param $t_x (int) movement to the right (or left for RTL)
14747 * @public
14748 * @since 2.1.000 (2008-01-07)
14749 * @see StartTransform(), StopTransform()
14750 */
14751 public function TranslateX($t_x) {
14752 $this->Translate($t_x, 0);
14753 }
14754
14755 /**
14756 * Translate graphic object vertically.
14757 * @param $t_y (int) movement to the bottom
14758 * @public
14759 * @since 2.1.000 (2008-01-07)
14760 * @see StartTransform(), StopTransform()
14761 */
14762 public function TranslateY($t_y) {
14763 $this->Translate(0, $t_y);
14764 }
14765
14766 /**
14767 * Translate graphic object horizontally and vertically.
14768 * @param $t_x (int) movement to the right
14769 * @param $t_y (int) movement to the bottom
14770 * @public
14771 * @since 2.1.000 (2008-01-07)
14772 * @see StartTransform(), StopTransform()
14773 */
14774 public function Translate($t_x, $t_y) {
14775 //calculate elements of transformation matrix
14776 $tm = array();
14777 $tm[0] = 1;
14778 $tm[1] = 0;
14779 $tm[2] = 0;
14780 $tm[3] = 1;
14781 $tm[4] = $t_x * $this->k;
14782 $tm[5] = -$t_y * $this->k;
14783 //translate the coordinate system
14784 $this->Transform($tm);
14785 }
14786
14787 /**
14788 * Rotate object.
14789 * @param $angle (float) angle in degrees for counter-clockwise rotation
14790 * @param $x (int) abscissa of the rotation center. Default is current x position
14791 * @param $y (int) ordinate of the rotation center. Default is current y position
14792 * @public
14793 * @since 2.1.000 (2008-01-07)
14794 * @see StartTransform(), StopTransform()
14795 */
14796 public function Rotate($angle, $x='', $y='') {
14797 if ($x === '') {
14798 $x = $this->x;
14799 }
14800 if ($y === '') {
14801 $y = $this->y;
14802 }
14803 $y = ($this->h - $y) * $this->k;
14804 $x *= $this->k;
14805 //calculate elements of transformation matrix
14806 $tm = array();
14807 $tm[0] = cos(deg2rad($angle));
14808 $tm[1] = sin(deg2rad($angle));
14809 $tm[2] = -$tm[1];
14810 $tm[3] = $tm[0];
14811 $tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
14812 $tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
14813 //rotate the coordinate system around ($x,$y)
14814 $this->Transform($tm);
14815 }
14816
14817 /**
14818 * Skew horizontally.
14819 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
14820 * @param $x (int) abscissa of the skewing center. default is current x position
14821 * @param $y (int) ordinate of the skewing center. default is current y position
14822 * @public
14823 * @since 2.1.000 (2008-01-07)
14824 * @see StartTransform(), StopTransform()
14825 */
14826 public function SkewX($angle_x, $x='', $y='') {
14827 $this->Skew($angle_x, 0, $x, $y);
14828 }
14829
14830 /**
14831 * Skew vertically.
14832 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
14833 * @param $x (int) abscissa of the skewing center. default is current x position
14834 * @param $y (int) ordinate of the skewing center. default is current y position
14835 * @public
14836 * @since 2.1.000 (2008-01-07)
14837 * @see StartTransform(), StopTransform()
14838 */
14839 public function SkewY($angle_y, $x='', $y='') {
14840 $this->Skew(0, $angle_y, $x, $y);
14841 }
14842
14843 /**
14844 * Skew.
14845 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
14846 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
14847 * @param $x (int) abscissa of the skewing center. default is current x position
14848 * @param $y (int) ordinate of the skewing center. default is current y position
14849 * @public
14850 * @since 2.1.000 (2008-01-07)
14851 * @see StartTransform(), StopTransform()
14852 */
14853 public function Skew($angle_x, $angle_y, $x='', $y='') {
14854 if ($x === '') {
14855 $x = $this->x;
14856 }
14857 if ($y === '') {
14858 $y = $this->y;
14859 }
14860 if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
14861 $this->Error('Please use values between -90 and +90 degrees for Skewing.');
14862 }
14863 $x *= $this->k;
14864 $y = ($this->h - $y) * $this->k;
14865 //calculate elements of transformation matrix
14866 $tm = array();
14867 $tm[0] = 1;
14868 $tm[1] = tan(deg2rad($angle_y));
14869 $tm[2] = tan(deg2rad($angle_x));
14870 $tm[3] = 1;
14871 $tm[4] = -$tm[2] * $y;
14872 $tm[5] = -$tm[1] * $x;
14873 //skew the coordinate system
14874 $this->Transform($tm);
14875 }
14876
14877 /**
14878 * Apply graphic transformations.
14879 * @param $tm (array) transformation matrix
14880 * @protected
14881 * @since 2.1.000 (2008-01-07)
14882 * @see StartTransform(), StopTransform()
14883 */
14884 protected function Transform($tm) {
14885 $this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
14886 // add tranformation matrix
14887 $this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
14888 // update transformation mark
14889 if ($this->inxobj) {
14890 // we are inside an XObject template
14891 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
14892 $key = key($this->xobjects[$this->xobjid]['transfmrk']);
14893 $this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
14894 }
14895 } elseif (end($this->transfmrk[$this->page]) !== false) {
14896 $key = key($this->transfmrk[$this->page]);
14897 $this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
14898 }
14899 }
14900
14901 // END TRANSFORMATIONS SECTION -------------------------
14902
14903 // START GRAPHIC FUNCTIONS SECTION ---------------------
14904 // The following section is based on the code provided by David Hernandez Sanz
14905
14906 /**
14907 * 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.
14908 * @param $width (float) The width.
14909 * @public
14910 * @since 1.0
14911 * @see Line(), Rect(), Cell(), MultiCell()
14912 */
14913 public function SetLineWidth($width) {
14914 //Set line width
14915 $this->LineWidth = $width;
14916 $this->linestyleWidth = sprintf('%F w', ($width * $this->k));
14917 if ($this->page > 0) {
14918 $this->_out($this->linestyleWidth);
14919 }
14920 }
14921
14922 /**
14923 * Returns the current the line width.
14924 * @return int Line width
14925 * @public
14926 * @since 2.1.000 (2008-01-07)
14927 * @see Line(), SetLineWidth()
14928 */
14929 public function GetLineWidth() {
14930 return $this->LineWidth;
14931 }
14932
14933 /**
14934 * Set line style.
14935 * @param $style (array) Line style. Array with keys among the following:
14936 * <ul>
14937 * <li>width (float): Width of the line in user units.</li>
14938 * <li>cap (string): Type of cap to put on the line. Possible values are:
14939 * butt, round, square. The difference between "square" and "butt" is that
14940 * "square" projects a flat end past the end of the line.</li>
14941 * <li>join (string): Type of join. Possible values are: miter, round,
14942 * bevel.</li>
14943 * <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
14944 * series of length values, which are the lengths of the on and off dashes.
14945 * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
14946 * 1 off, 2 on, 1 off, ...</li>
14947 * <li>phase (integer): Modifier on the dash pattern which is used to shift
14948 * the point at which the pattern starts.</li>
14949 * <li>color (array): Draw color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K).</li>
14950 * </ul>
14951 * @param $ret (boolean) if true do not send the command.
14952 * @return string the PDF command
14953 * @public
14954 * @since 2.1.000 (2008-01-08)
14955 */
14956 public function SetLineStyle($style, $ret=false) {
14957 $s = ''; // string to be returned
14958 if (!is_array($style)) {
14959 return;
14960 }
14961 if (isset($style['width'])) {
14962 $this->LineWidth = $style['width'];
14963 $this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
14964 $s .= $this->linestyleWidth.' ';
14965 }
14966 if (isset($style['cap'])) {
14967 $ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
14968 if (isset($ca[$style['cap']])) {
14969 $this->linestyleCap = $ca[$style['cap']].' J';
14970 $s .= $this->linestyleCap.' ';
14971 }
14972 }
14973 if (isset($style['join'])) {
14974 $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
14975 if (isset($ja[$style['join']])) {
14976 $this->linestyleJoin = $ja[$style['join']].' j';
14977 $s .= $this->linestyleJoin.' ';
14978 }
14979 }
14980 if (isset($style['dash'])) {
14981 $dash_string = '';
14982 if ($style['dash']) {
14983 if (preg_match('/^.+,/', $style['dash']) > 0) {
14984 $tab = explode(',', $style['dash']);
14985 } else {
14986 $tab = array($style['dash']);
14987 }
14988 $dash_string = '';
14989 foreach ($tab as $i => $v) {
14990 if ($i) {
14991 $dash_string .= ' ';
14992 }
14993 $dash_string .= sprintf('%F', $v);
14994 }
14995 }
14996 if (!isset($style['phase']) OR !$style['dash']) {
14997 $style['phase'] = 0;
14998 }
14999 $this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
15000 $s .= $this->linestyleDash.' ';
15001 }
15002 if (isset($style['color'])) {
15003 $s .= $this->SetDrawColorArray($style['color'], true).' ';
15004 }
15005 if (!$ret) {
15006 $this->_out($s);
15007 }
15008 return $s;
15009 }
15010
15011 /**
15012 * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
15013 * @param $x (float) Abscissa of point.
15014 * @param $y (float) Ordinate of point.
15015 * @protected
15016 * @since 2.1.000 (2008-01-08)
15017 */
15018 protected function _outPoint($x, $y) {
15019 $this->_out(sprintf('%F %F m', $x * $this->k, ($this->h - $y) * $this->k));
15020 }
15021
15022 /**
15023 * Append a straight line segment from the current point to the point (x, y).
15024 * The new current point shall be (x, y).
15025 * @param $x (float) Abscissa of end point.
15026 * @param $y (float) Ordinate of end point.
15027 * @protected
15028 * @since 2.1.000 (2008-01-08)
15029 */
15030 protected function _outLine($x, $y) {
15031 $this->_out(sprintf('%F %F l', $x * $this->k, ($this->h - $y) * $this->k));
15032 }
15033
15034 /**
15035 * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
15036 * @param $x (float) Abscissa of upper-left corner.
15037 * @param $y (float) Ordinate of upper-left corner.
15038 * @param $w (float) Width.
15039 * @param $h (float) Height.
15040 * @param $op (string) options
15041 * @protected
15042 * @since 2.1.000 (2008-01-08)
15043 */
15044 protected function _outRect($x, $y, $w, $h, $op) {
15045 $this->_out(sprintf('%F %F %F %F re %s', $x * $this->k, ($this->h - $y) * $this->k, $w * $this->k, -$h * $this->k, $op));
15046 }
15047
15048 /**
15049 * Append a cubic B�zier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x2, y2) as the B�zier control points.
15050 * The new current point shall be (x3, y3).
15051 * @param $x1 (float) Abscissa of control point 1.
15052 * @param $y1 (float) Ordinate of control point 1.
15053 * @param $x2 (float) Abscissa of control point 2.
15054 * @param $y2 (float) Ordinate of control point 2.
15055 * @param $x3 (float) Abscissa of end point.
15056 * @param $y3 (float) Ordinate of end point.
15057 * @protected
15058 * @since 2.1.000 (2008-01-08)
15059 */
15060 protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
15061 $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));
15062 }
15063
15064 /**
15065 * Append a cubic B�zier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using the current point and (x2, y2) as the B�zier control points.
15066 * The new current point shall be (x3, y3).
15067 * @param $x2 (float) Abscissa of control point 2.
15068 * @param $y2 (float) Ordinate of control point 2.
15069 * @param $x3 (float) Abscissa of end point.
15070 * @param $y3 (float) Ordinate of end point.
15071 * @protected
15072 * @since 4.9.019 (2010-04-26)
15073 */
15074 protected function _outCurveV($x2, $y2, $x3, $y3) {
15075 $this->_out(sprintf('%F %F %F %F v', $x2 * $this->k, ($this->h - $y2) * $this->k, $x3 * $this->k, ($this->h - $y3) * $this->k));
15076 }
15077
15078 /**
15079 * Append a cubic B�zier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x3, y3) as the B�zier control points.
15080 * The new current point shall be (x3, y3).
15081 * @param $x1 (float) Abscissa of control point 1.
15082 * @param $y1 (float) Ordinate of control point 1.
15083 * @param $x3 (float) Abscissa of end point.
15084 * @param $y3 (float) Ordinate of end point.
15085 * @protected
15086 * @since 2.1.000 (2008-01-08)
15087 */
15088 protected function _outCurveY($x1, $y1, $x3, $y3) {
15089 $this->_out(sprintf('%F %F %F %F y', $x1 * $this->k, ($this->h - $y1) * $this->k, $x3 * $this->k, ($this->h - $y3) * $this->k));
15090 }
15091
15092 /**
15093 * Draws a line between two points.
15094 * @param $x1 (float) Abscissa of first point.
15095 * @param $y1 (float) Ordinate of first point.
15096 * @param $x2 (float) Abscissa of second point.
15097 * @param $y2 (float) Ordinate of second point.
15098 * @param $style (array) Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
15099 * @public
15100 * @since 1.0
15101 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
15102 */
15103 public function Line($x1, $y1, $x2, $y2, $style=array()) {
15104 if (is_array($style)) {
15105 $this->SetLineStyle($style);
15106 }
15107 $this->_outPoint($x1, $y1);
15108 $this->_outLine($x2, $y2);
15109 $this->_out('S');
15110 }
15111
15112 /**
15113 * Draws a rectangle.
15114 * @param $x (float) Abscissa of upper-left corner.
15115 * @param $y (float) Ordinate of upper-left corner.
15116 * @param $w (float) Width.
15117 * @param $h (float) Height.
15118 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15119 * @param $border_style (array) Border style of rectangle. Array with keys among the following:
15120 * <ul>
15121 * <li>all: Line style of all borders. Array like for SetLineStyle().</li>
15122 * <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
15123 * </ul>
15124 * If a key is not present or is null, not draws the border. Default value: default line style (empty array).
15125 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
15126 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
15127 * @public
15128 * @since 1.0
15129 * @see SetLineStyle()
15130 */
15131 public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
15132 if (!(false === strpos($style, 'F')) AND !empty($fill_color)) {
15133 $this->SetFillColorArray($fill_color);
15134 }
15135 $op = $this->getPathPaintOperator($style);
15136 if ((!$border_style) OR (isset($border_style['all']))) {
15137 if (isset($border_style['all']) AND $border_style['all']) {
15138 $this->SetLineStyle($border_style['all']);
15139 $border_style = array();
15140 }
15141 }
15142 $this->_outRect($x, $y, $w, $h, $op);
15143 if ($border_style) {
15144 $border_style2 = array();
15145 foreach ($border_style as $line => $value) {
15146 $length = strlen($line);
15147 for ($i = 0; $i < $length; ++$i) {
15148 $border_style2[$line[$i]] = $value;
15149 }
15150 }
15151 $border_style = $border_style2;
15152 if (isset($border_style['L']) AND $border_style['L']) {
15153 $this->Line($x, $y, $x, $y + $h, $border_style['L']);
15154 }
15155 if (isset($border_style['T']) AND $border_style['T']) {
15156 $this->Line($x, $y, $x + $w, $y, $border_style['T']);
15157 }
15158 if (isset($border_style['R']) AND $border_style['R']) {
15159 $this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
15160 }
15161 if (isset($border_style['B']) AND $border_style['B']) {
15162 $this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
15163 }
15164 }
15165 }
15166
15167 /**
15168 * Draws a Bezier curve.
15169 * The Bezier curve is a tangent to the line between the control points at
15170 * either end of the curve.
15171 * @param $x0 (float) Abscissa of start point.
15172 * @param $y0 (float) Ordinate of start point.
15173 * @param $x1 (float) Abscissa of control point 1.
15174 * @param $y1 (float) Ordinate of control point 1.
15175 * @param $x2 (float) Abscissa of control point 2.
15176 * @param $y2 (float) Ordinate of control point 2.
15177 * @param $x3 (float) Abscissa of end point.
15178 * @param $y3 (float) Ordinate of end point.
15179 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15180 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
15181 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
15182 * @public
15183 * @see SetLineStyle()
15184 * @since 2.1.000 (2008-01-08)
15185 */
15186 public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
15187 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
15188 $this->SetFillColorArray($fill_color);
15189 }
15190 $op = $this->getPathPaintOperator($style);
15191 if ($line_style) {
15192 $this->SetLineStyle($line_style);
15193 }
15194 $this->_outPoint($x0, $y0);
15195 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
15196 $this->_out($op);
15197 }
15198
15199 /**
15200 * Draws a poly-Bezier curve.
15201 * Each Bezier curve segment is a tangent to the line between the control points at
15202 * either end of the curve.
15203 * @param $x0 (float) Abscissa of start point.
15204 * @param $y0 (float) Ordinate of start point.
15205 * @param $segments (float) An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
15206 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15207 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
15208 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
15209 * @public
15210 * @see SetLineStyle()
15211 * @since 3.0008 (2008-05-12)
15212 */
15213 public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
15214 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
15215 $this->SetFillColorArray($fill_color);
15216 }
15217 $op = $this->getPathPaintOperator($style);
15218 if ($op == 'f') {
15219 $line_style = array();
15220 }
15221 if ($line_style) {
15222 $this->SetLineStyle($line_style);
15223 }
15224 $this->_outPoint($x0, $y0);
15225 foreach ($segments as $segment) {
15226 list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
15227 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
15228 }
15229 $this->_out($op);
15230 }
15231
15232 /**
15233 * Draws an ellipse.
15234 * An ellipse is formed from n Bezier curves.
15235 * @param $x0 (float) Abscissa of center point.
15236 * @param $y0 (float) Ordinate of center point.
15237 * @param $rx (float) Horizontal radius.
15238 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
15239 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
15240 * @param $astart: (float) Angle start of draw line. Default value: 0.
15241 * @param $afinish: (float) Angle finish of draw line. Default value: 360.
15242 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15243 * @param $line_style (array) Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
15244 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
15245 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
15246 * @author Nicola Asuni
15247 * @public
15248 * @since 2.1.000 (2008-01-08)
15249 */
15250 public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
15251 if ($this->empty_string($ry) OR ($ry == 0)) {
15252 $ry = $rx;
15253 }
15254 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
15255 $this->SetFillColorArray($fill_color);
15256 }
15257 $op = $this->getPathPaintOperator($style);
15258 if ($op == 'f') {
15259 $line_style = array();
15260 }
15261 if ($line_style) {
15262 $this->SetLineStyle($line_style);
15263 }
15264 $this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
15265 $this->_out($op);
15266 }
15267
15268 /**
15269 * Append an elliptical arc to the current path.
15270 * An ellipse is formed from n Bezier curves.
15271 * @param $xc (float) Abscissa of center point.
15272 * @param $yc (float) Ordinate of center point.
15273 * @param $rx (float) Horizontal radius.
15274 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
15275 * @param $xang: (float) Angle between the X-axis and the major axis of the ellipse. Default value: 0.
15276 * @param $angs: (float) Angle start of draw line. Default value: 0.
15277 * @param $angf: (float) Angle finish of draw line. Default value: 360.
15278 * @param $pie (boolean) if true do not mark the border point (used to draw pie sectors).
15279 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
15280 * @param $startpoint (boolean) if true output a starting point.
15281 * @param $ccw (boolean) if true draws in counter-clockwise.
15282 * @param $svg (boolean) if true the angles are in svg mode (already calculated).
15283 * @return array bounding box coordinates (x min, y min, x max, y max)
15284 * @author Nicola Asuni
15285 * @protected
15286 * @since 4.9.019 (2010-04-26)
15287 */
15288 protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
15289 $k = $this->k;
15290 if ($nc < 2) {
15291 $nc = 2;
15292 }
15293 $xmin = 2147483647;
15294 $ymin = 2147483647;
15295 $xmax = 0;
15296 $ymax = 0;
15297 if ($pie) {
15298 // center of the arc
15299 $this->_outPoint($xc, $yc);
15300 }
15301 $xang = deg2rad((float) $xang);
15302 $angs = deg2rad((float) $angs);
15303 $angf = deg2rad((float) $angf);
15304 if ($svg) {
15305 $as = $angs;
15306 $af = $angf;
15307 } else {
15308 $as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
15309 $af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
15310 }
15311 if ($as < 0) {
15312 $as += (2 * M_PI);
15313 }
15314 if ($af < 0) {
15315 $af += (2 * M_PI);
15316 }
15317 if ($ccw AND ($as > $af)) {
15318 // reverse rotation
15319 $as -= (2 * M_PI);
15320 } elseif (!$ccw AND ($as < $af)) {
15321 // reverse rotation
15322 $af -= (2 * M_PI);
15323 }
15324 $total_angle = ($af - $as);
15325 if ($nc < 2) {
15326 $nc = 2;
15327 }
15328 // total arcs to draw
15329 $nc *= (2 * abs($total_angle) / M_PI);
15330 $nc = round($nc) + 1;
15331 // angle of each arc
15332 $arcang = ($total_angle / $nc);
15333 // center point in PDF coordinates
15334 $x0 = $xc;
15335 $y0 = ($this->h - $yc);
15336 // starting angle
15337 $ang = $as;
15338 $alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
15339 $cos_xang = cos($xang);
15340 $sin_xang = sin($xang);
15341 $cos_ang = cos($ang);
15342 $sin_ang = sin($ang);
15343 // first arc point
15344 $px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
15345 $py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
15346 // first Bezier control point
15347 $qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
15348 $qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
15349 if ($pie) {
15350 // line from center to arc starting point
15351 $this->_outLine($px1, $this->h - $py1);
15352 } elseif ($startpoint) {
15353 // arc starting point
15354 $this->_outPoint($px1, $this->h - $py1);
15355 }
15356 // draw arcs
15357 for ($i = 1; $i <= $nc; ++$i) {
15358 // starting angle
15359 $ang = $as + ($i * $arcang);
15360 if ($i == $nc) {
15361 $ang = $af;
15362 }
15363 $cos_ang = cos($ang);
15364 $sin_ang = sin($ang);
15365 // second arc point
15366 $px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
15367 $py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
15368 // second Bezier control point
15369 $qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
15370 $qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
15371 // draw arc
15372 $cx1 = ($px1 + $qx1);
15373 $cy1 = ($this->h - ($py1 + $qy1));
15374 $cx2 = ($px2 - $qx2);
15375 $cy2 = ($this->h - ($py2 - $qy2));
15376 $cx3 = $px2;
15377 $cy3 = ($this->h - $py2);
15378 $this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
15379 // get bounding box coordinates
15380 $xmin = min($xmin, $cx1, $cx2, $cx3);
15381 $ymin = min($ymin, $cy1, $cy2, $cy3);
15382 $xmax = max($xmax, $cx1, $cx2, $cx3);
15383 $ymax = max($ymax, $cy1, $cy2, $cy3);
15384 // move to next point
15385 $px1 = $px2;
15386 $py1 = $py2;
15387 $qx1 = $qx2;
15388 $qy1 = $qy2;
15389 }
15390 if ($pie) {
15391 $this->_outLine($xc, $yc);
15392 // get bounding box coordinates
15393 $xmin = min($xmin, $xc);
15394 $ymin = min($ymin, $yc);
15395 $xmax = max($xmax, $xc);
15396 $ymax = max($ymax, $yc);
15397 }
15398 return array($xmin, $ymin, $xmax, $ymax);
15399 }
15400
15401 /**
15402 * Draws a circle.
15403 * A circle is formed from n Bezier curves.
15404 * @param $x0 (float) Abscissa of center point.
15405 * @param $y0 (float) Ordinate of center point.
15406 * @param $r (float) Radius.
15407 * @param $angstr: (float) Angle start of draw line. Default value: 0.
15408 * @param $angend: (float) Angle finish of draw line. Default value: 360.
15409 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15410 * @param $line_style (array) Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
15411 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
15412 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of circle.
15413 * @public
15414 * @since 2.1.000 (2008-01-08)
15415 */
15416 public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
15417 $this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
15418 }
15419
15420 /**
15421 * Draws a polygonal line
15422 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
15423 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15424 * @param $line_style (array) Line style of polygon. Array with keys among the following:
15425 * <ul>
15426 * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
15427 * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
15428 * </ul>
15429 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
15430 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
15431 * @since 4.8.003 (2009-09-15)
15432 * @public
15433 */
15434 public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
15435 $this->Polygon($p, $style, $line_style, $fill_color, false);
15436 }
15437
15438 /**
15439 * Draws a polygon.
15440 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
15441 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15442 * @param $line_style (array) Line style of polygon. Array with keys among the following:
15443 * <ul>
15444 * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
15445 * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
15446 * </ul>
15447 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
15448 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
15449 * @param $closed (boolean) if true the polygon is closes, otherwise will remain open
15450 * @public
15451 * @since 2.1.000 (2008-01-08)
15452 */
15453 public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
15454 $nc = count($p); // number of coordinates
15455 $np = $nc / 2; // number of points
15456 if ($closed) {
15457 // close polygon by adding the first 2 points at the end (one line)
15458 for ($i = 0; $i < 4; ++$i) {
15459 $p[$nc + $i] = $p[$i];
15460 }
15461 // copy style for the last added line
15462 if (isset($line_style[0])) {
15463 $line_style[$np] = $line_style[0];
15464 }
15465 $nc += 4;
15466 }
15467 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
15468 $this->SetFillColorArray($fill_color);
15469 }
15470 $op = $this->getPathPaintOperator($style);
15471 if ($op == 'f') {
15472 $line_style = array();
15473 }
15474 $draw = true;
15475 if ($line_style) {
15476 if (isset($line_style['all'])) {
15477 $this->SetLineStyle($line_style['all']);
15478 } else {
15479 $draw = false;
15480 if ($op == 'B') {
15481 // draw fill
15482 $op = 'f';
15483 $this->_outPoint($p[0], $p[1]);
15484 for ($i = 2; $i < $nc; $i = $i + 2) {
15485 $this->_outLine($p[$i], $p[$i + 1]);
15486 }
15487 $this->_out($op);
15488 }
15489 // draw outline
15490 $this->_outPoint($p[0], $p[1]);
15491 for ($i = 2; $i < $nc; $i = $i + 2) {
15492 $line_num = ($i / 2) - 1;
15493 if (isset($line_style[$line_num])) {
15494 if ($line_style[$line_num] != 0) {
15495 if (is_array($line_style[$line_num])) {
15496 $this->_out('S');
15497 $this->SetLineStyle($line_style[$line_num]);
15498 $this->_outPoint($p[$i - 2], $p[$i - 1]);
15499 $this->_outLine($p[$i], $p[$i + 1]);
15500 $this->_out('S');
15501 $this->_outPoint($p[$i], $p[$i + 1]);
15502 } else {
15503 $this->_outLine($p[$i], $p[$i + 1]);
15504 }
15505 }
15506 } else {
15507 $this->_outLine($p[$i], $p[$i + 1]);
15508 }
15509 }
15510 $this->_out($op);
15511 }
15512 }
15513 if ($draw) {
15514 $this->_outPoint($p[0], $p[1]);
15515 for ($i = 2; $i < $nc; $i = $i + 2) {
15516 $this->_outLine($p[$i], $p[$i + 1]);
15517 }
15518 $this->_out($op);
15519 }
15520 }
15521
15522 /**
15523 * Draws a regular polygon.
15524 * @param $x0 (float) Abscissa of center point.
15525 * @param $y0 (float) Ordinate of center point.
15526 * @param $r: (float) Radius of inscribed circle.
15527 * @param $ns (integer) Number of sides.
15528 * @param $angle (float) Angle oriented (anti-clockwise). Default value: 0.
15529 * @param $draw_circle (boolean) Draw inscribed circle or not. Default value: false.
15530 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15531 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
15532 * <ul>
15533 * <li>all: Line style of all sides. Array like for SetLineStyle().</li>
15534 * <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
15535 * </ul>
15536 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
15537 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
15538 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
15539 * <ul>
15540 * <li>D or empty string: Draw (default).</li>
15541 * <li>F: Fill.</li>
15542 * <li>DF or FD: Draw and fill.</li>
15543 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
15544 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
15545 * </ul>
15546 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
15547 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
15548 * @public
15549 * @since 2.1.000 (2008-01-08)
15550 */
15551 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()) {
15552 if (3 > $ns) {
15553 $ns = 3;
15554 }
15555 if ($draw_circle) {
15556 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
15557 }
15558 $p = array();
15559 for ($i = 0; $i < $ns; ++$i) {
15560 $a = $angle + ($i * 360 / $ns);
15561 $a_rad = deg2rad((float) $a);
15562 $p[] = $x0 + ($r * sin($a_rad));
15563 $p[] = $y0 + ($r * cos($a_rad));
15564 }
15565 $this->Polygon($p, $style, $line_style, $fill_color);
15566 }
15567
15568 /**
15569 * Draws a star polygon
15570 * @param $x0 (float) Abscissa of center point.
15571 * @param $y0 (float) Ordinate of center point.
15572 * @param $r (float) Radius of inscribed circle.
15573 * @param $nv (integer) Number of vertices.
15574 * @param $ng (integer) Number of gap (if ($ng % $nv = 1) then is a regular polygon).
15575 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
15576 * @param $draw_circle: (boolean) Draw inscribed circle or not. Default value is false.
15577 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15578 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
15579 * <ul>
15580 * <li>all: Line style of all sides. Array like for
15581 * SetLineStyle().</li>
15582 * <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
15583 * </ul>
15584 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
15585 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
15586 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
15587 * <ul>
15588 * <li>D or empty string: Draw (default).</li>
15589 * <li>F: Fill.</li>
15590 * <li>DF or FD: Draw and fill.</li>
15591 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
15592 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
15593 * </ul>
15594 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
15595 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
15596 * @public
15597 * @since 2.1.000 (2008-01-08)
15598 */
15599 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()) {
15600 if ($nv < 2) {
15601 $nv = 2;
15602 }
15603 if ($draw_circle) {
15604 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
15605 }
15606 $p2 = array();
15607 $visited = array();
15608 for ($i = 0; $i < $nv; ++$i) {
15609 $a = $angle + ($i * 360 / $nv);
15610 $a_rad = deg2rad((float) $a);
15611 $p2[] = $x0 + ($r * sin($a_rad));
15612 $p2[] = $y0 + ($r * cos($a_rad));
15613 $visited[] = false;
15614 }
15615 $p = array();
15616 $i = 0;
15617 do {
15618 $p[] = $p2[$i * 2];
15619 $p[] = $p2[($i * 2) + 1];
15620 $visited[$i] = true;
15621 $i += $ng;
15622 $i %= $nv;
15623 } while (!$visited[$i]);
15624 $this->Polygon($p, $style, $line_style, $fill_color);
15625 }
15626
15627 /**
15628 * Draws a rounded rectangle.
15629 * @param $x (float) Abscissa of upper-left corner.
15630 * @param $y (float) Ordinate of upper-left corner.
15631 * @param $w (float) Width.
15632 * @param $h (float) Height.
15633 * @param $r (float) the radius of the circle used to round off the corners of the rectangle.
15634 * @param $round_corner (string) Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
15635 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15636 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
15637 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
15638 * @public
15639 * @since 2.1.000 (2008-01-08)
15640 */
15641 public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
15642 $this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
15643 }
15644
15645 /**
15646 * Draws a rounded rectangle.
15647 * @param $x (float) Abscissa of upper-left corner.
15648 * @param $y (float) Ordinate of upper-left corner.
15649 * @param $w (float) Width.
15650 * @param $h (float) Height.
15651 * @param $rx (float) the x-axis radius of the ellipse used to round off the corners of the rectangle.
15652 * @param $ry (float) the y-axis radius of the ellipse used to round off the corners of the rectangle.
15653 * @param $round_corner (string) Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
15654 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
15655 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
15656 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
15657 * @public
15658 * @since 4.9.019 (2010-04-22)
15659 */
15660 public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
15661 if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
15662 // Not rounded
15663 $this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
15664 return;
15665 }
15666 // Rounded
15667 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
15668 $this->SetFillColorArray($fill_color);
15669 }
15670 $op = $this->getPathPaintOperator($style);
15671 if ($op == 'f') {
15672 $border_style = array();
15673 }
15674 if ($border_style) {
15675 $this->SetLineStyle($border_style);
15676 }
15677 $MyArc = 4 / 3 * (sqrt(2) - 1);
15678 $this->_outPoint($x + $rx, $y);
15679 $xc = $x + $w - $rx;
15680 $yc = $y + $ry;
15681 $this->_outLine($xc, $y);
15682 if ($round_corner[0]) {
15683 $this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
15684 } else {
15685 $this->_outLine($x + $w, $y);
15686 }
15687 $xc = $x + $w - $rx;
15688 $yc = $y + $h - $ry;
15689 $this->_outLine($x + $w, $yc);
15690 if ($round_corner[1]) {
15691 $this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
15692 } else {
15693 $this->_outLine($x + $w, $y + $h);
15694 }
15695 $xc = $x + $rx;
15696 $yc = $y + $h - $ry;
15697 $this->_outLine($xc, $y + $h);
15698 if ($round_corner[2]) {
15699 $this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
15700 } else {
15701 $this->_outLine($x, $y + $h);
15702 }
15703 $xc = $x + $rx;
15704 $yc = $y + $ry;
15705 $this->_outLine($x, $yc);
15706 if ($round_corner[3]) {
15707 $this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
15708 } else {
15709 $this->_outLine($x, $y);
15710 $this->_outLine($x + $rx, $y);
15711 }
15712 $this->_out($op);
15713 }
15714
15715 /**
15716 * Draws a grahic arrow.
15717 * @param $x0 (float) Abscissa of first point.
15718 * @param $y0 (float) Ordinate of first point.
15719 * @param $x1 (float) Abscissa of second point.
15720 * @param $y1 (float) Ordinate of second point.
15721 * @param $head_style (int) (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
15722 * @param $arm_size (float) length of arrowhead arms
15723 * @param $arm_angle (int) angle between an arm and the shaft
15724 * @author Piotr Galecki, Nicola Asuni, Andy Meier
15725 * @since 4.6.018 (2009-07-10)
15726 */
15727 public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
15728 // getting arrow direction angle
15729 // 0 deg angle is when both arms go along X axis. angle grows clockwise.
15730 $dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
15731 if ($dir_angle < 0) {
15732 $dir_angle += (2 * M_PI);
15733 }
15734 $arm_angle = deg2rad($arm_angle);
15735 $sx1 = $x1;
15736 $sy1 = $y1;
15737 if ($head_style > 0) {
15738 // calculate the stopping point for the arrow shaft
15739 $sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
15740 $sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
15741 }
15742 // main arrow line / shaft
15743 $this->Line($x0, $y0, $sx1, $sy1);
15744 // left arrowhead arm tip
15745 $x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
15746 $y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
15747 // right arrowhead arm tip
15748 $x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
15749 $y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
15750 $mode = 'D';
15751 $style = array();
15752 switch ($head_style) {
15753 case 0: {
15754 // draw only arrowhead arms
15755 $mode = 'D';
15756 $style = array(1, 1, 0);
15757 break;
15758 }
15759 case 1: {
15760 // draw closed arrowhead, but no fill
15761 $mode = 'D';
15762 break;
15763 }
15764 case 2: {
15765 // closed and filled arrowhead
15766 $mode = 'DF';
15767 break;
15768 }
15769 case 3: {
15770 // filled arrowhead
15771 $mode = 'F';
15772 break;
15773 }
15774 }
15775 $this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
15776 }
15777
15778 // END GRAPHIC FUNCTIONS SECTION -----------------------
15779
15780 // BIDIRECTIONAL TEXT SECTION --------------------------
15781
15782 /**
15783 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
15784 * @param $str (string) string to manipulate.
15785 * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF)
15786 * @param $forcertl (bool) if true forces RTL text direction
15787 * @return string
15788 * @protected
15789 * @author Nicola Asuni
15790 * @since 2.1.000 (2008-01-08)
15791 */
15792 protected function utf8StrRev($str, $setbom=false, $forcertl=false) {
15793 return $this->utf8StrArrRev($this->UTF8StringToArray($str), $str, $setbom, $forcertl);
15794 }
15795
15796 /**
15797 * Reverse the RLT substrings array using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
15798 * @param $arr (array) array of unicode values.
15799 * @param $str (string) string to manipulate (or empty value).
15800 * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF)
15801 * @param $forcertl (bool) if true forces RTL text direction
15802 * @return string
15803 * @protected
15804 * @author Nicola Asuni
15805 * @since 4.9.000 (2010-03-27)
15806 */
15807 protected function utf8StrArrRev($arr, $str='', $setbom=false, $forcertl=false) {
15808 return $this->arrUTF8ToUTF16BE($this->utf8Bidi($arr, $str, $forcertl), $setbom);
15809 }
15810
15811 /**
15812 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
15813 * @param $ta (array) array of characters composing the string.
15814 * @param $str (string) string to process
15815 * @param $forcertl (bool) if 'R' forces RTL, if 'L' forces LTR
15816 * @return array of unicode chars
15817 * @author Nicola Asuni
15818 * @protected
15819 * @since 2.4.000 (2008-03-06)
15820 */
15821 protected function utf8Bidi($ta, $str='', $forcertl=false) {
15822 // paragraph embedding level
15823 $pel = 0;
15824 // max level
15825 $maxlevel = 0;
15826 if ($this->empty_string($str)) {
15827 // create string from array
15828 $str = $this->UTF8ArrSubString($ta);
15829 }
15830 // check if string contains arabic text
15831 if (preg_match($this->unicode->uni_RE_PATTERN_ARABIC, $str)) {
15832 $arabic = true;
15833 } else {
15834 $arabic = false;
15835 }
15836 // check if string contains RTL text
15837 if (!($forcertl OR $arabic OR preg_match($this->unicode->uni_RE_PATTERN_RTL, $str))) {
15838 return $ta;
15839 }
15840
15841 // get number of chars
15842 $numchars = count($ta);
15843
15844 if ($forcertl == 'R') {
15845 $pel = 1;
15846 } elseif ($forcertl == 'L') {
15847 $pel = 0;
15848 } else {
15849 // P2. In each paragraph, find the first character of type L, AL, or R.
15850 // P3. If a character is found in P2 and it is of type AL or R, then set the paragraph embedding level to one; otherwise, set it to zero.
15851 for ($i=0; $i < $numchars; ++$i) {
15852 $type = $this->unicode->uni_type[$ta[$i]];
15853 if ($type == 'L') {
15854 $pel = 0;
15855 break;
15856 } elseif (($type == 'AL') OR ($type == 'R')) {
15857 $pel = 1;
15858 break;
15859 }
15860 }
15861 }
15862
15863 // Current Embedding Level
15864 $cel = $pel;
15865 // directional override status
15866 $dos = 'N';
15867 $remember = array();
15868 // start-of-level-run
15869 $sor = $pel % 2 ? 'R' : 'L';
15870 $eor = $sor;
15871
15872 // Array of characters data
15873 $chardata = Array();
15874
15875 // X1. Begin by setting the current embedding level to the paragraph embedding level. Set the directional override status to neutral. Process each character iteratively, applying rules X2 through X9. Only embedding levels from 0 to 61 are valid in this phase.
15876 // In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
15877 for ($i=0; $i < $numchars; ++$i) {
15878 if ($ta[$i] == $this->unicode->uni_RLE) {
15879 // X2. With each RLE, compute the least greater odd embedding level.
15880 // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
15881 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
15882 $next_level = $cel + ($cel % 2) + 1;
15883 if ($next_level < 62) {
15884 $remember[] = array('num' => $this->unicode->uni_RLE, 'cel' => $cel, 'dos' => $dos);
15885 $cel = $next_level;
15886 $dos = 'N';
15887 $sor = $eor;
15888 $eor = $cel % 2 ? 'R' : 'L';
15889 }
15890 } elseif ($ta[$i] == $this->unicode->uni_LRE) {
15891 // X3. With each LRE, compute the least greater even embedding level.
15892 // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
15893 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
15894 $next_level = $cel + 2 - ($cel % 2);
15895 if ( $next_level < 62 ) {
15896 $remember[] = array('num' => $this->unicode->uni_LRE, 'cel' => $cel, 'dos' => $dos);
15897 $cel = $next_level;
15898 $dos = 'N';
15899 $sor = $eor;
15900 $eor = $cel % 2 ? 'R' : 'L';
15901 }
15902 } elseif ($ta[$i] == $this->unicode->uni_RLO) {
15903 // X4. With each RLO, compute the least greater odd embedding level.
15904 // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to right-to-left.
15905 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
15906 $next_level = $cel + ($cel % 2) + 1;
15907 if ($next_level < 62) {
15908 $remember[] = array('num' => $this->unicode->uni_RLO, 'cel' => $cel, 'dos' => $dos);
15909 $cel = $next_level;
15910 $dos = 'R';
15911 $sor = $eor;
15912 $eor = $cel % 2 ? 'R' : 'L';
15913 }
15914 } elseif ($ta[$i] == $this->unicode->uni_LRO) {
15915 // X5. With each LRO, compute the least greater even embedding level.
15916 // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to left-to-right.
15917 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
15918 $next_level = $cel + 2 - ($cel % 2);
15919 if ( $next_level < 62 ) {
15920 $remember[] = array('num' => $this->unicode->uni_LRO, 'cel' => $cel, 'dos' => $dos);
15921 $cel = $next_level;
15922 $dos = 'L';
15923 $sor = $eor;
15924 $eor = $cel % 2 ? 'R' : 'L';
15925 }
15926 } elseif ($ta[$i] == $this->unicode->uni_PDF) {
15927 // X7. With each PDF, determine the matching embedding or override code. If there was a valid matching code, restore (pop) the last remembered (pushed) embedding level and directional override.
15928 if (count($remember)) {
15929 $last = count($remember ) - 1;
15930 if (($remember[$last]['num'] == $this->unicode->uni_RLE) OR
15931 ($remember[$last]['num'] == $this->unicode->uni_LRE) OR
15932 ($remember[$last]['num'] == $this->unicode->uni_RLO) OR
15933 ($remember[$last]['num'] == $this->unicode->uni_LRO)) {
15934 $match = array_pop($remember);
15935 $cel = $match['cel'];
15936 $dos = $match['dos'];
15937 $sor = $eor;
15938 $eor = ($cel > $match['cel'] ? $cel : $match['cel']) % 2 ? 'R' : 'L';
15939 }
15940 }
15941 } elseif (($ta[$i] != $this->unicode->uni_RLE) AND
15942 ($ta[$i] != $this->unicode->uni_LRE) AND
15943 ($ta[$i] != $this->unicode->uni_RLO) AND
15944 ($ta[$i] != $this->unicode->uni_LRO) AND
15945 ($ta[$i] != $this->unicode->uni_PDF)) {
15946 // X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
15947 // a. Set the level of the current character to the current embedding level.
15948 // b. Whenever the directional override status is not neutral, reset the current character type to the directional override status.
15949 if ($dos != 'N') {
15950 $chardir = $dos;
15951 } else {
15952 if (isset($this->unicode->uni_type[$ta[$i]])) {
15953 $chardir = $this->unicode->uni_type[$ta[$i]];
15954 } else {
15955 $chardir = 'L';
15956 }
15957 }
15958 // stores string characters and other information
15959 $chardata[] = array('char' => $ta[$i], 'level' => $cel, 'type' => $chardir, 'sor' => $sor, 'eor' => $eor);
15960 }
15961 } // end for each char
15962
15963 // X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph. Paragraph separators are not included in the embedding.
15964 // X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes.
15965 // X10. The remaining rules are applied to each run of characters at the same level. For each run, determine the start-of-level-run (sor) and end-of-level-run (eor) type, either L or R. This depends on the higher of the two levels on either side of the boundary (at the start or end of the paragraph, the level of the 'other' run is the base embedding level). If the higher level is odd, the type is R; otherwise, it is L.
15966
15967 // 3.3.3 Resolving Weak Types
15968 // Weak types are now resolved one level run at a time. At level run boundaries where the type of the character on the other side of the boundary is required, the type assigned to sor or eor is used.
15969 // Nonspacing marks are now resolved based on the previous characters.
15970 $numchars = count($chardata);
15971
15972 // W1. Examine each nonspacing mark (NSM) in the level run, and change the type of the NSM to the type of the previous character. If the NSM is at the start of the level run, it will get the type of sor.
15973 $prevlevel = -1; // track level changes
15974 $levcount = 0; // counts consecutive chars at the same level
15975 for ($i=0; $i < $numchars; ++$i) {
15976 if ($chardata[$i]['type'] == 'NSM') {
15977 if ($levcount) {
15978 $chardata[$i]['type'] = $chardata[$i]['sor'];
15979 } elseif ($i > 0) {
15980 $chardata[$i]['type'] = $chardata[($i-1)]['type'];
15981 }
15982 }
15983 if ($chardata[$i]['level'] != $prevlevel) {
15984 $levcount = 0;
15985 } else {
15986 ++$levcount;
15987 }
15988 $prevlevel = $chardata[$i]['level'];
15989 }
15990
15991 // W2. Search backward from each instance of a European number until the first strong type (R, L, AL, or sor) is found. If an AL is found, change the type of the European number to Arabic number.
15992 $prevlevel = -1;
15993 $levcount = 0;
15994 for ($i=0; $i < $numchars; ++$i) {
15995 if ($chardata[$i]['char'] == 'EN') {
15996 for ($j=$levcount; $j >= 0; $j--) {
15997 if ($chardata[$j]['type'] == 'AL') {
15998 $chardata[$i]['type'] = 'AN';
15999 } elseif (($chardata[$j]['type'] == 'L') OR ($chardata[$j]['type'] == 'R')) {
16000 break;
16001 }
16002 }
16003 }
16004 if ($chardata[$i]['level'] != $prevlevel) {
16005 $levcount = 0;
16006 } else {
16007 ++$levcount;
16008 }
16009 $prevlevel = $chardata[$i]['level'];
16010 }
16011
16012 // W3. Change all ALs to R.
16013 for ($i=0; $i < $numchars; ++$i) {
16014 if ($chardata[$i]['type'] == 'AL') {
16015 $chardata[$i]['type'] = 'R';
16016 }
16017 }
16018
16019 // W4. A single European separator between two European numbers changes to a European number. A single common separator between two numbers of the same type changes to that type.
16020 $prevlevel = -1;
16021 $levcount = 0;
16022 for ($i=0; $i < $numchars; ++$i) {
16023 if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
16024 if (($chardata[$i]['type'] == 'ES') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
16025 $chardata[$i]['type'] = 'EN';
16026 } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
16027 $chardata[$i]['type'] = 'EN';
16028 } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'AN') AND ($chardata[($i+1)]['type'] == 'AN')) {
16029 $chardata[$i]['type'] = 'AN';
16030 }
16031 }
16032 if ($chardata[$i]['level'] != $prevlevel) {
16033 $levcount = 0;
16034 } else {
16035 ++$levcount;
16036 }
16037 $prevlevel = $chardata[$i]['level'];
16038 }
16039
16040 // W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
16041 $prevlevel = -1;
16042 $levcount = 0;
16043 for ($i=0; $i < $numchars; ++$i) {
16044 if ($chardata[$i]['type'] == 'ET') {
16045 if (($levcount > 0) AND ($chardata[($i-1)]['type'] == 'EN')) {
16046 $chardata[$i]['type'] = 'EN';
16047 } else {
16048 $j = $i+1;
16049 while (($j < $numchars) AND ($chardata[$j]['level'] == $prevlevel)) {
16050 if ($chardata[$j]['type'] == 'EN') {
16051 $chardata[$i]['type'] = 'EN';
16052 break;
16053 } elseif ($chardata[$j]['type'] != 'ET') {
16054 break;
16055 }
16056 ++$j;
16057 }
16058 }
16059 }
16060 if ($chardata[$i]['level'] != $prevlevel) {
16061 $levcount = 0;
16062 } else {
16063 ++$levcount;
16064 }
16065 $prevlevel = $chardata[$i]['level'];
16066 }
16067
16068 // W6. Otherwise, separators and terminators change to Other Neutral.
16069 $prevlevel = -1;
16070 $levcount = 0;
16071 for ($i=0; $i < $numchars; ++$i) {
16072 if (($chardata[$i]['type'] == 'ET') OR ($chardata[$i]['type'] == 'ES') OR ($chardata[$i]['type'] == 'CS')) {
16073 $chardata[$i]['type'] = 'ON';
16074 }
16075 if ($chardata[$i]['level'] != $prevlevel) {
16076 $levcount = 0;
16077 } else {
16078 ++$levcount;
16079 }
16080 $prevlevel = $chardata[$i]['level'];
16081 }
16082
16083 //W7. Search backward from each instance of a European number until the first strong type (R, L, or sor) is found. If an L is found, then change the type of the European number to L.
16084 $prevlevel = -1;
16085 $levcount = 0;
16086 for ($i=0; $i < $numchars; ++$i) {
16087 if ($chardata[$i]['char'] == 'EN') {
16088 for ($j=$levcount; $j >= 0; $j--) {
16089 if ($chardata[$j]['type'] == 'L') {
16090 $chardata[$i]['type'] = 'L';
16091 } elseif ($chardata[$j]['type'] == 'R') {
16092 break;
16093 }
16094 }
16095 }
16096 if ($chardata[$i]['level'] != $prevlevel) {
16097 $levcount = 0;
16098 } else {
16099 ++$levcount;
16100 }
16101 $prevlevel = $chardata[$i]['level'];
16102 }
16103
16104 // N1. A sequence of neutrals takes the direction of the surrounding strong text if the text on both sides has the same direction. European and Arabic numbers act as if they were R in terms of their influence on neutrals. Start-of-level-run (sor) and end-of-level-run (eor) are used at level run boundaries.
16105 $prevlevel = -1;
16106 $levcount = 0;
16107 for ($i=0; $i < $numchars; ++$i) {
16108 if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
16109 if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
16110 $chardata[$i]['type'] = 'L';
16111 } elseif (($chardata[$i]['type'] == 'N') AND
16112 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
16113 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
16114 $chardata[$i]['type'] = 'R';
16115 } elseif ($chardata[$i]['type'] == 'N') {
16116 // N2. Any remaining neutrals take the embedding direction
16117 $chardata[$i]['type'] = $chardata[$i]['sor'];
16118 }
16119 } elseif (($levcount == 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
16120 // first char
16121 if (($chardata[$i]['type'] == 'N') AND ($chardata[$i]['sor'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
16122 $chardata[$i]['type'] = 'L';
16123 } elseif (($chardata[$i]['type'] == 'N') AND
16124 (($chardata[$i]['sor'] == 'R') OR ($chardata[$i]['sor'] == 'EN') OR ($chardata[$i]['sor'] == 'AN')) AND
16125 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
16126 $chardata[$i]['type'] = 'R';
16127 } elseif ($chardata[$i]['type'] == 'N') {
16128 // N2. Any remaining neutrals take the embedding direction
16129 $chardata[$i]['type'] = $chardata[$i]['sor'];
16130 }
16131 } elseif (($levcount > 0) AND ((($i+1) == $numchars) OR (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] != $prevlevel))) {
16132 //last char
16133 if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[$i]['eor'] == 'L')) {
16134 $chardata[$i]['type'] = 'L';
16135 } elseif (($chardata[$i]['type'] == 'N') AND
16136 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
16137 (($chardata[$i]['eor'] == 'R') OR ($chardata[$i]['eor'] == 'EN') OR ($chardata[$i]['eor'] == 'AN'))) {
16138 $chardata[$i]['type'] = 'R';
16139 } elseif ($chardata[$i]['type'] == 'N') {
16140 // N2. Any remaining neutrals take the embedding direction
16141 $chardata[$i]['type'] = $chardata[$i]['sor'];
16142 }
16143 } elseif ($chardata[$i]['type'] == 'N') {
16144 // N2. Any remaining neutrals take the embedding direction
16145 $chardata[$i]['type'] = $chardata[$i]['sor'];
16146 }
16147 if ($chardata[$i]['level'] != $prevlevel) {
16148 $levcount = 0;
16149 } else {
16150 ++$levcount;
16151 }
16152 $prevlevel = $chardata[$i]['level'];
16153 }
16154
16155 // I1. For all characters with an even (left-to-right) embedding direction, those of type R go up one level and those of type AN or EN go up two levels.
16156 // I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
16157 for ($i=0; $i < $numchars; ++$i) {
16158 $odd = $chardata[$i]['level'] % 2;
16159 if ($odd) {
16160 if (($chardata[$i]['type'] == 'L') OR ($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
16161 $chardata[$i]['level'] += 1;
16162 }
16163 } else {
16164 if ($chardata[$i]['type'] == 'R') {
16165 $chardata[$i]['level'] += 1;
16166 } elseif (($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
16167 $chardata[$i]['level'] += 2;
16168 }
16169 }
16170 $maxlevel = max($chardata[$i]['level'],$maxlevel);
16171 }
16172
16173 // L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
16174 // 1. Segment separators,
16175 // 2. Paragraph separators,
16176 // 3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and
16177 // 4. Any sequence of white space characters at the end of the line.
16178 for ($i=0; $i < $numchars; ++$i) {
16179 if (($chardata[$i]['type'] == 'B') OR ($chardata[$i]['type'] == 'S')) {
16180 $chardata[$i]['level'] = $pel;
16181 } elseif ($chardata[$i]['type'] == 'WS') {
16182 $j = $i+1;
16183 while ($j < $numchars) {
16184 if ((($chardata[$j]['type'] == 'B') OR ($chardata[$j]['type'] == 'S')) OR
16185 (($j == ($numchars-1)) AND ($chardata[$j]['type'] == 'WS'))) {
16186 $chardata[$i]['level'] = $pel;
16187 break;
16188 } elseif ($chardata[$j]['type'] != 'WS') {
16189 break;
16190 }
16191 ++$j;
16192 }
16193 }
16194 }
16195
16196 // Arabic Shaping
16197 // Cursively connected scripts, such as Arabic or Syriac, require the selection of positional character shapes that depend on adjacent characters. Shaping is logically applied after the Bidirectional Algorithm is used and is limited to characters within the same directional run.
16198 if ($arabic) {
16199 $endedletter = array(1569,1570,1571,1572,1573,1575,1577,1583,1584,1585,1586,1608,1688);
16200 $alfletter = array(1570,1571,1573,1575);
16201 $chardata2 = $chardata;
16202 $laaletter = false;
16203 $charAL = array();
16204 $x = 0;
16205 for ($i=0; $i < $numchars; ++$i) {
16206 if (($this->unicode->uni_type[$chardata[$i]['char']] == 'AL') OR ($chardata[$i]['char'] == 32) OR ($chardata[$i]['char'] == 8204)) {
16207 $charAL[$x] = $chardata[$i];
16208 $charAL[$x]['i'] = $i;
16209 $chardata[$i]['x'] = $x;
16210 ++$x;
16211 }
16212 }
16213 $numAL = $x;
16214 for ($i=0; $i < $numchars; ++$i) {
16215 $thischar = $chardata[$i];
16216 if ($i > 0) {
16217 $prevchar = $chardata[($i-1)];
16218 } else {
16219 $prevchar = false;
16220 }
16221 if (($i+1) < $numchars) {
16222 $nextchar = $chardata[($i+1)];
16223 } else {
16224 $nextchar = false;
16225 }
16226 if ($this->unicode->uni_type[$thischar['char']] == 'AL') {
16227 $x = $thischar['x'];
16228 if ($x > 0) {
16229 $prevchar = $charAL[($x-1)];
16230 } else {
16231 $prevchar = false;
16232 }
16233 if (($x+1) < $numAL) {
16234 $nextchar = $charAL[($x+1)];
16235 } else {
16236 $nextchar = false;
16237 }
16238 // if laa letter
16239 if (($prevchar !== false) AND ($prevchar['char'] == 1604) AND (in_array($thischar['char'], $alfletter))) {
16240 $arabicarr = $this->unicode->uni_laa_array;
16241 $laaletter = true;
16242 if ($x > 1) {
16243 $prevchar = $charAL[($x-2)];
16244 } else {
16245 $prevchar = false;
16246 }
16247 } else {
16248 $arabicarr = $this->unicode->uni_arabicsubst;
16249 $laaletter = false;
16250 }
16251 if (($prevchar !== false) AND ($nextchar !== false) AND
16252 (($this->unicode->uni_type[$prevchar['char']] == 'AL') OR ($this->unicode->uni_type[$prevchar['char']] == 'NSM')) AND
16253 (($this->unicode->uni_type[$nextchar['char']] == 'AL') OR ($this->unicode->uni_type[$nextchar['char']] == 'NSM')) AND
16254 ($prevchar['type'] == $thischar['type']) AND
16255 ($nextchar['type'] == $thischar['type']) AND
16256 ($nextchar['char'] != 1567)) {
16257 if (in_array($prevchar['char'], $endedletter)) {
16258 if (isset($arabicarr[$thischar['char']][2])) {
16259 // initial
16260 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
16261 }
16262 } else {
16263 if (isset($arabicarr[$thischar['char']][3])) {
16264 // medial
16265 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][3];
16266 }
16267 }
16268 } elseif (($nextchar !== false) AND
16269 (($this->unicode->uni_type[$nextchar['char']] == 'AL') OR ($this->unicode->uni_type[$nextchar['char']] == 'NSM')) AND
16270 ($nextchar['type'] == $thischar['type']) AND
16271 ($nextchar['char'] != 1567)) {
16272 if (isset($arabicarr[$chardata[$i]['char']][2])) {
16273 // initial
16274 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
16275 }
16276 } elseif ((($prevchar !== false) AND
16277 (($this->unicode->uni_type[$prevchar['char']] == 'AL') OR ($this->unicode->uni_type[$prevchar['char']] == 'NSM')) AND
16278 ($prevchar['type'] == $thischar['type'])) OR
16279 (($nextchar !== false) AND ($nextchar['char'] == 1567))) {
16280 // final
16281 if (($i > 1) AND ($thischar['char'] == 1607) AND
16282 ($chardata[$i-1]['char'] == 1604) AND
16283 ($chardata[$i-2]['char'] == 1604)) {
16284 //Allah Word
16285 // mark characters to delete with false
16286 $chardata2[$i-2]['char'] = false;
16287 $chardata2[$i-1]['char'] = false;
16288 $chardata2[$i]['char'] = 65010;
16289 } else {
16290 if (($prevchar !== false) AND in_array($prevchar['char'], $endedletter)) {
16291 if (isset($arabicarr[$thischar['char']][0])) {
16292 // isolated
16293 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
16294 }
16295 } else {
16296 if (isset($arabicarr[$thischar['char']][1])) {
16297 // final
16298 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][1];
16299 }
16300 }
16301 }
16302 } elseif (isset($arabicarr[$thischar['char']][0])) {
16303 // isolated
16304 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
16305 }
16306 // if laa letter
16307 if ($laaletter) {
16308 // mark characters to delete with false
16309 $chardata2[($charAL[($x-1)]['i'])]['char'] = false;
16310 }
16311 } // end if AL (Arabic Letter)
16312 } // end for each char
16313 /*
16314 * Combining characters that can occur with Arabic Shadda (0651 HEX, 1617 DEC) are replaced.
16315 * Putting the combining mark and shadda in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner.
16316 */
16317 for ($i = 0; $i < ($numchars-1); ++$i) {
16318 if (($chardata2[$i]['char'] == 1617) AND (isset($this->unicode->uni_diacritics[($chardata2[$i+1]['char'])]))) {
16319 // check if the subtitution font is defined on current font
16320 if (isset($this->CurrentFont['cw'][($this->unicode->uni_diacritics[($chardata2[$i+1]['char'])])])) {
16321 $chardata2[$i]['char'] = false;
16322 $chardata2[$i+1]['char'] = $this->unicode->uni_diacritics[($chardata2[$i+1]['char'])];
16323 }
16324 }
16325 }
16326 // remove marked characters
16327 foreach ($chardata2 as $key => $value) {
16328 if ($value['char'] === false) {
16329 unset($chardata2[$key]);
16330 }
16331 }
16332 $chardata = array_values($chardata2);
16333 $numchars = count($chardata);
16334 unset($chardata2);
16335 unset($arabicarr);
16336 unset($laaletter);
16337 unset($charAL);
16338 }
16339
16340 // L2. From the highest level found in the text to the lowest odd level on each line, including intermediate levels not actually present in the text, reverse any contiguous sequence of characters that are at that level or higher.
16341 for ($j=$maxlevel; $j > 0; $j--) {
16342 $ordarray = Array();
16343 $revarr = Array();
16344 $onlevel = false;
16345 for ($i=0; $i < $numchars; ++$i) {
16346 if ($chardata[$i]['level'] >= $j) {
16347 $onlevel = true;
16348 if (isset($this->unicode->uni_mirror[$chardata[$i]['char']])) {
16349 // L4. A character is depicted by a mirrored glyph if and only if (a) the resolved directionality of that character is R, and (b) the Bidi_Mirrored property value of that character is true.
16350 $chardata[$i]['char'] = $this->unicode->uni_mirror[$chardata[$i]['char']];
16351 }
16352 $revarr[] = $chardata[$i];
16353 } else {
16354 if ($onlevel) {
16355 $revarr = array_reverse($revarr);
16356 $ordarray = array_merge($ordarray, $revarr);
16357 $revarr = Array();
16358 $onlevel = false;
16359 }
16360 $ordarray[] = $chardata[$i];
16361 }
16362 }
16363 if ($onlevel) {
16364 $revarr = array_reverse($revarr);
16365 $ordarray = array_merge($ordarray, $revarr);
16366 }
16367 $chardata = $ordarray;
16368 }
16369
16370 $ordarray = array();
16371 for ($i=0; $i < $numchars; ++$i) {
16372 $ordarray[] = $chardata[$i]['char'];
16373 // store char values for subsetting
16374 $this->CurrentFont['subsetchars'][$chardata[$i]['char']] = true;
16375 }
16376 // update font subsetchars
16377 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
16378 return $ordarray;
16379 }
16380
16381 // END OF BIDIRECTIONAL TEXT SECTION -------------------
16382
16383 /**
16384 * Encode a name object.
16385 * @param $name (string) Name object to encode.
16386 * @return (string) Encoded name object.
16387 * @protected
16388 * @author Nicola Asuni
16389 * @since 5.9.097 (2011-06-23)
16390 */
16391 protected function encodeNameObject($name) {
16392 $escname = '';
16393 $length = strlen($name);
16394 for ($i = 0; $i < $length; ++$i) {
16395 $chr = $name[$i];
16396 if (preg_match('/[0-9a-zA-Z]/', $chr) == 1) {
16397 $escname .= $chr;
16398 } else {
16399 $escname .= sprintf('#%02X', ord($chr));
16400 }
16401 }
16402 return $escname;
16403 }
16404
16405 /**
16406 * Add a Named Destination.
16407 * NOTE: destination names are unique, so only last entry will be saved.
16408 * @param $name (string) Destination name.
16409 * @param $y (float) Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
16410 * @param $page (int) Target page number (leave empty for current page).
16411 * @return (string) Stripped named destination identifier or false in case of error.
16412 * @public
16413 * @author Christian Deligant, Nicola Asuni
16414 * @since 5.9.097 (2011-06-23)
16415 */
16416 public function setDestination($name, $y=-1, $page='') {
16417 // remove unsupported characters
16418 $name = $this->encodeNameObject($name);
16419 if ($this->empty_string($name)) {
16420 return false;
16421 }
16422 if ($y == -1) {
16423 $y = $this->GetY();
16424 }
16425 if (empty($page)) {
16426 $page = $this->PageNo();
16427 if (empty($page)) {
16428 return;
16429 }
16430 }
16431 $this->dests[$name] = array('y' => $y, 'p' => $page);
16432 return $name;
16433 }
16434
16435 /**
16436 * Return the Named Destination array.
16437 * @return (array) Named Destination array.
16438 * @public
16439 * @author Nicola Asuni
16440 * @since 5.9.097 (2011-06-23)
16441 */
16442 public function getDestination() {
16443 return $this->dests;
16444 }
16445
16446 /**
16447 * Create a javascript PDF string.
16448 * @protected
16449 * @author Johannes G�ntert, Nicola Asuni
16450 * @since 5.9.098 (2011-06-23)
16451 */
16452 protected function _putdests() {
16453 if (empty($this->dests)) {
16454 return;
16455 }
16456 $this->n_dests = $this->_newobj();
16457 $out = ' <<';
16458 foreach($this->dests as $name => $o) {
16459 $out .= ' /'.$name.' '.sprintf('[%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($o['p'])], ($this->pagedim[$o['p']]['h'] - ($o['y'] * $this->k)));
16460 }
16461 $out .= ' >>';
16462 $out .= "\n".'endobj';
16463 $this->_out($out);
16464 }
16465
16466 /**
16467 * Adds a bookmark - alias for Bookmark().
16468 * @param $txt (string) Bookmark description.
16469 * @param $level (int) Bookmark level (minimum value is 0).
16470 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
16471 * @param $page (int) Target page number (leave empty for current page).
16472 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
16473 * @param $color (array) RGB color array (values from 0 to 255).
16474 * @public
16475 */
16476 public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0)) {
16477 $this->Bookmark($txt, $level, $y, $page, $style, $color);
16478 }
16479
16480 /**
16481 * Adds a bookmark.
16482 * @param $txt (string) Bookmark description.
16483 * @param $level (int) Bookmark level (minimum value is 0).
16484 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
16485 * @param $page (int) Target page number (leave empty for current page).
16486 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
16487 * @param $color (array) RGB color array (values from 0 to 255).
16488 * @public
16489 * @author Olivier Plathey, Nicola Asuni
16490 * @since 2.1.002 (2008-02-12)
16491 */
16492 public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0)) {
16493 if ($level < 0) {
16494 $level = 0;
16495 }
16496 if (isset($this->outlines[0])) {
16497 $lastoutline = end($this->outlines);
16498 $maxlevel = $lastoutline['l'] + 1;
16499 } else {
16500 $maxlevel = 0;
16501 }
16502 if ($level > $maxlevel) {
16503 $level = $maxlevel;
16504 }
16505 if ($y == -1) {
16506 $y = $this->GetY();
16507 }
16508 if (empty($page)) {
16509 $page = $this->PageNo();
16510 if (empty($page)) {
16511 return;
16512 }
16513 }
16514 $this->outlines[] = array('t' => $txt, 'l' => $level, 'y' => $y, 'p' => $page, 's' => strtoupper($style), 'c' => $color);
16515 }
16516
16517 /**
16518 * Sort bookmarks for page and key.
16519 * @protected
16520 * @since 5.9.119 (2011-09-19)
16521 */
16522 protected function sortBookmarks() {
16523 // get sorting columns
16524 $outline_p = array();
16525 $outline_y = array();
16526 foreach ($this->outlines as $key => $row) {
16527 $outline_p[$key] = $row['p'];
16528 $outline_k[$key] = $key;
16529 }
16530 // sort outlines by page and original position
16531 array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
16532 }
16533
16534 /**
16535 * Create a bookmark PDF string.
16536 * @protected
16537 * @author Olivier Plathey, Nicola Asuni
16538 * @since 2.1.002 (2008-02-12)
16539 */
16540 protected function _putbookmarks() {
16541 $nb = count($this->outlines);
16542 if ($nb == 0) {
16543 return;
16544 }
16545 // sort bookmarks
16546 $this->sortBookmarks();
16547 $lru = array();
16548 $level = 0;
16549 foreach ($this->outlines as $i => $o) {
16550 if ($o['l'] > 0) {
16551 $parent = $lru[($o['l'] - 1)];
16552 //Set parent and last pointers
16553 $this->outlines[$i]['parent'] = $parent;
16554 $this->outlines[$parent]['last'] = $i;
16555 if ($o['l'] > $level) {
16556 //Level increasing: set first pointer
16557 $this->outlines[$parent]['first'] = $i;
16558 }
16559 } else {
16560 $this->outlines[$i]['parent'] = $nb;
16561 }
16562 if (($o['l'] <= $level) AND ($i > 0)) {
16563 //Set prev and next pointers
16564 $prev = $lru[$o['l']];
16565 $this->outlines[$prev]['next'] = $i;
16566 $this->outlines[$i]['prev'] = $prev;
16567 }
16568 $lru[$o['l']] = $i;
16569 $level = $o['l'];
16570 }
16571 //Outline items
16572 $n = $this->n + 1;
16573 $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';
16574 foreach ($this->outlines as $i => $o) {
16575 $oid = $this->_newobj();
16576 // covert HTML title to string
16577 $title = preg_replace($nltags, "\n", $o['t']);
16578 $title = preg_replace("/[\r]+/si", '', $title);
16579 $title = preg_replace("/[\n]+/si", "\n", $title);
16580 $title = strip_tags($title);
16581 $title = $this->stringTrim($title);
16582 $out = '<</Title '.$this->_textstring($title, $oid);
16583 $out .= ' /Parent '.($n + $o['parent']).' 0 R';
16584 if (isset($o['prev'])) {
16585 $out .= ' /Prev '.($n + $o['prev']).' 0 R';
16586 }
16587 if (isset($o['next'])) {
16588 $out .= ' /Next '.($n + $o['next']).' 0 R';
16589 }
16590 if (isset($o['first'])) {
16591 $out .= ' /First '.($n + $o['first']).' 0 R';
16592 }
16593 if (isset($o['last'])) {
16594 $out .= ' /Last '.($n + $o['last']).' 0 R';
16595 }
16596 if (isset($this->page_obj_id[($o['p'])])) {
16597 $out .= ' '.sprintf('/Dest [%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($o['p'])], ($this->pagedim[$o['p']]['h'] - ($o['y'] * $this->k)));
16598 }
16599 // set font style
16600 $style = 0;
16601 if (!empty($o['s'])) {
16602 // bold
16603 if (strpos($o['s'], 'B') !== false) {
16604 $style |= 2;
16605 }
16606 // oblique
16607 if (strpos($o['s'], 'I') !== false) {
16608 $style |= 1;
16609 }
16610 }
16611 $out .= sprintf(' /F %d', $style);
16612 // set bookmark color
16613 if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
16614 $color = array_values($o['c']);
16615 $out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
16616 } else {
16617 // black
16618 $out .= ' /C [0.0 0.0 0.0]';
16619 }
16620 $out .= ' /Count 0'; // normally closed item
16621 $out .= ' >>';
16622 $out .= "\n".'endobj';
16623 $this->_out($out);
16624 }
16625 //Outline root
16626 $this->OutlineRoot = $this->_newobj();
16627 $this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
16628 }
16629
16630 // --- JAVASCRIPT ------------------------------------------------------
16631
16632 /**
16633 * Adds a javascript
16634 * @param $script (string) Javascript code
16635 * @public
16636 * @author Johannes G�ntert, Nicola Asuni
16637 * @since 2.1.002 (2008-02-12)
16638 */
16639 public function IncludeJS($script) {
16640 $this->javascript .= $script;
16641 }
16642
16643 /**
16644 * Adds a javascript object and return object ID
16645 * @param $script (string) Javascript code
16646 * @param $onload (boolean) if true executes this object when opening the document
16647 * @return int internal object ID
16648 * @public
16649 * @author Nicola Asuni
16650 * @since 4.8.000 (2009-09-07)
16651 */
16652 public function addJavascriptObject($script, $onload=false) {
16653 if ($this->pdfa_mode) {
16654 // javascript is not allowed in PDF/A mode
16655 return false;
16656 }
16657 ++$this->n;
16658 $this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
16659 return $this->n;
16660 }
16661
16662 /**
16663 * Create a javascript PDF string.
16664 * @protected
16665 * @author Johannes G�ntert, Nicola Asuni
16666 * @since 2.1.002 (2008-02-12)
16667 */
16668 protected function _putjavascript() {
16669 if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
16670 return;
16671 }
16672 if (strpos($this->javascript, 'this.addField') > 0) {
16673 if (!$this->ur['enabled']) {
16674 //$this->setUserRights();
16675 }
16676 // the following two lines are used to avoid form fields duplication after saving
16677 // The addField method only works when releasing user rights (UR3)
16678 $jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
16679 $jsb = "getField('tcpdfdocsaved').value='saved';";
16680 $this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
16681 }
16682 $this->n_js = $this->_newobj();
16683 $out = ' << /Names [';
16684 if (!empty($this->javascript)) {
16685 $out .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
16686 }
16687 if (!empty($this->js_objects)) {
16688 foreach ($this->js_objects as $key => $val) {
16689 if ($val['onload']) {
16690 $out .= ' (JS'.$key.') '.$key.' 0 R';
16691 }
16692 }
16693 }
16694 $out .= ' ] >>';
16695 $out .= "\n".'endobj';
16696 $this->_out($out);
16697 // default Javascript object
16698 if (!empty($this->javascript)) {
16699 $obj_id = $this->_newobj();
16700 $out = '<< /S /JavaScript';
16701 $out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
16702 $out .= ' >>';
16703 $out .= "\n".'endobj';
16704 $this->_out($out);
16705 }
16706 // additional Javascript objects
16707 if (!empty($this->js_objects)) {
16708 foreach ($this->js_objects as $key => $val) {
16709 $out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
16710 $this->_out($out);
16711 }
16712 }
16713 }
16714
16715 /**
16716 * Convert color to javascript color.
16717 * @param $color (string) color name or "#RRGGBB"
16718 * @protected
16719 * @author Denis Van Nuffelen, Nicola Asuni
16720 * @since 2.1.002 (2008-02-12)
16721 */
16722 protected function _JScolor($color) {
16723 static $aColors = array('transparent', 'black', 'white', 'red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'dkGray', 'gray', 'ltGray');
16724 if (substr($color,0,1) == '#') {
16725 return sprintf("['RGB',%F,%F,%F]", hexdec(substr($color,1,2))/255, hexdec(substr($color,3,2))/255, hexdec(substr($color,5,2))/255);
16726 }
16727 if (!in_array($color,$aColors)) {
16728 $this->Error('Invalid color: '.$color);
16729 }
16730 return 'color.'.$color;
16731 }
16732
16733 /**
16734 * Adds a javascript form field.
16735 * @param $type (string) field type
16736 * @param $name (string) field name
16737 * @param $x (int) horizontal position
16738 * @param $y (int) vertical position
16739 * @param $w (int) width
16740 * @param $h (int) height
16741 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
16742 * @protected
16743 * @author Denis Van Nuffelen, Nicola Asuni
16744 * @since 2.1.002 (2008-02-12)
16745 */
16746 protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
16747 if ($this->rtl) {
16748 $x = $x - $w;
16749 }
16750 // the followind avoid fields duplication after saving the document
16751 $this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
16752 $k = $this->k;
16753 $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";
16754 $this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
16755 while (list($key, $val) = each($prop)) {
16756 if (strcmp(substr($key, -5), 'Color') == 0) {
16757 $val = $this->_JScolor($val);
16758 } else {
16759 $val = "'".$val."'";
16760 }
16761 $this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
16762 }
16763 if ($this->rtl) {
16764 $this->x -= $w;
16765 } else {
16766 $this->x += $w;
16767 }
16768 $this->javascript .= '}';
16769 }
16770
16771 // --- FORM FIELDS -----------------------------------------------------
16772
16773 /**
16774 * Convert JavaScript form fields properties array to Annotation Properties array.
16775 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
16776 * @return array of annotation properties
16777 * @protected
16778 * @author Nicola Asuni
16779 * @since 4.8.000 (2009-09-06)
16780 */
16781 protected function getAnnotOptFromJSProp($prop) {
16782 if (isset($prop['aopt']) AND is_array($prop['aopt'])) {
16783 // the annotation options area lready defined
16784 return $prop['aopt'];
16785 }
16786 $opt = array(); // value to be returned
16787 // alignment: Controls how the text is laid out within the text field.
16788 if (isset($prop['alignment'])) {
16789 switch ($prop['alignment']) {
16790 case 'left': {
16791 $opt['q'] = 0;
16792 break;
16793 }
16794 case 'center': {
16795 $opt['q'] = 1;
16796 break;
16797 }
16798 case 'right': {
16799 $opt['q'] = 2;
16800 break;
16801 }
16802 default: {
16803 $opt['q'] = ($this->rtl)?2:0;
16804 break;
16805 }
16806 }
16807 }
16808 // lineWidth: Specifies the thickness of the border when stroking the perimeter of a field's rectangle.
16809 if (isset($prop['lineWidth'])) {
16810 $linewidth = intval($prop['lineWidth']);
16811 } else {
16812 $linewidth = 1;
16813 }
16814 // borderStyle: The border style for a field.
16815 if (isset($prop['borderStyle'])) {
16816 switch ($prop['borderStyle']) {
16817 case 'border.d':
16818 case 'dashed': {
16819 $opt['border'] = array(0, 0, $linewidth, array(3, 2));
16820 $opt['bs'] = array('w'=>$linewidth, 's'=>'D', 'd'=>array(3, 2));
16821 break;
16822 }
16823 case 'border.b':
16824 case 'beveled': {
16825 $opt['border'] = array(0, 0, $linewidth);
16826 $opt['bs'] = array('w'=>$linewidth, 's'=>'B');
16827 break;
16828 }
16829 case 'border.i':
16830 case 'inset': {
16831 $opt['border'] = array(0, 0, $linewidth);
16832 $opt['bs'] = array('w'=>$linewidth, 's'=>'I');
16833 break;
16834 }
16835 case 'border.u':
16836 case 'underline': {
16837 $opt['border'] = array(0, 0, $linewidth);
16838 $opt['bs'] = array('w'=>$linewidth, 's'=>'U');
16839 break;
16840 }
16841 case 'border.s':
16842 case 'solid': {
16843 $opt['border'] = array(0, 0, $linewidth);
16844 $opt['bs'] = array('w'=>$linewidth, 's'=>'S');
16845 break;
16846 }
16847 default: {
16848 break;
16849 }
16850 }
16851 }
16852 if (isset($prop['border']) AND is_array($prop['border'])) {
16853 $opt['border'] = $prop['border'];
16854 }
16855 if (!isset($opt['mk'])) {
16856 $opt['mk'] = array();
16857 }
16858 if (!isset($opt['mk']['if'])) {
16859 $opt['mk']['if'] = array();
16860 }
16861 $opt['mk']['if']['a'] = array(0.5, 0.5);
16862 // buttonAlignX: Controls how space is distributed from the left of the button face with respect to the icon.
16863 if (isset($prop['buttonAlignX'])) {
16864 $opt['mk']['if']['a'][0] = $prop['buttonAlignX'];
16865 }
16866 // buttonAlignY: Controls how unused space is distributed from the bottom of the button face with respect to the icon.
16867 if (isset($prop['buttonAlignY'])) {
16868 $opt['mk']['if']['a'][1] = $prop['buttonAlignY'];
16869 }
16870 // buttonFitBounds: If true, the extent to which the icon may be scaled is set to the bounds of the button field.
16871 if (isset($prop['buttonFitBounds']) AND ($prop['buttonFitBounds'] == 'true')) {
16872 $opt['mk']['if']['fb'] = true;
16873 }
16874 // buttonScaleHow: Controls how the icon is scaled (if necessary) to fit inside the button face.
16875 if (isset($prop['buttonScaleHow'])) {
16876 switch ($prop['buttonScaleHow']) {
16877 case 'scaleHow.proportional': {
16878 $opt['mk']['if']['s'] = 'P';
16879 break;
16880 }
16881 case 'scaleHow.anamorphic': {
16882 $opt['mk']['if']['s'] = 'A';
16883 break;
16884 }
16885 }
16886 }
16887 // buttonScaleWhen: Controls when an icon is scaled to fit inside the button face.
16888 if (isset($prop['buttonScaleWhen'])) {
16889 switch ($prop['buttonScaleWhen']) {
16890 case 'scaleWhen.always': {
16891 $opt['mk']['if']['sw'] = 'A';
16892 break;
16893 }
16894 case 'scaleWhen.never': {
16895 $opt['mk']['if']['sw'] = 'N';
16896 break;
16897 }
16898 case 'scaleWhen.tooBig': {
16899 $opt['mk']['if']['sw'] = 'B';
16900 break;
16901 }
16902 case 'scaleWhen.tooSmall': {
16903 $opt['mk']['if']['sw'] = 'S';
16904 break;
16905 }
16906 }
16907 }
16908 // buttonPosition: Controls how the text and the icon of the button are positioned with respect to each other within the button face.
16909 if (isset($prop['buttonPosition'])) {
16910 switch ($prop['buttonPosition']) {
16911 case 0:
16912 case 'position.textOnly': {
16913 $opt['mk']['tp'] = 0;
16914 break;
16915 }
16916 case 1:
16917 case 'position.iconOnly': {
16918 $opt['mk']['tp'] = 1;
16919 break;
16920 }
16921 case 2:
16922 case 'position.iconTextV': {
16923 $opt['mk']['tp'] = 2;
16924 break;
16925 }
16926 case 3:
16927 case 'position.textIconV': {
16928 $opt['mk']['tp'] = 3;
16929 break;
16930 }
16931 case 4:
16932 case 'position.iconTextH': {
16933 $opt['mk']['tp'] = 4;
16934 break;
16935 }
16936 case 5:
16937 case 'position.textIconH': {
16938 $opt['mk']['tp'] = 5;
16939 break;
16940 }
16941 case 6:
16942 case 'position.overlay': {
16943 $opt['mk']['tp'] = 6;
16944 break;
16945 }
16946 }
16947 }
16948 // fillColor: Specifies the background color for a field.
16949 if (isset($prop['fillColor'])) {
16950 if (is_array($prop['fillColor'])) {
16951 $opt['mk']['bg'] = $prop['fillColor'];
16952 } else {
16953 $opt['mk']['bg'] = $this->convertHTMLColorToDec($prop['fillColor']);
16954 }
16955 }
16956 // strokeColor: Specifies the stroke color for a field that is used to stroke the rectangle of the field with a line as large as the line width.
16957 if (isset($prop['strokeColor'])) {
16958 if (is_array($prop['strokeColor'])) {
16959 $opt['mk']['bc'] = $prop['strokeColor'];
16960 } else {
16961 $opt['mk']['bc'] = $this->convertHTMLColorToDec($prop['strokeColor']);
16962 }
16963 }
16964 // rotation: The rotation of a widget in counterclockwise increments.
16965 if (isset($prop['rotation'])) {
16966 $opt['mk']['r'] = $prop['rotation'];
16967 }
16968 // charLimit: Limits the number of characters that a user can type into a text field.
16969 if (isset($prop['charLimit'])) {
16970 $opt['maxlen'] = intval($prop['charLimit']);
16971 }
16972 if (!isset($ff)) {
16973 $ff = 0; // default value
16974 }
16975 // readonly: The read-only characteristic of a field. If a field is read-only, the user can see the field but cannot change it.
16976 if (isset($prop['readonly']) AND ($prop['readonly'] == 'true')) {
16977 $ff += 1 << 0;
16978 }
16979 // required: Specifies whether a field requires a value.
16980 if (isset($prop['required']) AND ($prop['required'] == 'true')) {
16981 $ff += 1 << 1;
16982 }
16983 // multiline: Controls how text is wrapped within the field.
16984 if (isset($prop['multiline']) AND ($prop['multiline'] == 'true')) {
16985 $ff += 1 << 12;
16986 }
16987 // password: Specifies whether the field should display asterisks when data is entered in the field.
16988 if (isset($prop['password']) AND ($prop['password'] == 'true')) {
16989 $ff += 1 << 13;
16990 }
16991 // NoToggleToOff: If set, exactly one radio button shall be selected at all times; selecting the currently selected button has no effect.
16992 if (isset($prop['NoToggleToOff']) AND ($prop['NoToggleToOff'] == 'true')) {
16993 $ff += 1 << 14;
16994 }
16995 // Radio: If set, the field is a set of radio buttons.
16996 if (isset($prop['Radio']) AND ($prop['Radio'] == 'true')) {
16997 $ff += 1 << 15;
16998 }
16999 // Pushbutton: If set, the field is a pushbutton that does not retain a permanent value.
17000 if (isset($prop['Pushbutton']) AND ($prop['Pushbutton'] == 'true')) {
17001 $ff += 1 << 16;
17002 }
17003 // Combo: If set, the field is a combo box; if clear, the field is a list box.
17004 if (isset($prop['Combo']) AND ($prop['Combo'] == 'true')) {
17005 $ff += 1 << 17;
17006 }
17007 // editable: Controls whether a combo box is editable.
17008 if (isset($prop['editable']) AND ($prop['editable'] == 'true')) {
17009 $ff += 1 << 18;
17010 }
17011 // Sort: If set, the field's option items shall be sorted alphabetically.
17012 if (isset($prop['Sort']) AND ($prop['Sort'] == 'true')) {
17013 $ff += 1 << 19;
17014 }
17015 // fileSelect: If true, sets the file-select flag in the Options tab of the text field (Field is Used for File Selection).
17016 if (isset($prop['fileSelect']) AND ($prop['fileSelect'] == 'true')) {
17017 $ff += 1 << 20;
17018 }
17019 // multipleSelection: If true, indicates that a list box allows a multiple selection of items.
17020 if (isset($prop['multipleSelection']) AND ($prop['multipleSelection'] == 'true')) {
17021 $ff += 1 << 21;
17022 }
17023 // doNotSpellCheck: If true, spell checking is not performed on this editable text field.
17024 if (isset($prop['doNotSpellCheck']) AND ($prop['doNotSpellCheck'] == 'true')) {
17025 $ff += 1 << 22;
17026 }
17027 // doNotScroll: If true, the text field does not scroll and the user, therefore, is limited by the rectangular region designed for the field.
17028 if (isset($prop['doNotScroll']) AND ($prop['doNotScroll'] == 'true')) {
17029 $ff += 1 << 23;
17030 }
17031 // comb: If set to true, the field background is drawn as series of boxes (one for each character in the value of the field) and each character of the content is drawn within those boxes. The number of boxes drawn is determined from the charLimit property. It applies only to text fields. The setter will also raise if any of the following field properties are also set multiline, password, and fileSelect. A side-effect of setting this property is that the doNotScroll property is also set.
17032 if (isset($prop['comb']) AND ($prop['comb'] == 'true')) {
17033 $ff += 1 << 24;
17034 }
17035 // radiosInUnison: If false, even if a group of radio buttons have the same name and export value, they behave in a mutually exclusive fashion, like HTML radio buttons.
17036 if (isset($prop['radiosInUnison']) AND ($prop['radiosInUnison'] == 'true')) {
17037 $ff += 1 << 25;
17038 }
17039 // richText: If true, the field allows rich text formatting.
17040 if (isset($prop['richText']) AND ($prop['richText'] == 'true')) {
17041 $ff += 1 << 25;
17042 }
17043 // commitOnSelChange: Controls whether a field value is committed after a selection change.
17044 if (isset($prop['commitOnSelChange']) AND ($prop['commitOnSelChange'] == 'true')) {
17045 $ff += 1 << 26;
17046 }
17047 $opt['ff'] = $ff;
17048 // defaultValue: The default value of a field - that is, the value that the field is set to when the form is reset.
17049 if (isset($prop['defaultValue'])) {
17050 $opt['dv'] = $prop['defaultValue'];
17051 }
17052 $f = 4; // default value for annotation flags
17053 // readonly: The read-only characteristic of a field. If a field is read-only, the user can see the field but cannot change it.
17054 if (isset($prop['readonly']) AND ($prop['readonly'] == 'true')) {
17055 $f += 1 << 6;
17056 }
17057 // display: Controls whether the field is hidden or visible on screen and in print.
17058 if (isset($prop['display'])) {
17059 if ($prop['display'] == 'display.visible') {
17060 //
17061 } elseif ($prop['display'] == 'display.hidden') {
17062 $f += 1 << 1;
17063 } elseif ($prop['display'] == 'display.noPrint') {
17064 $f -= 1 << 2;
17065 } elseif ($prop['display'] == 'display.noView') {
17066 $f += 1 << 5;
17067 }
17068 }
17069 $opt['f'] = $f;
17070 // currentValueIndices: Reads and writes single or multiple values of a list box or combo box.
17071 if (isset($prop['currentValueIndices']) AND is_array($prop['currentValueIndices'])) {
17072 $opt['i'] = $prop['currentValueIndices'];
17073 }
17074 // value: The value of the field data that the user has entered.
17075 if (isset($prop['value'])) {
17076 if (is_array($prop['value'])) {
17077 $opt['opt'] = array();
17078 foreach ($prop['value'] AS $key => $optval) {
17079 // exportValues: An array of strings representing the export values for the field.
17080 if (isset($prop['exportValues'][$key])) {
17081 $opt['opt'][$key] = array($prop['exportValues'][$key], $prop['value'][$key]);
17082 } else {
17083 $opt['opt'][$key] = $prop['value'][$key];
17084 }
17085 }
17086 } else {
17087 $opt['v'] = $prop['value'];
17088 }
17089 }
17090 // richValue: This property specifies the text contents and formatting of a rich text field.
17091 if (isset($prop['richValue'])) {
17092 $opt['rv'] = $prop['richValue'];
17093 }
17094 // submitName: If nonempty, used during form submission instead of name. Only applicable if submitting in HTML format (that is, URL-encoded).
17095 if (isset($prop['submitName'])) {
17096 $opt['tm'] = $prop['submitName'];
17097 }
17098 // name: Fully qualified field name.
17099 if (isset($prop['name'])) {
17100 $opt['t'] = $prop['name'];
17101 }
17102 // userName: The user name (short description string) of the field.
17103 if (isset($prop['userName'])) {
17104 $opt['tu'] = $prop['userName'];
17105 }
17106 // highlight: Defines how a button reacts when a user clicks it.
17107 if (isset($prop['highlight'])) {
17108 switch ($prop['highlight']) {
17109 case 'none':
17110 case 'highlight.n': {
17111 $opt['h'] = 'N';
17112 break;
17113 }
17114 case 'invert':
17115 case 'highlight.i': {
17116 $opt['h'] = 'i';
17117 break;
17118 }
17119 case 'push':
17120 case 'highlight.p': {
17121 $opt['h'] = 'P';
17122 break;
17123 }
17124 case 'outline':
17125 case 'highlight.o': {
17126 $opt['h'] = 'O';
17127 break;
17128 }
17129 }
17130 }
17131 // Unsupported options:
17132 // - calcOrderIndex: Changes the calculation order of fields in the document.
17133 // - delay: Delays the redrawing of a field's appearance.
17134 // - defaultStyle: This property defines the default style attributes for the form field.
17135 // - style: Allows the user to set the glyph style of a check box or radio button.
17136 // - textColor, textFont, textSize
17137 return $opt;
17138 }
17139
17140 /**
17141 * Set default properties for form fields.
17142 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
17143 * @public
17144 * @author Nicola Asuni
17145 * @since 4.8.000 (2009-09-06)
17146 */
17147 public function setFormDefaultProp($prop=array()) {
17148 $this->default_form_prop = $prop;
17149 }
17150
17151 /**
17152 * Return the default properties for form fields.
17153 * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
17154 * @public
17155 * @author Nicola Asuni
17156 * @since 4.8.000 (2009-09-06)
17157 */
17158 public function getFormDefaultProp() {
17159 return $this->default_form_prop;
17160 }
17161
17162 /**
17163 * Creates a text field
17164 * @param $name (string) field name
17165 * @param $w (float) Width of the rectangle
17166 * @param $h (float) Height of the rectangle
17167 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
17168 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
17169 * @param $x (float) Abscissa of the upper-left corner of the rectangle
17170 * @param $y (float) Ordinate of the upper-left corner of the rectangle
17171 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
17172 * @public
17173 * @author Nicola Asuni
17174 * @since 4.8.000 (2009-09-07)
17175 */
17176 public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
17177 if ($x === '') {
17178 $x = $this->x;
17179 }
17180 if ($y === '') {
17181 $y = $this->y;
17182 }
17183 // check page for no-write regions and adapt page margins if necessary
17184 list($x, $y) = $this->checkPageRegions($h, $x, $y);
17185 if ($js) {
17186 $this->_addfield('text', $name, $x, $y, $w, $h, $prop);
17187 return;
17188 }
17189 // get default style
17190 $prop = array_merge($this->getFormDefaultProp(), $prop);
17191 // get annotation data
17192 $popt = $this->getAnnotOptFromJSProp($prop);
17193 // set default appearance stream
17194 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
17195 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
17196 $popt['da'] = $fontstyle;
17197 // build appearance stream
17198 $popt['ap'] = array();
17199 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
17200 $text = '';
17201 if (isset($prop['value']) AND !empty($prop['value'])) {
17202 $text = $prop['value'];
17203 } elseif (isset($opt['v']) AND !empty($opt['v'])) {
17204 $text = $opt['v'];
17205 }
17206 $tmpid = $this->startTemplate($w, $h, false);
17207 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
17208 $this->endTemplate();
17209 --$this->n;
17210 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
17211 unset($this->xobjects[$tmpid]);
17212 $popt['ap']['n'] .= 'Q EMC';
17213 // merge options
17214 $opt = array_merge($popt, $opt);
17215 // remove some conflicting options
17216 unset($opt['bs']);
17217 // set remaining annotation data
17218 $opt['Subtype'] = 'Widget';
17219 $opt['ft'] = 'Tx';
17220 $opt['t'] = $name;
17221 // Additional annotation's parameters (check _putannotsobj() method):
17222 //$opt['f']
17223 //$opt['as']
17224 //$opt['bs']
17225 //$opt['be']
17226 //$opt['c']
17227 //$opt['border']
17228 //$opt['h']
17229 //$opt['mk'];
17230 //$opt['mk']['r']
17231 //$opt['mk']['bc'];
17232 //$opt['mk']['bg'];
17233 unset($opt['mk']['ca']);
17234 unset($opt['mk']['rc']);
17235 unset($opt['mk']['ac']);
17236 unset($opt['mk']['i']);
17237 unset($opt['mk']['ri']);
17238 unset($opt['mk']['ix']);
17239 unset($opt['mk']['if']);
17240 //$opt['mk']['if']['sw'];
17241 //$opt['mk']['if']['s'];
17242 //$opt['mk']['if']['a'];
17243 //$opt['mk']['if']['fb'];
17244 unset($opt['mk']['tp']);
17245 //$opt['tu']
17246 //$opt['tm']
17247 //$opt['ff']
17248 //$opt['v']
17249 //$opt['dv']
17250 //$opt['a']
17251 //$opt['aa']
17252 //$opt['q']
17253 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
17254 if ($this->rtl) {
17255 $this->x -= $w;
17256 } else {
17257 $this->x += $w;
17258 }
17259 }
17260
17261 /**
17262 * Creates a RadioButton field.
17263 * @param $name (string) Field name.
17264 * @param $w (int) Width or the radio button.
17265 * @param $prop (array) Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
17266 * @param $opt (array) Annotation parameters. Possible values are described on official PDF32000_2008 reference.
17267 * @param $onvalue (string) Value to be returned if selected.
17268 * @param $checked (boolean) Define the initial state.
17269 * @param $x (float) Abscissa of the upper-left corner of the rectangle
17270 * @param $y (float) Ordinate of the upper-left corner of the rectangle
17271 * @param $js (boolean) If true put the field using JavaScript (requires Acrobat Writer to be rendered).
17272 * @public
17273 * @author Nicola Asuni
17274 * @since 4.8.000 (2009-09-07)
17275 */
17276 public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
17277 if ($x === '') {
17278 $x = $this->x;
17279 }
17280 if ($y === '') {
17281 $y = $this->y;
17282 }
17283 // check page for no-write regions and adapt page margins if necessary
17284 list($x, $y) = $this->checkPageRegions($w, $x, $y);
17285 if ($js) {
17286 $this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
17287 return;
17288 }
17289 if ($this->empty_string($onvalue)) {
17290 $onvalue = 'On';
17291 }
17292 if ($checked) {
17293 $defval = $onvalue;
17294 } else {
17295 $defval = 'Off';
17296 }
17297 // set font
17298 $font = 'zapfdingbats';
17299 if ($this->pdfa_mode) {
17300 // all fonts must be embedded
17301 $font = 'pdfa'.$font;
17302 }
17303 $this->AddFont($font);
17304 $tmpfont = $this->getFontBuffer($font);
17305 // set data for parent group
17306 if (!isset($this->radiobutton_groups[$this->page])) {
17307 $this->radiobutton_groups[$this->page] = array();
17308 }
17309 if (!isset($this->radiobutton_groups[$this->page][$name])) {
17310 $this->radiobutton_groups[$this->page][$name] = array();
17311 ++$this->n;
17312 $this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
17313 $this->radio_groups[] = $this->n;
17314 }
17315 $kid = ($this->n + 1);
17316 // save object ID to be added on Kids entry on parent object
17317 $this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
17318 // get default style
17319 $prop = array_merge($this->getFormDefaultProp(), $prop);
17320 $prop['NoToggleToOff'] = 'true';
17321 $prop['Radio'] = 'true';
17322 $prop['borderStyle'] = 'inset';
17323 // get annotation data
17324 $popt = $this->getAnnotOptFromJSProp($prop);
17325 // set additional default options
17326 $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
17327 $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
17328 $popt['da'] = $fontstyle;
17329 // build appearance stream
17330 $popt['ap'] = array();
17331 $popt['ap']['n'] = array();
17332 $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
17333 $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, 0, $fy);
17334 $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, 0, $fy);
17335 if (!isset($popt['mk'])) {
17336 $popt['mk'] = array();
17337 }
17338 $popt['mk']['ca'] = '(l)';
17339 // merge options
17340 $opt = array_merge($popt, $opt);
17341 // set remaining annotation data
17342 $opt['Subtype'] = 'Widget';
17343 $opt['ft'] = 'Btn';
17344 if ($checked) {
17345 $opt['v'] = array('/'.$onvalue);
17346 $opt['as'] = $onvalue;
17347 } else {
17348 $opt['as'] = 'Off';
17349 }
17350 // store readonly flag
17351 if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
17352 $this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
17353 }
17354 $this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
17355 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
17356 if ($this->rtl) {
17357 $this->x -= $w;
17358 } else {
17359 $this->x += $w;
17360 }
17361 }
17362
17363 /**
17364 * Creates a List-box field
17365 * @param $name (string) field name
17366 * @param $w (int) width
17367 * @param $h (int) height
17368 * @param $values (array) array containing the list of values.
17369 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
17370 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
17371 * @param $x (float) Abscissa of the upper-left corner of the rectangle
17372 * @param $y (float) Ordinate of the upper-left corner of the rectangle
17373 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
17374 * @public
17375 * @author Nicola Asuni
17376 * @since 4.8.000 (2009-09-07)
17377 */
17378 public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
17379 if ($x === '') {
17380 $x = $this->x;
17381 }
17382 if ($y === '') {
17383 $y = $this->y;
17384 }
17385 // check page for no-write regions and adapt page margins if necessary
17386 list($x, $y) = $this->checkPageRegions($h, $x, $y);
17387 if ($js) {
17388 $this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
17389 $s = '';
17390 foreach ($values as $value) {
17391 if (is_array($value)) {
17392 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
17393 } else {
17394 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
17395 }
17396 }
17397 $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
17398 return;
17399 }
17400 // get default style
17401 $prop = array_merge($this->getFormDefaultProp(), $prop);
17402 // get annotation data
17403 $popt = $this->getAnnotOptFromJSProp($prop);
17404 // set additional default values
17405 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
17406 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
17407 $popt['da'] = $fontstyle;
17408 // build appearance stream
17409 $popt['ap'] = array();
17410 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
17411 $text = '';
17412 foreach($values as $item) {
17413 if (is_array($item)) {
17414 $text .= $item[1]."\n";
17415 } else {
17416 $text .= $item."\n";
17417 }
17418 }
17419 $tmpid = $this->startTemplate($w, $h, false);
17420 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
17421 $this->endTemplate();
17422 --$this->n;
17423 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
17424 unset($this->xobjects[$tmpid]);
17425 $popt['ap']['n'] .= 'Q EMC';
17426 // merge options
17427 $opt = array_merge($popt, $opt);
17428 // set remaining annotation data
17429 $opt['Subtype'] = 'Widget';
17430 $opt['ft'] = 'Ch';
17431 $opt['t'] = $name;
17432 $opt['opt'] = $values;
17433 unset($opt['mk']['ca']);
17434 unset($opt['mk']['rc']);
17435 unset($opt['mk']['ac']);
17436 unset($opt['mk']['i']);
17437 unset($opt['mk']['ri']);
17438 unset($opt['mk']['ix']);
17439 unset($opt['mk']['if']);
17440 unset($opt['mk']['tp']);
17441 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
17442 if ($this->rtl) {
17443 $this->x -= $w;
17444 } else {
17445 $this->x += $w;
17446 }
17447 }
17448
17449 /**
17450 * Creates a Combo-box field
17451 * @param $name (string) field name
17452 * @param $w (int) width
17453 * @param $h (int) height
17454 * @param $values (array) array containing the list of values.
17455 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
17456 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
17457 * @param $x (float) Abscissa of the upper-left corner of the rectangle
17458 * @param $y (float) Ordinate of the upper-left corner of the rectangle
17459 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
17460 * @public
17461 * @author Nicola Asuni
17462 * @since 4.8.000 (2009-09-07)
17463 */
17464 public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
17465 if ($x === '') {
17466 $x = $this->x;
17467 }
17468 if ($y === '') {
17469 $y = $this->y;
17470 }
17471 // check page for no-write regions and adapt page margins if necessary
17472 list($x, $y) = $this->checkPageRegions($h, $x, $y);
17473 if ($js) {
17474 $this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
17475 $s = '';
17476 foreach ($values as $value) {
17477 if (is_array($value)) {
17478 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
17479 } else {
17480 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
17481 }
17482 }
17483 $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
17484 return;
17485 }
17486 // get default style
17487 $prop = array_merge($this->getFormDefaultProp(), $prop);
17488 $prop['Combo'] = true;
17489 // get annotation data
17490 $popt = $this->getAnnotOptFromJSProp($prop);
17491 // set additional default options
17492 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
17493 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
17494 $popt['da'] = $fontstyle;
17495 // build appearance stream
17496 $popt['ap'] = array();
17497 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
17498 $text = '';
17499 foreach($values as $item) {
17500 if (is_array($item)) {
17501 $text .= $item[1]."\n";
17502 } else {
17503 $text .= $item."\n";
17504 }
17505 }
17506 $tmpid = $this->startTemplate($w, $h, false);
17507 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
17508 $this->endTemplate();
17509 --$this->n;
17510 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
17511 unset($this->xobjects[$tmpid]);
17512 $popt['ap']['n'] .= 'Q EMC';
17513 // merge options
17514 $opt = array_merge($popt, $opt);
17515 // set remaining annotation data
17516 $opt['Subtype'] = 'Widget';
17517 $opt['ft'] = 'Ch';
17518 $opt['t'] = $name;
17519 $opt['opt'] = $values;
17520 unset($opt['mk']['ca']);
17521 unset($opt['mk']['rc']);
17522 unset($opt['mk']['ac']);
17523 unset($opt['mk']['i']);
17524 unset($opt['mk']['ri']);
17525 unset($opt['mk']['ix']);
17526 unset($opt['mk']['if']);
17527 unset($opt['mk']['tp']);
17528 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
17529 if ($this->rtl) {
17530 $this->x -= $w;
17531 } else {
17532 $this->x += $w;
17533 }
17534 }
17535
17536 /**
17537 * Creates a CheckBox field
17538 * @param $name (string) field name
17539 * @param $w (int) width
17540 * @param $checked (boolean) define the initial state.
17541 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
17542 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
17543 * @param $onvalue (string) value to be returned if selected.
17544 * @param $x (float) Abscissa of the upper-left corner of the rectangle
17545 * @param $y (float) Ordinate of the upper-left corner of the rectangle
17546 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
17547 * @public
17548 * @author Nicola Asuni
17549 * @since 4.8.000 (2009-09-07)
17550 */
17551 public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
17552 if ($x === '') {
17553 $x = $this->x;
17554 }
17555 if ($y === '') {
17556 $y = $this->y;
17557 }
17558 // check page for no-write regions and adapt page margins if necessary
17559 list($x, $y) = $this->checkPageRegions($w, $x, $y);
17560 if ($js) {
17561 $this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
17562 return;
17563 }
17564 if (!isset($prop['value'])) {
17565 $prop['value'] = array('Yes');
17566 }
17567 // get default style
17568 $prop = array_merge($this->getFormDefaultProp(), $prop);
17569 $prop['borderStyle'] = 'inset';
17570 // get annotation data
17571 $popt = $this->getAnnotOptFromJSProp($prop);
17572 // set additional default options
17573 $font = 'zapfdingbats';
17574 if ($this->pdfa_mode) {
17575 // all fonts must be embedded
17576 $font = 'pdfa'.$font;
17577 }
17578 $this->AddFont($font);
17579 $tmpfont = $this->getFontBuffer($font);
17580 $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
17581 $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
17582 $popt['da'] = $fontstyle;
17583 // build appearance stream
17584 $popt['ap'] = array();
17585 $popt['ap']['n'] = array();
17586 $fy = ((($tmpfont['desc']['Ascent'] + $tmpfont['desc']['Descent']) * $this->FontSizePt) / (1000 * $this->k));
17587 $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
17588 $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, 0, $fy);
17589 $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, 0, $fy);
17590 // merge options
17591 $opt = array_merge($popt, $opt);
17592 // set remaining annotation data
17593 $opt['Subtype'] = 'Widget';
17594 $opt['ft'] = 'Btn';
17595 $opt['t'] = $name;
17596 if ($this->empty_string($onvalue)) {
17597 $onvalue = 'Yes';
17598 }
17599 $opt['opt'] = array($onvalue);
17600 if ($checked) {
17601 $opt['v'] = array('/Yes');
17602 $opt['as'] = 'Yes';
17603 } else {
17604 $opt['v'] = array('/Off');
17605 $opt['as'] = 'Off';
17606 }
17607 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
17608 if ($this->rtl) {
17609 $this->x -= $w;
17610 } else {
17611 $this->x += $w;
17612 }
17613 }
17614
17615 /**
17616 * Creates a button field
17617 * @param $name (string) field name
17618 * @param $w (int) width
17619 * @param $h (int) height
17620 * @param $caption (string) caption.
17621 * @param $action (mixed) action triggered by pressing the button. Use a string to specify a javascript action. Use an array to specify a form action options as on section 12.7.5 of PDF32000_2008.
17622 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
17623 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
17624 * @param $x (float) Abscissa of the upper-left corner of the rectangle
17625 * @param $y (float) Ordinate of the upper-left corner of the rectangle
17626 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
17627 * @public
17628 * @author Nicola Asuni
17629 * @since 4.8.000 (2009-09-07)
17630 */
17631 public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
17632 if ($x === '') {
17633 $x = $this->x;
17634 }
17635 if ($y === '') {
17636 $y = $this->y;
17637 }
17638 // check page for no-write regions and adapt page margins if necessary
17639 list($x, $y) = $this->checkPageRegions($h, $x, $y);
17640 if ($js) {
17641 $this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
17642 $this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
17643 $this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
17644 $this->javascript .= 'f'.$name.".highlight='push';\n";
17645 $this->javascript .= 'f'.$name.".print=false;\n";
17646 return;
17647 }
17648 // get default style
17649 $prop = array_merge($this->getFormDefaultProp(), $prop);
17650 $prop['Pushbutton'] = 'true';
17651 $prop['highlight'] = 'push';
17652 $prop['display'] = 'display.noPrint';
17653 // get annotation data
17654 $popt = $this->getAnnotOptFromJSProp($prop);
17655 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
17656 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
17657 $popt['da'] = $fontstyle;
17658 // build appearance stream
17659 $popt['ap'] = array();
17660 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
17661 $tmpid = $this->startTemplate($w, $h, false);
17662 $bw = (2 / $this->k); // border width
17663 $border = array(
17664 'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
17665 'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
17666 'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
17667 'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
17668 $this->SetFillColor(204);
17669 $this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
17670 $this->endTemplate();
17671 --$this->n;
17672 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
17673 unset($this->xobjects[$tmpid]);
17674 $popt['ap']['n'] .= 'Q EMC';
17675 // set additional default options
17676 if (!isset($popt['mk'])) {
17677 $popt['mk'] = array();
17678 }
17679 $ann_obj_id = ($this->n + 1);
17680 if (!empty($action) AND !is_array($action)) {
17681 $ann_obj_id = ($this->n + 2);
17682 }
17683 $popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
17684 $popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
17685 $popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
17686 // merge options
17687 $opt = array_merge($popt, $opt);
17688 // set remaining annotation data
17689 $opt['Subtype'] = 'Widget';
17690 $opt['ft'] = 'Btn';
17691 $opt['t'] = $caption;
17692 $opt['v'] = $name;
17693 if (!empty($action)) {
17694 if (is_array($action)) {
17695 // form action options as on section 12.7.5 of PDF32000_2008.
17696 $opt['aa'] = '/D <<';
17697 $bmode = array('SubmitForm', 'ResetForm', 'ImportData');
17698 foreach ($action AS $key => $val) {
17699 if (($key == 'S') AND in_array($val, $bmode)) {
17700 $opt['aa'] .= ' /S /'.$val;
17701 } elseif (($key == 'F') AND (!empty($val))) {
17702 $opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
17703 } elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
17704 $opt['aa'] .= ' /Fields [';
17705 foreach ($val AS $field) {
17706 $opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
17707 }
17708 $opt['aa'] .= ']';
17709 } elseif (($key == 'Flags')) {
17710 $ff = 0;
17711 if (is_array($val)) {
17712 foreach ($val AS $flag) {
17713 switch ($flag) {
17714 case 'Include/Exclude': {
17715 $ff += 1 << 0;
17716 break;
17717 }
17718 case 'IncludeNoValueFields': {
17719 $ff += 1 << 1;
17720 break;
17721 }
17722 case 'ExportFormat': {
17723 $ff += 1 << 2;
17724 break;
17725 }
17726 case 'GetMethod': {
17727 $ff += 1 << 3;
17728 break;
17729 }
17730 case 'SubmitCoordinates': {
17731 $ff += 1 << 4;
17732 break;
17733 }
17734 case 'XFDF': {
17735 $ff += 1 << 5;
17736 break;
17737 }
17738 case 'IncludeAppendSaves': {
17739 $ff += 1 << 6;
17740 break;
17741 }
17742 case 'IncludeAnnotations': {
17743 $ff += 1 << 7;
17744 break;
17745 }
17746 case 'SubmitPDF': {
17747 $ff += 1 << 8;
17748 break;
17749 }
17750 case 'CanonicalFormat': {
17751 $ff += 1 << 9;
17752 break;
17753 }
17754 case 'ExclNonUserAnnots': {
17755 $ff += 1 << 10;
17756 break;
17757 }
17758 case 'ExclFKey': {
17759 $ff += 1 << 11;
17760 break;
17761 }
17762 case 'EmbedForm': {
17763 $ff += 1 << 13;
17764 break;
17765 }
17766 }
17767 }
17768 } else {
17769 $ff = intval($val);
17770 }
17771 $opt['aa'] .= ' /Flags '.$ff;
17772 }
17773 }
17774 $opt['aa'] .= ' >>';
17775 } else {
17776 // Javascript action or raw action command
17777 $js_obj_id = $this->addJavascriptObject($action);
17778 $opt['aa'] = '/D '.$js_obj_id.' 0 R';
17779 }
17780 }
17781 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
17782 if ($this->rtl) {
17783 $this->x -= $w;
17784 } else {
17785 $this->x += $w;
17786 }
17787 }
17788
17789 // --- END FORMS FIELDS ------------------------------------------------
17790
17791 /**
17792 * Add certification signature (DocMDP or UR3)
17793 * You can set only one signature type
17794 * @protected
17795 * @author Nicola Asuni
17796 * @since 4.6.008 (2009-05-07)
17797 */
17798 protected function _putsignature() {
17799 if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
17800 return;
17801 }
17802 $sigobjid = ($this->sig_obj_id + 1);
17803 $out = $this->_getobj($sigobjid)."\n";
17804 $out .= '<< /Type /Sig';
17805 $out .= ' /Filter /Adobe.PPKLite';
17806 $out .= ' /SubFilter /adbe.pkcs7.detached';
17807 $out .= ' '.$this->byterange_string;
17808 $out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
17809 $out .= ' /Reference ['; // array of signature reference dictionaries
17810 $out .= ' << /Type /SigRef';
17811 if ($this->signature_data['cert_type'] > 0) {
17812 $out .= ' /TransformMethod /DocMDP';
17813 $out .= ' /TransformParams <<';
17814 $out .= ' /Type /TransformParams';
17815 $out .= ' /P '.$this->signature_data['cert_type'];
17816 $out .= ' /V /1.2';
17817 } else {
17818 $out .= ' /TransformMethod /UR3';
17819 $out .= ' /TransformParams <<';
17820 $out .= ' /Type /TransformParams';
17821 $out .= ' /V /2.2';
17822 if (!$this->empty_string($this->ur['document'])) {
17823 $out .= ' /Document['.$this->ur['document'].']';
17824 }
17825 if (!$this->empty_string($this->ur['form'])) {
17826 $out .= ' /Form['.$this->ur['form'].']';
17827 }
17828 if (!$this->empty_string($this->ur['signature'])) {
17829 $out .= ' /Signature['.$this->ur['signature'].']';
17830 }
17831 if (!$this->empty_string($this->ur['annots'])) {
17832 $out .= ' /Annots['.$this->ur['annots'].']';
17833 }
17834 if (!$this->empty_string($this->ur['ef'])) {
17835 $out .= ' /EF['.$this->ur['ef'].']';
17836 }
17837 if (!$this->empty_string($this->ur['formex'])) {
17838 $out .= ' /FormEX['.$this->ur['formex'].']';
17839 }
17840 }
17841 $out .= ' >>'; // close TransformParams
17842 // optional digest data (values must be calculated and replaced later)
17843 //$out .= ' /Data ********** 0 R';
17844 //$out .= ' /DigestMethod/MD5';
17845 //$out .= ' /DigestLocation[********** 34]';
17846 //$out .= ' /DigestValue<********************************>';
17847 $out .= ' >>';
17848 $out .= ' ]'; // end of reference
17849 if (isset($this->signature_data['info']['Name']) AND !$this->empty_string($this->signature_data['info']['Name'])) {
17850 $out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
17851 }
17852 if (isset($this->signature_data['info']['Location']) AND !$this->empty_string($this->signature_data['info']['Location'])) {
17853 $out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
17854 }
17855 if (isset($this->signature_data['info']['Reason']) AND !$this->empty_string($this->signature_data['info']['Reason'])) {
17856 $out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
17857 }
17858 if (isset($this->signature_data['info']['ContactInfo']) AND !$this->empty_string($this->signature_data['info']['ContactInfo'])) {
17859 $out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
17860 }
17861 $out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
17862 $out .= ' >>';
17863 $out .= "\n".'endobj';
17864 $this->_out($out);
17865 }
17866
17867 /**
17868 * Set User's Rights for PDF Reader
17869 * WARNING: This is experimental and currently do not work.
17870 * Check the PDF Reference 8.7.1 Transform Methods,
17871 * Table 8.105 Entries in the UR transform parameters dictionary
17872 * @param $enable (boolean) if true enable user's rights on PDF reader
17873 * @param $document (string) Names specifying additional document-wide usage rights for the document. The only defined value is "/FullSave", which permits a user to save the document along with modified form and/or annotation data.
17874 * @param $annots (string) Names specifying additional annotation-related usage rights for the document. Valid names in PDF 1.5 and later are /Create/Delete/Modify/Copy/Import/Export, which permit the user to perform the named operation on annotations.
17875 * @param $form (string) Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
17876 * @param $signature (string) Names specifying additional signature-related usage rights for the document. The only defined value is /Modify, which permits a user to apply a digital signature to an existing signature form field or clear a signed signature form field.
17877 * @param $ef (string) Names specifying additional usage rights for named embedded files in the document. Valid names are /Create/Delete/Modify/Import, which permit the user to perform the named operation on named embedded files
17878 Names specifying additional embedded-files-related usage rights for the document.
17879 * @param $formex (string) Names specifying additional form-field-related usage rights. The only valid name is BarcodePlaintext, which permits text form field data to be encoded as a plaintext two-dimensional barcode.
17880 * @public
17881 * @author Nicola Asuni
17882 * @since 2.9.000 (2008-03-26)
17883 */
17884 public function setUserRights(
17885 $enable=true,
17886 $document='/FullSave',
17887 $annots='/Create/Delete/Modify/Copy/Import/Export',
17888 $form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
17889 $signature='/Modify',
17890 $ef='/Create/Delete/Modify/Import',
17891 $formex='') {
17892 $this->ur['enabled'] = $enable;
17893 $this->ur['document'] = $document;
17894 $this->ur['annots'] = $annots;
17895 $this->ur['form'] = $form;
17896 $this->ur['signature'] = $signature;
17897 $this->ur['ef'] = $ef;
17898 $this->ur['formex'] = $formex;
17899 if (!$this->sign) {
17900 $this->setSignature('', '', '', '', 0, array());
17901 }
17902 }
17903
17904 /**
17905 * Enable document signature (requires the OpenSSL Library).
17906 * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
17907 * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
17908 * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
17909 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
17910 * @param $signing_cert (mixed) signing certificate (string or filename prefixed with 'file://')
17911 * @param $private_key (mixed) private key (string or filename prefixed with 'file://')
17912 * @param $private_key_password (string) password
17913 * @param $extracerts (string) specifies the name of a file containing a bunch of extra certificates to include in the signature which can for example be used to help the recipient to verify the certificate that you used.
17914 * @param $cert_type (int) The access permissions granted for this document. Valid values shall be: 1 = No changes to the document shall be permitted; any change to the document shall invalidate the signature; 2 = Permitted changes shall be filling in forms, instantiating page templates, and signing; other changes shall invalidate the signature; 3 = Permitted changes shall be the same as for 2, as well as annotation creation, deletion, and modification; other changes shall invalidate the signature.
17915 * @param $info (array) array of option information: Name, Location, Reason, ContactInfo.
17916 * @public
17917 * @author Nicola Asuni
17918 * @since 4.6.005 (2009-04-24)
17919 */
17920 public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array()) {
17921 // to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
17922 // to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
17923 // to convert pfx certificate to pem: openssl
17924 // OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
17925 $this->sign = true;
17926 ++$this->n;
17927 $this->sig_obj_id = $this->n; // signature widget
17928 ++$this->n; // signature object ($this->sig_obj_id + 1)
17929 $this->signature_data = array();
17930 if (strlen($signing_cert) == 0) {
17931 $signing_cert = 'file://'.dirname(__FILE__).'/tcpdf.crt';
17932 $private_key_password = 'tcpdfdemo';
17933 }
17934 if (strlen($private_key) == 0) {
17935 $private_key = $signing_cert;
17936 }
17937 $this->signature_data['signcert'] = $signing_cert;
17938 $this->signature_data['privkey'] = $private_key;
17939 $this->signature_data['password'] = $private_key_password;
17940 $this->signature_data['extracerts'] = $extracerts;
17941 $this->signature_data['cert_type'] = $cert_type;
17942 $this->signature_data['info'] = $info;
17943 }
17944
17945 /**
17946 * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
17947 * @param $x (float) Abscissa of the upper-left corner.
17948 * @param $y (float) Ordinate of the upper-left corner.
17949 * @param $w (float) Width of the signature area.
17950 * @param $h (float) Height of the signature area.
17951 * @param $page (int) option page number (if < 0 the current page is used).
17952 * @public
17953 * @author Nicola Asuni
17954 * @since 5.3.011 (2010-06-17)
17955 */
17956 public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1) {
17957 $this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page);
17958 }
17959
17960 /**
17961 * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
17962 * @param $x (float) Abscissa of the upper-left corner.
17963 * @param $y (float) Ordinate of the upper-left corner.
17964 * @param $w (float) Width of the signature area.
17965 * @param $h (float) Height of the signature area.
17966 * @param $page (int) option page number (if < 0 the current page is used).
17967 * @public
17968 * @author Nicola Asuni
17969 * @since 5.9.101 (2011-07-06)
17970 */
17971 public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1) {
17972 ++$this->n;
17973 $this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page);
17974 }
17975
17976 /**
17977 * Get the array that defines the signature appearance (page and rectangle coordinates).
17978 * @param $x (float) Abscissa of the upper-left corner.
17979 * @param $y (float) Ordinate of the upper-left corner.
17980 * @param $w (float) Width of the signature area.
17981 * @param $h (float) Height of the signature area.
17982 * @param $page (int) option page number (if < 0 the current page is used).
17983 * @return (array) Array defining page and rectangle coordinates of signature appearance.
17984 * @protected
17985 * @author Nicola Asuni
17986 * @since 5.9.101 (2011-07-06)
17987 */
17988 protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1) {
17989 $sigapp = array();
17990 if (($page < 1) OR ($page > $this->numpages)) {
17991 $sigapp['page'] = $this->page;
17992 } else {
17993 $sigapp['page'] = intval($page);
17994 }
17995 $a = $x * $this->k;
17996 $b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
17997 $c = $w * $this->k;
17998 $d = $h * $this->k;
17999 $sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
18000 return $sigapp;
18001 }
18002
18003 /**
18004 * Create a new page group.
18005 * NOTE: call this function before calling AddPage()
18006 * @param $page (int) starting group page (leave empty for next page).
18007 * @public
18008 * @since 3.0.000 (2008-03-27)
18009 */
18010 public function startPageGroup($page='') {
18011 if (empty($page)) {
18012 $page = $this->page + 1;
18013 }
18014 $this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
18015 }
18016
18017 /**
18018 * This method is DEPRECATED and doesn't have any effect.
18019 * Please remove any reference to this method.
18020 * @param $s (string) Empty parameter.
18021 * @deprecated deprecated since version 5.9.089 (2011-06-13)
18022 * @public
18023 */
18024 public function AliasNbPages($s='') {}
18025
18026 /**
18027 * This method is DEPRECATED and doesn't have any effect.
18028 * Please remove any reference to this method.
18029 * @param $s (string) Empty parameter.
18030 * @deprecated deprecated since version 5.9.089 (2011-06-13)
18031 * @public
18032 */
18033 public function AliasNumPage($s='') {}
18034
18035 /**
18036 * Set the starting page number.
18037 * @param $num (int) Starting page number.
18038 * @since 5.9.093 (2011-06-16)
18039 * @public
18040 */
18041 public function setStartingPageNumber($num=1) {
18042 $this->starting_page_number = max(0, intval($num));
18043 }
18044
18045 /**
18046 * Returns the string alias used right align page numbers.
18047 * If the current font is unicode type, the returned string wil contain an additional open curly brace.
18048 * @return string
18049 * @since 5.9.099 (2011-06-27)
18050 * @public
18051 */
18052 public function getAliasRightShift() {
18053 // calculate aproximatively the ratio between widths of aliases and replacements.
18054 $ref = '{'.$this->alias_right_shift.'}{'.$this->alias_tot_pages.'}{'.$this->alias_num_page.'}';
18055 $rep = str_repeat(' ', $this->GetNumChars($ref));
18056 $wdiff = max(1, ($this->GetStringWidth($ref) / $this->GetStringWidth($rep)));
18057 $sdiff = sprintf('%F', $wdiff);
18058 $alias = $this->alias_right_shift.$sdiff.'}';
18059 if ($this->isUnicodeFont()) {
18060 $alias = '{'.$alias;
18061 }
18062 return $alias;
18063 }
18064
18065 /**
18066 * Returns the string alias used for the total number of pages.
18067 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
18068 * This alias will be replaced by the total number of pages in the document.
18069 * @return string
18070 * @since 4.0.018 (2008-08-08)
18071 * @public
18072 */
18073 public function getAliasNbPages() {
18074 if ($this->isUnicodeFont()) {
18075 return '{'.$this->alias_tot_pages.'}';
18076 }
18077 return $this->alias_tot_pages;
18078 }
18079
18080 /**
18081 * Returns the string alias used for the page number.
18082 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
18083 * This alias will be replaced by the page number.
18084 * @return string
18085 * @since 4.5.000 (2009-01-02)
18086 * @public
18087 */
18088 public function getAliasNumPage() {
18089 if ($this->isUnicodeFont()) {
18090 return '{'.$this->alias_num_page.'}';
18091 }
18092 return $this->alias_num_page;
18093 }
18094
18095 /**
18096 * Return the alias for the total number of pages in the current page group.
18097 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
18098 * This alias will be replaced by the total number of pages in this group.
18099 * @return alias of the current page group
18100 * @public
18101 * @since 3.0.000 (2008-03-27)
18102 */
18103 public function getPageGroupAlias() {
18104 if ($this->isUnicodeFont()) {
18105 return '{'.$this->alias_group_tot_pages.'}';
18106 }
18107 return $this->alias_group_tot_pages;
18108 }
18109
18110 /**
18111 * Return the alias for the page number on the current page group.
18112 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
18113 * This alias will be replaced by the page number (relative to the belonging group).
18114 * @return alias of the current page group
18115 * @public
18116 * @since 4.5.000 (2009-01-02)
18117 */
18118 public function getPageNumGroupAlias() {
18119 if ($this->isUnicodeFont()) {
18120 return '{'.$this->alias_group_num_page.'}';
18121 }
18122 return $this->alias_group_num_page;
18123 }
18124
18125 /**
18126 * Return the current page in the group.
18127 * @return current page in the group
18128 * @public
18129 * @since 3.0.000 (2008-03-27)
18130 */
18131 public function getGroupPageNo() {
18132 return $this->pagegroups[$this->currpagegroup];
18133 }
18134
18135 /**
18136 * Returns the current group page number formatted as a string.
18137 * @public
18138 * @since 4.3.003 (2008-11-18)
18139 * @see PaneNo(), formatPageNumber()
18140 */
18141 public function getGroupPageNoFormatted() {
18142 return $this->formatPageNumber($this->getGroupPageNo());
18143 }
18144
18145 /**
18146 * Format the page numbers.
18147 * This method can be overriden for custom formats.
18148 * @param $num (int) page number
18149 * @protected
18150 * @since 4.2.005 (2008-11-06)
18151 */
18152 protected function formatPageNumber($num) {
18153 return number_format((float)$num, 0, '', '.');
18154 }
18155
18156 /**
18157 * Format the page numbers on the Table Of Content.
18158 * This method can be overriden for custom formats.
18159 * @param $num (int) page number
18160 * @protected
18161 * @since 4.5.001 (2009-01-04)
18162 * @see addTOC(), addHTMLTOC()
18163 */
18164 protected function formatTOCPageNumber($num) {
18165 return number_format((float)$num, 0, '', '.');
18166 }
18167
18168 /**
18169 * Returns the current page number formatted as a string.
18170 * @public
18171 * @since 4.2.005 (2008-11-06)
18172 * @see PaneNo(), formatPageNumber()
18173 */
18174 public function PageNoFormatted() {
18175 return $this->formatPageNumber($this->PageNo());
18176 }
18177
18178 /**
18179 * Put pdf layers.
18180 * @protected
18181 * @since 3.0.000 (2008-03-27)
18182 */
18183 protected function _putocg() {
18184 if (empty($this->pdflayers)) {
18185 return;
18186 }
18187 foreach ($this->pdflayers as $key => $layer) {
18188 $this->pdflayers[$key]['objid'] = $this->_newobj();
18189 $out = '<< /Type /OCG';
18190 $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
18191 $out .= ' /Usage <<';
18192 $out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
18193 $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
18194 $out .= ' >> >>';
18195 $out .= "\n".'endobj';
18196 $this->_out($out);
18197 }
18198 }
18199
18200 /**
18201 * Start a new pdf layer.
18202 * @param $name (string) Layer name (only a-z letters and numbers). Leave empty for automatic name.
18203 * @param $print (boolean) Set to true to print this layer.
18204 * @param $view (boolean) Set to true to view this layer.
18205 * @public
18206 * @since 5.9.102 (2011-07-13)
18207 */
18208 public function startLayer($name='', $print=true, $view=true) {
18209 $layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
18210 if (empty($name)) {
18211 $name = $layer;
18212 } else {
18213 $name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
18214 }
18215 $this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view);
18216 $this->openMarkedContent = true;
18217 $this->_out('/OC /'.$layer.' BDC');
18218 }
18219
18220 /**
18221 * End the current PDF layer.
18222 * @public
18223 * @since 5.9.102 (2011-07-13)
18224 */
18225 public function endLayer() {
18226 if ($this->openMarkedContent) {
18227 // close existing open marked-content layer
18228 $this->_out('EMC');
18229 $this->openMarkedContent = false;
18230 }
18231 }
18232
18233 /**
18234 * Set the visibility of the successive elements.
18235 * This can be useful, for instance, to put a background
18236 * image or color that will show on screen but won't print.
18237 * @param $v (string) visibility mode. Legal values are: all, print, screen or view.
18238 * @public
18239 * @since 3.0.000 (2008-03-27)
18240 */
18241 public function setVisibility($v) {
18242 $this->endLayer();
18243 switch($v) {
18244 case 'print': {
18245 $this->startLayer('Print', true, false);
18246 break;
18247 }
18248 case 'view':
18249 case 'screen': {
18250 $this->startLayer('View', false, true);
18251 break;
18252 }
18253 case 'all': {
18254 $this->_out('');
18255 break;
18256 }
18257 default: {
18258 $this->Error('Incorrect visibility: '.$v);
18259 break;
18260 }
18261 }
18262 }
18263
18264 /**
18265 * Add transparency parameters to the current extgstate
18266 * @param $parms (array) parameters
18267 * @return the number of extgstates
18268 * @protected
18269 * @since 3.0.000 (2008-03-27)
18270 */
18271 protected function addExtGState($parms) {
18272 if ($this->pdfa_mode) {
18273 // transparencies are not allowed in PDF/A mode
18274 return;
18275 }
18276 // check if this ExtGState already exist
18277 foreach ($this->extgstates as $i => $ext) {
18278 if ($ext['parms'] == $parms) {
18279 if ($this->inxobj) {
18280 // we are inside an XObject template
18281 $this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
18282 }
18283 // return reference to existing ExtGState
18284 return $i;
18285 }
18286 }
18287 $n = (count($this->extgstates) + 1);
18288 $this->extgstates[$n] = array('parms' => $parms);
18289 if ($this->inxobj) {
18290 // we are inside an XObject template
18291 $this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
18292 }
18293 return $n;
18294 }
18295
18296 /**
18297 * Add an extgstate
18298 * @param $gs (array) extgstate
18299 * @protected
18300 * @since 3.0.000 (2008-03-27)
18301 */
18302 protected function setExtGState($gs) {
18303 if ($this->pdfa_mode) {
18304 // transparency is not allowed in PDF/A mode
18305 return;
18306 }
18307 $this->_out(sprintf('/GS%d gs', $gs));
18308 }
18309
18310 /**
18311 * Put extgstates for object transparency
18312 * @protected
18313 * @since 3.0.000 (2008-03-27)
18314 */
18315 protected function _putextgstates() {
18316 foreach ($this->extgstates as $i => $ext) {
18317 $this->extgstates[$i]['n'] = $this->_newobj();
18318 $out = '<< /Type /ExtGState';
18319 foreach ($ext['parms'] as $k => $v) {
18320 if (is_float($v)) {
18321 $v = sprintf('%F', $v);
18322 } elseif ($v === true) {
18323 $v = 'true';
18324 } elseif ($v === false) {
18325 $v = 'false';
18326 }
18327 $out .= ' /'.$k.' '.$v;
18328 }
18329 $out .= ' >>';
18330 $out .= "\n".'endobj';
18331 $this->_out($out);
18332 }
18333 }
18334
18335 /**
18336 * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
18337 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
18338 * @param $stroking (boolean) If true apply overprint for stroking operations.
18339 * @param $nonstroking (boolean) If true apply overprint for painting operations other than stroking.
18340 * @param $mode (integer) Overprint mode: (0 = each source colour component value replaces the value previously painted for the corresponding device colorant; 1 = a tint value of 0.0 for a source colour component shall leave the corresponding component of the previously painted colour unchanged).
18341 * @public
18342 * @since 5.9.152 (2012-03-23)
18343 */
18344 public function setOverprint($stroking=true, $nonstroking='', $mode=0) {
18345 $stroking = $stroking ? true : false;
18346 if ($this->empty_string($nonstroking)) {
18347 // default value if not set
18348 $nonstroking = $stroking;
18349 } else {
18350 $nonstroking = $nonstroking ? true : false;
18351 }
18352 if (($mode != 0) AND ($mode != 1)) {
18353 $mode = 0;
18354 }
18355 $this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
18356 $gs = $this->addExtGState($this->overprint);
18357 $this->setExtGState($gs);
18358 }
18359
18360 /**
18361 * Get the overprint mode array (OP, op, OPM).
18362 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
18363 * @return array.
18364 * @public
18365 * @since 5.9.152 (2012-03-23)
18366 */
18367 public function getOverprint() {
18368 return $this->overprint;
18369 }
18370
18371 /**
18372 * Set alpha for stroking (CA) and non-stroking (ca) operations.
18373 * @param $stroking (float) Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
18374 * @param $bm (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity
18375 * @param $nonstroking (float) Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
18376 * @param $ais (boolean)
18377 * @public
18378 * @since 3.0.000 (2008-03-27)
18379 */
18380 public function setAlpha($stroking=1, $bm='Normal', $nonstroking='', $ais=false) {
18381 if ($this->pdfa_mode) {
18382 // transparency is not allowed in PDF/A mode
18383 return;
18384 }
18385 $stroking = floatval($stroking);
18386 if ($this->empty_string($nonstroking)) {
18387 // default value if not set
18388 $nonstroking = $stroking;
18389 } else {
18390 $nonstroking = floatval($nonstroking);
18391 }
18392 if ($bm[0] == '/') {
18393 // remove trailing slash
18394 $bm = substr($bm, 1);
18395 }
18396 if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
18397 $bm = 'Normal';
18398 }
18399 $ais = $ais ? true : false;
18400 $this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
18401 $gs = $this->addExtGState($this->alpha);
18402 $this->setExtGState($gs);
18403 }
18404
18405 /**
18406 * Get the alpha mode array (CA, ca, BM, AIS).
18407 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
18408 * @return array.
18409 * @public
18410 * @since 5.9.152 (2012-03-23)
18411 */
18412 public function getAlpha() {
18413 return $this->alpha;
18414 }
18415
18416 /**
18417 * Set the default JPEG compression quality (1-100)
18418 * @param $quality (int) JPEG quality, integer between 1 and 100
18419 * @public
18420 * @since 3.0.000 (2008-03-27)
18421 */
18422 public function setJPEGQuality($quality) {
18423 if (($quality < 1) OR ($quality > 100)) {
18424 $quality = 75;
18425 }
18426 $this->jpeg_quality = intval($quality);
18427 }
18428
18429 /**
18430 * Set the default number of columns in a row for HTML tables.
18431 * @param $cols (int) number of columns
18432 * @public
18433 * @since 3.0.014 (2008-06-04)
18434 */
18435 public function setDefaultTableColumns($cols=4) {
18436 $this->default_table_columns = intval($cols);
18437 }
18438
18439 /**
18440 * Set the height of the cell (line height) respect the font height.
18441 * @param $h (int) cell proportion respect font height (typical value = 1.25).
18442 * @public
18443 * @since 3.0.014 (2008-06-04)
18444 */
18445 public function setCellHeightRatio($h) {
18446 $this->cell_height_ratio = $h;
18447 }
18448
18449 /**
18450 * return the height of cell repect font height.
18451 * @public
18452 * @since 4.0.012 (2008-07-24)
18453 */
18454 public function getCellHeightRatio() {
18455 return $this->cell_height_ratio;
18456 }
18457
18458 /**
18459 * Set the PDF version (check PDF reference for valid values).
18460 * @param $version (string) PDF document version.
18461 * @public
18462 * @since 3.1.000 (2008-06-09)
18463 */
18464 public function setPDFVersion($version='1.7') {
18465 if ($this->pdfa_mode) {
18466 // PDF/A mode
18467 $this->PDFVersion = '1.4';
18468 } else {
18469 $this->PDFVersion = $version;
18470 }
18471 }
18472
18473 /**
18474 * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
18475 * (see Section 8.1 of PDF reference, "Viewer Preferences").
18476 * <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>
18477 * @param $preferences (array) array of options.
18478 * @author Nicola Asuni
18479 * @public
18480 * @since 3.1.000 (2008-06-09)
18481 */
18482 public function setViewerPreferences($preferences) {
18483 $this->viewer_preferences = $preferences;
18484 }
18485
18486 /**
18487 * Paints color transition registration bars
18488 * @param $x (float) abscissa of the top left corner of the rectangle.
18489 * @param $y (float) ordinate of the top left corner of the rectangle.
18490 * @param $w (float) width of the rectangle.
18491 * @param $h (float) height of the rectangle.
18492 * @param $transition (boolean) if true prints tcolor transitions to white.
18493 * @param $vertical (boolean) if true prints bar vertically.
18494 * @param $colors (string) colors to print, one letter per color separated by comma (for example 'A,W,R,G,B,C,M,Y,K'): A=black, W=white, R=red, G=green, B=blue, C=cyan, M=magenta, Y=yellow, K=black.
18495 * @author Nicola Asuni
18496 * @since 4.9.000 (2010-03-26)
18497 * @public
18498 */
18499 public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
18500 $bars = explode(',', $colors);
18501 $numbars = count($bars); // number of bars to print
18502 // set bar measures
18503 if ($vertical) {
18504 $coords = array(0, 0, 0, 1);
18505 $wb = $w / $numbars; // bar width
18506 $hb = $h; // bar height
18507 $xd = $wb; // delta x
18508 $yd = 0; // delta y
18509 } else {
18510 $coords = array(1, 0, 0, 0);
18511 $wb = $w; // bar width
18512 $hb = $h / $numbars; // bar height
18513 $xd = 0; // delta x
18514 $yd = $hb; // delta y
18515 }
18516 $xb = $x;
18517 $yb = $y;
18518 foreach ($bars as $col) {
18519 switch ($col) {
18520 // set transition colors
18521 case 'A': { // BLACK
18522 $col_a = array(255);
18523 $col_b = array(0);
18524 break;
18525 }
18526 case 'W': { // WHITE
18527 $col_a = array(0);
18528 $col_b = array(255);
18529 break;
18530 }
18531 case 'R': { // R
18532 $col_a = array(255,255,255);
18533 $col_b = array(255,0,0);
18534 break;
18535 }
18536 case 'G': { // G
18537 $col_a = array(255,255,255);
18538 $col_b = array(0,255,0);
18539 break;
18540 }
18541 case 'B': { // B
18542 $col_a = array(255,255,255);
18543 $col_b = array(0,0,255);
18544 break;
18545 }
18546 case 'C': { // C
18547 $col_a = array(0,0,0,0);
18548 $col_b = array(100,0,0,0);
18549 break;
18550 }
18551 case 'M': { // M
18552 $col_a = array(0,0,0,0);
18553 $col_b = array(0,100,0,0);
18554 break;
18555 }
18556 case 'Y': { // Y
18557 $col_a = array(0,0,0,0);
18558 $col_b = array(0,0,100,0);
18559 break;
18560 }
18561 case 'K': { // K
18562 $col_a = array(0,0,0,0);
18563 $col_b = array(0,0,0,100);
18564 break;
18565 }
18566 default: { // GRAY
18567 $col_a = array(255);
18568 $col_b = array(0);
18569 break;
18570 }
18571 }
18572 if ($transition) {
18573 // color gradient
18574 $this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
18575 } else {
18576 // color rectangle
18577 $this->SetFillColorArray($col_b);
18578 $this->Rect($xb, $yb, $wb, $hb, 'F', array());
18579 }
18580 $xb += $xd;
18581 $yb += $yd;
18582 }
18583 }
18584
18585 /**
18586 * Paints crop marks.
18587 * @param $x (float) abscissa of the crop mark center.
18588 * @param $y (float) ordinate of the crop mark center.
18589 * @param $w (float) width of the crop mark.
18590 * @param $h (float) height of the crop mark.
18591 * @param $type (string) type of crop mark, one symbol per type separated by comma: T = TOP, F = BOTTOM, L = LEFT, R = RIGHT, TL = A = TOP-LEFT, TR = B = TOP-RIGHT, BL = C = BOTTOM-LEFT, BR = D = BOTTOM-RIGHT.
18592 * @param $color (array) crop mark color (default black).
18593 * @author Nicola Asuni
18594 * @since 4.9.000 (2010-03-26)
18595 * @public
18596 */
18597 public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(0,0,0)) {
18598 $this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
18599 $type = strtoupper($type);
18600 $type = preg_replace('/[^A-Z\-\,]*/', '', $type);
18601 // split type in single components
18602 $type = str_replace('-', ',', $type);
18603 $type = str_replace('TL', 'T,L', $type);
18604 $type = str_replace('TR', 'T,R', $type);
18605 $type = str_replace('BL', 'F,L', $type);
18606 $type = str_replace('BR', 'F,R', $type);
18607 $type = str_replace('A', 'T,L', $type);
18608 $type = str_replace('B', 'T,R', $type);
18609 $type = str_replace('T,RO', 'BO', $type);
18610 $type = str_replace('C', 'F,L', $type);
18611 $type = str_replace('D', 'F,R', $type);
18612 $crops = explode(',', strtoupper($type));
18613 // remove duplicates
18614 $crops = array_unique($crops);
18615 $dw = ($w / 4); // horizontal space to leave before the intersection point
18616 $dh = ($h / 4); // vertical space to leave before the intersection point
18617 foreach ($crops as $crop) {
18618 switch ($crop) {
18619 case 'T':
18620 case 'TOP': {
18621 $x1 = $x;
18622 $y1 = ($y - $h);
18623 $x2 = $x;
18624 $y2 = ($y - $dh);
18625 break;
18626 }
18627 case 'F':
18628 case 'BOTTOM': {
18629 $x1 = $x;
18630 $y1 = ($y + $dh);
18631 $x2 = $x;
18632 $y2 = ($y + $h);
18633 break;
18634 }
18635 case 'L':
18636 case 'LEFT': {
18637 $x1 = ($x - $w);
18638 $y1 = $y;
18639 $x2 = ($x - $dw);
18640 $y2 = $y;
18641 break;
18642 }
18643 case 'R':
18644 case 'RIGHT': {
18645 $x1 = ($x + $dw);
18646 $y1 = $y;
18647 $x2 = ($x + $w);
18648 $y2 = $y;
18649 break;
18650 }
18651 }
18652 $this->Line($x1, $y1, $x2, $y2);
18653 }
18654 }
18655
18656 /**
18657 * Paints a registration mark
18658 * @param $x (float) abscissa of the registration mark center.
18659 * @param $y (float) ordinate of the registration mark center.
18660 * @param $r (float) radius of the crop mark.
18661 * @param $double (boolean) if true print two concentric crop marks.
18662 * @param $cola (array) crop mark color (default black).
18663 * @param $colb (array) second crop mark color.
18664 * @author Nicola Asuni
18665 * @since 4.9.000 (2010-03-26)
18666 * @public
18667 */
18668 public function registrationMark($x, $y, $r, $double=false, $cola=array(0,0,0), $colb=array(255,255,255)) {
18669 $line_style = array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
18670 $this->SetFillColorArray($cola);
18671 $this->PieSector($x, $y, $r, 90, 180, 'F');
18672 $this->PieSector($x, $y, $r, 270, 360, 'F');
18673 $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
18674 if ($double) {
18675 $r2 = $r * 0.5;
18676 $this->SetFillColorArray($colb);
18677 $this->PieSector($x, $y, $r2, 90, 180, 'F');
18678 $this->PieSector($x, $y, $r2, 270, 360, 'F');
18679 $this->SetFillColorArray($cola);
18680 $this->PieSector($x, $y, $r2, 0, 90, 'F');
18681 $this->PieSector($x, $y, $r2, 180, 270, 'F');
18682 $this->Circle($x, $y, $r2, 0, 360, 'C', $line_style, array(), 8);
18683 }
18684 }
18685
18686 /**
18687 * Paints a linear colour gradient.
18688 * @param $x (float) abscissa of the top left corner of the rectangle.
18689 * @param $y (float) ordinate of the top left corner of the rectangle.
18690 * @param $w (float) width of the rectangle.
18691 * @param $h (float) height of the rectangle.
18692 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
18693 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
18694 * @param $coords (array) array of the form (x1, y1, x2, y2) which defines the gradient vector (see linear_gradient_coords.jpg). The default value is from left to right (x1=0, y1=0, x2=1, y2=0).
18695 * @author Andreas W�rmser, Nicola Asuni
18696 * @since 3.1.000 (2008-06-09)
18697 * @public
18698 */
18699 public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
18700 $this->Clip($x, $y, $w, $h);
18701 $this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
18702 }
18703
18704 /**
18705 * Paints a radial colour gradient.
18706 * @param $x (float) abscissa of the top left corner of the rectangle.
18707 * @param $y (float) ordinate of the top left corner of the rectangle.
18708 * @param $w (float) width of the rectangle.
18709 * @param $h (float) height of the rectangle.
18710 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
18711 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
18712 * @param $coords (array) array of the form (fx, fy, cx, cy, r) where (fx, fy) is the starting point of the gradient with color1, (cx, cy) is the center of the circle with color2, and r is the radius of the circle (see radial_gradient_coords.jpg). (fx, fy) should be inside the circle, otherwise some areas will not be defined.
18713 * @author Andreas W�rmser, Nicola Asuni
18714 * @since 3.1.000 (2008-06-09)
18715 * @public
18716 */
18717 public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
18718 $this->Clip($x, $y, $w, $h);
18719 $this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
18720 }
18721
18722 /**
18723 * Paints a coons patch mesh.
18724 * @param $x (float) abscissa of the top left corner of the rectangle.
18725 * @param $y (float) ordinate of the top left corner of the rectangle.
18726 * @param $w (float) width of the rectangle.
18727 * @param $h (float) height of the rectangle.
18728 * @param $col1 (array) first color (lower left corner) (RGB components).
18729 * @param $col2 (array) second color (lower right corner) (RGB components).
18730 * @param $col3 (array) third color (upper right corner) (RGB components).
18731 * @param $col4 (array) fourth color (upper left corner) (RGB components).
18732 * @param $coords (array) <ul><li>for one patch mesh: array(float x1, float y1, .... float x12, float y12): 12 pairs of coordinates (normally from 0 to 1) which specify the Bezier control points that define the patch. First pair is the lower left edge point, next is its right control point (control point 2). Then the other points are defined in the order: control point 1, edge point, control point 2 going counter-clockwise around the patch. Last (x12, y12) is the first edge point's left control point (control point 1).</li><li>for two or more patch meshes: array[number of patches]: arrays with the following keys for each patch: f: where to put that patch (0 = first patch, 1, 2, 3 = right, top and left of precedent patch - I didn't figure this out completely - just try and error ;-) points: 12 pairs of coordinates of the Bezier control points as above for the first patch, 8 pairs of coordinates for the following patches, ignoring the coordinates already defined by the precedent patch (I also didn't figure out the order of these - also: try and see what's happening) colors: must be 4 colors for the first patch, 2 colors for the following patches</li></ul>
18733 * @param $coords_min (array) minimum value used by the coordinates. If a coordinate's value is smaller than this it will be cut to coords_min. default: 0
18734 * @param $coords_max (array) maximum value used by the coordinates. If a coordinate's value is greater than this it will be cut to coords_max. default: 1
18735 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
18736 * @author Andreas W�rmser, Nicola Asuni
18737 * @since 3.1.000 (2008-06-09)
18738 * @public
18739 */
18740 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) {
18741 if ($this->pdfa_mode) {
18742 return;
18743 }
18744 $this->Clip($x, $y, $w, $h);
18745 $n = count($this->gradients) + 1;
18746 $this->gradients[$n] = array();
18747 $this->gradients[$n]['type'] = 6; //coons patch mesh
18748 $this->gradients[$n]['coords'] = array();
18749 $this->gradients[$n]['antialias'] = $antialias;
18750 $this->gradients[$n]['colors'] = array();
18751 $this->gradients[$n]['transparency'] = false;
18752 //check the coords array if it is the simple array or the multi patch array
18753 if (!isset($coords[0]['f'])) {
18754 //simple array -> convert to multi patch array
18755 if (!isset($col1[1])) {
18756 $col1[1] = $col1[2] = $col1[0];
18757 }
18758 if (!isset($col2[1])) {
18759 $col2[1] = $col2[2] = $col2[0];
18760 }
18761 if (!isset($col3[1])) {
18762 $col3[1] = $col3[2] = $col3[0];
18763 }
18764 if (!isset($col4[1])) {
18765 $col4[1] = $col4[2] = $col4[0];
18766 }
18767 $patch_array[0]['f'] = 0;
18768 $patch_array[0]['points'] = $coords;
18769 $patch_array[0]['colors'][0]['r'] = $col1[0];
18770 $patch_array[0]['colors'][0]['g'] = $col1[1];
18771 $patch_array[0]['colors'][0]['b'] = $col1[2];
18772 $patch_array[0]['colors'][1]['r'] = $col2[0];
18773 $patch_array[0]['colors'][1]['g'] = $col2[1];
18774 $patch_array[0]['colors'][1]['b'] = $col2[2];
18775 $patch_array[0]['colors'][2]['r'] = $col3[0];
18776 $patch_array[0]['colors'][2]['g'] = $col3[1];
18777 $patch_array[0]['colors'][2]['b'] = $col3[2];
18778 $patch_array[0]['colors'][3]['r'] = $col4[0];
18779 $patch_array[0]['colors'][3]['g'] = $col4[1];
18780 $patch_array[0]['colors'][3]['b'] = $col4[2];
18781 } else {
18782 //multi patch array
18783 $patch_array = $coords;
18784 }
18785 $bpcd = 65535; //16 bits per coordinate
18786 //build the data stream
18787 $this->gradients[$n]['stream'] = '';
18788 $count_patch = count($patch_array);
18789 for ($i=0; $i < $count_patch; ++$i) {
18790 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
18791 $count_points = count($patch_array[$i]['points']);
18792 for ($j=0; $j < $count_points; ++$j) {
18793 //each point as 16 bit
18794 $patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
18795 if ($patch_array[$i]['points'][$j] < 0) {
18796 $patch_array[$i]['points'][$j] = 0;
18797 }
18798 if ($patch_array[$i]['points'][$j] > $bpcd) {
18799 $patch_array[$i]['points'][$j] = $bpcd;
18800 }
18801 $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
18802 $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
18803 }
18804 $count_cols = count($patch_array[$i]['colors']);
18805 for ($j=0; $j < $count_cols; ++$j) {
18806 //each color component as 8 bit
18807 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
18808 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
18809 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
18810 }
18811 }
18812 //paint the gradient
18813 $this->_out('/Sh'.$n.' sh');
18814 //restore previous Graphic State
18815 $this->_out('Q');
18816 if ($this->inxobj) {
18817 // we are inside an XObject template
18818 $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
18819 }
18820 }
18821
18822 /**
18823 * Set a rectangular clipping area.
18824 * @param $x (float) abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
18825 * @param $y (float) ordinate of the top left corner of the rectangle.
18826 * @param $w (float) width of the rectangle.
18827 * @param $h (float) height of the rectangle.
18828 * @author Andreas W�rmser, Nicola Asuni
18829 * @since 3.1.000 (2008-06-09)
18830 * @protected
18831 */
18832 protected function Clip($x, $y, $w, $h) {
18833 if ($this->rtl) {
18834 $x = $this->w - $x - $w;
18835 }
18836 //save current Graphic State
18837 $s = 'q';
18838 //set clipping area
18839 $s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
18840 //set up transformation matrix for gradient
18841 $s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
18842 $this->_out($s);
18843 }
18844
18845 /**
18846 * Output gradient.
18847 * @param $type (int) type of gradient (1 Function-based shading; 2 Axial shading; 3 Radial shading; 4 Free-form Gouraud-shaded triangle mesh; 5 Lattice-form Gouraud-shaded triangle mesh; 6 Coons patch mesh; 7 Tensor-product patch mesh). (Not all types are currently supported)
18848 * @param $coords (array) array of coordinates.
18849 * @param $stops (array) array gradient color components: color = array of GRAY, RGB or CMYK color components; offset = (0 to 1) represents a location along the gradient vector; exponent = exponent of the exponential interpolation function (default = 1).
18850 * @param $background (array) An array of colour components appropriate to the colour space, specifying a single background colour value.
18851 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
18852 * @author Nicola Asuni
18853 * @since 3.1.000 (2008-06-09)
18854 * @public
18855 */
18856 public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
18857 if ($this->pdfa_mode) {
18858 return;
18859 }
18860 $n = count($this->gradients) + 1;
18861 $this->gradients[$n] = array();
18862 $this->gradients[$n]['type'] = $type;
18863 $this->gradients[$n]['coords'] = $coords;
18864 $this->gradients[$n]['antialias'] = $antialias;
18865 $this->gradients[$n]['colors'] = array();
18866 $this->gradients[$n]['transparency'] = false;
18867 // color space
18868 $numcolspace = count($stops[0]['color']);
18869 $bcolor = array_values($background);
18870 switch($numcolspace) {
18871 case 4: { // CMYK
18872 $this->gradients[$n]['colspace'] = 'DeviceCMYK';
18873 if (!empty($background)) {
18874 $this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
18875 }
18876 break;
18877 }
18878 case 3: { // RGB
18879 $this->gradients[$n]['colspace'] = 'DeviceRGB';
18880 if (!empty($background)) {
18881 $this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
18882 }
18883 break;
18884 }
18885 case 1: { // Gray scale
18886 $this->gradients[$n]['colspace'] = 'DeviceGray';
18887 if (!empty($background)) {
18888 $this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
18889 }
18890 break;
18891 }
18892 }
18893 $num_stops = count($stops);
18894 $last_stop_id = $num_stops - 1;
18895 foreach ($stops as $key => $stop) {
18896 $this->gradients[$n]['colors'][$key] = array();
18897 // offset represents a location along the gradient vector
18898 if (isset($stop['offset'])) {
18899 $this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
18900 } else {
18901 if ($key == 0) {
18902 $this->gradients[$n]['colors'][$key]['offset'] = 0;
18903 } elseif ($key == $last_stop_id) {
18904 $this->gradients[$n]['colors'][$key]['offset'] = 1;
18905 } else {
18906 $offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
18907 $this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
18908 }
18909 }
18910 if (isset($stop['opacity'])) {
18911 $this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
18912 if ((!$this->pdfa_mode) AND ($stop['opacity'] < 1)) {
18913 $this->gradients[$n]['transparency'] = true;
18914 }
18915 } else {
18916 $this->gradients[$n]['colors'][$key]['opacity'] = 1;
18917 }
18918 // exponent for the exponential interpolation function
18919 if (isset($stop['exponent'])) {
18920 $this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
18921 } else {
18922 $this->gradients[$n]['colors'][$key]['exponent'] = 1;
18923 }
18924 // set colors
18925 $color = array_values($stop['color']);
18926 switch($numcolspace) {
18927 case 4: { // CMYK
18928 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
18929 break;
18930 }
18931 case 3: { // RGB
18932 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
18933 break;
18934 }
18935 case 1: { // Gray scale
18936 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
18937 break;
18938 }
18939 }
18940 }
18941 if ($this->gradients[$n]['transparency']) {
18942 // paint luminosity gradient
18943 $this->_out('/TGS'.$n.' gs');
18944 }
18945 //paint the gradient
18946 $this->_out('/Sh'.$n.' sh');
18947 //restore previous Graphic State
18948 $this->_out('Q');
18949 if ($this->inxobj) {
18950 // we are inside an XObject template
18951 $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
18952 }
18953 }
18954
18955 /**
18956 * Output gradient shaders.
18957 * @author Nicola Asuni
18958 * @since 3.1.000 (2008-06-09)
18959 * @protected
18960 */
18961 function _putshaders() {
18962 if ($this->pdfa_mode) {
18963 return;
18964 }
18965 $idt = count($this->gradients); //index for transparency gradients
18966 foreach ($this->gradients as $id => $grad) {
18967 if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
18968 $fc = $this->_newobj();
18969 $out = '<<';
18970 $out .= ' /FunctionType 3';
18971 $out .= ' /Domain [0 1]';
18972 $functions = '';
18973 $bounds = '';
18974 $encode = '';
18975 $i = 1;
18976 $num_cols = count($grad['colors']);
18977 $lastcols = $num_cols - 1;
18978 for ($i = 1; $i < $num_cols; ++$i) {
18979 $functions .= ($fc + $i).' 0 R ';
18980 if ($i < $lastcols) {
18981 $bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
18982 }
18983 $encode .= '0 1 ';
18984 }
18985 $out .= ' /Functions ['.trim($functions).']';
18986 $out .= ' /Bounds ['.trim($bounds).']';
18987 $out .= ' /Encode ['.trim($encode).']';
18988 $out .= ' >>';
18989 $out .= "\n".'endobj';
18990 $this->_out($out);
18991 for ($i = 1; $i < $num_cols; ++$i) {
18992 $this->_newobj();
18993 $out = '<<';
18994 $out .= ' /FunctionType 2';
18995 $out .= ' /Domain [0 1]';
18996 $out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
18997 $out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
18998 $out .= ' /N '.$grad['colors'][$i]['exponent'];
18999 $out .= ' >>';
19000 $out .= "\n".'endobj';
19001 $this->_out($out);
19002 }
19003 // set transparency fuctions
19004 if ($grad['transparency']) {
19005 $ft = $this->_newobj();
19006 $out = '<<';
19007 $out .= ' /FunctionType 3';
19008 $out .= ' /Domain [0 1]';
19009 $functions = '';
19010 $i = 1;
19011 $num_cols = count($grad['colors']);
19012 for ($i = 1; $i < $num_cols; ++$i) {
19013 $functions .= ($ft + $i).' 0 R ';
19014 }
19015 $out .= ' /Functions ['.trim($functions).']';
19016 $out .= ' /Bounds ['.trim($bounds).']';
19017 $out .= ' /Encode ['.trim($encode).']';
19018 $out .= ' >>';
19019 $out .= "\n".'endobj';
19020 $this->_out($out);
19021 for ($i = 1; $i < $num_cols; ++$i) {
19022 $this->_newobj();
19023 $out = '<<';
19024 $out .= ' /FunctionType 2';
19025 $out .= ' /Domain [0 1]';
19026 $out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
19027 $out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
19028 $out .= ' /N '.$grad['colors'][$i]['exponent'];
19029 $out .= ' >>';
19030 $out .= "\n".'endobj';
19031 $this->_out($out);
19032 }
19033 }
19034 }
19035 // set shading object
19036 $this->_newobj();
19037 $out = '<< /ShadingType '.$grad['type'];
19038 if (isset($grad['colspace'])) {
19039 $out .= ' /ColorSpace /'.$grad['colspace'];
19040 } else {
19041 $out .= ' /ColorSpace /DeviceRGB';
19042 }
19043 if (isset($grad['background']) AND !empty($grad['background'])) {
19044 $out .= ' /Background ['.$grad['background'].']';
19045 }
19046 if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
19047 $out .= ' /AntiAlias true';
19048 }
19049 if ($grad['type'] == 2) {
19050 $out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
19051 $out .= ' /Domain [0 1]';
19052 $out .= ' /Function '.$fc.' 0 R';
19053 $out .= ' /Extend [true true]';
19054 $out .= ' >>';
19055 } elseif ($grad['type'] == 3) {
19056 //x0, y0, r0, x1, y1, r1
19057 //at this this time radius of inner circle is 0
19058 $out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
19059 $out .= ' /Domain [0 1]';
19060 $out .= ' /Function '.$fc.' 0 R';
19061 $out .= ' /Extend [true true]';
19062 $out .= ' >>';
19063 } elseif ($grad['type'] == 6) {
19064 $out .= ' /BitsPerCoordinate 16';
19065 $out .= ' /BitsPerComponent 8';
19066 $out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
19067 $out .= ' /BitsPerFlag 8';
19068 $stream = $this->_getrawstream($grad['stream']);
19069 $out .= ' /Length '.strlen($stream);
19070 $out .= ' >>';
19071 $out .= ' stream'."\n".$stream."\n".'endstream';
19072 }
19073 $out .= "\n".'endobj';
19074 $this->_out($out);
19075 if ($grad['transparency']) {
19076 $shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
19077 $shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
19078 }
19079 $this->gradients[$id]['id'] = $this->n;
19080 // set pattern object
19081 $this->_newobj();
19082 $out = '<< /Type /Pattern /PatternType 2';
19083 $out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
19084 $out .= ' >>';
19085 $out .= "\n".'endobj';
19086 $this->_out($out);
19087 $this->gradients[$id]['pattern'] = $this->n;
19088 // set shading and pattern for transparency mask
19089 if ($grad['transparency']) {
19090 // luminosity pattern
19091 $idgs = $id + $idt;
19092 $this->_newobj();
19093 $this->_out($shading_transparency);
19094 $this->gradients[$idgs]['id'] = $this->n;
19095 $this->_newobj();
19096 $out = '<< /Type /Pattern /PatternType 2';
19097 $out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
19098 $out .= ' >>';
19099 $out .= "\n".'endobj';
19100 $this->_out($out);
19101 $this->gradients[$idgs]['pattern'] = $this->n;
19102 // luminosity XObject
19103 $oid = $this->_newobj();
19104 $this->xobjects['LX'.$oid] = array('n' => $oid);
19105 $filter = '';
19106 $stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
19107 if ($this->compress) {
19108 $filter = ' /Filter /FlateDecode';
19109 $stream = gzcompress($stream);
19110 }
19111 $stream = $this->_getrawstream($stream);
19112 $out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
19113 $out .= ' /Length '.strlen($stream);
19114 $rect = sprintf('%F %F', $this->wPt, $this->hPt);
19115 $out .= ' /BBox [0 0 '.$rect.']';
19116 $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
19117 $out .= ' /Resources <<';
19118 $out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
19119 $out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
19120 $out .= ' >>';
19121 $out .= ' >> ';
19122 $out .= ' stream'."\n".$stream."\n".'endstream';
19123 $out .= "\n".'endobj';
19124 $this->_out($out);
19125 // SMask
19126 $this->_newobj();
19127 $out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
19128 $this->_out($out);
19129 // ExtGState
19130 $this->_newobj();
19131 $out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
19132 $this->_out($out);
19133 $this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
19134 }
19135 }
19136 }
19137
19138 /**
19139 * Draw the sector of a circle.
19140 * It can be used for instance to render pie charts.
19141 * @param $xc (float) abscissa of the center.
19142 * @param $yc (float) ordinate of the center.
19143 * @param $r (float) radius.
19144 * @param $a (float) start angle (in degrees).
19145 * @param $b (float) end angle (in degrees).
19146 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
19147 * @param $cw: (float) indicates whether to go clockwise (default: true).
19148 * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). Default: 90.
19149 * @author Maxime Delorme, Nicola Asuni
19150 * @since 3.1.000 (2008-06-09)
19151 * @public
19152 */
19153 public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
19154 $this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
19155 }
19156
19157 /**
19158 * Draw the sector of an ellipse.
19159 * It can be used for instance to render pie charts.
19160 * @param $xc (float) abscissa of the center.
19161 * @param $yc (float) ordinate of the center.
19162 * @param $rx (float) the x-axis radius.
19163 * @param $ry (float) the y-axis radius.
19164 * @param $a (float) start angle (in degrees).
19165 * @param $b (float) end angle (in degrees).
19166 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
19167 * @param $cw: (float) indicates whether to go clockwise.
19168 * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
19169 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of arc.
19170 * @author Maxime Delorme, Nicola Asuni
19171 * @since 3.1.000 (2008-06-09)
19172 * @public
19173 */
19174 public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
19175 if ($this->rtl) {
19176 $xc = $this->w - $xc;
19177 }
19178 $op = $this->getPathPaintOperator($style);
19179 if ($op == 'f') {
19180 $line_style = array();
19181 }
19182 if ($cw) {
19183 $d = $b;
19184 $b = 360 - $a + $o;
19185 $a = 360 - $d + $o;
19186 } else {
19187 $b += $o;
19188 $a += $o;
19189 }
19190 $this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
19191 $this->_out($op);
19192 }
19193
19194 /**
19195 * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
19196 * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
19197 * Only vector drawing is supported, not text or bitmap.
19198 * 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).
19199 * @param $file (string) Name of the file containing the image or a '@' character followed by the EPS/AI data string.
19200 * @param $x (float) Abscissa of the upper-left corner.
19201 * @param $y (float) Ordinate of the upper-left corner.
19202 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
19203 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
19204 * @param $link (mixed) URL or identifier returned by AddLink().
19205 * @param $useBoundingBox (boolean) specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
19206 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
19207 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
19208 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
19209 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
19210 * @param $fixoutvals (boolean) if true remove values outside the bounding box.
19211 * @author Valentin Schmidt, Nicola Asuni
19212 * @since 3.1.000 (2008-06-09)
19213 * @public
19214 */
19215 public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
19216 if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
19217 // convert EPS to raster image using GD or ImageMagick libraries
19218 return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
19219 }
19220 if ($x === '') {
19221 $x = $this->x;
19222 }
19223 if ($y === '') {
19224 $y = $this->y;
19225 }
19226 // check page for no-write regions and adapt page margins if necessary
19227 list($x, $y) = $this->checkPageRegions($h, $x, $y);
19228 $k = $this->k;
19229 if ($file{0} === '@') { // image from string
19230 $data = substr($file, 1);
19231 } else { // EPS/AI file
19232 $data = file_get_contents($file);
19233 }
19234 if ($data === false) {
19235 $this->Error('EPS file not found: '.$file);
19236 }
19237 $regs = array();
19238 // EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
19239 preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
19240 if (count($regs) > 1) {
19241 $version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
19242 if (strpos($version_str, 'Adobe Illustrator') !== false) {
19243 $versexp = explode(' ', $version_str);
19244 $version = (float)array_pop($versexp);
19245 if ($version >= 9) {
19246 $this->Error('This version of Adobe Illustrator file is not supported: '.$file);
19247 }
19248 }
19249 }
19250 // strip binary bytes in front of PS-header
19251 $start = strpos($data, '%!PS-Adobe');
19252 if ($start > 0) {
19253 $data = substr($data, $start);
19254 }
19255 // find BoundingBox params
19256 preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
19257 if (count($regs) > 1) {
19258 list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
19259 } else {
19260 $this->Error('No BoundingBox found in EPS/AI file: '.$file);
19261 }
19262 $start = strpos($data, '%%EndSetup');
19263 if ($start === false) {
19264 $start = strpos($data, '%%EndProlog');
19265 }
19266 if ($start === false) {
19267 $start = strpos($data, '%%BoundingBox');
19268 }
19269 $data = substr($data, $start);
19270 $end = strpos($data, '%%PageTrailer');
19271 if ($end===false) {
19272 $end = strpos($data, 'showpage');
19273 }
19274 if ($end) {
19275 $data = substr($data, 0, $end);
19276 }
19277 // calculate image width and height on document
19278 if (($w <= 0) AND ($h <= 0)) {
19279 $w = ($x2 - $x1) / $k;
19280 $h = ($y2 - $y1) / $k;
19281 } elseif ($w <= 0) {
19282 $w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
19283 } elseif ($h <= 0) {
19284 $h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
19285 }
19286 // fit the image on available space
19287 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
19288 if ($this->rasterize_vector_images) {
19289 // convert EPS to raster image using GD or ImageMagick libraries
19290 return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
19291 }
19292 // set scaling factors
19293 $scale_x = $w / (($x2 - $x1) / $k);
19294 $scale_y = $h / (($y2 - $y1) / $k);
19295 // set alignment
19296 $this->img_rb_y = $y + $h;
19297 // set alignment
19298 if ($this->rtl) {
19299 if ($palign == 'L') {
19300 $ximg = $this->lMargin;
19301 } elseif ($palign == 'C') {
19302 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
19303 } elseif ($palign == 'R') {
19304 $ximg = $this->w - $this->rMargin - $w;
19305 } else {
19306 $ximg = $x - $w;
19307 }
19308 $this->img_rb_x = $ximg;
19309 } else {
19310 if ($palign == 'L') {
19311 $ximg = $this->lMargin;
19312 } elseif ($palign == 'C') {
19313 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
19314 } elseif ($palign == 'R') {
19315 $ximg = $this->w - $this->rMargin - $w;
19316 } else {
19317 $ximg = $x;
19318 }
19319 $this->img_rb_x = $ximg + $w;
19320 }
19321 if ($useBoundingBox) {
19322 $dx = $ximg * $k - $x1;
19323 $dy = $y * $k - $y1;
19324 } else {
19325 $dx = $ximg * $k;
19326 $dy = $y * $k;
19327 }
19328 // save the current graphic state
19329 $this->_out('q'.$this->epsmarker);
19330 // translate
19331 $this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
19332 // scale
19333 if (isset($scale_x)) {
19334 $this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
19335 }
19336 // handle pc/unix/mac line endings
19337 $lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
19338 $u=0;
19339 $cnt = count($lines);
19340 for ($i=0; $i < $cnt; ++$i) {
19341 $line = $lines[$i];
19342 if (($line == '') OR ($line{0} == '%')) {
19343 continue;
19344 }
19345 $len = strlen($line);
19346 // check for spot color names
19347 $color_name = '';
19348 if (strcasecmp('x', substr(trim($line), -1)) == 0) {
19349 if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
19350 // extract spot color name
19351 $color_name = $matches[0];
19352 // remove color name from string
19353 $line = str_replace(' '.$color_name, '', $line);
19354 // remove pharentesis from color name
19355 $color_name = substr($color_name, 1, -1);
19356 }
19357 }
19358 $chunks = explode(' ', $line);
19359 $cmd = trim(array_pop($chunks));
19360 // RGB
19361 if (($cmd == 'Xa') OR ($cmd == 'XA')) {
19362 $b = array_pop($chunks);
19363 $g = array_pop($chunks);
19364 $r = array_pop($chunks);
19365 $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!
19366 continue;
19367 }
19368 $skip = false;
19369 if ($fixoutvals) {
19370 // check for values outside the bounding box
19371 switch ($cmd) {
19372 case 'm':
19373 case 'l':
19374 case 'L': {
19375 // skip values outside bounding box
19376 foreach ($chunks as $key => $val) {
19377 if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
19378 $skip = true;
19379 } elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
19380 $skip = true;
19381 }
19382 }
19383 }
19384 }
19385 }
19386 switch ($cmd) {
19387 case 'm':
19388 case 'l':
19389 case 'v':
19390 case 'y':
19391 case 'c':
19392 case 'k':
19393 case 'K':
19394 case 'g':
19395 case 'G':
19396 case 's':
19397 case 'S':
19398 case 'J':
19399 case 'j':
19400 case 'w':
19401 case 'M':
19402 case 'd':
19403 case 'n': {
19404 if ($skip) {
19405 break;
19406 }
19407 $this->_out($line);
19408 break;
19409 }
19410 case 'x': {// custom fill color
19411 if (empty($color_name)) {
19412 // CMYK color
19413 list($col_c, $col_m, $col_y, $col_k) = $chunks;
19414 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
19415 } else {
19416 // Spot Color (CMYK + tint)
19417 list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
19418 $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
19419 $color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
19420 $this->_out($color_cmd);
19421 }
19422 break;
19423 }
19424 case 'X': { // custom stroke color
19425 if (empty($color_name)) {
19426 // CMYK color
19427 list($col_c, $col_m, $col_y, $col_k) = $chunks;
19428 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
19429 } else {
19430 // Spot Color (CMYK + tint)
19431 list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
19432 $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
19433 $color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
19434 $this->_out($color_cmd);
19435 }
19436 break;
19437 }
19438 case 'Y':
19439 case 'N':
19440 case 'V':
19441 case 'L':
19442 case 'C': {
19443 if ($skip) {
19444 break;
19445 }
19446 $line[($len - 1)] = strtolower($cmd);
19447 $this->_out($line);
19448 break;
19449 }
19450 case 'b':
19451 case 'B': {
19452 $this->_out($cmd . '*');
19453 break;
19454 }
19455 case 'f':
19456 case 'F': {
19457 if ($u > 0) {
19458 $isU = false;
19459 $max = min(($i + 5), $cnt);
19460 for ($j = ($i + 1); $j < $max; ++$j) {
19461 $isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
19462 }
19463 if ($isU) {
19464 $this->_out('f*');
19465 }
19466 } else {
19467 $this->_out('f*');
19468 }
19469 break;
19470 }
19471 case '*u': {
19472 ++$u;
19473 break;
19474 }
19475 case '*U': {
19476 --$u;
19477 break;
19478 }
19479 }
19480 }
19481 // restore previous graphic state
19482 $this->_out($this->epsmarker.'Q');
19483 if (!empty($border)) {
19484 $bx = $this->x;
19485 $by = $this->y;
19486 $this->x = $ximg;
19487 if ($this->rtl) {
19488 $this->x += $w;
19489 }
19490 $this->y = $y;
19491 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
19492 $this->x = $bx;
19493 $this->y = $by;
19494 }
19495 if ($link) {
19496 $this->Link($ximg, $y, $w, $h, $link, 0);
19497 }
19498 // set pointer to align the next text/objects
19499 switch($align) {
19500 case 'T':{
19501 $this->y = $y;
19502 $this->x = $this->img_rb_x;
19503 break;
19504 }
19505 case 'M':{
19506 $this->y = $y + round($h/2);
19507 $this->x = $this->img_rb_x;
19508 break;
19509 }
19510 case 'B':{
19511 $this->y = $this->img_rb_y;
19512 $this->x = $this->img_rb_x;
19513 break;
19514 }
19515 case 'N':{
19516 $this->SetY($this->img_rb_y);
19517 break;
19518 }
19519 default:{
19520 break;
19521 }
19522 }
19523 $this->endlinex = $this->img_rb_x;
19524 }
19525
19526 /**
19527 * Set document barcode.
19528 * @param $bc (string) barcode
19529 * @public
19530 */
19531 public function setBarcode($bc='') {
19532 $this->barcode = $bc;
19533 }
19534
19535 /**
19536 * Get current barcode.
19537 * @return string
19538 * @public
19539 * @since 4.0.012 (2008-07-24)
19540 */
19541 public function getBarcode() {
19542 return $this->barcode;
19543 }
19544
19545 /**
19546 * Print a Linear Barcode.
19547 * @param $code (string) code to print
19548 * @param $type (string) type of barcode (see barcodes.php for supported formats).
19549 * @param $x (int) x position in user units (empty string = current x position)
19550 * @param $y (int) y position in user units (empty string = current y position)
19551 * @param $w (int) width in user units (empty string = remaining page width)
19552 * @param $h (int) height in user units (empty string = remaining page height)
19553 * @param $xres (float) width of the smallest bar in user units (empty string = default value = 0.4mm)
19554 * @param $style (array) array of options:<ul>
19555 * <li>boolean $style['border'] if true prints a border</li>
19556 * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
19557 * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
19558 * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
19559 * <li>array $style['fgcolor'] color array for bars and text</li>
19560 * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
19561 * <li>boolean $style['text'] if true prints text below the barcode</li>
19562 * <li>string $style['label'] override default label</li>
19563 * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
19564 * <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>
19565 * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
19566 * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
19567 * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
19568 * <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>
19569 * <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>
19570 * @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
19571 * @author Nicola Asuni
19572 * @since 3.1.000 (2008-06-09)
19573 * @public
19574 */
19575 public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style='', $align='') {
19576 if ($this->empty_string(trim($code))) {
19577 return;
19578 }
19579 require_once(dirname(__FILE__).'/barcodes.php');
19580 // save current graphic settings
19581 $gvars = $this->getGraphicVars();
19582 // create new barcode object
19583 $barcodeobj = new TCPDFBarcode($code, $type);
19584 $arrcode = $barcodeobj->getBarcodeArray();
19585 if ($arrcode === false) {
19586 $this->Error('Error in 1D barcode string');
19587 }
19588 // set default values
19589 if (!isset($style['position'])) {
19590 $style['position'] = '';
19591 } elseif ($style['position'] == 'S') {
19592 // keep this for backward compatibility
19593 $style['position'] = '';
19594 $style['stretch'] = true;
19595 }
19596 if (!isset($style['fitwidth'])) {
19597 if (!isset($style['stretch'])) {
19598 $style['fitwidth'] = true;
19599 } else {
19600 $style['fitwidth'] = false;
19601 }
19602 }
19603 if ($style['fitwidth']) {
19604 // disable stretch
19605 $style['stretch'] = false;
19606 }
19607 if (!isset($style['stretch'])) {
19608 if (($w === '') OR ($w <= 0)) {
19609 $style['stretch'] = false;
19610 } else {
19611 $style['stretch'] = true;
19612 }
19613 }
19614 if (!isset($style['fgcolor'])) {
19615 $style['fgcolor'] = array(0,0,0); // default black
19616 }
19617 if (!isset($style['bgcolor'])) {
19618 $style['bgcolor'] = false; // default transparent
19619 }
19620 if (!isset($style['border'])) {
19621 $style['border'] = false;
19622 }
19623 $fontsize = 0;
19624 if (!isset($style['text'])) {
19625 $style['text'] = false;
19626 }
19627 if ($style['text'] AND isset($style['font'])) {
19628 if (isset($style['fontsize'])) {
19629 $fontsize = $style['fontsize'];
19630 }
19631 $this->SetFont($style['font'], '', $fontsize);
19632 }
19633 if (!isset($style['stretchtext'])) {
19634 $style['stretchtext'] = 4;
19635 }
19636 if ($x === '') {
19637 $x = $this->x;
19638 }
19639 if ($y === '') {
19640 $y = $this->y;
19641 }
19642 // check page for no-write regions and adapt page margins if necessary
19643 list($x, $y) = $this->checkPageRegions($h, $x, $y);
19644 if (($w === '') OR ($w <= 0)) {
19645 if ($this->rtl) {
19646 $w = $x - $this->lMargin;
19647 } else {
19648 $w = $this->w - $this->rMargin - $x;
19649 }
19650 }
19651 // padding
19652 if (!isset($style['padding'])) {
19653 $padding = 0;
19654 } elseif ($style['padding'] === 'auto') {
19655 $padding = 10 * ($w / ($arrcode['maxw'] + 20));
19656 } else {
19657 $padding = floatval($style['padding']);
19658 }
19659 // horizontal padding
19660 if (!isset($style['hpadding'])) {
19661 $hpadding = $padding;
19662 } elseif ($style['hpadding'] === 'auto') {
19663 $hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
19664 } else {
19665 $hpadding = floatval($style['hpadding']);
19666 }
19667 // vertical padding
19668 if (!isset($style['vpadding'])) {
19669 $vpadding = $padding;
19670 } elseif ($style['vpadding'] === 'auto') {
19671 $vpadding = ($hpadding / 2);
19672 } else {
19673 $vpadding = floatval($style['vpadding']);
19674 }
19675 // calculate xres (single bar width)
19676 $max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
19677 if ($style['stretch']) {
19678 $xres = $max_xres;
19679 } else {
19680 if ($this->empty_string($xres)) {
19681 $xres = (0.141 * $this->k); // default bar width = 0.4 mm
19682 }
19683 if ($xres > $max_xres) {
19684 // correct xres to fit on $w
19685 $xres = $max_xres;
19686 }
19687 if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
19688 OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
19689 $hpadding = 10 * $xres;
19690 if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
19691 $vpadding = ($hpadding / 2);
19692 }
19693 }
19694 }
19695 if ($style['fitwidth']) {
19696 $wold = $w;
19697 $w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
19698 if (isset($style['cellfitalign'])) {
19699 switch ($style['cellfitalign']) {
19700 case 'L': {
19701 if ($this->rtl) {
19702 $x -= ($wold - $w);
19703 }
19704 break;
19705 }
19706 case 'R': {
19707 if (!$this->rtl) {
19708 $x += ($wold - $w);
19709 }
19710 break;
19711 }
19712 case 'C': {
19713 if ($this->rtl) {
19714 $x -= (($wold - $w) / 2);
19715 } else {
19716 $x += (($wold - $w) / 2);
19717 }
19718 break;
19719 }
19720 default : {
19721 break;
19722 }
19723 }
19724 }
19725 }
19726 $text_height = ($this->cell_height_ratio * $fontsize / $this->k);
19727 // height
19728 if (($h === '') OR ($h <= 0)) {
19729 // set default height
19730 $h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
19731 }
19732 $barh = $h - $text_height - (2 * $vpadding);
19733 if ($barh <=0) {
19734 // try to reduce font or padding to fit barcode on available height
19735 if ($text_height > $h) {
19736 $fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
19737 $text_height = ($this->cell_height_ratio * $fontsize / $this->k);
19738 $this->SetFont($style['font'], '', $fontsize);
19739 }
19740 if ($vpadding > 0) {
19741 $vpadding = (($h - $text_height) / 4);
19742 }
19743 $barh = $h - $text_height - (2 * $vpadding);
19744 }
19745 // fit the barcode on available space
19746 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
19747 // set alignment
19748 $this->img_rb_y = $y + $h;
19749 // set alignment
19750 if ($this->rtl) {
19751 if ($style['position'] == 'L') {
19752 $xpos = $this->lMargin;
19753 } elseif ($style['position'] == 'C') {
19754 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
19755 } elseif ($style['position'] == 'R') {
19756 $xpos = $this->w - $this->rMargin - $w;
19757 } else {
19758 $xpos = $x - $w;
19759 }
19760 $this->img_rb_x = $xpos;
19761 } else {
19762 if ($style['position'] == 'L') {
19763 $xpos = $this->lMargin;
19764 } elseif ($style['position'] == 'C') {
19765 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
19766 } elseif ($style['position'] == 'R') {
19767 $xpos = $this->w - $this->rMargin - $w;
19768 } else {
19769 $xpos = $x;
19770 }
19771 $this->img_rb_x = $xpos + $w;
19772 }
19773 $xpos_rect = $xpos;
19774 if (!isset($style['align'])) {
19775 $style['align'] = 'C';
19776 }
19777 switch ($style['align']) {
19778 case 'L': {
19779 $xpos = $xpos_rect + $hpadding;
19780 break;
19781 }
19782 case 'R': {
19783 $xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
19784 break;
19785 }
19786 case 'C':
19787 default : {
19788 $xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
19789 break;
19790 }
19791 }
19792 $xpos_text = $xpos;
19793 // barcode is always printed in LTR direction
19794 $tempRTL = $this->rtl;
19795 $this->rtl = false;
19796 // print background color
19797 if ($style['bgcolor']) {
19798 $this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
19799 } elseif ($style['border']) {
19800 $this->Rect($xpos_rect, $y, $w, $h, 'D');
19801 }
19802 // set foreground color
19803 $this->SetDrawColorArray($style['fgcolor']);
19804 $this->SetTextColorArray($style['fgcolor']);
19805 // print bars
19806 foreach ($arrcode['bcode'] as $k => $v) {
19807 $bw = ($v['w'] * $xres);
19808 if ($v['t']) {
19809 // draw a vertical bar
19810 $ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
19811 $this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
19812 }
19813 $xpos += $bw;
19814 }
19815 // print text
19816 if ($style['text']) {
19817 if (isset($style['label']) AND !$this->empty_string($style['label'])) {
19818 $label = $style['label'];
19819 } else {
19820 $label = $code;
19821 }
19822 $txtwidth = ($arrcode['maxw'] * $xres);
19823 if ($this->GetStringWidth($label) > $txtwidth) {
19824 $style['stretchtext'] = 2;
19825 }
19826 // print text
19827 $this->x = $xpos_text;
19828 $this->y = $y + $vpadding + $barh;
19829 $cellpadding = $this->cell_padding;
19830 $this->SetCellPadding(0);
19831 $this->Cell($txtwidth, '', $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
19832 $this->cell_padding = $cellpadding;
19833 }
19834 // restore original direction
19835 $this->rtl = $tempRTL;
19836 // restore previous settings
19837 $this->setGraphicVars($gvars);
19838 // set pointer to align the next text/objects
19839 switch($align) {
19840 case 'T':{
19841 $this->y = $y;
19842 $this->x = $this->img_rb_x;
19843 break;
19844 }
19845 case 'M':{
19846 $this->y = $y + round($h / 2);
19847 $this->x = $this->img_rb_x;
19848 break;
19849 }
19850 case 'B':{
19851 $this->y = $this->img_rb_y;
19852 $this->x = $this->img_rb_x;
19853 break;
19854 }
19855 case 'N':{
19856 $this->SetY($this->img_rb_y);
19857 break;
19858 }
19859 default:{
19860 break;
19861 }
19862 }
19863 $this->endlinex = $this->img_rb_x;
19864 }
19865
19866 /**
19867 * This function is DEPRECATED, please use the new write1DBarcode() function.
19868 * @param $x (int) x position in user units
19869 * @param $y (int) y position in user units
19870 * @param $w (int) width in user units
19871 * @param $h (int) height position in user units
19872 * @param $type (string) type of barcode
19873 * @param $style (string) barcode style
19874 * @param $font (string) font for text
19875 * @param $xres (int) x resolution
19876 * @param $code (string) code to print
19877 * @deprecated deprecated since version 3.1.000 (2008-06-10)
19878 * @public
19879 * @see write1DBarcode()
19880 */
19881 public function writeBarcode($x, $y, $w, $h, $type, $style, $font, $xres, $code) {
19882 // convert old settings for the new write1DBarcode() function.
19883 $xres = 1 / $xres;
19884 $newstyle = array(
19885 'position' => '',
19886 'align' => '',
19887 'stretch' => false,
19888 'fitwidth' => false,
19889 'cellfitalign' => '',
19890 'border' => false,
19891 'padding' => 0,
19892 'fgcolor' => array(0,0,0),
19893 'bgcolor' => false,
19894 'text' => true,
19895 'font' => $font,
19896 'fontsize' => 8,
19897 'stretchtext' => 4
19898 );
19899 if ($style & 1) {
19900 $newstyle['border'] = true;
19901 }
19902 if ($style & 2) {
19903 $newstyle['bgcolor'] = false;
19904 }
19905 if ($style & 4) {
19906 $newstyle['position'] = 'C';
19907 } elseif ($style & 8) {
19908 $newstyle['position'] = 'L';
19909 } elseif ($style & 16) {
19910 $newstyle['position'] = 'R';
19911 }
19912 if ($style & 128) {
19913 $newstyle['text'] = true;
19914 }
19915 if ($style & 256) {
19916 $newstyle['stretchtext'] = 4;
19917 }
19918 $this->write1DBarcode($code, $type, $x, $y, $w, $h, $xres, $newstyle, '');
19919 }
19920
19921 /**
19922 * Print 2D Barcode.
19923 * @param $code (string) code to print
19924 * @param $type (string) type of barcode (see 2dbarcodes.php for supported formats).
19925 * @param $x (int) x position in user units
19926 * @param $y (int) y position in user units
19927 * @param $w (int) width in user units
19928 * @param $h (int) height in user units
19929 * @param $style (array) array of options:<ul>
19930 * <li>boolean $style['border'] if true prints a border around the barcode</li>
19931 * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
19932 * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
19933 * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
19934 * <li>int $style['module_width'] width of a single module in points</li>
19935 * <li>int $style['module_height'] height of a single module in points</li>
19936 * <li>array $style['fgcolor'] color array for bars and text</li>
19937 * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
19938 * <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li><li>$style['module_width'] width of a single module in points</li>
19939 * <li>$style['module_height'] height of a single module in points</li></ul>
19940 * @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
19941 * @param $distort (boolean) if true distort the barcode to fit width and height, otherwise preserve aspect ratio
19942 * @author Nicola Asuni
19943 * @since 4.5.037 (2009-04-07)
19944 * @public
19945 */
19946 public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style='', $align='', $distort=false) {
19947 if ($this->empty_string(trim($code))) {
19948 return;
19949 }
19950 require_once(dirname(__FILE__).'/2dbarcodes.php');
19951 // save current graphic settings
19952 $gvars = $this->getGraphicVars();
19953 // create new barcode object
19954 $barcodeobj = new TCPDF2DBarcode($code, $type);
19955 $arrcode = $barcodeobj->getBarcodeArray();
19956 if (($arrcode === false) OR empty($arrcode)) {
19957 $this->Error('Error in 2D barcode string');
19958 }
19959 // set default values
19960 if (!isset($style['position'])) {
19961 $style['position'] = '';
19962 }
19963 if (!isset($style['fgcolor'])) {
19964 $style['fgcolor'] = array(0,0,0); // default black
19965 }
19966 if (!isset($style['bgcolor'])) {
19967 $style['bgcolor'] = false; // default transparent
19968 }
19969 if (!isset($style['border'])) {
19970 $style['border'] = false;
19971 }
19972 // padding
19973 if (!isset($style['padding'])) {
19974 $style['padding'] = 0;
19975 } elseif ($style['padding'] === 'auto') {
19976 $style['padding'] = 4;
19977 }
19978 if (!isset($style['hpadding'])) {
19979 $style['hpadding'] = $style['padding'];
19980 } elseif ($style['hpadding'] === 'auto') {
19981 $style['hpadding'] = 4;
19982 }
19983 if (!isset($style['vpadding'])) {
19984 $style['vpadding'] = $style['padding'];
19985 } elseif ($style['vpadding'] === 'auto') {
19986 $style['vpadding'] = 4;
19987 }
19988 $hpad = (2 * $style['hpadding']);
19989 $vpad = (2 * $style['vpadding']);
19990 // cell (module) dimension
19991 if (!isset($style['module_width'])) {
19992 $style['module_width'] = 1; // width of a single module in points
19993 }
19994 if (!isset($style['module_height'])) {
19995 $style['module_height'] = 1; // height of a single module in points
19996 }
19997 if ($x === '') {
19998 $x = $this->x;
19999 }
20000 if ($y === '') {
20001 $y = $this->y;
20002 }
20003 // check page for no-write regions and adapt page margins if necessary
20004 list($x, $y) = $this->checkPageRegions($h, $x, $y);
20005 // number of barcode columns and rows
20006 $rows = $arrcode['num_rows'];
20007 $cols = $arrcode['num_cols'];
20008 // module width and height
20009 $mw = $style['module_width'];
20010 $mh = $style['module_height'];
20011 // get max dimensions
20012 if ($this->rtl) {
20013 $maxw = $x - $this->lMargin;
20014 } else {
20015 $maxw = $this->w - $this->rMargin - $x;
20016 }
20017 $maxh = ($this->h - $this->tMargin - $this->bMargin);
20018 $ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
20019 $ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
20020 if (!$distort) {
20021 if (($maxw * $ratioHW) > $maxh) {
20022 $maxw = $maxh * $ratioWH;
20023 }
20024 if (($maxh * $ratioWH) > $maxw) {
20025 $maxh = $maxw * $ratioHW;
20026 }
20027 }
20028 // set maximum dimesions
20029 if ($w > $maxw) {
20030 $w = $maxw;
20031 }
20032 if ($h > $maxh) {
20033 $h = $maxh;
20034 }
20035 // set dimensions
20036 if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
20037 $w = ($cols + $hpad) * ($mw / $this->k);
20038 $h = ($rows + $vpad) * ($mh / $this->k);
20039 } elseif (($w === '') OR ($w <= 0)) {
20040 $w = $h * $ratioWH;
20041 } elseif (($h === '') OR ($h <= 0)) {
20042 $h = $w * $ratioHW;
20043 }
20044 // barcode size (excluding padding)
20045 $bw = ($w * $cols) / ($cols + $hpad);
20046 $bh = ($h * $rows) / ($rows + $vpad);
20047 // dimension of single barcode cell unit
20048 $cw = $bw / $cols;
20049 $ch = $bh / $rows;
20050 if (!$distort) {
20051 if (($cw / $ch) > ($mw / $mh)) {
20052 // correct horizontal distortion
20053 $cw = $ch * $mw / $mh;
20054 $bw = $cw * $cols;
20055 $style['hpadding'] = ($w - $bw) / (2 * $cw);
20056 } else {
20057 // correct vertical distortion
20058 $ch = $cw * $mh / $mw;
20059 $bh = $ch * $rows;
20060 $style['vpadding'] = ($h - $bh) / (2 * $ch);
20061 }
20062 }
20063 // fit the barcode on available space
20064 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
20065 // set alignment
20066 $this->img_rb_y = $y + $h;
20067 // set alignment
20068 if ($this->rtl) {
20069 if ($style['position'] == 'L') {
20070 $xpos = $this->lMargin;
20071 } elseif ($style['position'] == 'C') {
20072 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
20073 } elseif ($style['position'] == 'R') {
20074 $xpos = $this->w - $this->rMargin - $w;
20075 } else {
20076 $xpos = $x - $w;
20077 }
20078 $this->img_rb_x = $xpos;
20079 } else {
20080 if ($style['position'] == 'L') {
20081 $xpos = $this->lMargin;
20082 } elseif ($style['position'] == 'C') {
20083 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
20084 } elseif ($style['position'] == 'R') {
20085 $xpos = $this->w - $this->rMargin - $w;
20086 } else {
20087 $xpos = $x;
20088 }
20089 $this->img_rb_x = $xpos + $w;
20090 }
20091 $xstart = $xpos + ($style['hpadding'] * $cw);
20092 $ystart = $y + ($style['vpadding'] * $ch);
20093 // barcode is always printed in LTR direction
20094 $tempRTL = $this->rtl;
20095 $this->rtl = false;
20096 // print background color
20097 if ($style['bgcolor']) {
20098 $this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
20099 } elseif ($style['border']) {
20100 $this->Rect($xpos, $y, $w, $h, 'D');
20101 }
20102 // set foreground color
20103 $this->SetDrawColorArray($style['fgcolor']);
20104 // print barcode cells
20105 // for each row
20106 for ($r = 0; $r < $rows; ++$r) {
20107 $xr = $xstart;
20108 // for each column
20109 for ($c = 0; $c < $cols; ++$c) {
20110 if ($arrcode['bcode'][$r][$c] == 1) {
20111 // draw a single barcode cell
20112 $this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
20113 }
20114 $xr += $cw;
20115 }
20116 $ystart += $ch;
20117 }
20118 // restore original direction
20119 $this->rtl = $tempRTL;
20120 // restore previous settings
20121 $this->setGraphicVars($gvars);
20122 // set pointer to align the next text/objects
20123 switch($align) {
20124 case 'T':{
20125 $this->y = $y;
20126 $this->x = $this->img_rb_x;
20127 break;
20128 }
20129 case 'M':{
20130 $this->y = $y + round($h/2);
20131 $this->x = $this->img_rb_x;
20132 break;
20133 }
20134 case 'B':{
20135 $this->y = $this->img_rb_y;
20136 $this->x = $this->img_rb_x;
20137 break;
20138 }
20139 case 'N':{
20140 $this->SetY($this->img_rb_y);
20141 break;
20142 }
20143 default:{
20144 break;
20145 }
20146 }
20147 $this->endlinex = $this->img_rb_x;
20148 }
20149
20150 /**
20151 * Returns an array containing current margins:
20152 * <ul>
20153 <li>$ret['left'] = left margin</li>
20154 <li>$ret['right'] = right margin</li>
20155 <li>$ret['top'] = top margin</li>
20156 <li>$ret['bottom'] = bottom margin</li>
20157 <li>$ret['header'] = header margin</li>
20158 <li>$ret['footer'] = footer margin</li>
20159 <li>$ret['cell'] = cell padding array</li>
20160 <li>$ret['padding_left'] = cell left padding</li>
20161 <li>$ret['padding_top'] = cell top padding</li>
20162 <li>$ret['padding_right'] = cell right padding</li>
20163 <li>$ret['padding_bottom'] = cell bottom padding</li>
20164 * </ul>
20165 * @return array containing all margins measures
20166 * @public
20167 * @since 3.2.000 (2008-06-23)
20168 */
20169 public function getMargins() {
20170 $ret = array(
20171 'left' => $this->lMargin,
20172 'right' => $this->rMargin,
20173 'top' => $this->tMargin,
20174 'bottom' => $this->bMargin,
20175 'header' => $this->header_margin,
20176 'footer' => $this->footer_margin,
20177 'cell' => $this->cell_padding,
20178 'padding_left' => $this->cell_padding['L'],
20179 'padding_top' => $this->cell_padding['T'],
20180 'padding_right' => $this->cell_padding['R'],
20181 'padding_bottom' => $this->cell_padding['B']
20182 );
20183 return $ret;
20184 }
20185
20186 /**
20187 * Returns an array containing original margins:
20188 * <ul>
20189 <li>$ret['left'] = left margin</li>
20190 <li>$ret['right'] = right margin</li>
20191 * </ul>
20192 * @return array containing all margins measures
20193 * @public
20194 * @since 4.0.012 (2008-07-24)
20195 */
20196 public function getOriginalMargins() {
20197 $ret = array(
20198 'left' => $this->original_lMargin,
20199 'right' => $this->original_rMargin
20200 );
20201 return $ret;
20202 }
20203
20204 /**
20205 * Returns the current font size.
20206 * @return current font size
20207 * @public
20208 * @since 3.2.000 (2008-06-23)
20209 */
20210 public function getFontSize() {
20211 return $this->FontSize;
20212 }
20213
20214 /**
20215 * Returns the current font size in points unit.
20216 * @return current font size in points unit
20217 * @public
20218 * @since 3.2.000 (2008-06-23)
20219 */
20220 public function getFontSizePt() {
20221 return $this->FontSizePt;
20222 }
20223
20224 /**
20225 * Returns the current font family name.
20226 * @return string current font family name
20227 * @public
20228 * @since 4.3.008 (2008-12-05)
20229 */
20230 public function getFontFamily() {
20231 return $this->FontFamily;
20232 }
20233
20234 /**
20235 * Returns the current font style.
20236 * @return string current font style
20237 * @public
20238 * @since 4.3.008 (2008-12-05)
20239 */
20240 public function getFontStyle() {
20241 return $this->FontStyle;
20242 }
20243
20244 /**
20245 * Cleanup HTML code (requires HTML Tidy library).
20246 * @param $html (string) htmlcode to fix
20247 * @param $default_css (string) CSS commands to add
20248 * @param $tagvs (array) parameters for setHtmlVSpace method
20249 * @param $tidy_options (array) options for tidy_parse_string function
20250 * @return string XHTML code cleaned up
20251 * @author Nicola Asuni
20252 * @public
20253 * @since 5.9.017 (2010-11-16)
20254 * @see setHtmlVSpace()
20255 */
20256 public function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='') {
20257 // configure parameters for HTML Tidy
20258 if ($tidy_options === '') {
20259 $tidy_options = array (
20260 'clean' => 1,
20261 'drop-empty-paras' => 0,
20262 'drop-proprietary-attributes' => 1,
20263 'fix-backslash' => 1,
20264 'hide-comments' => 1,
20265 'join-styles' => 1,
20266 'lower-literals' => 1,
20267 'merge-divs' => 1,
20268 'merge-spans' => 1,
20269 'output-xhtml' => 1,
20270 'word-2000' => 1,
20271 'wrap' => 0,
20272 'output-bom' => 0,
20273 //'char-encoding' => 'utf8',
20274 //'input-encoding' => 'utf8',
20275 //'output-encoding' => 'utf8'
20276 );
20277 }
20278 // clean up the HTML code
20279 $tidy = tidy_parse_string($html, $tidy_options);
20280 // fix the HTML
20281 $tidy->cleanRepair();
20282 // get the CSS part
20283 $tidy_head = tidy_get_head($tidy);
20284 $css = $tidy_head->value;
20285 $css = preg_replace('/<style([^>]+)>/ims', '<style>', $css);
20286 $css = preg_replace('/<\/style>(.*)<style>/ims', "\n", $css);
20287 $css = str_replace('/*<![CDATA[*/', '', $css);
20288 $css = str_replace('/*]]>*/', '', $css);
20289 preg_match('/<style>(.*)<\/style>/ims', $css, $matches);
20290 if (isset($matches[1])) {
20291 $css = strtolower($matches[1]);
20292 } else {
20293 $css = '';
20294 }
20295 // include default css
20296 $css = '<style>'.$default_css.$css.'</style>';
20297 // get the body part
20298 $tidy_body = tidy_get_body($tidy);
20299 $html = $tidy_body->value;
20300 // fix some self-closing tags
20301 $html = str_replace('<br>', '<br />', $html);
20302 // remove some empty tag blocks
20303 $html = preg_replace('/<div([^\>]*)><\/div>/', '', $html);
20304 $html = preg_replace('/<p([^\>]*)><\/p>/', '', $html);
20305 if ($tagvs !== '') {
20306 // set vertical space for some XHTML tags
20307 $this->setHtmlVSpace($tagvs);
20308 }
20309 // return the cleaned XHTML code + CSS
20310 return $css.$html;
20311 }
20312
20313 /**
20314 * Extracts the CSS properties from a CSS string.
20315 * @param $cssdata (string) string containing CSS definitions.
20316 * @return An array where the keys are the CSS selectors and the values are the CSS properties.
20317 * @author Nicola Asuni
20318 * @since 5.1.000 (2010-05-25)
20319 * @protected
20320 */
20321 protected function extractCSSproperties($cssdata) {
20322 if (empty($cssdata)) {
20323 return array();
20324 }
20325 // remove comments
20326 $cssdata = preg_replace('/\/\*[^\*]*\*\//', '', $cssdata);
20327 // remove newlines and multiple spaces
20328 $cssdata = preg_replace('/[\s]+/', ' ', $cssdata);
20329 // remove some spaces
20330 $cssdata = preg_replace('/[\s]*([;:\{\}]{1})[\s]*/', '\\1', $cssdata);
20331 // remove empty blocks
20332 $cssdata = preg_replace('/([^\}\{]+)\{\}/', '', $cssdata);
20333 // replace media type parenthesis
20334 $cssdata = preg_replace('/@media[\s]+([^\{]*)\{/i', '@media \\1�', $cssdata);
20335 $cssdata = preg_replace('/\}\}/si', '}�', $cssdata);
20336 // trim string
20337 $cssdata = trim($cssdata);
20338 // find media blocks (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
20339 $cssblocks = array();
20340 $matches = array();
20341 if (preg_match_all('/@media[\s]+([^\�]*)�([^�]*)�/i', $cssdata, $matches) > 0) {
20342 foreach ($matches[1] as $key => $type) {
20343 $cssblocks[$type] = $matches[2][$key];
20344 }
20345 // remove media blocks
20346 $cssdata = preg_replace('/@media[\s]+([^\�]*)�([^�]*)�/i', '', $cssdata);
20347 }
20348 // keep 'all' and 'print' media, other media types are discarded
20349 if (isset($cssblocks['all']) AND !empty($cssblocks['all'])) {
20350 $cssdata .= $cssblocks['all'];
20351 }
20352 if (isset($cssblocks['print']) AND !empty($cssblocks['print'])) {
20353 $cssdata .= $cssblocks['print'];
20354 }
20355 // reset css blocks array
20356 $cssblocks = array();
20357 $matches = array();
20358 // explode css data string into array
20359 if (substr($cssdata, -1) == '}') {
20360 // remove last parethesis
20361 $cssdata = substr($cssdata, 0, -1);
20362 }
20363 $matches = explode('}', $cssdata);
20364 foreach ($matches as $key => $block) {
20365 // index 0 contains the CSS selector, index 1 contains CSS properties
20366 $cssblocks[$key] = explode('{', $block);
20367 if (!isset($cssblocks[$key][1])) {
20368 // remove empty definitions
20369 unset($cssblocks[$key]);
20370 }
20371 }
20372 // split groups of selectors (comma-separated list of selectors)
20373 foreach ($cssblocks as $key => $block) {
20374 if (strpos($block[0], ',') > 0) {
20375 $selectors = explode(',', $block[0]);
20376 foreach ($selectors as $sel) {
20377 $cssblocks[] = array(0 => trim($sel), 1 => $block[1]);
20378 }
20379 unset($cssblocks[$key]);
20380 }
20381 }
20382 // covert array to selector => properties
20383 $cssdata = array();
20384 foreach ($cssblocks as $block) {
20385 $selector = $block[0];
20386 // calculate selector's specificity
20387 $matches = array();
20388 $a = 0; // the declaration is not from is a 'style' attribute
20389 $b = intval(preg_match_all('/[\#]/', $selector, $matches)); // number of ID attributes
20390 $c = intval(preg_match_all('/[\[\.]/', $selector, $matches)); // number of other attributes
20391 $c += intval(preg_match_all('/[\:]link|visited|hover|active|focus|target|lang|enabled|disabled|checked|indeterminate|root|nth|first|last|only|empty|contains|not/i', $selector, $matches)); // number of pseudo-classes
20392 $d = intval(preg_match_all('/[\>\+\~\s]{1}[a-zA-Z0-9]+/', ' '.$selector, $matches)); // number of element names
20393 $d += intval(preg_match_all('/[\:][\:]/', $selector, $matches)); // number of pseudo-elements
20394 $specificity = $a.$b.$c.$d;
20395 // add specificity to the beginning of the selector
20396 $cssdata[$specificity.' '.$selector] = $block[1];
20397 }
20398 // sort selectors alphabetically to account for specificity
20399 ksort($cssdata, SORT_STRING);
20400 // return array
20401 return $cssdata;
20402 }
20403
20404 /**
20405 * Returns true if the CSS selector is valid for the selected HTML tag
20406 * @param $dom (array) array of HTML tags and properties
20407 * @param $key (int) key of the current HTML tag
20408 * @param $selector (string) CSS selector string
20409 * @return true if the selector is valid, false otherwise
20410 * @protected
20411 * @since 5.1.000 (2010-05-25)
20412 */
20413 protected function isValidCSSSelectorForTag($dom, $key, $selector) {
20414 $valid = false; // value to be returned
20415 $tag = $dom[$key]['value'];
20416 $class = array();
20417 if (isset($dom[$key]['attribute']['class']) AND !empty($dom[$key]['attribute']['class'])) {
20418 $class = explode(' ', strtolower($dom[$key]['attribute']['class']));
20419 }
20420 $id = '';
20421 if (isset($dom[$key]['attribute']['id']) AND !empty($dom[$key]['attribute']['id'])) {
20422 $id = strtolower($dom[$key]['attribute']['id']);
20423 }
20424 $selector = preg_replace('/([\>\+\~\s]{1})([\.]{1})([^\>\+\~\s]*)/si', '\\1*.\\3', $selector);
20425 $matches = array();
20426 if (preg_match_all('/([\>\+\~\s]{1})([a-zA-Z0-9\*]+)([^\>\+\~\s]*)/si', $selector, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE) > 0) {
20427 $parentop = array_pop($matches[1]);
20428 $operator = $parentop[0];
20429 $offset = $parentop[1];
20430 $lasttag = array_pop($matches[2]);
20431 $lasttag = strtolower(trim($lasttag[0]));
20432 if (($lasttag == '*') OR ($lasttag == $tag)) {
20433 // the last element on selector is our tag or 'any tag'
20434 $attrib = array_pop($matches[3]);
20435 $attrib = strtolower(trim($attrib[0]));
20436 if (!empty($attrib)) {
20437 // check if matches class, id, attribute, pseudo-class or pseudo-element
20438 switch ($attrib{0}) {
20439 case '.': { // class
20440 if (in_array(substr($attrib, 1), $class)) {
20441 $valid = true;
20442 }
20443 break;
20444 }
20445 case '#': { // ID
20446 if (substr($attrib, 1) == $id) {
20447 $valid = true;
20448 }
20449 break;
20450 }
20451 case '[': { // attribute
20452 $attrmatch = array();
20453 if (preg_match('/\[([a-zA-Z0-9]*)[\s]*([\~\^\$\*\|\=]*)[\s]*["]?([^"\]]*)["]?\]/i', $attrib, $attrmatch) > 0) {
20454 $att = strtolower($attrmatch[1]);
20455 $val = $attrmatch[3];
20456 if (isset($dom[$key]['attribute'][$att])) {
20457 switch ($attrmatch[2]) {
20458 case '=': {
20459 if ($dom[$key]['attribute'][$att] == $val) {
20460 $valid = true;
20461 }
20462 break;
20463 }
20464 case '~=': {
20465 if (in_array($val, explode(' ', $dom[$key]['attribute'][$att]))) {
20466 $valid = true;
20467 }
20468 break;
20469 }
20470 case '^=': {
20471 if ($val == substr($dom[$key]['attribute'][$att], 0, strlen($val))) {
20472 $valid = true;
20473 }
20474 break;
20475 }
20476 case '$=': {
20477 if ($val == substr($dom[$key]['attribute'][$att], -strlen($val))) {
20478 $valid = true;
20479 }
20480 break;
20481 }
20482 case '*=': {
20483 if (strpos($dom[$key]['attribute'][$att], $val) !== false) {
20484 $valid = true;
20485 }
20486 break;
20487 }
20488 case '|=': {
20489 if ($dom[$key]['attribute'][$att] == $val) {
20490 $valid = true;
20491 } elseif (preg_match('/'.$val.'[\-]{1}/i', $dom[$key]['attribute'][$att]) > 0) {
20492 $valid = true;
20493 }
20494 break;
20495 }
20496 default: {
20497 $valid = true;
20498 }
20499 }
20500 }
20501 }
20502 break;
20503 }
20504 case ':': { // pseudo-class or pseudo-element
20505 if ($attrib{1} == ':') { // pseudo-element
20506 // pseudo-elements are not supported!
20507 // (::first-line, ::first-letter, ::before, ::after)
20508 } else { // pseudo-class
20509 // pseudo-classes are not supported!
20510 // (:root, :nth-child(n), :nth-last-child(n), :nth-of-type(n), :nth-last-of-type(n), :first-child, :last-child, :first-of-type, :last-of-type, :only-child, :only-of-type, :empty, :link, :visited, :active, :hover, :focus, :target, :lang(fr), :enabled, :disabled, :checked)
20511 }
20512 break;
20513 }
20514 } // end of switch
20515 } else {
20516 $valid = true;
20517 }
20518 if ($valid AND ($offset > 0)) {
20519 $valid = false;
20520 // check remaining selector part
20521 $selector = substr($selector, 0, $offset);
20522 switch ($operator) {
20523 case ' ': { // descendant of an element
20524 while ($dom[$key]['parent'] > 0) {
20525 if ($this->isValidCSSSelectorForTag($dom, $dom[$key]['parent'], $selector)) {
20526 $valid = true;
20527 break;
20528 } else {
20529 $key = $dom[$key]['parent'];
20530 }
20531 }
20532 break;
20533 }
20534 case '>': { // child of an element
20535 $valid = $this->isValidCSSSelectorForTag($dom, $dom[$key]['parent'], $selector);
20536 break;
20537 }
20538 case '+': { // immediately preceded by an element
20539 for ($i = ($key - 1); $i > $dom[$key]['parent']; --$i) {
20540 if ($dom[$i]['tag'] AND $dom[$i]['opening']) {
20541 $valid = $this->isValidCSSSelectorForTag($dom, $i, $selector);
20542 break;
20543 }
20544 }
20545 break;
20546 }
20547 case '~': { // preceded by an element
20548 for ($i = ($key - 1); $i > $dom[$key]['parent']; --$i) {
20549 if ($dom[$i]['tag'] AND $dom[$i]['opening']) {
20550 if ($this->isValidCSSSelectorForTag($dom, $i, $selector)) {
20551 break;
20552 }
20553 }
20554 }
20555 break;
20556 }
20557 }
20558 }
20559 }
20560 }
20561 return $valid;
20562 }
20563
20564 /**
20565 * Returns the styles array that apply for the selected HTML tag.
20566 * @param $dom (array) array of HTML tags and properties
20567 * @param $key (int) key of the current HTML tag
20568 * @param $css (array) array of CSS properties
20569 * @return array containing CSS properties
20570 * @protected
20571 * @since 5.1.000 (2010-05-25)
20572 */
20573 protected function getCSSdataArray($dom, $key, $css) {
20574 $cssarray = array(); // style to be returned
20575 // get parent CSS selectors
20576 $selectors = array();
20577 if (isset($dom[($dom[$key]['parent'])]['csssel'])) {
20578 $selectors = $dom[($dom[$key]['parent'])]['csssel'];
20579 }
20580 // get all styles that apply
20581 foreach($css as $selector => $style) {
20582 $pos = strpos($selector, ' ');
20583 // get specificity
20584 $specificity = substr($selector, 0, $pos);
20585 // remove specificity
20586 $selector = substr($selector, $pos);
20587 // check if this selector apply to current tag
20588 if ($this->isValidCSSSelectorForTag($dom, $key, $selector)) {
20589 if (!in_array($selector, $selectors)) {
20590 // add style if not already added on parent selector
20591 $cssarray[] = array('k' => $selector, 's' => $specificity, 'c' => $style);
20592 $selectors[] = $selector;
20593 }
20594 }
20595 }
20596 if (isset($dom[$key]['attribute']['style'])) {
20597 // attach inline style (latest properties have high priority)
20598 $cssarray[] = array('k' => '', 's' => '1000', 'c' => $dom[$key]['attribute']['style']);
20599 }
20600 // order the css array to account for specificity
20601 $cssordered = array();
20602 foreach ($cssarray as $key => $val) {
20603 $skey = sprintf('%04d', $key);
20604 $cssordered[$val['s'].'_'.$skey] = $val;
20605 }
20606 // sort selectors alphabetically to account for specificity
20607 ksort($cssordered, SORT_STRING);
20608 return array($selectors, $cssordered);
20609 }
20610
20611 /**
20612 * Compact CSS data array into single string.
20613 * @param $css (array) array of CSS properties
20614 * @return string containing merged CSS properties
20615 * @protected
20616 * @since 5.9.070 (2011-04-19)
20617 */
20618 protected function getTagStyleFromCSSarray($css) {
20619 $tagstyle = ''; // value to be returned
20620 foreach ($css as $style) {
20621 // split single css commands
20622 $csscmds = explode(';', $style['c']);
20623 foreach ($csscmds as $cmd) {
20624 if (!empty($cmd)) {
20625 $pos = strpos($cmd, ':');
20626 if ($pos !== false) {
20627 $cmd = substr($cmd, 0, ($pos + 1));
20628 if (strpos($tagstyle, $cmd) !== false) {
20629 // remove duplicate commands (last commands have high priority)
20630 $tagstyle = preg_replace('/'.$cmd.'[^;]+/i', '', $tagstyle);
20631 }
20632 }
20633 }
20634 }
20635 $tagstyle .= ';'.$style['c'];
20636 }
20637 // remove multiple semicolons
20638 $tagstyle = preg_replace('/[;]+/', ';', $tagstyle);
20639 return $tagstyle;
20640 }
20641
20642 /**
20643 * Returns the border width from CSS property
20644 * @param $width (string) border width
20645 * @return int with in user units
20646 * @protected
20647 * @since 5.7.000 (2010-08-02)
20648 */
20649 protected function getCSSBorderWidth($width) {
20650 if ($width == 'thin') {
20651 $width = (2 / $this->k);
20652 } elseif ($width == 'medium') {
20653 $width = (4 / $this->k);
20654 } elseif ($width == 'thick') {
20655 $width = (6 / $this->k);
20656 } else {
20657 $width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
20658 }
20659 return $width;
20660 }
20661
20662 /**
20663 * Returns the border dash style from CSS property
20664 * @param $style (string) border style to convert
20665 * @return int sash style (return -1 in case of none or hidden border)
20666 * @protected
20667 * @since 5.7.000 (2010-08-02)
20668 */
20669 protected function getCSSBorderDashStyle($style) {
20670 switch (strtolower($style)) {
20671 case 'none':
20672 case 'hidden': {
20673 $dash = -1;
20674 break;
20675 }
20676 case 'dotted': {
20677 $dash = 1;
20678 break;
20679 }
20680 case 'dashed': {
20681 $dash = 3;
20682 break;
20683 }
20684 case 'double':
20685 case 'groove':
20686 case 'ridge':
20687 case 'inset':
20688 case 'outset':
20689 case 'solid':
20690 default: {
20691 $dash = 0;
20692 break;
20693 }
20694 }
20695 return $dash;
20696 }
20697
20698 /**
20699 * Returns the border style array from CSS border properties
20700 * @param $cssborder (string) border properties
20701 * @return array containing border properties
20702 * @protected
20703 * @since 5.7.000 (2010-08-02)
20704 */
20705 protected function getCSSBorderStyle($cssborder) {
20706 $bprop = preg_split('/[\s]+/', trim($cssborder));
20707 $border = array(); // value to be returned
20708 switch (count($bprop)) {
20709 case 3: {
20710 $width = $bprop[0];
20711 $style = $bprop[1];
20712 $color = $bprop[2];
20713 break;
20714 }
20715 case 2: {
20716 $width = 'medium';
20717 $style = $bprop[0];
20718 $color = $bprop[1];
20719 break;
20720 }
20721 case 1: {
20722 $width = 'medium';
20723 $style = $bprop[0];
20724 $color = 'black';
20725 break;
20726 }
20727 default: {
20728 $width = 'medium';
20729 $style = 'solid';
20730 $color = 'black';
20731 break;
20732 }
20733 }
20734 if ($style == 'none') {
20735 return array();
20736 }
20737 $border['cap'] = 'square';
20738 $border['join'] = 'miter';
20739 $border['dash'] = $this->getCSSBorderDashStyle($style);
20740 if ($border['dash'] < 0) {
20741 return array();
20742 }
20743 $border['width'] = $this->getCSSBorderWidth($width);
20744 $border['color'] = $this->convertHTMLColorToDec($color);
20745 return $border;
20746 }
20747
20748 /**
20749 * Get the internal Cell padding from CSS attribute.
20750 * @param $csspadding (string) padding properties
20751 * @param $width (float) width of the containing element
20752 * @return array of cell paddings
20753 * @public
20754 * @since 5.9.000 (2010-10-04)
20755 */
20756 public function getCSSPadding($csspadding, $width=0) {
20757 $padding = preg_split('/[\s]+/', trim($csspadding));
20758 $cell_padding = array(); // value to be returned
20759 switch (count($padding)) {
20760 case 4: {
20761 $cell_padding['T'] = $padding[0];
20762 $cell_padding['R'] = $padding[1];
20763 $cell_padding['B'] = $padding[2];
20764 $cell_padding['L'] = $padding[3];
20765 break;
20766 }
20767 case 3: {
20768 $cell_padding['T'] = $padding[0];
20769 $cell_padding['R'] = $padding[1];
20770 $cell_padding['B'] = $padding[2];
20771 $cell_padding['L'] = $padding[1];
20772 break;
20773 }
20774 case 2: {
20775 $cell_padding['T'] = $padding[0];
20776 $cell_padding['R'] = $padding[1];
20777 $cell_padding['B'] = $padding[0];
20778 $cell_padding['L'] = $padding[1];
20779 break;
20780 }
20781 case 1: {
20782 $cell_padding['T'] = $padding[0];
20783 $cell_padding['R'] = $padding[0];
20784 $cell_padding['B'] = $padding[0];
20785 $cell_padding['L'] = $padding[0];
20786 break;
20787 }
20788 default: {
20789 return $this->cell_padding;
20790 }
20791 }
20792 if ($width == 0) {
20793 $width = $this->w - $this->lMargin - $this->rMargin;
20794 }
20795 $cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
20796 $cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
20797 $cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
20798 $cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
20799 return $cell_padding;
20800 }
20801
20802 /**
20803 * Get the internal Cell margin from CSS attribute.
20804 * @param $cssmargin (string) margin properties
20805 * @param $width (float) width of the containing element
20806 * @return array of cell margins
20807 * @public
20808 * @since 5.9.000 (2010-10-04)
20809 */
20810 public function getCSSMargin($cssmargin, $width=0) {
20811 $margin = preg_split('/[\s]+/', trim($cssmargin));
20812 $cell_margin = array(); // value to be returned
20813 switch (count($margin)) {
20814 case 4: {
20815 $cell_margin['T'] = $margin[0];
20816 $cell_margin['R'] = $margin[1];
20817 $cell_margin['B'] = $margin[2];
20818 $cell_margin['L'] = $margin[3];
20819 break;
20820 }
20821 case 3: {
20822 $cell_margin['T'] = $margin[0];
20823 $cell_margin['R'] = $margin[1];
20824 $cell_margin['B'] = $margin[2];
20825 $cell_margin['L'] = $margin[1];
20826 break;
20827 }
20828 case 2: {
20829 $cell_margin['T'] = $margin[0];
20830 $cell_margin['R'] = $margin[1];
20831 $cell_margin['B'] = $margin[0];
20832 $cell_margin['L'] = $margin[1];
20833 break;
20834 }
20835 case 1: {
20836 $cell_margin['T'] = $margin[0];
20837 $cell_margin['R'] = $margin[0];
20838 $cell_margin['B'] = $margin[0];
20839 $cell_margin['L'] = $margin[0];
20840 break;
20841 }
20842 default: {
20843 return $this->cell_margin;
20844 }
20845 }
20846 if ($width == 0) {
20847 $width = $this->w - $this->lMargin - $this->rMargin;
20848 }
20849 $cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
20850 $cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
20851 $cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
20852 $cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
20853 return $cell_margin;
20854 }
20855
20856 /**
20857 * Get the border-spacing from CSS attribute.
20858 * @param $cssbspace (string) border-spacing CSS properties
20859 * @param $width (float) width of the containing element
20860 * @return array of border spacings
20861 * @public
20862 * @since 5.9.010 (2010-10-27)
20863 */
20864 public function getCSSBorderMargin($cssbspace, $width=0) {
20865 $space = preg_split('/[\s]+/', trim($cssbspace));
20866 $border_spacing = array(); // value to be returned
20867 switch (count($space)) {
20868 case 2: {
20869 $border_spacing['H'] = $space[0];
20870 $border_spacing['V'] = $space[1];
20871 break;
20872 }
20873 case 1: {
20874 $border_spacing['H'] = $space[0];
20875 $border_spacing['V'] = $space[0];
20876 break;
20877 }
20878 default: {
20879 return array('H' => 0, 'V' => 0);
20880 }
20881 }
20882 if ($width == 0) {
20883 $width = $this->w - $this->lMargin - $this->rMargin;
20884 }
20885 $border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
20886 $border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
20887 return $border_spacing;
20888 }
20889
20890 /**
20891 * Returns the letter-spacing value from CSS value
20892 * @param $spacing (string) letter-spacing value
20893 * @param $parent (float) font spacing (tracking/kerning) value of the parent element
20894 * @return float quantity to increases or decreases the space between characters in a text.
20895 * @protected
20896 * @since 5.9.000 (2010-10-02)
20897 */
20898 protected function getCSSFontSpacing($spacing, $parent=0) {
20899 $val = 0; // value to be returned
20900 $spacing = trim($spacing);
20901 switch ($spacing) {
20902 case 'normal': {
20903 $val = 0;
20904 break;
20905 }
20906 case 'inherit': {
20907 if ($parent == 'normal') {
20908 $val = 0;
20909 } else {
20910 $val = $parent;
20911 }
20912 break;
20913 }
20914 default: {
20915 $val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
20916 }
20917 }
20918 return $val;
20919 }
20920
20921 /**
20922 * Returns the percentage of font stretching from CSS value
20923 * @param $stretch (string) stretch mode
20924 * @param $parent (float) stretch value of the parent element
20925 * @return float font stretching percentage
20926 * @protected
20927 * @since 5.9.000 (2010-10-02)
20928 */
20929 protected function getCSSFontStretching($stretch, $parent=100) {
20930 $val = 100; // value to be returned
20931 $stretch = trim($stretch);
20932 switch ($stretch) {
20933 case 'ultra-condensed': {
20934 $val = 40;
20935 break;
20936 }
20937 case 'extra-condensed': {
20938 $val = 55;
20939 break;
20940 }
20941 case 'condensed': {
20942 $val = 70;
20943 break;
20944 }
20945 case 'semi-condensed': {
20946 $val = 85;
20947 break;
20948 }
20949 case 'normal': {
20950 $val = 100;
20951 break;
20952 }
20953 case 'semi-expanded': {
20954 $val = 115;
20955 break;
20956 }
20957 case 'expanded': {
20958 $val = 130;
20959 break;
20960 }
20961 case 'extra-expanded': {
20962 $val = 145;
20963 break;
20964 }
20965 case 'ultra-expanded': {
20966 $val = 160;
20967 break;
20968 }
20969 case 'wider': {
20970 $val = $parent + 10;
20971 break;
20972 }
20973 case 'narrower': {
20974 $val = $parent - 10;
20975 break;
20976 }
20977 case 'inherit': {
20978 if ($parent == 'normal') {
20979 $val = 100;
20980 } else {
20981 $val = $parent;
20982 }
20983 break;
20984 }
20985 default: {
20986 $val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
20987 }
20988 }
20989 return $val;
20990 }
20991
20992 /**
20993 * Returns the HTML DOM array.
20994 * @param $html (string) html code
20995 * @return array
20996 * @protected
20997 * @since 3.2.000 (2008-06-20)
20998 */
20999 protected function getHtmlDomArray($html) {
21000 // array of CSS styles ( selector => properties).
21001 $css = array();
21002 // get CSS array defined at previous call
21003 $matches = array();
21004 if (preg_match_all('/<cssarray>([^\<]*)<\/cssarray>/isU', $html, $matches) > 0) {
21005 if (isset($matches[1][0])) {
21006 $css = array_merge($css, unserialize($this->unhtmlentities($matches[1][0])));
21007 }
21008 $html = preg_replace('/<cssarray>(.*?)<\/cssarray>/isU', '', $html);
21009 }
21010 // extract external CSS files
21011 $matches = array();
21012 if (preg_match_all('/<link([^\>]*)>/isU', $html, $matches) > 0) {
21013 foreach ($matches[1] as $key => $link) {
21014 $type = array();
21015 if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
21016 $type = array();
21017 preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
21018 // get 'all' and 'print' media, other media types are discarded
21019 // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
21020 if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
21021 $type = array();
21022 if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
21023 // read CSS data file
21024 $cssdata = file_get_contents(trim($type[1]));
21025 $css = array_merge($css, $this->extractCSSproperties($cssdata));
21026 }
21027 }
21028 }
21029 }
21030 }
21031 // extract style tags
21032 $matches = array();
21033 if (preg_match_all('/<style([^\>]*)>([^\<]*)<\/style>/isU', $html, $matches) > 0) {
21034 foreach ($matches[1] as $key => $media) {
21035 $type = array();
21036 preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
21037 // get 'all' and 'print' media, other media types are discarded
21038 // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
21039 if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
21040 $cssdata = $matches[2][$key];
21041 $css = array_merge($css, $this->extractCSSproperties($cssdata));
21042 }
21043 }
21044 }
21045 // create a special tag to contain the CSS array (used for table content)
21046 $csstagarray = '<cssarray>'.htmlentities(serialize($css)).'</cssarray>';
21047 // remove head and style blocks
21048 $html = preg_replace('/<head([^\>]*)>(.*?)<\/head>/siU', '', $html);
21049 $html = preg_replace('/<style([^\>]*)>([^\<]*)<\/style>/isU', '', $html);
21050 // define block tags
21051 $blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
21052 // define self-closing tags
21053 $selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
21054 // remove all unsupported tags (the line below lists all supported tags)
21055 $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>');
21056 //replace some blank characters
21057 $html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
21058 $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);
21059 $html = preg_replace('@(\r\n|\r)@', "\n", $html);
21060 $repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
21061 $html = strtr($html, $repTable);
21062 $offset = 0;
21063 while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
21064 $html_a = substr($html, 0, $offset);
21065 $html_b = substr($html, $offset, ($pos - $offset + 6));
21066 while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
21067 // preserve newlines on <pre> tag
21068 $html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
21069 }
21070 while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
21071 // preserve spaces on <pre> tag
21072 $html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
21073 }
21074 $html = $html_a.$html_b.substr($html, $pos + 6);
21075 $offset = strlen($html_a.$html_b);
21076 }
21077 $offset = 0;
21078 while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
21079 $html_a = substr($html, 0, $offset);
21080 $html_b = substr($html, $offset, ($pos - $offset + 11));
21081 while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
21082 // preserve newlines on <textarea> tag
21083 $html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
21084 $html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
21085 }
21086 $html = $html_a.$html_b.substr($html, $pos + 11);
21087 $offset = strlen($html_a.$html_b);
21088 }
21089 $html = preg_replace('/([\s]*)<option/si', '<option', $html);
21090 $html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
21091 $offset = 0;
21092 while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
21093 $html_a = substr($html, 0, $offset);
21094 $html_b = substr($html, $offset, ($pos - $offset + 9));
21095 while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
21096 $html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
21097 $html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
21098 }
21099 $html = $html_a.$html_b.substr($html, $pos + 9);
21100 $offset = strlen($html_a.$html_b);
21101 }
21102 if (preg_match("'</select'si", $html)) {
21103 $html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
21104 $html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
21105 }
21106 $html = str_replace("\n", ' ', $html);
21107 // restore textarea newlines
21108 $html = str_replace('<TBR>', "\n", $html);
21109 // remove extra spaces from code
21110 $html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
21111 $html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
21112 $html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
21113 $html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
21114 $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);
21115 $html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
21116 $html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
21117 $html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
21118 $html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
21119 $html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
21120 $html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
21121 $html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
21122 $html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
21123 $html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
21124 $html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
21125 $html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
21126 $html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
21127 // trim string
21128 $html = $this->stringTrim($html);
21129 // fix first image tag alignment
21130 $html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
21131 // pattern for generic tag
21132 $tagpattern = '/(<[^>]+>)/';
21133 // explodes the string
21134 $a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
21135 // count elements
21136 $maxel = count($a);
21137 $elkey = 0;
21138 $key = 0;
21139 // create an array of elements
21140 $dom = array();
21141 $dom[$key] = array();
21142 // set inheritable properties fot the first void element
21143 // 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
21144 $dom[$key]['tag'] = false;
21145 $dom[$key]['block'] = false;
21146 $dom[$key]['value'] = '';
21147 $dom[$key]['parent'] = 0;
21148 $dom[$key]['hide'] = false;
21149 $dom[$key]['fontname'] = $this->FontFamily;
21150 $dom[$key]['fontstyle'] = $this->FontStyle;
21151 $dom[$key]['fontsize'] = $this->FontSizePt;
21152 $dom[$key]['font-stretch'] = $this->font_stretching;
21153 $dom[$key]['letter-spacing'] = $this->font_spacing;
21154 $dom[$key]['stroke'] = $this->textstrokewidth;
21155 $dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
21156 $dom[$key]['clip'] = ($this->textrendermode > 3);
21157 $dom[$key]['line-height'] = $this->cell_height_ratio;
21158 $dom[$key]['bgcolor'] = false;
21159 $dom[$key]['fgcolor'] = $this->fgcolor; // color
21160 $dom[$key]['strokecolor'] = $this->strokecolor;
21161 $dom[$key]['align'] = '';
21162 $dom[$key]['listtype'] = '';
21163 $dom[$key]['text-indent'] = 0;
21164 $dom[$key]['border'] = array();
21165 $dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
21166 $thead = false; // true when we are inside the THEAD tag
21167 ++$key;
21168 $level = array();
21169 array_push($level, 0); // root
21170 while ($elkey < $maxel) {
21171 $dom[$key] = array();
21172 $element = $a[$elkey];
21173 $dom[$key]['elkey'] = $elkey;
21174 if (preg_match($tagpattern, $element)) {
21175 // html tag
21176 $element = substr($element, 1, -1);
21177 // get tag name
21178 preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
21179 $tagname = strtolower($tag[1]);
21180 // check if we are inside a table header
21181 if ($tagname == 'thead') {
21182 if ($element{0} == '/') {
21183 $thead = false;
21184 } else {
21185 $thead = true;
21186 }
21187 ++$elkey;
21188 continue;
21189 }
21190 $dom[$key]['tag'] = true;
21191 $dom[$key]['value'] = $tagname;
21192 if (in_array($dom[$key]['value'], $blocktags)) {
21193 $dom[$key]['block'] = true;
21194 } else {
21195 $dom[$key]['block'] = false;
21196 }
21197 if ($element{0} == '/') {
21198 // *** closing html tag
21199 $dom[$key]['opening'] = false;
21200 $dom[$key]['parent'] = end($level);
21201 array_pop($level);
21202 $dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
21203 $dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
21204 $dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
21205 $dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
21206 $dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
21207 $dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
21208 $dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
21209 $dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
21210 $dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
21211 $dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
21212 $dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
21213 $dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
21214 $dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
21215 $dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
21216 $dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
21217 if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
21218 $dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
21219 }
21220 // set the number of columns in table tag
21221 if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
21222 $dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
21223 }
21224 if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
21225 $dom[($dom[$key]['parent'])]['content'] = $csstagarray;
21226 for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
21227 $dom[($dom[$key]['parent'])]['content'] .= $a[$dom[$i]['elkey']];
21228 }
21229 $key = $i;
21230 // mark nested tables
21231 $dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
21232 // remove thead sections from nested tables
21233 $dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
21234 $dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
21235 }
21236 // store header rows on a new table
21237 if (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['thead'] === true)) {
21238 if ($this->empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
21239 $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
21240 }
21241 for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
21242 $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
21243 }
21244 if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
21245 $dom[($dom[$key]['parent'])]['attribute'] = array();
21246 }
21247 // header elements must be always contained in a single page
21248 $dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
21249 }
21250 if (($dom[$key]['value'] == 'table') AND (!$this->empty_string($dom[($dom[$key]['parent'])]['thead']))) {
21251 // remove the nobr attributes from the table header
21252 $dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
21253 $dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
21254 }
21255 } else {
21256 // *** opening or self-closing html tag
21257 $dom[$key]['opening'] = true;
21258 $dom[$key]['parent'] = end($level);
21259 if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
21260 // self-closing tag
21261 $dom[$key]['self'] = true;
21262 } else {
21263 // opening tag
21264 array_push($level, $key);
21265 $dom[$key]['self'] = false;
21266 }
21267 // copy some values from parent
21268 $parentkey = 0;
21269 if ($key > 0) {
21270 $parentkey = $dom[$key]['parent'];
21271 $dom[$key]['hide'] = $dom[$parentkey]['hide'];
21272 $dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
21273 $dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
21274 $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
21275 $dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
21276 $dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
21277 $dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
21278 $dom[$key]['fill'] = $dom[$parentkey]['fill'];
21279 $dom[$key]['clip'] = $dom[$parentkey]['clip'];
21280 $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
21281 $dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
21282 $dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
21283 $dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
21284 $dom[$key]['align'] = $dom[$parentkey]['align'];
21285 $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
21286 $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
21287 $dom[$key]['border'] = array();
21288 $dom[$key]['dir'] = $dom[$parentkey]['dir'];
21289 }
21290 // get attributes
21291 preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
21292 $dom[$key]['attribute'] = array(); // reset attribute array
21293 while (list($id, $name) = each($attr_array[1])) {
21294 $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
21295 }
21296 if (!empty($css)) {
21297 // merge CSS style to current style
21298 list($dom[$key]['csssel'], $dom[$key]['cssdata']) = $this->getCSSdataArray($dom, $key, $css);
21299 $dom[$key]['attribute']['style'] = $this->getTagStyleFromCSSarray($dom[$key]['cssdata']);
21300 }
21301 // split style attributes
21302 if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
21303 // get style attributes
21304 preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
21305 $dom[$key]['style'] = array(); // reset style attribute array
21306 while (list($id, $name) = each($style_array[1])) {
21307 // in case of duplicate attribute the last replace the previous
21308 $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
21309 }
21310 // --- get some style attributes ---
21311 // text direction
21312 if (isset($dom[$key]['style']['direction'])) {
21313 $dom[$key]['dir'] = $dom[$key]['style']['direction'];
21314 }
21315 // display
21316 if (isset($dom[$key]['style']['display'])) {
21317 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
21318 }
21319 // font family
21320 if (isset($dom[$key]['style']['font-family'])) {
21321 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
21322 }
21323 // list-style-type
21324 if (isset($dom[$key]['style']['list-style-type'])) {
21325 $dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
21326 if ($dom[$key]['listtype'] == 'inherit') {
21327 $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
21328 }
21329 }
21330 // text-indent
21331 if (isset($dom[$key]['style']['text-indent'])) {
21332 $dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
21333 if ($dom[$key]['text-indent'] == 'inherit') {
21334 $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
21335 }
21336 }
21337 // font size
21338 if (isset($dom[$key]['style']['font-size'])) {
21339 $fsize = trim($dom[$key]['style']['font-size']);
21340 switch ($fsize) {
21341 // absolute-size
21342 case 'xx-small': {
21343 $dom[$key]['fontsize'] = $dom[0]['fontsize'] - 4;
21344 break;
21345 }
21346 case 'x-small': {
21347 $dom[$key]['fontsize'] = $dom[0]['fontsize'] - 3;
21348 break;
21349 }
21350 case 'small': {
21351 $dom[$key]['fontsize'] = $dom[0]['fontsize'] - 2;
21352 break;
21353 }
21354 case 'medium': {
21355 $dom[$key]['fontsize'] = $dom[0]['fontsize'];
21356 break;
21357 }
21358 case 'large': {
21359 $dom[$key]['fontsize'] = $dom[0]['fontsize'] + 2;
21360 break;
21361 }
21362 case 'x-large': {
21363 $dom[$key]['fontsize'] = $dom[0]['fontsize'] + 4;
21364 break;
21365 }
21366 case 'xx-large': {
21367 $dom[$key]['fontsize'] = $dom[0]['fontsize'] + 6;
21368 break;
21369 }
21370 // relative-size
21371 case 'smaller': {
21372 $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'] - 3;
21373 break;
21374 }
21375 case 'larger': {
21376 $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'] + 3;
21377 break;
21378 }
21379 default: {
21380 $dom[$key]['fontsize'] = $this->getHTMLUnitToUnits($fsize, $dom[$parentkey]['fontsize'], 'pt', true);
21381 }
21382 }
21383 }
21384 // font-stretch
21385 if (isset($dom[$key]['style']['font-stretch'])) {
21386 $dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
21387 }
21388 // letter-spacing
21389 if (isset($dom[$key]['style']['letter-spacing'])) {
21390 $dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
21391 }
21392 // line-height
21393 if (isset($dom[$key]['style']['line-height'])) {
21394 $lineheight = trim($dom[$key]['style']['line-height']);
21395 switch ($lineheight) {
21396 // A normal line height. This is default
21397 case 'normal': {
21398 $dom[$key]['line-height'] = $dom[0]['line-height'];
21399 break;
21400 }
21401 default: {
21402 if (is_numeric($lineheight)) {
21403 $lineheight = $lineheight * 100;
21404 }
21405 $dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
21406 }
21407 }
21408 }
21409 // font style
21410 if (isset($dom[$key]['style']['font-weight'])) {
21411 if (strtolower($dom[$key]['style']['font-weight']{0}) == 'n') {
21412 if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
21413 $dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
21414 }
21415 } elseif (strtolower($dom[$key]['style']['font-weight']{0}) == 'b') {
21416 $dom[$key]['fontstyle'] .= 'B';
21417 }
21418 }
21419 if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style']{0}) == 'i')) {
21420 $dom[$key]['fontstyle'] .= 'I';
21421 }
21422 // font color
21423 if (isset($dom[$key]['style']['color']) AND (!$this->empty_string($dom[$key]['style']['color']))) {
21424 $dom[$key]['fgcolor'] = $this->convertHTMLColorToDec($dom[$key]['style']['color']);
21425 } elseif ($dom[$key]['value'] == 'a') {
21426 $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
21427 }
21428 // background color
21429 if (isset($dom[$key]['style']['background-color']) AND (!$this->empty_string($dom[$key]['style']['background-color']))) {
21430 $dom[$key]['bgcolor'] = $this->convertHTMLColorToDec($dom[$key]['style']['background-color']);
21431 }
21432 // text-decoration
21433 if (isset($dom[$key]['style']['text-decoration'])) {
21434 $decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
21435 foreach ($decors as $dec) {
21436 $dec = trim($dec);
21437 if (!$this->empty_string($dec)) {
21438 if ($dec{0} == 'u') {
21439 // underline
21440 $dom[$key]['fontstyle'] .= 'U';
21441 } elseif ($dec{0} == 'l') {
21442 // line-trough
21443 $dom[$key]['fontstyle'] .= 'D';
21444 } elseif ($dec{0} == 'o') {
21445 // overline
21446 $dom[$key]['fontstyle'] .= 'O';
21447 }
21448 }
21449 }
21450 } elseif ($dom[$key]['value'] == 'a') {
21451 $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
21452 }
21453 // check for width attribute
21454 if (isset($dom[$key]['style']['width'])) {
21455 $dom[$key]['width'] = $dom[$key]['style']['width'];
21456 }
21457 // check for height attribute
21458 if (isset($dom[$key]['style']['height'])) {
21459 $dom[$key]['height'] = $dom[$key]['style']['height'];
21460 }
21461 // check for text alignment
21462 if (isset($dom[$key]['style']['text-align'])) {
21463 $dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align']{0});
21464 }
21465 // check for CSS border properties
21466 if (isset($dom[$key]['style']['border'])) {
21467 $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
21468 if (!empty($borderstyle)) {
21469 $dom[$key]['border']['LTRB'] = $borderstyle;
21470 }
21471 }
21472 if (isset($dom[$key]['style']['border-color'])) {
21473 $brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
21474 if (isset($brd_colors[3])) {
21475 $dom[$key]['border']['L']['color'] = $this->convertHTMLColorToDec($brd_colors[3]);
21476 }
21477 if (isset($brd_colors[1])) {
21478 $dom[$key]['border']['R']['color'] = $this->convertHTMLColorToDec($brd_colors[1]);
21479 }
21480 if (isset($brd_colors[0])) {
21481 $dom[$key]['border']['T']['color'] = $this->convertHTMLColorToDec($brd_colors[0]);
21482 }
21483 if (isset($brd_colors[2])) {
21484 $dom[$key]['border']['B']['color'] = $this->convertHTMLColorToDec($brd_colors[2]);
21485 }
21486 }
21487 if (isset($dom[$key]['style']['border-width'])) {
21488 $brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
21489 if (isset($brd_widths[3])) {
21490 $dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
21491 }
21492 if (isset($brd_widths[1])) {
21493 $dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
21494 }
21495 if (isset($brd_widths[0])) {
21496 $dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
21497 }
21498 if (isset($brd_widths[2])) {
21499 $dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
21500 }
21501 }
21502 if (isset($dom[$key]['style']['border-style'])) {
21503 $brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
21504 if (isset($brd_styles[3])) {
21505 $dom[$key]['border']['L']['cap'] = 'square';
21506 $dom[$key]['border']['L']['join'] = 'miter';
21507 $dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
21508 if ($dom[$key]['border']['L']['dash'] < 0) {
21509 $dom[$key]['border']['L'] = array();
21510 }
21511 }
21512 if (isset($brd_styles[1])) {
21513 $dom[$key]['border']['R']['cap'] = 'square';
21514 $dom[$key]['border']['R']['join'] = 'miter';
21515 $dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
21516 if ($dom[$key]['border']['R']['dash'] < 0) {
21517 $dom[$key]['border']['R'] = array();
21518 }
21519 }
21520 if (isset($brd_styles[0])) {
21521 $dom[$key]['border']['T']['cap'] = 'square';
21522 $dom[$key]['border']['T']['join'] = 'miter';
21523 $dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
21524 if ($dom[$key]['border']['T']['dash'] < 0) {
21525 $dom[$key]['border']['T'] = array();
21526 }
21527 }
21528 if (isset($brd_styles[2])) {
21529 $dom[$key]['border']['B']['cap'] = 'square';
21530 $dom[$key]['border']['B']['join'] = 'miter';
21531 $dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
21532 if ($dom[$key]['border']['B']['dash'] < 0) {
21533 $dom[$key]['border']['B'] = array();
21534 }
21535 }
21536 }
21537 $cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
21538 foreach ($cellside as $bsk => $bsv) {
21539 if (isset($dom[$key]['style']['border-'.$bsv])) {
21540 $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
21541 if (!empty($borderstyle)) {
21542 $dom[$key]['border'][$bsk] = $borderstyle;
21543 }
21544 }
21545 if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
21546 $dom[$key]['border'][$bsk]['color'] = $this->convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color']);
21547 }
21548 if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
21549 $dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
21550 }
21551 if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
21552 $dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
21553 if ($dom[$key]['border'][$bsk]['dash'] < 0) {
21554 $dom[$key]['border'][$bsk] = array();
21555 }
21556 }
21557 }
21558 // check for CSS padding properties
21559 if (isset($dom[$key]['style']['padding'])) {
21560 $dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
21561 } else {
21562 $dom[$key]['padding'] = $this->cell_padding;
21563 }
21564 foreach ($cellside as $psk => $psv) {
21565 if (isset($dom[$key]['style']['padding-'.$psv])) {
21566 $dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
21567 }
21568 }
21569 // check for CSS margin properties
21570 if (isset($dom[$key]['style']['margin'])) {
21571 $dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
21572 } else {
21573 $dom[$key]['margin'] = $this->cell_margin;
21574 }
21575 foreach ($cellside as $psk => $psv) {
21576 if (isset($dom[$key]['style']['margin-'.$psv])) {
21577 $dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
21578 }
21579 }
21580 // check for CSS border-spacing properties
21581 if (isset($dom[$key]['style']['border-spacing'])) {
21582 $dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
21583 }
21584 // page-break-inside
21585 if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
21586 $dom[$key]['attribute']['nobr'] = 'true';
21587 }
21588 // page-break-before
21589 if (isset($dom[$key]['style']['page-break-before'])) {
21590 if ($dom[$key]['style']['page-break-before'] == 'always') {
21591 $dom[$key]['attribute']['pagebreak'] = 'true';
21592 } elseif ($dom[$key]['style']['page-break-before'] == 'left') {
21593 $dom[$key]['attribute']['pagebreak'] = 'left';
21594 } elseif ($dom[$key]['style']['page-break-before'] == 'right') {
21595 $dom[$key]['attribute']['pagebreak'] = 'right';
21596 }
21597 }
21598 // page-break-after
21599 if (isset($dom[$key]['style']['page-break-after'])) {
21600 if ($dom[$key]['style']['page-break-after'] == 'always') {
21601 $dom[$key]['attribute']['pagebreakafter'] = 'true';
21602 } elseif ($dom[$key]['style']['page-break-after'] == 'left') {
21603 $dom[$key]['attribute']['pagebreakafter'] = 'left';
21604 } elseif ($dom[$key]['style']['page-break-after'] == 'right') {
21605 $dom[$key]['attribute']['pagebreakafter'] = 'right';
21606 }
21607 }
21608 }
21609 if (isset($dom[$key]['attribute']['display'])) {
21610 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
21611 }
21612 if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
21613 $borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
21614 if (!empty($borderstyle)) {
21615 $dom[$key]['border']['LTRB'] = $borderstyle;
21616 }
21617 }
21618 // check for font tag
21619 if ($dom[$key]['value'] == 'font') {
21620 // font family
21621 if (isset($dom[$key]['attribute']['face'])) {
21622 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
21623 }
21624 // font size
21625 if (isset($dom[$key]['attribute']['size'])) {
21626 if ($key > 0) {
21627 if ($dom[$key]['attribute']['size']{0} == '+') {
21628 $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
21629 } elseif ($dom[$key]['attribute']['size']{0} == '-') {
21630 $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
21631 } else {
21632 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
21633 }
21634 } else {
21635 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
21636 }
21637 }
21638 }
21639 // force natural alignment for lists
21640 if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
21641 AND (!isset($dom[$key]['align']) OR $this->empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
21642 if ($this->rtl) {
21643 $dom[$key]['align'] = 'R';
21644 } else {
21645 $dom[$key]['align'] = 'L';
21646 }
21647 }
21648 if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
21649 if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
21650 $dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
21651 }
21652 }
21653 if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
21654 $dom[$key]['fontstyle'] .= 'B';
21655 }
21656 if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
21657 $dom[$key]['fontstyle'] .= 'I';
21658 }
21659 if ($dom[$key]['value'] == 'u') {
21660 $dom[$key]['fontstyle'] .= 'U';
21661 }
21662 if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
21663 $dom[$key]['fontstyle'] .= 'D';
21664 }
21665 if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
21666 $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
21667 }
21668 if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
21669 $dom[$key]['fontname'] = $this->default_monospaced_font;
21670 }
21671 if (($dom[$key]['value']{0} == 'h') AND (intval($dom[$key]['value']{1}) > 0) AND (intval($dom[$key]['value']{1}) < 7)) {
21672 // headings h1, h2, h3, h4, h5, h6
21673 if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
21674 $headsize = (4 - intval($dom[$key]['value']{1})) * 2;
21675 $dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
21676 }
21677 if (!isset($dom[$key]['style']['font-weight'])) {
21678 $dom[$key]['fontstyle'] .= 'B';
21679 }
21680 }
21681 if (($dom[$key]['value'] == 'table')) {
21682 $dom[$key]['rows'] = 0; // number of rows
21683 $dom[$key]['trids'] = array(); // IDs of TR elements
21684 $dom[$key]['thead'] = ''; // table header rows
21685 }
21686 if (($dom[$key]['value'] == 'tr')) {
21687 $dom[$key]['cols'] = 0;
21688 if ($thead) {
21689 $dom[$key]['thead'] = true;
21690 // rows on thead block are printed as a separate table
21691 } else {
21692 $dom[$key]['thead'] = false;
21693 // store the number of rows on table element
21694 ++$dom[($dom[$key]['parent'])]['rows'];
21695 // store the TR elements IDs on table element
21696 array_push($dom[($dom[$key]['parent'])]['trids'], $key);
21697 }
21698 }
21699 if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
21700 if (isset($dom[$key]['attribute']['colspan'])) {
21701 $colspan = intval($dom[$key]['attribute']['colspan']);
21702 } else {
21703 $colspan = 1;
21704 }
21705 $dom[$key]['attribute']['colspan'] = $colspan;
21706 $dom[($dom[$key]['parent'])]['cols'] += $colspan;
21707 }
21708 // text direction
21709 if (isset($dom[$key]['attribute']['dir'])) {
21710 $dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
21711 }
21712 // set foreground color attribute
21713 if (isset($dom[$key]['attribute']['color']) AND (!$this->empty_string($dom[$key]['attribute']['color']))) {
21714 $dom[$key]['fgcolor'] = $this->convertHTMLColorToDec($dom[$key]['attribute']['color']);
21715 } elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
21716 $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
21717 }
21718 // set background color attribute
21719 if (isset($dom[$key]['attribute']['bgcolor']) AND (!$this->empty_string($dom[$key]['attribute']['bgcolor']))) {
21720 $dom[$key]['bgcolor'] = $this->convertHTMLColorToDec($dom[$key]['attribute']['bgcolor']);
21721 }
21722 // set stroke color attribute
21723 if (isset($dom[$key]['attribute']['strokecolor']) AND (!$this->empty_string($dom[$key]['attribute']['strokecolor']))) {
21724 $dom[$key]['strokecolor'] = $this->convertHTMLColorToDec($dom[$key]['attribute']['strokecolor']);
21725 }
21726 // check for width attribute
21727 if (isset($dom[$key]['attribute']['width'])) {
21728 $dom[$key]['width'] = $dom[$key]['attribute']['width'];
21729 }
21730 // check for height attribute
21731 if (isset($dom[$key]['attribute']['height'])) {
21732 $dom[$key]['height'] = $dom[$key]['attribute']['height'];
21733 }
21734 // check for text alignment
21735 if (isset($dom[$key]['attribute']['align']) AND (!$this->empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
21736 $dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align']{0});
21737 }
21738 // check for text rendering mode (the following attributes do not exist in HTML)
21739 if (isset($dom[$key]['attribute']['stroke'])) {
21740 // font stroke width
21741 $dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
21742 }
21743 if (isset($dom[$key]['attribute']['fill'])) {
21744 // font fill
21745 if ($dom[$key]['attribute']['fill'] == 'true') {
21746 $dom[$key]['fill'] = true;
21747 } else {
21748 $dom[$key]['fill'] = false;
21749 }
21750 }
21751 if (isset($dom[$key]['attribute']['clip'])) {
21752 // clipping mode
21753 if ($dom[$key]['attribute']['clip'] == 'true') {
21754 $dom[$key]['clip'] = true;
21755 } else {
21756 $dom[$key]['clip'] = false;
21757 }
21758 }
21759 } // end opening tag
21760 } else {
21761 // text
21762 $dom[$key]['tag'] = false;
21763 $dom[$key]['block'] = false;
21764 //$element = str_replace('&nbsp;', $this->unichr(160), $element);
21765 $dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
21766 $dom[$key]['parent'] = end($level);
21767 $dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
21768 }
21769 ++$elkey;
21770 ++$key;
21771 }
21772 return $dom;
21773 }
21774
21775 /**
21776 * Returns the string used to find spaces
21777 * @return string
21778 * @protected
21779 * @author Nicola Asuni
21780 * @since 4.8.024 (2010-01-15)
21781 */
21782 protected function getSpaceString() {
21783 $spacestr = chr(32);
21784 if ($this->isUnicodeFont()) {
21785 $spacestr = chr(0).chr(32);
21786 }
21787 return $spacestr;
21788 }
21789
21790 /**
21791 * Prints a cell (rectangular area) with optional borders, background color and html text string.
21792 * 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 />
21793 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
21794 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
21795 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
21796 * @param $x (float) upper-left corner X coordinate
21797 * @param $y (float) upper-left corner Y coordinate
21798 * @param $html (string) html text to print. Default value: empty string.
21799 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
21800 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL language)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
21801 Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
21802 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
21803 * @param $reseth (boolean) if true reset the last cell height (default true).
21804 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
21805 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
21806 * @see Multicell(), writeHTML()
21807 * @public
21808 */
21809 public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
21810 return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
21811 }
21812
21813 /**
21814 * Allows to preserve some HTML formatting (limited support).<br />
21815 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
21816 * 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
21817 * @param $html (string) text to display
21818 * @param $ln (boolean) if true add a new line after text (default = true)
21819 * @param $fill (boolean) Indicates if the background must be painted (true) or transparent (false).
21820 * @param $reseth (boolean) if true reset the last cell height (default false).
21821 * @param $cell (boolean) if true add the current left (or right for RTL) padding to each Write (default false).
21822 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
21823 * @public
21824 */
21825 public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
21826 $gvars = $this->getGraphicVars();
21827 // store current values
21828 $prev_cell_margin = $this->cell_margin;
21829 $prev_cell_padding = $this->cell_padding;
21830 $prevPage = $this->page;
21831 $prevlMargin = $this->lMargin;
21832 $prevrMargin = $this->rMargin;
21833 $curfontname = $this->FontFamily;
21834 $curfontstyle = $this->FontStyle;
21835 $curfontsize = $this->FontSizePt;
21836 $curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
21837 $curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
21838 $curfontstretcing = $this->font_stretching;
21839 $curfontkerning = $this->font_spacing;
21840 $this->newline = true;
21841 $newline = true;
21842 $startlinepage = $this->page;
21843 $minstartliney = $this->y;
21844 $maxbottomliney = 0;
21845 $startlinex = $this->x;
21846 $startliney = $this->y;
21847 $yshift = 0;
21848 $loop = 0;
21849 $curpos = 0;
21850 $this_method_vars = array();
21851 $undo = false;
21852 $fontaligned = false;
21853 $reverse_dir = false; // true when the text direction is reversed
21854 $this->premode = false;
21855 if ($this->inxobj) {
21856 // we are inside an XObject template
21857 $pask = count($this->xobjects[$this->xobjid]['annotations']);
21858 } elseif (isset($this->PageAnnots[$this->page])) {
21859 $pask = count($this->PageAnnots[$this->page]);
21860 } else {
21861 $pask = 0;
21862 }
21863 if ($this->inxobj) {
21864 // we are inside an XObject template
21865 $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
21866 } elseif (!$this->InFooter) {
21867 if (isset($this->footerlen[$this->page])) {
21868 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
21869 } else {
21870 $this->footerpos[$this->page] = $this->pagelen[$this->page];
21871 }
21872 $startlinepos = $this->footerpos[$this->page];
21873 } else {
21874 // we are inside the footer
21875 $startlinepos = $this->pagelen[$this->page];
21876 }
21877 $lalign = $align;
21878 $plalign = $align;
21879 if ($this->rtl) {
21880 $w = $this->x - $this->lMargin;
21881 } else {
21882 $w = $this->w - $this->rMargin - $this->x;
21883 }
21884 $w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
21885 if ($cell) {
21886 if ($this->rtl) {
21887 $this->x -= $this->cell_padding['R'];
21888 $this->lMargin += $this->cell_padding['R'];
21889 } else {
21890 $this->x += $this->cell_padding['L'];
21891 $this->rMargin += $this->cell_padding['L'];
21892 }
21893 }
21894 if ($this->customlistindent >= 0) {
21895 $this->listindent = $this->customlistindent;
21896 } else {
21897 $this->listindent = $this->GetStringWidth('000000');
21898 }
21899 $this->listindentlevel = 0;
21900 // save previous states
21901 $prev_cell_height_ratio = $this->cell_height_ratio;
21902 $prev_listnum = $this->listnum;
21903 $prev_listordered = $this->listordered;
21904 $prev_listcount = $this->listcount;
21905 $prev_lispacer = $this->lispacer;
21906 $this->listnum = 0;
21907 $this->listordered = array();
21908 $this->listcount = array();
21909 $this->lispacer = '';
21910 if (($this->empty_string($this->lasth)) OR ($reseth)) {
21911 // reset row height
21912 $this->resetLastH();
21913 }
21914 $dom = $this->getHtmlDomArray($html);
21915 $maxel = count($dom);
21916 $key = 0;
21917 $hidden_node_key = -1;
21918 while ($key < $maxel) {
21919 if ($dom[$key]['tag']) {
21920 if ($dom[$key]['opening']) {
21921 if (($hidden_node_key <= 0) AND $dom[$key]['hide']) {
21922 // store the node key
21923 $hidden_node_key = $key;
21924 }
21925 } elseif (($hidden_node_key > 0) AND ($dom[$key]['parent'] == $hidden_node_key)) {
21926 // we have reached the closing tag of the hidden node
21927 $hidden_node_key = 0;
21928 }
21929 }
21930 if ($hidden_node_key >= 0) {
21931 // skip this node
21932 ++$key;
21933 if ($hidden_node_key == 0) {
21934 // reset hidden mode
21935 $hidden_node_key = -1;
21936 }
21937 continue;
21938 }
21939 if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
21940 // check for pagebreak
21941 if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
21942 // add a page (or trig AcceptPageBreak() for multicolumn mode)
21943 $this->checkPageBreak($this->PageBreakTrigger + 1);
21944 $this->htmlvspace = ($this->PageBreakTrigger + 1);
21945 }
21946 if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
21947 OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
21948 // add a page (or trig AcceptPageBreak() for multicolumn mode)
21949 $this->checkPageBreak($this->PageBreakTrigger + 1);
21950 $this->htmlvspace = ($this->PageBreakTrigger + 1);
21951 }
21952 }
21953 if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
21954 if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
21955 $dom[$key]['attribute']['nobr'] = false;
21956 } else {
21957 // store current object
21958 $this->startTransaction();
21959 // save this method vars
21960 $this_method_vars['html'] = $html;
21961 $this_method_vars['ln'] = $ln;
21962 $this_method_vars['fill'] = $fill;
21963 $this_method_vars['reseth'] = $reseth;
21964 $this_method_vars['cell'] = $cell;
21965 $this_method_vars['align'] = $align;
21966 $this_method_vars['gvars'] = $gvars;
21967 $this_method_vars['prevPage'] = $prevPage;
21968 $this_method_vars['prev_cell_margin'] = $prev_cell_margin;
21969 $this_method_vars['prev_cell_padding'] = $prev_cell_padding;
21970 $this_method_vars['prevlMargin'] = $prevlMargin;
21971 $this_method_vars['prevrMargin'] = $prevrMargin;
21972 $this_method_vars['curfontname'] = $curfontname;
21973 $this_method_vars['curfontstyle'] = $curfontstyle;
21974 $this_method_vars['curfontsize'] = $curfontsize;
21975 $this_method_vars['curfontascent'] = $curfontascent;
21976 $this_method_vars['curfontdescent'] = $curfontdescent;
21977 $this_method_vars['curfontstretcing'] = $curfontstretcing;
21978 $this_method_vars['curfontkerning'] = $curfontkerning;
21979 $this_method_vars['minstartliney'] = $minstartliney;
21980 $this_method_vars['maxbottomliney'] = $maxbottomliney;
21981 $this_method_vars['yshift'] = $yshift;
21982 $this_method_vars['startlinepage'] = $startlinepage;
21983 $this_method_vars['startlinepos'] = $startlinepos;
21984 $this_method_vars['startlinex'] = $startlinex;
21985 $this_method_vars['startliney'] = $startliney;
21986 $this_method_vars['newline'] = $newline;
21987 $this_method_vars['loop'] = $loop;
21988 $this_method_vars['curpos'] = $curpos;
21989 $this_method_vars['pask'] = $pask;
21990 $this_method_vars['lalign'] = $lalign;
21991 $this_method_vars['plalign'] = $plalign;
21992 $this_method_vars['w'] = $w;
21993 $this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
21994 $this_method_vars['prev_listnum'] = $prev_listnum;
21995 $this_method_vars['prev_listordered'] = $prev_listordered;
21996 $this_method_vars['prev_listcount'] = $prev_listcount;
21997 $this_method_vars['prev_lispacer'] = $prev_lispacer;
21998 $this_method_vars['fontaligned'] = $fontaligned;
21999 $this_method_vars['key'] = $key;
22000 $this_method_vars['dom'] = $dom;
22001 }
22002 }
22003 // print THEAD block
22004 if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
22005 if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !$this->empty_string($dom[$dom[$key]['parent']]['thead'])) {
22006 $this->inthead = true;
22007 // print table header (thead)
22008 $this->writeHTML($this->thead, false, false, false, false, '');
22009 // check if we are on a new page or on a new column
22010 if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
22011 // we are on a new page or on a new column and the total object height is less than the available vertical space.
22012 // restore previous object
22013 $this->rollbackTransaction(true);
22014 // restore previous values
22015 foreach ($this_method_vars as $vkey => $vval) {
22016 $$vkey = $vval;
22017 }
22018 // disable table header
22019 $tmp_thead = $this->thead;
22020 $this->thead = '';
22021 // add a page (or trig AcceptPageBreak() for multicolumn mode)
22022 $pre_y = $this->y;
22023 if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
22024 // fix for multicolumn mode
22025 $startliney = $this->y;
22026 }
22027 $this->start_transaction_page = $this->page;
22028 $this->start_transaction_y = $this->y;
22029 // restore table header
22030 $this->thead = $tmp_thead;
22031 // fix table border properties
22032 if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
22033 $tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
22034 } elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
22035 $tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
22036 } else {
22037 $tmp_cellspacing = 0;
22038 }
22039 $dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
22040 $dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
22041 $dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
22042 $xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
22043 $dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
22044 $dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
22045 // print table header (thead)
22046 $this->writeHTML($this->thead, false, false, false, false, '');
22047 }
22048 }
22049 // move $key index forward to skip THEAD block
22050 while ( ($key < $maxel) AND (!(
22051 ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
22052 OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
22053 ++$key;
22054 }
22055 }
22056 if ($dom[$key]['tag'] OR ($key == 0)) {
22057 if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
22058 $dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
22059 }
22060 // vertically align image in line
22061 if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
22062 // get image height
22063 $imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], $this->lasth, 'px');
22064 $autolinebreak = false;
22065 if (isset($dom[$key]['width']) AND ($dom[$key]['width'] > 0)) {
22066 $imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], 1, 'px', false);
22067 if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
22068 AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
22069 OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
22070 // add automatic line break
22071 $autolinebreak = true;
22072 $this->Ln('', $cell);
22073 if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
22074 // go back to evaluate this line break
22075 --$key;
22076 }
22077 }
22078 }
22079 if (!$autolinebreak) {
22080 if ($this->inPageBody()) {
22081 $pre_y = $this->y;
22082 // check for page break
22083 if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
22084 // fix for multicolumn mode
22085 $startliney = $this->y;
22086 }
22087 }
22088 if ($this->page > $startlinepage) {
22089 // fix line splitted over two pages
22090 if (isset($this->footerlen[$startlinepage])) {
22091 $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
22092 }
22093 // line to be moved one page forward
22094 $pagebuff = $this->getPageBuffer($startlinepage);
22095 $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
22096 $tstart = substr($pagebuff, 0, $startlinepos);
22097 $tend = substr($this->getPageBuffer($startlinepage), $curpos);
22098 // remove line from previous page
22099 $this->setPageBuffer($startlinepage, $tstart.''.$tend);
22100 $pagebuff = $this->getPageBuffer($this->page);
22101 $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
22102 $tend = substr($pagebuff, $this->cntmrk[$this->page]);
22103 // add line start to current page
22104 $yshift = ($minstartliney - $this->y);
22105 if ($fontaligned) {
22106 $yshift += ($curfontsize / $this->k);
22107 }
22108 $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
22109 $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
22110 // shift the annotations and links
22111 if (isset($this->PageAnnots[$this->page])) {
22112 $next_pask = count($this->PageAnnots[$this->page]);
22113 } else {
22114 $next_pask = 0;
22115 }
22116 if (isset($this->PageAnnots[$startlinepage])) {
22117 foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
22118 if ($pak >= $pask) {
22119 $this->PageAnnots[$this->page][] = $pac;
22120 unset($this->PageAnnots[$startlinepage][$pak]);
22121 $npak = count($this->PageAnnots[$this->page]) - 1;
22122 $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
22123 }
22124 }
22125 }
22126 $pask = $next_pask;
22127 $startlinepos = $this->cntmrk[$this->page];
22128 $startlinepage = $this->page;
22129 $startliney = $this->y;
22130 $this->newline = false;
22131 }
22132 $this->y += ((($curfontsize * $this->cell_height_ratio / $this->k) + $curfontascent - $curfontdescent) / 2) - $imgh;
22133 $minstartliney = min($this->y, $minstartliney);
22134 $maxbottomliney = ($startliney + ($this->FontSize * $this->cell_height_ratio));
22135 }
22136 } elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
22137 // account for different font size
22138 $pfontname = $curfontname;
22139 $pfontstyle = $curfontstyle;
22140 $pfontsize = $curfontsize;
22141 $fontname = isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname;
22142 $fontstyle = isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle;
22143 $fontsize = isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize;
22144 $fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
22145 $fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
22146 if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
22147 OR ($this->cell_height_ratio != $dom[$key]['line-height'])
22148 OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
22149 if (($key < ($maxel - 1)) AND (
22150 ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
22151 OR ($this->cell_height_ratio != $dom[$key]['line-height'])
22152 OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize) AND ($fontsize >= 0) AND ($curfontsize >= 0) AND ($fontsize != $curfontsize))
22153 )) {
22154 if ($this->page > $startlinepage) {
22155 // fix lines splitted over two pages
22156 if (isset($this->footerlen[$startlinepage])) {
22157 $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
22158 }
22159 // line to be moved one page forward
22160 $pagebuff = $this->getPageBuffer($startlinepage);
22161 $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
22162 $tstart = substr($pagebuff, 0, $startlinepos);
22163 $tend = substr($this->getPageBuffer($startlinepage), $curpos);
22164 // remove line start from previous page
22165 $this->setPageBuffer($startlinepage, $tstart.''.$tend);
22166 $pagebuff = $this->getPageBuffer($this->page);
22167 $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
22168 $tend = substr($pagebuff, $this->cntmrk[$this->page]);
22169 // add line start to current page
22170 $yshift = ($minstartliney - $this->y);
22171 $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
22172 $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
22173 // shift the annotations and links
22174 if (isset($this->PageAnnots[$this->page])) {
22175 $next_pask = count($this->PageAnnots[$this->page]);
22176 } else {
22177 $next_pask = 0;
22178 }
22179 if (isset($this->PageAnnots[$startlinepage])) {
22180 foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
22181 if ($pak >= $pask) {
22182 $this->PageAnnots[$this->page][] = $pac;
22183 unset($this->PageAnnots[$startlinepage][$pak]);
22184 $npak = count($this->PageAnnots[$this->page]) - 1;
22185 $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
22186 }
22187 }
22188 }
22189 $pask = $next_pask;
22190 $startlinepos = $this->cntmrk[$this->page];
22191 $startlinepage = $this->page;
22192 $startliney = $this->y;
22193 }
22194 if (!isset($dom[$key]['line-height'])) {
22195 $dom[$key]['line-height'] = $this->cell_height_ratio;
22196 }
22197 if (!$dom[$key]['block']) {
22198 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']))) {
22199 $this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
22200 }
22201 if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
22202 $current_line_align_data = array($key, $minstartliney, $maxbottomliney);
22203 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)))) {
22204 $minstartliney = min($this->y, $line_align_data[1]);
22205 $maxbottomliney = max(($this->y + (($fontsize * $this->cell_height_ratio) / $this->k)), $line_align_data[2]);
22206 } else {
22207 $minstartliney = min($this->y, $minstartliney);
22208 $maxbottomliney = max(($this->y + (($fontsize * $this->cell_height_ratio) / $this->k)), $maxbottomliney);
22209 }
22210 $line_align_data = $current_line_align_data;
22211 }
22212 }
22213 $this->cell_height_ratio = $dom[$key]['line-height'];
22214 $fontaligned = true;
22215 }
22216 $this->SetFont($fontname, $fontstyle, $fontsize);
22217 // reset row height
22218 $this->resetLastH();
22219 $curfontname = $fontname;
22220 $curfontstyle = $fontstyle;
22221 $curfontsize = $fontsize;
22222 $curfontascent = $fontascent;
22223 $curfontdescent = $fontdescent;
22224 }
22225 }
22226 // set text rendering mode
22227 $textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
22228 $textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
22229 $textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
22230 $this->setTextRenderingMode($textstroke, $textfill, $textclip);
22231 if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
22232 $this->setFontStretching($dom[$key]['font-stretch']);
22233 }
22234 if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
22235 $this->setFontSpacing($dom[$key]['letter-spacing']);
22236 }
22237 if (($plalign == 'J') AND $dom[$key]['block']) {
22238 $plalign = '';
22239 }
22240 // get current position on page buffer
22241 $curpos = $this->pagelen[$startlinepage];
22242 if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
22243 $this->SetFillColorArray($dom[$key]['bgcolor']);
22244 $wfill = true;
22245 } else {
22246 $wfill = $fill | false;
22247 }
22248 if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
22249 $this->SetTextColorArray($dom[$key]['fgcolor']);
22250 }
22251 if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
22252 $this->SetDrawColorArray($dom[$key]['strokecolor']);
22253 }
22254 if (isset($dom[$key]['align'])) {
22255 $lalign = $dom[$key]['align'];
22256 }
22257 if ($this->empty_string($lalign)) {
22258 $lalign = $align;
22259 }
22260 }
22261 // align lines
22262 if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
22263 $newline = true;
22264 $fontaligned = false;
22265 // we are at the beginning of a new line
22266 if (isset($startlinex)) {
22267 $yshift = ($minstartliney - $startliney);
22268 if (($yshift > 0) OR ($this->page > $startlinepage)) {
22269 $yshift = 0;
22270 }
22271 $t_x = 0;
22272 // the last line must be shifted to be aligned as requested
22273 $linew = abs($this->endlinex - $startlinex);
22274 if ($this->inxobj) {
22275 // we are inside an XObject template
22276 $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
22277 if (isset($opentagpos)) {
22278 $midpos = $opentagpos;
22279 } else {
22280 $midpos = 0;
22281 }
22282 if ($midpos > 0) {
22283 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
22284 $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
22285 } else {
22286 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
22287 $pend = '';
22288 }
22289 } else {
22290 $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
22291 if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
22292 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
22293 $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
22294 } elseif (isset($opentagpos)) {
22295 $midpos = $opentagpos;
22296 } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
22297 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
22298 $midpos = $this->footerpos[$startlinepage];
22299 } else {
22300 $midpos = 0;
22301 }
22302 if ($midpos > 0) {
22303 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
22304 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
22305 } else {
22306 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
22307 $pend = '';
22308 }
22309 }
22310 if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
22311 // calculate shifting amount
22312 $tw = $w;
22313 if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
22314 $tw += $this->cell_padding['R'];
22315 }
22316 if ($this->lMargin != $prevlMargin) {
22317 $tw += ($prevlMargin - $this->lMargin);
22318 }
22319 if ($this->rMargin != $prevrMargin) {
22320 $tw += ($prevrMargin - $this->rMargin);
22321 }
22322 $one_space_width = $this->GetStringWidth(chr(32));
22323 $no = 0; // number of spaces on a line contained on a single block
22324 if ($this->isRTLTextDir()) { // RTL
22325 // remove left space if exist
22326 $pos1 = $this->revstrpos($pmid, '[(');
22327 if ($pos1 > 0) {
22328 $pos1 = intval($pos1);
22329 if ($this->isUnicodeFont()) {
22330 $pos2 = intval($this->revstrpos($pmid, '[('.chr(0).chr(32)));
22331 $spacelen = 2;
22332 } else {
22333 $pos2 = intval($this->revstrpos($pmid, '[('.chr(32)));
22334 $spacelen = 1;
22335 }
22336 if ($pos1 == $pos2) {
22337 $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
22338 if (substr($pmid, $pos1, 4) == '[()]') {
22339 $linew -= $one_space_width;
22340 } elseif ($pos1 == strpos($pmid, '[(')) {
22341 $no = 1;
22342 }
22343 }
22344 }
22345 } else { // LTR
22346 // remove right space if exist
22347 $pos1 = $this->revstrpos($pmid, ')]');
22348 if ($pos1 > 0) {
22349 $pos1 = intval($pos1);
22350 if ($this->isUnicodeFont()) {
22351 $pos2 = intval($this->revstrpos($pmid, chr(0).chr(32).')]')) + 2;
22352 $spacelen = 2;
22353 } else {
22354 $pos2 = intval($this->revstrpos($pmid, chr(32).')]')) + 1;
22355 $spacelen = 1;
22356 }
22357 if ($pos1 == $pos2) {
22358 $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
22359 $linew -= $one_space_width;
22360 }
22361 }
22362 }
22363 $mdiff = ($tw - $linew);
22364 if ($plalign == 'C') {
22365 if ($this->rtl) {
22366 $t_x = -($mdiff / 2);
22367 } else {
22368 $t_x = ($mdiff / 2);
22369 }
22370 } elseif ($plalign == 'R') {
22371 // right alignment on LTR document
22372 $t_x = $mdiff;
22373 } elseif ($plalign == 'L') {
22374 // left alignment on RTL document
22375 $t_x = -$mdiff;
22376 } elseif (($plalign == 'J') AND ($plalign == $lalign)) {
22377 // Justification
22378 if ($this->isRTLTextDir()) {
22379 // align text on the left
22380 $t_x = -$mdiff;
22381 }
22382 $ns = 0; // number of spaces
22383 $pmidtemp = $pmid;
22384 // escape special characters
22385 $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
22386 $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
22387 // search spaces
22388 if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
22389 $spacestr = $this->getSpaceString();
22390 $maxkk = count($lnstring[1]) - 1;
22391 for ($kk=0; $kk <= $maxkk; ++$kk) {
22392 // restore special characters
22393 $lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
22394 $lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
22395 // store number of spaces on the strings
22396 $lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
22397 // count total spaces on line
22398 $ns += $lnstring[2][$kk];
22399 $lnstring[3][$kk] = $ns;
22400 }
22401 if ($ns == 0) {
22402 $ns = 1;
22403 }
22404 // calculate additional space to add to each existing space
22405 $spacewidth = ($mdiff / ($ns - $no)) * $this->k;
22406 $spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
22407 if ($this->font_spacing != 0) {
22408 // fixed spacing mode
22409 $osw = -1000 * $this->font_spacing / $this->FontSize;
22410 $spacewidthu += $osw;
22411 }
22412 $nsmax = $ns;
22413 $ns = 0;
22414 reset($lnstring);
22415 $offset = 0;
22416 $strcount = 0;
22417 $prev_epsposbeg = 0;
22418 $textpos = 0;
22419 if ($this->isRTLTextDir()) {
22420 $textpos = $this->wPt;
22421 }
22422 global $spacew;
22423 while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
22424 // check if we are inside a string section '[( ... )]'
22425 $stroffset = strpos($pmid, '[(', $offset);
22426 if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
22427 // set offset to the end of string section
22428 $offset = strpos($pmid, ')]', $stroffset);
22429 while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
22430 $offset = strpos($pmid, ')]', ($offset + 1));
22431 }
22432 if ($offset === false) {
22433 $this->Error('HTML Justification: malformed PDF code.');
22434 }
22435 continue;
22436 }
22437 if ($this->isRTLTextDir()) {
22438 $spacew = ($spacewidth * ($nsmax - $ns));
22439 } else {
22440 $spacew = ($spacewidth * $ns);
22441 }
22442 $offset = $strpiece[2][1] + strlen($strpiece[2][0]);
22443 $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
22444 $epsposend = strpos($pmid, $this->epsmarker.'Q', $offset) + strlen($this->epsmarker.'Q');
22445 if ((($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend))
22446 OR (($epsposbeg === false) AND ($epsposend > 0) AND ($offset < $epsposend))) {
22447 // shift EPS images
22448 $trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
22449 $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
22450 $pmid_b = substr($pmid, 0, $epsposbeg);
22451 $pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
22452 $pmid_e = substr($pmid, $epsposend);
22453 $pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
22454 $offset = $epsposend;
22455 continue;
22456
22457 }
22458 $prev_epsposbeg = $epsposbeg;
22459 $currentxpos = 0;
22460 // shift blocks of code
22461 switch ($strpiece[2][0]) {
22462 case 'Td':
22463 case 'cm':
22464 case 'm':
22465 case 'l': {
22466 // get current X position
22467 preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
22468 $currentxpos = $xmatches[1];
22469 $textpos = $currentxpos;
22470 if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
22471 $ns = $lnstring[3][$strcount];
22472 if ($this->isRTLTextDir()) {
22473 $spacew = ($spacewidth * ($nsmax - $ns));
22474 }
22475 ++$strcount;
22476 }
22477 // justify block
22478 $pmid = preg_replace_callback('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x',
22479 create_function('$matches', 'global $spacew;
22480 $newx = sprintf("%F",(floatval($matches[1]) + $spacew));
22481 return "".$newx." ".$matches[2]." x*#!#*x".$matches[3].$matches[4];'), $pmid, 1);
22482 break;
22483 }
22484 case 're': {
22485 // justify block
22486 if (!$this->empty_string($this->lispacer)) {
22487 $this->lispacer = '';
22488 continue;
22489 }
22490 preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
22491 $currentxpos = $xmatches[1];
22492 global $x_diff, $w_diff;
22493 $x_diff = 0;
22494 $w_diff = 0;
22495 if ($this->isRTLTextDir()) { // RTL
22496 if ($currentxpos < $textpos) {
22497 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
22498 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
22499 } else {
22500 if ($strcount > 0) {
22501 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
22502 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
22503 }
22504 }
22505 } else { // LTR
22506 if ($currentxpos > $textpos) {
22507 if ($strcount > 0) {
22508 $x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
22509 }
22510 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
22511 } else {
22512 if ($strcount > 1) {
22513 $x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
22514 }
22515 if ($strcount > 0) {
22516 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
22517 }
22518 }
22519 }
22520 $pmid = preg_replace_callback('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x',
22521 create_function('$matches', 'global $x_diff, $w_diff;
22522 $newx = sprintf("%F",(floatval($matches[1]) + $x_diff));
22523 $neww = sprintf("%F",(floatval($matches[3]) + $w_diff));
22524 return "".$newx." ".$matches[2]." ".$neww." ".$matches[4]." x*#!#*x".$matches[5].$matches[6];'), $pmid, 1);
22525 break;
22526 }
22527 case 'c': {
22528 // get current X position
22529 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);
22530 $currentxpos = $xmatches[1];
22531 // justify block
22532 $pmid = preg_replace_callback('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$xmatches[4].')[\s]('.$xmatches[5].')[\s]('.$strpiece[1][0].')[\s](c)([\s]*)/x',
22533 create_function('$matches', 'global $spacew;
22534 $newx1 = sprintf("%F",(floatval($matches[1]) + $spacew));
22535 $newx2 = sprintf("%F",(floatval($matches[3]) + $spacew));
22536 $newx3 = sprintf("%F",(floatval($matches[5]) + $spacew));
22537 return "".$newx1." ".$matches[2]." ".$newx2." ".$matches[4]." ".$newx3." ".$matches[6]." x*#!#*x".$matches[7].$matches[8];'), $pmid, 1);
22538 break;
22539 }
22540 }
22541 // shift the annotations and links
22542 $cxpos = ($currentxpos / $this->k);
22543 $lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
22544 if ($this->inxobj) {
22545 // we are inside an XObject template
22546 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
22547 if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
22548 if ($cxpos > $lmpos) {
22549 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
22550 $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
22551 } else {
22552 $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
22553 }
22554 break;
22555 }
22556 }
22557 } elseif (isset($this->PageAnnots[$this->page])) {
22558 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
22559 if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
22560 if ($cxpos > $lmpos) {
22561 $this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
22562 $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
22563 } else {
22564 $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
22565 }
22566 break;
22567 }
22568 }
22569 }
22570 } // end of while
22571 // remove markers
22572 $pmid = str_replace('x*#!#*x', '', $pmid);
22573 if ($this->isUnicodeFont()) {
22574 // multibyte characters
22575 $spacew = $spacewidthu;
22576 if ($this->font_stretching != 100) {
22577 // word spacing is affected by stretching
22578 $spacew /= ($this->font_stretching / 100);
22579 }
22580 $pmidtemp = $pmid;
22581 // escape special characters
22582 $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
22583 $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
22584 $pmid = preg_replace_callback("/\[\(([^\)]*)\)\]/x",
22585 create_function('$matches', 'global $spacew;
22586 $matches[1] = str_replace("#!#OP#!#", "(", $matches[1]);
22587 $matches[1] = str_replace("#!#CP#!#", ")", $matches[1]);
22588 return "[(".str_replace(chr(0).chr(32), ") ".sprintf("%F", $spacew)." (", $matches[1]).")]";'), $pmidtemp);
22589 if ($this->inxobj) {
22590 // we are inside an XObject template
22591 $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
22592 } else {
22593 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
22594 }
22595 $endlinepos = strlen($pstart."\n".$pmid."\n");
22596 } else {
22597 // non-unicode (single-byte characters)
22598 if ($this->font_stretching != 100) {
22599 // word spacing (Tw) is affected by stretching
22600 $spacewidth /= ($this->font_stretching / 100);
22601 }
22602 $rs = sprintf('%F Tw', $spacewidth);
22603 $pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
22604 if ($this->inxobj) {
22605 // we are inside an XObject template
22606 $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
22607 } else {
22608 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
22609 }
22610 $endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
22611 }
22612 }
22613 } // end of J
22614 } // end if $startlinex
22615 if (($t_x != 0) OR ($yshift < 0)) {
22616 // shift the line
22617 $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
22618 $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
22619 $endlinepos = strlen($pstart);
22620 if ($this->inxobj) {
22621 // we are inside an XObject template
22622 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
22623 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
22624 if ($pak >= $pask) {
22625 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
22626 $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
22627 }
22628 }
22629 } else {
22630 $this->setPageBuffer($startlinepage, $pstart.$pend);
22631 // shift the annotations and links
22632 if (isset($this->PageAnnots[$this->page])) {
22633 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
22634 if ($pak >= $pask) {
22635 $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
22636 $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
22637 }
22638 }
22639 }
22640 }
22641 $this->y -= $yshift;
22642 }
22643 }
22644 $pbrk = $this->checkPageBreak($this->lasth);
22645 $this->newline = false;
22646 $startlinex = $this->x;
22647 $startliney = $this->y;
22648 if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
22649 $startliney -= ((0.3 * $this->FontSizePt) / $this->k);
22650 } elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
22651 $startliney -= (($this->FontSizePt / 0.7) / $this->k);
22652 } else {
22653 $minstartliney = $startliney;
22654 $maxbottomliney = ($this->y + (($fontsize * $this->cell_height_ratio) / $this->k));
22655 }
22656 $startlinepage = $this->page;
22657 if (isset($endlinepos) AND (!$pbrk)) {
22658 $startlinepos = $endlinepos;
22659 } else {
22660 if ($this->inxobj) {
22661 // we are inside an XObject template
22662 $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
22663 } elseif (!$this->InFooter) {
22664 if (isset($this->footerlen[$this->page])) {
22665 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
22666 } else {
22667 $this->footerpos[$this->page] = $this->pagelen[$this->page];
22668 }
22669 $startlinepos = $this->footerpos[$this->page];
22670 } else {
22671 $startlinepos = $this->pagelen[$this->page];
22672 }
22673 }
22674 unset($endlinepos);
22675 $plalign = $lalign;
22676 if (isset($this->PageAnnots[$this->page])) {
22677 $pask = count($this->PageAnnots[$this->page]);
22678 } else {
22679 $pask = 0;
22680 }
22681 if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
22682 AND (isset($this->emptypagemrk[$this->page]))
22683 AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
22684 $this->SetFont($fontname, $fontstyle, $fontsize);
22685 if ($wfill) {
22686 $this->SetFillColorArray($this->bgcolor);
22687 }
22688 }
22689 } // end newline
22690 if (isset($opentagpos)) {
22691 unset($opentagpos);
22692 }
22693 if ($dom[$key]['tag']) {
22694 if ($dom[$key]['opening']) {
22695 // get text indentation (if any)
22696 if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
22697 $this->textindent = $dom[$key]['text-indent'];
22698 $this->newline = true;
22699 }
22700 // table
22701 if ($dom[$key]['value'] == 'table') {
22702 // available page width
22703 if ($this->rtl) {
22704 $wtmp = $this->x - $this->lMargin;
22705 } else {
22706 $wtmp = $this->w - $this->rMargin - $this->x;
22707 }
22708 // get cell spacing
22709 if (isset($dom[$key]['attribute']['cellspacing'])) {
22710 $clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
22711 $cellspacing = array('H' => $clsp, 'V' => $clsp);
22712 } elseif (isset($dom[$key]['border-spacing'])) {
22713 $cellspacing = $dom[$key]['border-spacing'];
22714 } else {
22715 $cellspacing = array('H' => 0, 'V' => 0);
22716 }
22717 // table width
22718 if (isset($dom[$key]['width'])) {
22719 $table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
22720 } else {
22721 $table_width = $wtmp;
22722 }
22723 $table_width -= (2 * $cellspacing['H']);
22724 if (!$this->inthead) {
22725 $this->y += $cellspacing['V'];
22726 }
22727 if ($this->rtl) {
22728 $cellspacingx = -$cellspacing['H'];
22729 } else {
22730 $cellspacingx = $cellspacing['H'];
22731 }
22732 // total table width without cellspaces
22733 $table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
22734 // minimum column width
22735 $table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
22736 // array of custom column widths
22737 $table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
22738 }
22739 // table row
22740 if ($dom[$key]['value'] == 'tr') {
22741 // reset column counter
22742 $colid = 0;
22743 }
22744 // table cell
22745 if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
22746 $trid = $dom[$key]['parent'];
22747 $table_el = $dom[$trid]['parent'];
22748 if (!isset($dom[$table_el]['cols'])) {
22749 $dom[$table_el]['cols'] = $dom[$trid]['cols'];
22750 }
22751 // store border info
22752 $tdborder = 0;
22753 if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
22754 $tdborder = $dom[$key]['border'];
22755 }
22756 $colspan = $dom[$key]['attribute']['colspan'];
22757 $old_cell_padding = $this->cell_padding;
22758 if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
22759 $crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
22760 $current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
22761 } elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
22762 $current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
22763 } else {
22764 $current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
22765 }
22766 $this->cell_padding = $current_cell_padding;
22767 if (isset($dom[$key]['height'])) {
22768 // minimum cell height
22769 $cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
22770 } else {
22771 $cellh = 0;
22772 }
22773 if (isset($dom[$key]['content'])) {
22774 $cell_content = stripslashes($dom[$key]['content']);
22775 } else {
22776 $cell_content = '&nbsp;';
22777 }
22778 $tagtype = $dom[$key]['value'];
22779 $parentid = $key;
22780 while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
22781 // move $key index forward
22782 ++$key;
22783 }
22784 if (!isset($dom[$trid]['startpage'])) {
22785 $dom[$trid]['startpage'] = $this->page;
22786 } else {
22787 $this->setPage($dom[$trid]['startpage']);
22788 }
22789 if (!isset($dom[$trid]['startcolumn'])) {
22790 $dom[$trid]['startcolumn'] = $this->current_column;
22791 } elseif ($this->current_column != $dom[$trid]['startcolumn']) {
22792 $tmpx = $this->x;
22793 $this->selectColumn($dom[$trid]['startcolumn']);
22794 $this->x = $tmpx;
22795 }
22796 if (!isset($dom[$trid]['starty'])) {
22797 $dom[$trid]['starty'] = $this->y;
22798 } else {
22799 $this->y = $dom[$trid]['starty'];
22800 }
22801 if (!isset($dom[$trid]['startx'])) {
22802 $dom[$trid]['startx'] = $this->x;
22803 $this->x += $cellspacingx;
22804 } else {
22805 $this->x += ($cellspacingx / 2);
22806 }
22807 if (isset($dom[$parentid]['attribute']['rowspan'])) {
22808 $rowspan = intval($dom[$parentid]['attribute']['rowspan']);
22809 } else {
22810 $rowspan = 1;
22811 }
22812 // skip row-spanned cells started on the previous rows
22813 if (isset($dom[$table_el]['rowspans'])) {
22814 $rsk = 0;
22815 $rskmax = count($dom[$table_el]['rowspans']);
22816 while ($rsk < $rskmax) {
22817 $trwsp = $dom[$table_el]['rowspans'][$rsk];
22818 $rsstartx = $trwsp['startx'];
22819 $rsendx = $trwsp['endx'];
22820 // account for margin changes
22821 if ($trwsp['startpage'] < $this->page) {
22822 if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
22823 $dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
22824 $rsstartx -= $dl;
22825 $rsendx -= $dl;
22826 } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
22827 $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
22828 $rsstartx += $dl;
22829 $rsendx += $dl;
22830 }
22831 }
22832 if (($trwsp['rowspan'] > 0)
22833 AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
22834 AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
22835 AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
22836 // set the starting X position of the current cell
22837 $this->x = $rsendx + $cellspacingx;
22838 // increment column indicator
22839 $colid += $trwsp['colspan'];
22840 if (($trwsp['rowspan'] == 1)
22841 AND (isset($dom[$trid]['endy']))
22842 AND (isset($dom[$trid]['endpage']))
22843 AND (isset($dom[$trid]['endcolumn']))
22844 AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
22845 AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
22846 // set ending Y position for row
22847 $dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
22848 $dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
22849 }
22850 $rsk = 0;
22851 } else {
22852 ++$rsk;
22853 }
22854 }
22855 }
22856 if (isset($dom[$parentid]['width'])) {
22857 // user specified width
22858 $cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
22859 $tmpcw = ($cellw / $colspan);
22860 for ($i = 0; $i < $colspan; ++$i) {
22861 $table_colwidths[($colid + $i)] = $tmpcw;
22862 }
22863 } else {
22864 // inherit column width
22865 $cellw = 0;
22866 for ($i = 0; $i < $colspan; ++$i) {
22867 $cellw += $table_colwidths[($colid + $i)];
22868 }
22869 }
22870 $cellw += (($colspan - 1) * $cellspacing['H']);
22871 // increment column indicator
22872 $colid += $colspan;
22873 // add rowspan information to table element
22874 if ($rowspan > 1) {
22875 $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));
22876 }
22877 $cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
22878 if ($rowspan > 1) {
22879 $dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
22880 }
22881 // push background colors
22882 if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
22883 $dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
22884 }
22885 // store border info
22886 if (isset($tdborder) AND !empty($tdborder)) {
22887 $dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
22888 }
22889 $prevLastH = $this->lasth;
22890 // store some info for multicolumn mode
22891 if ($this->rtl) {
22892 $this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
22893 } else {
22894 $this->colxshift['x'] = $this->x - $this->lMargin;
22895 }
22896 $this->colxshift['s'] = $cellspacing;
22897 $this->colxshift['p'] = $current_cell_padding;
22898 // ****** write the cell content ******
22899 $this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
22900 // restore some values
22901 $this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
22902 $this->lasth = $prevLastH;
22903 $this->cell_padding = $old_cell_padding;
22904 $dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
22905 // update the end of row position
22906 if ($rowspan <= 1) {
22907 if (isset($dom[$trid]['endy'])) {
22908 if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
22909 $dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
22910 } elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
22911 $dom[$trid]['endy'] = $this->y;
22912 }
22913 } else {
22914 $dom[$trid]['endy'] = $this->y;
22915 }
22916 if (isset($dom[$trid]['endpage'])) {
22917 $dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
22918 } else {
22919 $dom[$trid]['endpage'] = $this->page;
22920 }
22921 if (isset($dom[$trid]['endcolumn'])) {
22922 $dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
22923 } else {
22924 $dom[$trid]['endcolumn'] = $this->current_column;
22925 }
22926 } else {
22927 // account for row-spanned cells
22928 $dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
22929 $dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
22930 $dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
22931 $dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
22932 }
22933 if (isset($dom[$table_el]['rowspans'])) {
22934 // update endy and endpage on rowspanned cells
22935 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
22936 if ($trwsp['rowspan'] > 0) {
22937 if (isset($dom[$trid]['endpage'])) {
22938 if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
22939 $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
22940 } elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
22941 $dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
22942 $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
22943 $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
22944 } else {
22945 $dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
22946 }
22947 }
22948 }
22949 }
22950 }
22951 $this->x += ($cellspacingx / 2);
22952 } else {
22953 // opening tag (or self-closing tag)
22954 if (!isset($opentagpos)) {
22955 if ($this->inxobj) {
22956 // we are inside an XObject template
22957 $opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
22958 } elseif (!$this->InFooter) {
22959 if (isset($this->footerlen[$this->page])) {
22960 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
22961 } else {
22962 $this->footerpos[$this->page] = $this->pagelen[$this->page];
22963 }
22964 $opentagpos = $this->footerpos[$this->page];
22965 }
22966 }
22967 $dom = $this->openHTMLTagHandler($dom, $key, $cell);
22968 }
22969 } else { // closing tag
22970 $prev_numpages = $this->numpages;
22971 $old_bordermrk = $this->bordermrk[$this->page];
22972 $dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
22973 if ($this->bordermrk[$this->page] > $old_bordermrk) {
22974 $startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
22975 }
22976 if ($prev_numpages > $this->numpages) {
22977 $startlinepage = $this->page;
22978 }
22979 }
22980 } elseif (strlen($dom[$key]['value']) > 0) {
22981 // print list-item
22982 if (!$this->empty_string($this->lispacer) AND ($this->lispacer != '^')) {
22983 $this->SetFont($pfontname, $pfontstyle, $pfontsize);
22984 $this->resetLastH();
22985 $minstartliney = $this->y;
22986 $maxbottomliney = ($startliney + ($this->FontSize * $this->cell_height_ratio));
22987 $this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
22988 $this->SetFont($curfontname, $curfontstyle, $curfontsize);
22989 $this->resetLastH();
22990 if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
22991 $pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
22992 $pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
22993 $this->y += ((($pfontsize - $curfontsize) * $this->cell_height_ratio / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
22994 $minstartliney = min($this->y, $minstartliney);
22995 $maxbottomliney = max(($this->y + (($pfontsize * $this->cell_height_ratio) / $this->k)), $maxbottomliney);
22996 }
22997 }
22998 // text
22999 $this->htmlvspace = 0;
23000 if ((!$this->premode) AND $this->isRTLTextDir()) {
23001 // reverse spaces order
23002 $lsp = ''; // left spaces
23003 $rsp = ''; // right spaces
23004 if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
23005 $lsp = $matches[1];
23006 }
23007 if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
23008 $rsp = $matches[1];
23009 }
23010 $dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
23011 }
23012 if ($newline) {
23013 if (!$this->premode) {
23014 $prelen = strlen($dom[$key]['value']);
23015 if ($this->isRTLTextDir()) {
23016 // right trim except non-breaking space
23017 $dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
23018 } else {
23019 // left trim except non-breaking space
23020 $dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
23021 }
23022 $postlen = strlen($dom[$key]['value']);
23023 if (($postlen == 0) AND ($prelen > 0)) {
23024 $dom[$key]['trimmed_space'] = true;
23025 }
23026 }
23027 $newline = false;
23028 $firstblock = true;
23029 } else {
23030 $firstblock = false;
23031 // replace empty multiple spaces string with a single space
23032 $dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
23033 }
23034 $strrest = '';
23035 if ($this->rtl) {
23036 $this->x -= $this->textindent;
23037 } else {
23038 $this->x += $this->textindent;
23039 }
23040 if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
23041 $strlinelen = $this->GetStringWidth($dom[$key]['value']);
23042 if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
23043 // HTML <a> Link
23044 $hrefcolor = '';
23045 if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
23046 $hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
23047 }
23048 $hrefstyle = -1;
23049 if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
23050 $hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
23051 }
23052 $strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
23053 } else {
23054 $wadj = 0; // space to leave for block continuity
23055 if ($this->rtl) {
23056 $cwa = $this->x - $this->lMargin;
23057 } else {
23058 $cwa = $this->w - $this->rMargin - $this->x;
23059 }
23060 if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
23061 // check the next text blocks for continuity
23062 $nkey = ($key + 1);
23063 $write_block = true;
23064 $same_textdir = true;
23065 $tmp_fontname = $this->FontFamily;
23066 $tmp_fontstyle = $this->FontStyle;
23067 $tmp_fontsize = $this->FontSizePt;
23068 while ($write_block AND isset($dom[$nkey])) {
23069 if ($dom[$nkey]['tag']) {
23070 if ($dom[$nkey]['block']) {
23071 // end of block
23072 $write_block = false;
23073 }
23074 $tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
23075 $tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
23076 $tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
23077 $same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
23078 } else {
23079 $nextstr = preg_split('/'.$this->re_space['p'].'+/'.$this->re_space['m'], $dom[$nkey]['value']);
23080 if (isset($nextstr[0]) AND $same_textdir) {
23081 $wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
23082 }
23083 if (isset($nextstr[1])) {
23084 $write_block = false;
23085 }
23086 }
23087 ++$nkey;
23088 }
23089 }
23090 if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
23091 $wadj = 0;
23092 $nextstr = preg_split('/'.$this->re_space['p'].'/'.$this->re_space['m'], $dom[$key]['value']);
23093 $numblks = count($nextstr);
23094 if ($numblks > 1) {
23095 // try to split on blank spaces
23096 $wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
23097 }
23098 }
23099 // check for reversed text direction
23100 if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
23101 // LTR text on RTL direction or RTL text on LTR direction
23102 $reverse_dir = true;
23103 $this->rtl = !$this->rtl;
23104 $revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
23105 if ($this->rtl) {
23106 $this->x += $revshift;
23107 } else {
23108 $this->x -= $revshift;
23109 }
23110 $xws = $this->x;
23111 }
23112 // ****** write only until the end of the line and get the rest ******
23113 $strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
23114 // restore default direction
23115 if ($reverse_dir AND ($wadj == 0)) {
23116 $this->x = $xws;
23117 $this->rtl = !$this->rtl;
23118 $reverse_dir = false;
23119 }
23120 }
23121 }
23122 $this->textindent = 0;
23123 if (strlen($strrest) > 0) {
23124 // store the remaining string on the previous $key position
23125 $this->newline = true;
23126 if ($strrest == $dom[$key]['value']) {
23127 // used to avoid infinite loop
23128 ++$loop;
23129 } else {
23130 $loop = 0;
23131 }
23132 $dom[$key]['value'] = $strrest;
23133 if ($cell) {
23134 if ($this->rtl) {
23135 $this->x -= $this->cell_padding['R'];
23136 } else {
23137 $this->x += $this->cell_padding['L'];
23138 }
23139 }
23140 if ($loop < 3) {
23141 --$key;
23142 }
23143 } else {
23144 $loop = 0;
23145 }
23146 }
23147 ++$key;
23148 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')) {
23149 // check if we are on a new page or on a new column
23150 if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
23151 // we are on a new page or on a new column and the total object height is less than the available vertical space.
23152 // restore previous object
23153 $this->rollbackTransaction(true);
23154 // restore previous values
23155 foreach ($this_method_vars as $vkey => $vval) {
23156 $$vkey = $vval;
23157 }
23158 // add a page (or trig AcceptPageBreak() for multicolumn mode)
23159 $pre_y = $this->y;
23160 if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
23161 $startliney = $this->y;
23162 }
23163 $undo = true; // avoid infinite loop
23164 } else {
23165 $undo = false;
23166 }
23167 }
23168 } // end for each $key
23169 // align the last line
23170 if (isset($startlinex)) {
23171 $yshift = ($minstartliney - $startliney);
23172 if (($yshift > 0) OR ($this->page > $startlinepage)) {
23173 $yshift = 0;
23174 }
23175 $t_x = 0;
23176 // the last line must be shifted to be aligned as requested
23177 $linew = abs($this->endlinex - $startlinex);
23178 if ($this->inxobj) {
23179 // we are inside an XObject template
23180 $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
23181 if (isset($opentagpos)) {
23182 $midpos = $opentagpos;
23183 } else {
23184 $midpos = 0;
23185 }
23186 if ($midpos > 0) {
23187 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
23188 $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
23189 } else {
23190 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
23191 $pend = '';
23192 }
23193 } else {
23194 $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
23195 if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
23196 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
23197 $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
23198 } elseif (isset($opentagpos)) {
23199 $midpos = $opentagpos;
23200 } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
23201 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
23202 $midpos = $this->footerpos[$startlinepage];
23203 } else {
23204 $midpos = 0;
23205 }
23206 if ($midpos > 0) {
23207 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
23208 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
23209 } else {
23210 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
23211 $pend = '';
23212 }
23213 }
23214 if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
23215 // calculate shifting amount
23216 $tw = $w;
23217 if ($this->lMargin != $prevlMargin) {
23218 $tw += ($prevlMargin - $this->lMargin);
23219 }
23220 if ($this->rMargin != $prevrMargin) {
23221 $tw += ($prevrMargin - $this->rMargin);
23222 }
23223 $one_space_width = $this->GetStringWidth(chr(32));
23224 $no = 0; // number of spaces on a line contained on a single block
23225 if ($this->isRTLTextDir()) { // RTL
23226 // remove left space if exist
23227 $pos1 = $this->revstrpos($pmid, '[(');
23228 if ($pos1 > 0) {
23229 $pos1 = intval($pos1);
23230 if ($this->isUnicodeFont()) {
23231 $pos2 = intval($this->revstrpos($pmid, '[('.chr(0).chr(32)));
23232 $spacelen = 2;
23233 } else {
23234 $pos2 = intval($this->revstrpos($pmid, '[('.chr(32)));
23235 $spacelen = 1;
23236 }
23237 if ($pos1 == $pos2) {
23238 $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
23239 if (substr($pmid, $pos1, 4) == '[()]') {
23240 $linew -= $one_space_width;
23241 } elseif ($pos1 == strpos($pmid, '[(')) {
23242 $no = 1;
23243 }
23244 }
23245 }
23246 } else { // LTR
23247 // remove right space if exist
23248 $pos1 = $this->revstrpos($pmid, ')]');
23249 if ($pos1 > 0) {
23250 $pos1 = intval($pos1);
23251 if ($this->isUnicodeFont()) {
23252 $pos2 = intval($this->revstrpos($pmid, chr(0).chr(32).')]')) + 2;
23253 $spacelen = 2;
23254 } else {
23255 $pos2 = intval($this->revstrpos($pmid, chr(32).')]')) + 1;
23256 $spacelen = 1;
23257 }
23258 if ($pos1 == $pos2) {
23259 $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
23260 $linew -= $one_space_width;
23261 }
23262 }
23263 }
23264 $mdiff = ($tw - $linew);
23265 if ($plalign == 'C') {
23266 if ($this->rtl) {
23267 $t_x = -($mdiff / 2);
23268 } else {
23269 $t_x = ($mdiff / 2);
23270 }
23271 } elseif ($plalign == 'R') {
23272 // right alignment on LTR document
23273 $t_x = $mdiff;
23274 } elseif ($plalign == 'L') {
23275 // left alignment on RTL document
23276 $t_x = -$mdiff;
23277 }
23278 } // end if startlinex
23279 if (($t_x != 0) OR ($yshift < 0)) {
23280 // shift the line
23281 $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
23282 $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
23283 $endlinepos = strlen($pstart);
23284 if ($this->inxobj) {
23285 // we are inside an XObject template
23286 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
23287 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
23288 if ($pak >= $pask) {
23289 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
23290 $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
23291 }
23292 }
23293 } else {
23294 $this->setPageBuffer($startlinepage, $pstart.$pend);
23295 // shift the annotations and links
23296 if (isset($this->PageAnnots[$this->page])) {
23297 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
23298 if ($pak >= $pask) {
23299 $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
23300 $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
23301 }
23302 }
23303 }
23304 }
23305 $this->y -= $yshift;
23306 $yshift = 0;
23307 }
23308 }
23309 // restore previous values
23310 $this->setGraphicVars($gvars);
23311 if ($this->num_columns > 1) {
23312 $this->selectColumn();
23313 } elseif ($this->page > $prevPage) {
23314 $this->lMargin = $this->pagedim[$this->page]['olm'];
23315 $this->rMargin = $this->pagedim[$this->page]['orm'];
23316 }
23317 // restore previous list state
23318 $this->cell_height_ratio = $prev_cell_height_ratio;
23319 $this->listnum = $prev_listnum;
23320 $this->listordered = $prev_listordered;
23321 $this->listcount = $prev_listcount;
23322 $this->lispacer = $prev_lispacer;
23323 if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
23324 $this->Ln($this->lasth);
23325 if ($this->y < $maxbottomliney) {
23326 $this->y = $maxbottomliney;
23327 }
23328 }
23329 unset($dom);
23330 }
23331
23332 /**
23333 * Process opening tags.
23334 * @param $dom (array) html dom array
23335 * @param $key (int) current element id
23336 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
23337 * @return $dom array
23338 * @protected
23339 */
23340 protected function openHTMLTagHandler($dom, $key, $cell) {
23341 $tag = $dom[$key];
23342 $parent = $dom[($dom[$key]['parent'])];
23343 $firsttag = ($key == 1);
23344 // check for text direction attribute
23345 if (isset($tag['dir'])) {
23346 $this->setTempRTL($tag['dir']);
23347 } else {
23348 $this->tmprtl = false;
23349 }
23350 if ($tag['block']) {
23351 $hbz = 0; // distance from y to line bottom
23352 $hb = 0; // vertical space between block tags
23353 // calculate vertical space for block tags
23354 if (isset($this->tagvspaces[$tag['value']][0]['h']) AND ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
23355 $cur_h = $this->tagvspaces[$tag['value']][0]['h'];
23356 } elseif (isset($tag['fontsize'])) {
23357 $cur_h = ($tag['fontsize'] / $this->k) * $this->cell_height_ratio;
23358 } else {
23359 $cur_h = $this->FontSize * $this->cell_height_ratio;
23360 }
23361 if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
23362 $n = $this->tagvspaces[$tag['value']][0]['n'];
23363 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
23364 $n = 0.6;
23365 } else {
23366 $n = 1;
23367 }
23368 if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br')))) {
23369 $hb = 0;
23370 } else {
23371 $hb = ($n * $cur_h);
23372 }
23373 if (($this->htmlvspace <= 0) AND ($n > 0)) {
23374 if (isset($parent['fontsize'])) {
23375 $hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
23376 } else {
23377 $hbz = $this->FontSize * $this->cell_height_ratio;
23378 }
23379 }
23380 }
23381 // Opening tag
23382 switch($tag['value']) {
23383 case 'table': {
23384 $cp = 0;
23385 $cs = 0;
23386 $dom[$key]['rowspans'] = array();
23387 if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
23388 // set table header
23389 if (!$this->empty_string($dom[$key]['thead'])) {
23390 // set table header
23391 $this->thead = $dom[$key]['thead'];
23392 if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
23393 $this->theadMargins = array();
23394 $this->theadMargins['cell_padding'] = $this->cell_padding;
23395 $this->theadMargins['lmargin'] = $this->lMargin;
23396 $this->theadMargins['rmargin'] = $this->rMargin;
23397 $this->theadMargins['page'] = $this->page;
23398 $this->theadMargins['cell'] = $cell;
23399 }
23400 }
23401 }
23402 // store current margins and page
23403 $dom[$key]['old_cell_padding'] = $this->cell_padding;
23404 if (isset($tag['attribute']['cellpadding'])) {
23405 $pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
23406 $this->SetCellPadding($pad);
23407 } elseif (isset($tag['padding'])) {
23408 $this->cell_padding = $tag['padding'];
23409 }
23410 if (isset($tag['attribute']['cellspacing'])) {
23411 $cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
23412 } elseif (isset($tag['border-spacing'])) {
23413 $cs = $tag['border-spacing']['V'];
23414 }
23415 $prev_y = $this->y;
23416 if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
23417 $this->inthead = true;
23418 // add a page (or trig AcceptPageBreak() for multicolumn mode)
23419 $this->checkPageBreak($this->PageBreakTrigger + 1);
23420 }
23421 break;
23422 }
23423 case 'tr': {
23424 // array of columns positions
23425 $dom[$key]['cellpos'] = array();
23426 break;
23427 }
23428 case 'hr': {
23429 if ((isset($tag['height'])) AND ($tag['height'] != '')) {
23430 $hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
23431 } else {
23432 $hrHeight = $this->GetLineWidth();
23433 }
23434 $this->addHTMLVertSpace($hbz, ($hrHeight / 2), $cell, $firsttag);
23435 $x = $this->GetX();
23436 $y = $this->GetY();
23437 $wtmp = $this->w - $this->lMargin - $this->rMargin;
23438 if ($cell) {
23439 $wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
23440 }
23441 if ((isset($tag['width'])) AND ($tag['width'] != '')) {
23442 $hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
23443 } else {
23444 $hrWidth = $wtmp;
23445 }
23446 $prevlinewidth = $this->GetLineWidth();
23447 $this->SetLineWidth($hrHeight);
23448 $this->Line($x, $y, $x + $hrWidth, $y);
23449 $this->SetLineWidth($prevlinewidth);
23450 $this->addHTMLVertSpace(($hrHeight / 2), 0, $cell, !isset($dom[($key + 1)]));
23451 break;
23452 }
23453 case 'a': {
23454 if (array_key_exists('href', $tag['attribute'])) {
23455 $this->HREF['url'] = $tag['attribute']['href'];
23456 }
23457 break;
23458 }
23459 case 'img': {
23460 if (isset($tag['attribute']['src'])) {
23461 if ($tag['attribute']['src']{0} === '@') {
23462 // data stream
23463 $tag['attribute']['src'] = '@'.base64_decode(substr($tag['attribute']['src'], 1));
23464 $type = '';
23465 } else {
23466 // check for images without protocol
23467 if (preg_match('%^/{2}%', $tag['attribute']['src'])) {
23468 $tag['attribute']['src'] = 'http:'.$tag['attribute']['src'];
23469 }
23470 // replace relative path with real server path
23471 if (($tag['attribute']['src'][0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
23472 $findroot = strpos($tag['attribute']['src'], $_SERVER['DOCUMENT_ROOT']);
23473 if (($findroot === false) OR ($findroot > 1)) {
23474 if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
23475 $tag['attribute']['src'] = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$tag['attribute']['src'];
23476 } else {
23477 $tag['attribute']['src'] = $_SERVER['DOCUMENT_ROOT'].$tag['attribute']['src'];
23478 }
23479 }
23480 }
23481 $tag['attribute']['src'] = htmlspecialchars_decode(urldecode($tag['attribute']['src']));
23482 $type = $this->getImageFileType($tag['attribute']['src']);
23483 $testscrtype = @parse_url($tag['attribute']['src']);
23484 if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
23485 // convert URL to server path
23486 $tag['attribute']['src'] = str_replace(K_PATH_URL, K_PATH_MAIN, $tag['attribute']['src']);
23487 }
23488 }
23489 if (!isset($tag['width'])) {
23490 $tag['width'] = 0;
23491 }
23492 if (!isset($tag['height'])) {
23493 $tag['height'] = 0;
23494 }
23495 //if (!isset($tag['attribute']['align'])) {
23496 // the only alignment supported is "bottom"
23497 // further development is required for other modes.
23498 $tag['attribute']['align'] = 'bottom';
23499 //}
23500 switch($tag['attribute']['align']) {
23501 case 'top': {
23502 $align = 'T';
23503 break;
23504 }
23505 case 'middle': {
23506 $align = 'M';
23507 break;
23508 }
23509 case 'bottom': {
23510 $align = 'B';
23511 break;
23512 }
23513 default: {
23514 $align = 'B';
23515 break;
23516 }
23517 }
23518 $prevy = $this->y;
23519 $xpos = $this->x;
23520 $imglink = '';
23521 if (isset($this->HREF['url']) AND !$this->empty_string($this->HREF['url'])) {
23522 $imglink = $this->HREF['url'];
23523 if ($imglink{0} == '#') {
23524 // convert url to internal link
23525 $lnkdata = explode(',', $imglink);
23526 if (isset($lnkdata[0])) {
23527 $page = intval(substr($lnkdata[0], 1));
23528 if (empty($page) OR ($page <= 0)) {
23529 $page = $this->page;
23530 }
23531 if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
23532 $lnky = floatval($lnkdata[1]);
23533 } else {
23534 $lnky = 0;
23535 }
23536 $imglink = $this->AddLink();
23537 $this->SetLink($imglink, $lnky, $page);
23538 }
23539 }
23540 }
23541 $border = 0;
23542 if (isset($tag['border']) AND !empty($tag['border'])) {
23543 // currently only support 1 (frame) or a combination of 'LTRB'
23544 $border = $tag['border'];
23545 }
23546 $iw = '';
23547 if (isset($tag['width'])) {
23548 $iw = $this->getHTMLUnitToUnits($tag['width'], 1, 'px', false);
23549 }
23550 $ih = '';
23551 if (isset($tag['height'])) {
23552 $ih = $this->getHTMLUnitToUnits($tag['height'], 1, 'px', false);
23553 }
23554 if (($type == 'eps') OR ($type == 'ai')) {
23555 $this->ImageEps($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
23556 } elseif ($type == 'svg') {
23557 $this->ImageSVG($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
23558 } else {
23559 $this->Image($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
23560 }
23561 switch($align) {
23562 case 'T': {
23563 $this->y = $prevy;
23564 break;
23565 }
23566 case 'M': {
23567 $this->y = (($this->img_rb_y + $prevy - ($tag['fontsize'] / $this->k)) / 2) ;
23568 break;
23569 }
23570 case 'B': {
23571 $this->y = $this->img_rb_y - ($tag['fontsize'] / $this->k);
23572 break;
23573 }
23574 }
23575 }
23576 break;
23577 }
23578 case 'dl': {
23579 ++$this->listnum;
23580 if ($this->listnum == 1) {
23581 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
23582 } else {
23583 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
23584 }
23585 break;
23586 }
23587 case 'dt': {
23588 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
23589 break;
23590 }
23591 case 'dd': {
23592 if ($this->rtl) {
23593 $this->rMargin += $this->listindent;
23594 } else {
23595 $this->lMargin += $this->listindent;
23596 }
23597 ++$this->listindentlevel;
23598 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
23599 break;
23600 }
23601 case 'ul':
23602 case 'ol': {
23603 ++$this->listnum;
23604 if ($tag['value'] == 'ol') {
23605 $this->listordered[$this->listnum] = true;
23606 } else {
23607 $this->listordered[$this->listnum] = false;
23608 }
23609 if (isset($tag['attribute']['start'])) {
23610 $this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
23611 } else {
23612 $this->listcount[$this->listnum] = 0;
23613 }
23614 if ($this->rtl) {
23615 $this->rMargin += $this->listindent;
23616 $this->x -= $this->listindent;
23617 } else {
23618 $this->lMargin += $this->listindent;
23619 $this->x += $this->listindent;
23620 }
23621 ++$this->listindentlevel;
23622 if ($this->listnum == 1) {
23623 if ($key > 1) {
23624 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
23625 }
23626 } else {
23627 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
23628 }
23629 break;
23630 }
23631 case 'li': {
23632 if ($key > 2) {
23633 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
23634 }
23635 if ($this->listordered[$this->listnum]) {
23636 // ordered item
23637 if (isset($parent['attribute']['type']) AND !$this->empty_string($parent['attribute']['type'])) {
23638 $this->lispacer = $parent['attribute']['type'];
23639 } elseif (isset($parent['listtype']) AND !$this->empty_string($parent['listtype'])) {
23640 $this->lispacer = $parent['listtype'];
23641 } elseif (isset($this->lisymbol) AND !$this->empty_string($this->lisymbol)) {
23642 $this->lispacer = $this->lisymbol;
23643 } else {
23644 $this->lispacer = '#';
23645 }
23646 ++$this->listcount[$this->listnum];
23647 if (isset($tag['attribute']['value'])) {
23648 $this->listcount[$this->listnum] = intval($tag['attribute']['value']);
23649 }
23650 } else {
23651 // unordered item
23652 if (isset($parent['attribute']['type']) AND !$this->empty_string($parent['attribute']['type'])) {
23653 $this->lispacer = $parent['attribute']['type'];
23654 } elseif (isset($parent['listtype']) AND !$this->empty_string($parent['listtype'])) {
23655 $this->lispacer = $parent['listtype'];
23656 } elseif (isset($this->lisymbol) AND !$this->empty_string($this->lisymbol)) {
23657 $this->lispacer = $this->lisymbol;
23658 } else {
23659 $this->lispacer = '!';
23660 }
23661 }
23662 break;
23663 }
23664 case 'blockquote': {
23665 if ($this->rtl) {
23666 $this->rMargin += $this->listindent;
23667 } else {
23668 $this->lMargin += $this->listindent;
23669 }
23670 ++$this->listindentlevel;
23671 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
23672 break;
23673 }
23674 case 'br': {
23675 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
23676 break;
23677 }
23678 case 'div': {
23679 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
23680 break;
23681 }
23682 case 'p': {
23683 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
23684 break;
23685 }
23686 case 'pre': {
23687 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
23688 $this->premode = true;
23689 break;
23690 }
23691 case 'sup': {
23692 $this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
23693 break;
23694 }
23695 case 'sub': {
23696 $this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
23697 break;
23698 }
23699 case 'h1':
23700 case 'h2':
23701 case 'h3':
23702 case 'h4':
23703 case 'h5':
23704 case 'h6': {
23705 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
23706 break;
23707 }
23708 // Form fields (since 4.8.000 - 2009-09-07)
23709 case 'form': {
23710 if (isset($tag['attribute']['action'])) {
23711 $this->form_action = $tag['attribute']['action'];
23712 } else {
23713 $this->form_action = K_PATH_URL.$_SERVER['SCRIPT_NAME'];
23714 }
23715 if (isset($tag['attribute']['enctype'])) {
23716 $this->form_enctype = $tag['attribute']['enctype'];
23717 } else {
23718 $this->form_enctype = 'application/x-www-form-urlencoded';
23719 }
23720 if (isset($tag['attribute']['method'])) {
23721 $this->form_mode = $tag['attribute']['method'];
23722 } else {
23723 $this->form_mode = 'post';
23724 }
23725 break;
23726 }
23727 case 'input': {
23728 if (isset($tag['attribute']['name']) AND !$this->empty_string($tag['attribute']['name'])) {
23729 $name = $tag['attribute']['name'];
23730 } else {
23731 break;
23732 }
23733 $prop = array();
23734 $opt = array();
23735 if (isset($tag['attribute']['readonly']) AND !$this->empty_string($tag['attribute']['readonly'])) {
23736 $prop['readonly'] = true;
23737 }
23738 if (isset($tag['attribute']['value']) AND !$this->empty_string($tag['attribute']['value'])) {
23739 $value = $tag['attribute']['value'];
23740 }
23741 if (isset($tag['attribute']['maxlength']) AND !$this->empty_string($tag['attribute']['maxlength'])) {
23742 $opt['maxlen'] = intval($tag['attribute']['value']);
23743 }
23744 $h = $this->FontSize * $this->cell_height_ratio;
23745 if (isset($tag['attribute']['size']) AND !$this->empty_string($tag['attribute']['size'])) {
23746 $w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
23747 } else {
23748 $w = $h;
23749 }
23750 if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
23751 $checked = true;
23752 } else {
23753 $checked = false;
23754 }
23755 if (isset($tag['align'])) {
23756 switch ($tag['align']) {
23757 case 'C': {
23758 $opt['q'] = 1;
23759 break;
23760 }
23761 case 'R': {
23762 $opt['q'] = 2;
23763 break;
23764 }
23765 case 'L':
23766 default: {
23767 break;
23768 }
23769 }
23770 }
23771 switch ($tag['attribute']['type']) {
23772 case 'text': {
23773 if (isset($value)) {
23774 $opt['v'] = $value;
23775 }
23776 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
23777 break;
23778 }
23779 case 'password': {
23780 if (isset($value)) {
23781 $opt['v'] = $value;
23782 }
23783 $prop['password'] = 'true';
23784 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
23785 break;
23786 }
23787 case 'checkbox': {
23788 $this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
23789 break;
23790 }
23791 case 'radio': {
23792 $this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
23793 break;
23794 }
23795 case 'submit': {
23796 $w = $this->GetStringWidth($value) * 1.5;
23797 $h *= 1.6;
23798 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
23799 $action = array();
23800 $action['S'] = 'SubmitForm';
23801 $action['F'] = $this->form_action;
23802 if ($this->form_enctype != 'FDF') {
23803 $action['Flags'] = array('ExportFormat');
23804 }
23805 if ($this->form_mode == 'get') {
23806 $action['Flags'] = array('GetMethod');
23807 }
23808 $this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
23809 break;
23810 }
23811 case 'reset': {
23812 $w = $this->GetStringWidth($value) * 1.5;
23813 $h *= 1.6;
23814 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
23815 $this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
23816 break;
23817 }
23818 case 'file': {
23819 $prop['fileSelect'] = 'true';
23820 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
23821 if (!isset($value)) {
23822 $value = '*';
23823 }
23824 $w = $this->GetStringWidth($value) * 2;
23825 $h *= 1.2;
23826 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
23827 $jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
23828 $this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
23829 break;
23830 }
23831 case 'hidden': {
23832 if (isset($value)) {
23833 $opt['v'] = $value;
23834 }
23835 $opt['f'] = array('invisible', 'hidden');
23836 $this->TextField($name, 0, 0, $prop, $opt, '', '', false);
23837 break;
23838 }
23839 case 'image': {
23840 // THIS TYPE MUST BE FIXED
23841 if (isset($tag['attribute']['src']) AND !$this->empty_string($tag['attribute']['src'])) {
23842 $img = $tag['attribute']['src'];
23843 } else {
23844 break;
23845 }
23846 $value = 'img';
23847 //$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
23848 if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
23849 $jsaction = $tag['attribute']['onclick'];
23850 } else {
23851 $jsaction = '';
23852 }
23853 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
23854 break;
23855 }
23856 case 'button': {
23857 $w = $this->GetStringWidth($value) * 1.5;
23858 $h *= 1.6;
23859 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
23860 if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
23861 $jsaction = $tag['attribute']['onclick'];
23862 } else {
23863 $jsaction = '';
23864 }
23865 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
23866 break;
23867 }
23868 }
23869 break;
23870 }
23871 case 'textarea': {
23872 $prop = array();
23873 $opt = array();
23874 if (isset($tag['attribute']['readonly']) AND !$this->empty_string($tag['attribute']['readonly'])) {
23875 $prop['readonly'] = true;
23876 }
23877 if (isset($tag['attribute']['name']) AND !$this->empty_string($tag['attribute']['name'])) {
23878 $name = $tag['attribute']['name'];
23879 } else {
23880 break;
23881 }
23882 if (isset($tag['attribute']['value']) AND !$this->empty_string($tag['attribute']['value'])) {
23883 $opt['v'] = $tag['attribute']['value'];
23884 }
23885 if (isset($tag['attribute']['cols']) AND !$this->empty_string($tag['attribute']['cols'])) {
23886 $w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
23887 } else {
23888 $w = 40;
23889 }
23890 if (isset($tag['attribute']['rows']) AND !$this->empty_string($tag['attribute']['rows'])) {
23891 $h = intval($tag['attribute']['rows']) * $this->FontSize * $this->cell_height_ratio;
23892 } else {
23893 $h = 10;
23894 }
23895 $prop['multiline'] = 'true';
23896 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
23897 break;
23898 }
23899 case 'select': {
23900 $h = $this->FontSize * $this->cell_height_ratio;
23901 if (isset($tag['attribute']['size']) AND !$this->empty_string($tag['attribute']['size'])) {
23902 $h *= ($tag['attribute']['size'] + 1);
23903 }
23904 $prop = array();
23905 $opt = array();
23906 if (isset($tag['attribute']['name']) AND !$this->empty_string($tag['attribute']['name'])) {
23907 $name = $tag['attribute']['name'];
23908 } else {
23909 break;
23910 }
23911 $w = 0;
23912 if (isset($tag['attribute']['opt']) AND !$this->empty_string($tag['attribute']['opt'])) {
23913 $options = explode('#!NwL!#', $tag['attribute']['opt']);
23914 $values = array();
23915 foreach ($options as $val) {
23916 if (strpos($val, '#!TaB!#') !== false) {
23917 $opts = explode('#!TaB!#', $val);
23918 $values[] = $opts;
23919 $w = max($w, $this->GetStringWidth($opts[1]));
23920 } else {
23921 $values[] = $val;
23922 $w = max($w, $this->GetStringWidth($val));
23923 }
23924 }
23925 } else {
23926 break;
23927 }
23928 $w *= 2;
23929 if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
23930 $prop['multipleSelection'] = 'true';
23931 $this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
23932 } else {
23933 $this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
23934 }
23935 break;
23936 }
23937 case 'tcpdf': {
23938 if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
23939 // Special tag used to call TCPDF methods
23940 if (isset($tag['attribute']['method'])) {
23941 $tcpdf_method = $tag['attribute']['method'];
23942 if (method_exists($this, $tcpdf_method)) {
23943 if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
23944 $params = unserialize(urldecode($tag['attribute']['params']));
23945 call_user_func_array(array($this, $tcpdf_method), $params);
23946 } else {
23947 $this->$tcpdf_method();
23948 }
23949 $this->newline = true;
23950 }
23951 }
23952 }
23953 break;
23954 }
23955 default: {
23956 break;
23957 }
23958 }
23959 // define tags that support borders and background colors
23960 $bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
23961 if (in_array($tag['value'], $bordertags)) {
23962 // set border
23963 $dom[$key]['borderposition'] = $this->getBorderStartPosition();
23964 }
23965 if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
23966 $pba = $dom[$key]['attribute']['pagebreakafter'];
23967 // check for pagebreak
23968 if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
23969 // add a page (or trig AcceptPageBreak() for multicolumn mode)
23970 $this->checkPageBreak($this->PageBreakTrigger + 1);
23971 }
23972 if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
23973 OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
23974 // add a page (or trig AcceptPageBreak() for multicolumn mode)
23975 $this->checkPageBreak($this->PageBreakTrigger + 1);
23976 }
23977 }
23978 return $dom;
23979 }
23980
23981 /**
23982 * Process closing tags.
23983 * @param $dom (array) html dom array
23984 * @param $key (int) current element id
23985 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
23986 * @param $maxbottomliney (int) maximum y value of current line
23987 * @return $dom array
23988 * @protected
23989 */
23990 protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
23991 $tag = $dom[$key];
23992 $parent = $dom[($dom[$key]['parent'])];
23993 $lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
23994 $in_table_head = false;
23995 // maximum x position (used to draw borders)
23996 if ($this->rtl) {
23997 $xmax = $this->w;
23998 } else {
23999 $xmax = 0;
24000 }
24001 if ($tag['block']) {
24002 $hbz = 0; // distance from y to line bottom
24003 $hb = 0; // vertical space between block tags
24004 // calculate vertical space for block tags
24005 if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
24006 $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
24007 } elseif (isset($parent['fontsize'])) {
24008 $pre_h = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
24009 } else {
24010 $pre_h = $this->FontSize * $this->cell_height_ratio;
24011 }
24012 if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
24013 $n = $this->tagvspaces[$tag['value']][1]['n'];
24014 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
24015 $n = 0.6;
24016 } else {
24017 $n = 1;
24018 }
24019 if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
24020 $hb = 0;
24021 } else {
24022 $hb = ($n * $pre_h);
24023 }
24024 if ($maxbottomliney > $this->PageBreakTrigger) {
24025 $hbz = ($this->FontSize * $this->cell_height_ratio);
24026 } elseif ($this->y < $maxbottomliney) {
24027 $hbz = ($maxbottomliney - $this->y);
24028 }
24029 }
24030 // Closing tag
24031 switch($tag['value']) {
24032 case 'tr': {
24033 $table_el = $dom[($dom[$key]['parent'])]['parent'];
24034 if (!isset($parent['endy'])) {
24035 $dom[($dom[$key]['parent'])]['endy'] = $this->y;
24036 $parent['endy'] = $this->y;
24037 }
24038 if (!isset($parent['endpage'])) {
24039 $dom[($dom[$key]['parent'])]['endpage'] = $this->page;
24040 $parent['endpage'] = $this->page;
24041 }
24042 if (!isset($parent['endcolumn'])) {
24043 $dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
24044 $parent['endcolumn'] = $this->current_column;
24045 }
24046 // update row-spanned cells
24047 if (isset($dom[$table_el]['rowspans'])) {
24048 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
24049 $dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
24050 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
24051 if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
24052 $dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
24053 } elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
24054 $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
24055 $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
24056 $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
24057 }
24058 }
24059 }
24060 // report new endy and endpage to the rowspanned cells
24061 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
24062 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
24063 $dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
24064 $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
24065 $dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
24066 $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
24067 $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
24068 $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
24069 }
24070 }
24071 // update remaining rowspanned cells
24072 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
24073 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
24074 $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
24075 $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
24076 $dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
24077 }
24078 }
24079 }
24080 $this->setPage($dom[($dom[$key]['parent'])]['endpage']);
24081 if ($this->num_columns > 1) {
24082 $this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
24083 }
24084 $this->y = $dom[($dom[$key]['parent'])]['endy'];
24085 if (isset($dom[$table_el]['attribute']['cellspacing'])) {
24086 $this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
24087 } elseif (isset($dom[$table_el]['border-spacing'])) {
24088 $this->y += $dom[$table_el]['border-spacing']['V'];
24089 }
24090 $this->Ln(0, $cell);
24091 if ($this->current_column == $parent['startcolumn']) {
24092 $this->x = $parent['startx'];
24093 }
24094 // account for booklet mode
24095 if ($this->page > $parent['startpage']) {
24096 if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
24097 $this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
24098 } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
24099 $this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
24100 }
24101 }
24102 break;
24103 }
24104 case 'tablehead':
24105 // closing tag used for the thead part
24106 $in_table_head = true;
24107 $this->inthead = false;
24108 case 'table': {
24109 $table_el = $parent;
24110 // set default border
24111 if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
24112 // set default border
24113 $border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
24114 } else {
24115 $border = 0;
24116 }
24117 $default_border = $border;
24118 // fix bottom line alignment of last line before page break
24119 foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
24120 // update row-spanned cells
24121 if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
24122 foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
24123 if ($trwsp['trid'] == $trkey) {
24124 $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
24125 }
24126 if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0)) {
24127 $dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
24128 }
24129 }
24130 }
24131 if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
24132 $pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
24133 $dom[$prevtrkey]['endy'] = $pgendy;
24134 // update row-spanned cells
24135 if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
24136 foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
24137 if (($trwsp['trid'] == $trkey) AND ($trwsp['mrowspan'] > 1) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
24138 $dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
24139 $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
24140 }
24141 }
24142 }
24143 }
24144 $prevtrkey = $trkey;
24145 $table_el = $dom[($dom[$key]['parent'])];
24146 }
24147 // for each row
24148 if (count($table_el['trids']) > 0) {
24149 unset($xmax);
24150 }
24151 foreach ($table_el['trids'] as $j => $trkey) {
24152 $parent = $dom[$trkey];
24153 if (!isset($xmax)) {
24154 $xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
24155 }
24156 // for each cell on the row
24157 foreach ($parent['cellpos'] as $k => $cellpos) {
24158 if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
24159 $cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
24160 $cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
24161 $endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
24162 $startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
24163 $endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
24164 $startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
24165 $endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
24166 } else {
24167 $endy = $parent['endy'];
24168 $startpage = $parent['startpage'];
24169 $endpage = $parent['endpage'];
24170 $startcolumn = $parent['startcolumn'];
24171 $endcolumn = $parent['endcolumn'];
24172 }
24173 if ($this->num_columns == 0) {
24174 $this->num_columns = 1;
24175 }
24176 if (isset($cellpos['border'])) {
24177 $border = $cellpos['border'];
24178 }
24179 if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
24180 $this->SetFillColorArray($cellpos['bgcolor']);
24181 $fill = true;
24182 } else {
24183 $fill = false;
24184 }
24185 $x = $cellpos['startx'];
24186 $y = $parent['starty'];
24187 $starty = $y;
24188 $w = abs($cellpos['endx'] - $cellpos['startx']);
24189 // get border modes
24190 $border_start = $this->getBorderMode($border, $position='start');
24191 $border_end = $this->getBorderMode($border, $position='end');
24192 $border_middle = $this->getBorderMode($border, $position='middle');
24193 // design borders around HTML cells.
24194 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
24195 $ccode = '';
24196 $this->setPage($page);
24197 if ($this->num_columns < 2) {
24198 // single-column mode
24199 $this->x = $x;
24200 $this->y = $this->tMargin;
24201 }
24202 // account for margin changes
24203 if ($page > $startpage) {
24204 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
24205 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
24206 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
24207 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
24208 }
24209 }
24210 if ($startpage == $endpage) { // single page
24211 $deltacol = 0;
24212 $deltath = 0;
24213 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
24214 $this->selectColumn($column);
24215 if ($startcolumn == $endcolumn) { // single column
24216 $cborder = $border;
24217 $h = $endy - $parent['starty'];
24218 $this->y = $y;
24219 $this->x = $x;
24220 } elseif ($column == $startcolumn) { // first column
24221 $cborder = $border_start;
24222 $this->y = $starty;
24223 $this->x = $x;
24224 $h = $this->h - $this->y - $this->bMargin;
24225 if ($this->rtl) {
24226 $deltacol = $this->x + $this->rMargin - $this->w;
24227 } else {
24228 $deltacol = $this->x - $this->lMargin;
24229 }
24230 } elseif ($column == $endcolumn) { // end column
24231 $cborder = $border_end;
24232 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
24233 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
24234 }
24235 $this->x += $deltacol;
24236 $h = $endy - $this->y;
24237 } else { // middle column
24238 $cborder = $border_middle;
24239 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
24240 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
24241 }
24242 $this->x += $deltacol;
24243 $h = $this->h - $this->y - $this->bMargin;
24244 }
24245 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
24246 } // end for each column
24247 } elseif ($page == $startpage) { // first page
24248 $deltacol = 0;
24249 $deltath = 0;
24250 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
24251 $this->selectColumn($column);
24252 if ($column == $startcolumn) { // first column
24253 $cborder = $border_start;
24254 $this->y = $starty;
24255 $this->x = $x;
24256 $h = $this->h - $this->y - $this->bMargin;
24257 if ($this->rtl) {
24258 $deltacol = $this->x + $this->rMargin - $this->w;
24259 } else {
24260 $deltacol = $this->x - $this->lMargin;
24261 }
24262 } else { // middle column
24263 $cborder = $border_middle;
24264 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
24265 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
24266 }
24267 $this->x += $deltacol;
24268 $h = $this->h - $this->y - $this->bMargin;
24269 }
24270 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
24271 } // end for each column
24272 } elseif ($page == $endpage) { // last page
24273 $deltacol = 0;
24274 $deltath = 0;
24275 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
24276 $this->selectColumn($column);
24277 if ($column == $endcolumn) { // end column
24278 $cborder = $border_end;
24279 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
24280 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
24281 }
24282 $this->x += $deltacol;
24283 $h = $endy - $this->y;
24284 } else { // middle column
24285 $cborder = $border_middle;
24286 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
24287 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
24288 }
24289 $this->x += $deltacol;
24290 $h = $this->h - $this->y - $this->bMargin;
24291 }
24292 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
24293 } // end for each column
24294 } else { // middle page
24295 $deltacol = 0;
24296 $deltath = 0;
24297 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
24298 $this->selectColumn($column);
24299 $cborder = $border_middle;
24300 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
24301 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
24302 }
24303 $this->x += $deltacol;
24304 $h = $this->h - $this->y - $this->bMargin;
24305 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
24306 } // end for each column
24307 }
24308 if ($cborder OR $fill) {
24309 $offsetlen = strlen($ccode);
24310 // draw border and fill
24311 if ($this->inxobj) {
24312 // we are inside an XObject template
24313 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
24314 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
24315 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
24316 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
24317 } else {
24318 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
24319 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
24320 }
24321 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
24322 $pstart = substr($pagebuff, 0, $pagemark);
24323 $pend = substr($pagebuff, $pagemark);
24324 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
24325 } else {
24326 // draw border and fill
24327 if (end($this->transfmrk[$this->page]) !== false) {
24328 $pagemarkkey = key($this->transfmrk[$this->page]);
24329 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
24330 $this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
24331 } elseif ($this->InFooter) {
24332 $pagemark = $this->footerpos[$this->page];
24333 $this->footerpos[$this->page] += $offsetlen;
24334 } else {
24335 $pagemark = $this->intmrk[$this->page];
24336 $this->intmrk[$this->page] += $offsetlen;
24337 }
24338 $pagebuff = $this->getPageBuffer($this->page);
24339 $pstart = substr($pagebuff, 0, $pagemark);
24340 $pend = substr($pagebuff, $pagemark);
24341 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
24342 }
24343 }
24344 } // end for each page
24345 // restore default border
24346 $border = $default_border;
24347 } // end for each cell on the row
24348 if (isset($table_el['attribute']['cellspacing'])) {
24349 $this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
24350 } elseif (isset($table_el['border-spacing'])) {
24351 $this->y += $table_el['border-spacing']['V'];
24352 }
24353 $this->Ln(0, $cell);
24354 $this->x = $parent['startx'];
24355 if ($endpage > $startpage) {
24356 if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
24357 $this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
24358 } elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
24359 $this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
24360 }
24361 }
24362 }
24363 if (!$in_table_head) { // we are not inside a thead section
24364 $this->cell_padding = $table_el['old_cell_padding'];
24365 // reset row height
24366 $this->resetLastH();
24367 if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
24368 $plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
24369 if (($plendiff > 0) AND ($plendiff < 60)) {
24370 $pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
24371 if (substr($pagediff, 0, 5) == 'BT /F') {
24372 // the difference is only a font setting
24373 $plendiff = 0;
24374 }
24375 }
24376 if ($plendiff == 0) {
24377 // remove last blank page
24378 $this->deletePage($this->numpages);
24379 }
24380 }
24381 if (isset($this->theadMargins['top'])) {
24382 // restore top margin
24383 $this->tMargin = $this->theadMargins['top'];
24384 }
24385 if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
24386 // reset main table header
24387 $this->thead = '';
24388 $this->theadMargins = array();
24389 $this->pagedim[$this->page]['tm'] = $this->tMargin;
24390 }
24391 }
24392 $parent = $table_el;
24393 break;
24394 }
24395 case 'a': {
24396 $this->HREF = '';
24397 break;
24398 }
24399 case 'sup': {
24400 $this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
24401 break;
24402 }
24403 case 'sub': {
24404 $this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize'])/$this->k));
24405 break;
24406 }
24407 case 'div': {
24408 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
24409 break;
24410 }
24411 case 'blockquote': {
24412 if ($this->rtl) {
24413 $this->rMargin -= $this->listindent;
24414 } else {
24415 $this->lMargin -= $this->listindent;
24416 }
24417 --$this->listindentlevel;
24418 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
24419 break;
24420 }
24421 case 'p': {
24422 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
24423 break;
24424 }
24425 case 'pre': {
24426 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
24427 $this->premode = false;
24428 break;
24429 }
24430 case 'dl': {
24431 --$this->listnum;
24432 if ($this->listnum <= 0) {
24433 $this->listnum = 0;
24434 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
24435 } else {
24436 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
24437 }
24438 $this->resetLastH();
24439 break;
24440 }
24441 case 'dt': {
24442 $this->lispacer = '';
24443 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
24444 break;
24445 }
24446 case 'dd': {
24447 $this->lispacer = '';
24448 if ($this->rtl) {
24449 $this->rMargin -= $this->listindent;
24450 } else {
24451 $this->lMargin -= $this->listindent;
24452 }
24453 --$this->listindentlevel;
24454 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
24455 break;
24456 }
24457 case 'ul':
24458 case 'ol': {
24459 --$this->listnum;
24460 $this->lispacer = '';
24461 if ($this->rtl) {
24462 $this->rMargin -= $this->listindent;
24463 } else {
24464 $this->lMargin -= $this->listindent;
24465 }
24466 --$this->listindentlevel;
24467 if ($this->listnum <= 0) {
24468 $this->listnum = 0;
24469 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
24470 } else {
24471 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
24472 }
24473 $this->resetLastH();
24474 break;
24475 }
24476 case 'li': {
24477 $this->lispacer = '';
24478 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
24479 break;
24480 }
24481 case 'h1':
24482 case 'h2':
24483 case 'h3':
24484 case 'h4':
24485 case 'h5':
24486 case 'h6': {
24487 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
24488 break;
24489 }
24490 // Form fields (since 4.8.000 - 2009-09-07)
24491 case 'form': {
24492 $this->form_action = '';
24493 $this->form_enctype = 'application/x-www-form-urlencoded';
24494 break;
24495 }
24496 default : {
24497 break;
24498 }
24499 }
24500 // draw border and background (if any)
24501 $this->drawHTMLTagBorder($parent, $xmax);
24502 if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
24503 $pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
24504 // check for pagebreak
24505 if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
24506 // add a page (or trig AcceptPageBreak() for multicolumn mode)
24507 $this->checkPageBreak($this->PageBreakTrigger + 1);
24508 }
24509 if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
24510 OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
24511 // add a page (or trig AcceptPageBreak() for multicolumn mode)
24512 $this->checkPageBreak($this->PageBreakTrigger + 1);
24513 }
24514 }
24515 $this->tmprtl = false;
24516 return $dom;
24517 }
24518
24519 /**
24520 * Add vertical spaces if needed.
24521 * @param $hbz (string) Distance between current y and line bottom.
24522 * @param $hb (string) The height of the break.
24523 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
24524 * @param $firsttag (boolean) set to true when the tag is the first.
24525 * @param $lasttag (boolean) set to true when the tag is the last.
24526 * @protected
24527 */
24528 protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
24529 if ($firsttag) {
24530 $this->Ln(0, $cell);
24531 $this->htmlvspace = 0;
24532 return;
24533 }
24534 if ($lasttag) {
24535 $this->Ln($hbz, $cell);
24536 $this->htmlvspace = 0;
24537 return;
24538 }
24539 if ($hb < $this->htmlvspace) {
24540 $hd = 0;
24541 } else {
24542 $hd = $hb - $this->htmlvspace;
24543 $this->htmlvspace = $hb;
24544 }
24545 $this->Ln(($hbz + $hd), $cell);
24546 }
24547
24548 /**
24549 * Return the starting coordinates to draw an html border
24550 * @return array containing top-left border coordinates
24551 * @protected
24552 * @since 5.7.000 (2010-08-03)
24553 */
24554 protected function getBorderStartPosition() {
24555 if ($this->rtl) {
24556 $xmax = $this->lMargin;
24557 } else {
24558 $xmax = $this->w - $this->rMargin;
24559 }
24560 return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
24561 }
24562
24563 /**
24564 * Draw an HTML block border and fill
24565 * @param $tag (array) array of tag properties.
24566 * @param $xmax (int) end X coordinate for border.
24567 * @protected
24568 * @since 5.7.000 (2010-08-03)
24569 */
24570 protected function drawHTMLTagBorder($tag, $xmax) {
24571 if (!isset($tag['borderposition'])) {
24572 // nothing to draw
24573 return;
24574 }
24575 $prev_x = $this->x;
24576 $prev_y = $this->y;
24577 $prev_lasth = $this->lasth;
24578 $border = 0;
24579 $fill = false;
24580 $this->lasth = 0;
24581 if (isset($tag['border']) AND !empty($tag['border'])) {
24582 // get border style
24583 $border = $tag['border'];
24584 if (!$this->empty_string($this->thead) AND (!$this->inthead)) {
24585 // border for table header
24586 $border = $this->getBorderMode($border, $position='middle');
24587 }
24588 }
24589 if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
24590 // get background color
24591 $old_bgcolor = $this->bgcolor;
24592 $this->SetFillColorArray($tag['bgcolor']);
24593 $fill = true;
24594 }
24595 if (!$border AND !$fill) {
24596 // nothing to draw
24597 return;
24598 }
24599 if (isset($tag['attribute']['cellspacing'])) {
24600 $clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
24601 $cellspacing = array('H' => $clsp, 'V' => $clsp);
24602 } elseif (isset($tag['border-spacing'])) {
24603 $cellspacing = $tag['border-spacing'];
24604 } else {
24605 $cellspacing = array('H' => 0, 'V' => 0);
24606 }
24607 if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
24608 // draw the border externally respect the sqare edge.
24609 $border['mode'] = 'ext';
24610 }
24611 if ($this->rtl) {
24612 if ($xmax >= $tag['borderposition']['x']) {
24613 $xmax = $tag['borderposition']['xmax'];
24614 }
24615 $w = ($tag['borderposition']['x'] - $xmax);
24616 } else {
24617 if ($xmax <= $tag['borderposition']['x']) {
24618 $xmax = $tag['borderposition']['xmax'];
24619 }
24620 $w = ($xmax - $tag['borderposition']['x']);
24621 }
24622 if ($w <= 0) {
24623 return;
24624 }
24625 $w += $cellspacing['H'];
24626 $startpage = $tag['borderposition']['page'];
24627 $startcolumn = $tag['borderposition']['column'];
24628 $x = $tag['borderposition']['x'];
24629 $y = $tag['borderposition']['y'];
24630 $endpage = $this->page;
24631 $starty = $tag['borderposition']['y'] - $cellspacing['V'];
24632 $currentY = $this->y;
24633 $this->x = $x;
24634 // get latest column
24635 $endcolumn = $this->current_column;
24636 if ($this->num_columns == 0) {
24637 $this->num_columns = 1;
24638 }
24639 // get border modes
24640 $border_start = $this->getBorderMode($border, $position='start');
24641 $border_end = $this->getBorderMode($border, $position='end');
24642 $border_middle = $this->getBorderMode($border, $position='middle');
24643 // temporary disable page regions
24644 $temp_page_regions = $this->page_regions;
24645 $this->page_regions = array();
24646 // design borders around HTML cells.
24647 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
24648 $ccode = '';
24649 $this->setPage($page);
24650 if ($this->num_columns < 2) {
24651 // single-column mode
24652 $this->x = $x;
24653 $this->y = $this->tMargin;
24654 }
24655 // account for margin changes
24656 if ($page > $startpage) {
24657 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
24658 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
24659 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
24660 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
24661 }
24662 }
24663 if ($startpage == $endpage) {
24664 // single page
24665 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
24666 $this->selectColumn($column);
24667 if ($startcolumn == $endcolumn) { // single column
24668 $cborder = $border;
24669 $h = ($currentY - $y) + $cellspacing['V'];
24670 $this->y = $starty;
24671 } elseif ($column == $startcolumn) { // first column
24672 $cborder = $border_start;
24673 $this->y = $starty;
24674 $h = $this->h - $this->y - $this->bMargin;
24675 } elseif ($column == $endcolumn) { // end column
24676 $cborder = $border_end;
24677 $h = $currentY - $this->y;
24678 } else { // middle column
24679 $cborder = $border_middle;
24680 $h = $this->h - $this->y - $this->bMargin;
24681 }
24682 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
24683 } // end for each column
24684 } elseif ($page == $startpage) { // first page
24685 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
24686 $this->selectColumn($column);
24687 if ($column == $startcolumn) { // first column
24688 $cborder = $border_start;
24689 $this->y = $starty;
24690 $h = $this->h - $this->y - $this->bMargin;
24691 } else { // middle column
24692 $cborder = $border_middle;
24693 $h = $this->h - $this->y - $this->bMargin;
24694 }
24695 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
24696 } // end for each column
24697 } elseif ($page == $endpage) { // last page
24698 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
24699 $this->selectColumn($column);
24700 if ($column == $endcolumn) {
24701 // end column
24702 $cborder = $border_end;
24703 $h = $currentY - $this->y;
24704 } else {
24705 // middle column
24706 $cborder = $border_middle;
24707 $h = $this->h - $this->y - $this->bMargin;
24708 }
24709 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
24710 } // end for each column
24711 } else { // middle page
24712 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
24713 $this->selectColumn($column);
24714 $cborder = $border_middle;
24715 $h = $this->h - $this->y - $this->bMargin;
24716 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
24717 } // end for each column
24718 }
24719 if ($cborder OR $fill) {
24720 $offsetlen = strlen($ccode);
24721 // draw border and fill
24722 if ($this->inxobj) {
24723 // we are inside an XObject template
24724 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
24725 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
24726 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
24727 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
24728 } else {
24729 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
24730 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
24731 }
24732 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
24733 $pstart = substr($pagebuff, 0, $pagemark);
24734 $pend = substr($pagebuff, $pagemark);
24735 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
24736 } else {
24737 if (end($this->transfmrk[$this->page]) !== false) {
24738 $pagemarkkey = key($this->transfmrk[$this->page]);
24739 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
24740 $this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
24741 } elseif ($this->InFooter) {
24742 $pagemark = $this->footerpos[$this->page];
24743 $this->footerpos[$this->page] += $offsetlen;
24744 } else {
24745 $pagemark = $this->intmrk[$this->page];
24746 $this->intmrk[$this->page] += $offsetlen;
24747 }
24748 $pagebuff = $this->getPageBuffer($this->page);
24749 $pstart = substr($pagebuff, 0, $this->bordermrk[$this->page]);
24750 $pend = substr($pagebuff, $this->bordermrk[$this->page]);
24751 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
24752 $this->bordermrk[$this->page] += $offsetlen;
24753 $this->cntmrk[$this->page] += $offsetlen;
24754 }
24755 }
24756 } // end for each page
24757 // restore page regions
24758 $this->page_regions = $temp_page_regions;
24759 if (isset($old_bgcolor)) {
24760 // restore background color
24761 $this->SetFillColorArray($old_bgcolor);
24762 }
24763 // restore pointer position
24764 $this->x = $prev_x;
24765 $this->y = $prev_y;
24766 $this->lasth = $prev_lasth;
24767 }
24768
24769 /**
24770 * Set the default bullet to be used as LI bullet symbol
24771 * @param $symbol (string) character or string to be used (legal values are: '' = automatic, '!' = auto bullet, '#' = auto numbering, 'disc', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek', 'img|type|width|height|image.ext')
24772 * @public
24773 * @since 4.0.028 (2008-09-26)
24774 */
24775 public function setLIsymbol($symbol='!') {
24776 // check for custom image symbol
24777 if (substr($symbol, 0, 4) == 'img|') {
24778 $this->lisymbol = $symbol;
24779 return;
24780 }
24781 $symbol = strtolower($symbol);
24782 switch ($symbol) {
24783 case '!' :
24784 case '#' :
24785 case 'disc' :
24786 case 'circle' :
24787 case 'square' :
24788 case '1':
24789 case 'decimal':
24790 case 'decimal-leading-zero':
24791 case 'i':
24792 case 'lower-roman':
24793 case 'I':
24794 case 'upper-roman':
24795 case 'a':
24796 case 'lower-alpha':
24797 case 'lower-latin':
24798 case 'A':
24799 case 'upper-alpha':
24800 case 'upper-latin':
24801 case 'lower-greek': {
24802 $this->lisymbol = $symbol;
24803 break;
24804 }
24805 default : {
24806 $this->lisymbol = '';
24807 }
24808 }
24809 }
24810
24811 /**
24812 * Set the booklet mode for double-sided pages.
24813 * @param $booklet (boolean) true set the booklet mode on, false otherwise.
24814 * @param $inner (float) Inner page margin.
24815 * @param $outer (float) Outer page margin.
24816 * @public
24817 * @since 4.2.000 (2008-10-29)
24818 */
24819 public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
24820 $this->booklet = $booklet;
24821 if ($inner >= 0) {
24822 $this->lMargin = $inner;
24823 }
24824 if ($outer >= 0) {
24825 $this->rMargin = $outer;
24826 }
24827 }
24828
24829 /**
24830 * Swap the left and right margins.
24831 * @param $reverse (boolean) if true swap left and right margins.
24832 * @protected
24833 * @since 4.2.000 (2008-10-29)
24834 */
24835 protected function swapMargins($reverse=true) {
24836 if ($reverse) {
24837 // swap left and right margins
24838 $mtemp = $this->original_lMargin;
24839 $this->original_lMargin = $this->original_rMargin;
24840 $this->original_rMargin = $mtemp;
24841 $deltam = $this->original_lMargin - $this->original_rMargin;
24842 $this->lMargin += $deltam;
24843 $this->rMargin -= $deltam;
24844 }
24845 }
24846
24847 /**
24848 * Set the vertical spaces for HTML tags.
24849 * The array must have the following structure (example):
24850 * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
24851 * The first array level contains the tag names,
24852 * the second level contains 0 for opening tags or 1 for closing tags,
24853 * the third level contains the vertical space unit (h) and the number spaces to add (n).
24854 * If the h parameter is not specified, default values are used.
24855 * @param $tagvs (array) array of tags and relative vertical spaces.
24856 * @public
24857 * @since 4.2.001 (2008-10-30)
24858 */
24859 public function setHtmlVSpace($tagvs) {
24860 $this->tagvspaces = $tagvs;
24861 }
24862
24863 /**
24864 * Set custom width for list indentation.
24865 * @param $width (float) width of the indentation. Use negative value to disable it.
24866 * @public
24867 * @since 4.2.007 (2008-11-12)
24868 */
24869 public function setListIndentWidth($width) {
24870 return $this->customlistindent = floatval($width);
24871 }
24872
24873 /**
24874 * Set the top/bottom cell sides to be open or closed when the cell cross the page.
24875 * @param $isopen (boolean) if true keeps the top/bottom border open for the cell sides that cross the page.
24876 * @public
24877 * @since 4.2.010 (2008-11-14)
24878 */
24879 public function setOpenCell($isopen) {
24880 $this->opencell = $isopen;
24881 }
24882
24883 /**
24884 * Set the color and font style for HTML links.
24885 * @param $color (array) RGB array of colors
24886 * @param $fontstyle (string) additional font styles to add
24887 * @public
24888 * @since 4.4.003 (2008-12-09)
24889 */
24890 public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
24891 $this->htmlLinkColorArray = $color;
24892 $this->htmlLinkFontStyle = $fontstyle;
24893 }
24894
24895 /**
24896 * Convert HTML string containing value and unit of measure to user's units or points.
24897 * @param $htmlval (string) string containing values and unit
24898 * @param $refsize (string) reference value in points
24899 * @param $defaultunit (string) default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
24900 * @param $points (boolean) if true returns points, otherwise returns value in user's units
24901 * @return float value in user's unit or point if $points=true
24902 * @public
24903 * @since 4.4.004 (2008-12-10)
24904 */
24905 public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
24906 $supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
24907 $retval = 0;
24908 $value = 0;
24909 $unit = 'px';
24910 $k = $this->k;
24911 if ($points) {
24912 $k = 1;
24913 }
24914 if (in_array($defaultunit, $supportedunits)) {
24915 $unit = $defaultunit;
24916 }
24917 if (is_numeric($htmlval)) {
24918 $value = floatval($htmlval);
24919 } elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
24920 $value = floatval($mnum[1]);
24921 if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
24922 if (in_array($munit[1], $supportedunits)) {
24923 $unit = $munit[1];
24924 }
24925 }
24926 }
24927 switch ($unit) {
24928 // percentage
24929 case '%': {
24930 $retval = (($value * $refsize) / 100);
24931 break;
24932 }
24933 // relative-size
24934 case 'em': {
24935 $retval = ($value * $refsize);
24936 break;
24937 }
24938 // height of lower case 'x' (about half the font-size)
24939 case 'ex': {
24940 $retval = $value * ($refsize / 2);
24941 break;
24942 }
24943 // absolute-size
24944 case 'in': {
24945 $retval = ($value * $this->dpi) / $k;
24946 break;
24947 }
24948 // centimeters
24949 case 'cm': {
24950 $retval = ($value / 2.54 * $this->dpi) / $k;
24951 break;
24952 }
24953 // millimeters
24954 case 'mm': {
24955 $retval = ($value / 25.4 * $this->dpi) / $k;
24956 break;
24957 }
24958 // one pica is 12 points
24959 case 'pc': {
24960 $retval = ($value * 12) / $k;
24961 break;
24962 }
24963 // points
24964 case 'pt': {
24965 $retval = $value / $k;
24966 break;
24967 }
24968 // pixels
24969 case 'px': {
24970 $retval = $this->pixelsToUnits($value);
24971 break;
24972 }
24973 }
24974 return $retval;
24975 }
24976
24977 /**
24978 * Returns the Roman representation of an integer number
24979 * @param $number (int) number to convert
24980 * @return string roman representation of the specified number
24981 * @since 4.4.004 (2008-12-10)
24982 * @public
24983 */
24984 public function intToRoman($number) {
24985 $roman = '';
24986 while ($number >= 1000) {
24987 $roman .= 'M';
24988 $number -= 1000;
24989 }
24990 while ($number >= 900) {
24991 $roman .= 'CM';
24992 $number -= 900;
24993 }
24994 while ($number >= 500) {
24995 $roman .= 'D';
24996 $number -= 500;
24997 }
24998 while ($number >= 400) {
24999 $roman .= 'CD';
25000 $number -= 400;
25001 }
25002 while ($number >= 100) {
25003 $roman .= 'C';
25004 $number -= 100;
25005 }
25006 while ($number >= 90) {
25007 $roman .= 'XC';
25008 $number -= 90;
25009 }
25010 while ($number >= 50) {
25011 $roman .= 'L';
25012 $number -= 50;
25013 }
25014 while ($number >= 40) {
25015 $roman .= 'XL';
25016 $number -= 40;
25017 }
25018 while ($number >= 10) {
25019 $roman .= 'X';
25020 $number -= 10;
25021 }
25022 while ($number >= 9) {
25023 $roman .= 'IX';
25024 $number -= 9;
25025 }
25026 while ($number >= 5) {
25027 $roman .= 'V';
25028 $number -= 5;
25029 }
25030 while ($number >= 4) {
25031 $roman .= 'IV';
25032 $number -= 4;
25033 }
25034 while ($number >= 1) {
25035 $roman .= 'I';
25036 --$number;
25037 }
25038 return $roman;
25039 }
25040
25041 /**
25042 * Output an HTML list bullet or ordered item symbol
25043 * @param $listdepth (int) list nesting level
25044 * @param $listtype (string) type of list
25045 * @param $size (float) current font size
25046 * @protected
25047 * @since 4.4.004 (2008-12-10)
25048 */
25049 protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
25050 $size /= $this->k;
25051 $fill = '';
25052 $bgcolor = $this->bgcolor;
25053 $color = $this->fgcolor;
25054 $strokecolor = $this->strokecolor;
25055 $width = 0;
25056 $textitem = '';
25057 $tmpx = $this->x;
25058 $lspace = $this->GetStringWidth(' ');
25059 if ($listtype == '^') {
25060 // special symbol used for avoid justification of rect bullet
25061 $this->lispacer = '';
25062 return;
25063 } elseif ($listtype == '!') {
25064 // set default list type for unordered list
25065 $deftypes = array('disc', 'circle', 'square');
25066 $listtype = $deftypes[($listdepth - 1) % 3];
25067 } elseif ($listtype == '#') {
25068 // set default list type for ordered list
25069 $listtype = 'decimal';
25070 } elseif (substr($listtype, 0, 4) == 'img|') {
25071 // custom image type ('img|type|width|height|image.ext')
25072 $img = explode('|', $listtype);
25073 $listtype = 'img';
25074 }
25075 switch ($listtype) {
25076 // unordered types
25077 case 'none': {
25078 break;
25079 }
25080 case 'disc': {
25081 $r = $size / 6;
25082 $lspace += (2 * $r);
25083 if ($this->rtl) {
25084 $this->x += $lspace;
25085 } else {
25086 $this->x -= $lspace;
25087 }
25088 $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
25089 break;
25090 }
25091 case 'circle': {
25092 $r = $size / 6;
25093 $lspace += (2 * $r);
25094 if ($this->rtl) {
25095 $this->x += $lspace;
25096 } else {
25097 $this->x -= $lspace;
25098 }
25099 $prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
25100 $new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
25101 $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
25102 $this->_out($prev_line_style); // restore line settings
25103 break;
25104 }
25105 case 'square': {
25106 $l = $size / 3;
25107 $lspace += $l;
25108 if ($this->rtl) {;
25109 $this->x += $lspace;
25110 } else {
25111 $this->x -= $lspace;
25112 }
25113 $this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
25114 break;
25115 }
25116 case 'img': {
25117 // 1=>type, 2=>width, 3=>height, 4=>image.ext
25118 $lspace += $img[2];
25119 if ($this->rtl) {;
25120 $this->x += $lspace;
25121 } else {
25122 $this->x -= $lspace;
25123 }
25124 $imgtype = strtolower($img[1]);
25125 $prev_y = $this->y;
25126 switch ($imgtype) {
25127 case 'svg': {
25128 $this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
25129 break;
25130 }
25131 case 'ai':
25132 case 'eps': {
25133 $this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
25134 break;
25135 }
25136 default: {
25137 $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);
25138 break;
25139 }
25140 }
25141 $this->y = $prev_y;
25142 break;
25143 }
25144 // ordered types
25145 // $this->listcount[$this->listnum];
25146 // $textitem
25147 case '1':
25148 case 'decimal': {
25149 $textitem = $this->listcount[$this->listnum];
25150 break;
25151 }
25152 case 'decimal-leading-zero': {
25153 $textitem = sprintf('%02d', $this->listcount[$this->listnum]);
25154 break;
25155 }
25156 case 'i':
25157 case 'lower-roman': {
25158 $textitem = strtolower($this->intToRoman($this->listcount[$this->listnum]));
25159 break;
25160 }
25161 case 'I':
25162 case 'upper-roman': {
25163 $textitem = $this->intToRoman($this->listcount[$this->listnum]);
25164 break;
25165 }
25166 case 'a':
25167 case 'lower-alpha':
25168 case 'lower-latin': {
25169 $textitem = chr(97 + $this->listcount[$this->listnum] - 1);
25170 break;
25171 }
25172 case 'A':
25173 case 'upper-alpha':
25174 case 'upper-latin': {
25175 $textitem = chr(65 + $this->listcount[$this->listnum] - 1);
25176 break;
25177 }
25178 case 'lower-greek': {
25179 $textitem = $this->unichr(945 + $this->listcount[$this->listnum] - 1);
25180 break;
25181 }
25182 /*
25183 // Types to be implemented (special handling)
25184 case 'hebrew': {
25185 break;
25186 }
25187 case 'armenian': {
25188 break;
25189 }
25190 case 'georgian': {
25191 break;
25192 }
25193 case 'cjk-ideographic': {
25194 break;
25195 }
25196 case 'hiragana': {
25197 break;
25198 }
25199 case 'katakana': {
25200 break;
25201 }
25202 case 'hiragana-iroha': {
25203 break;
25204 }
25205 case 'katakana-iroha': {
25206 break;
25207 }
25208 */
25209 default: {
25210 $textitem = $this->listcount[$this->listnum];
25211 }
25212 }
25213 if (!$this->empty_string($textitem)) {
25214 // Check whether we need a new page or new column
25215 $prev_y = $this->y;
25216 $h = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
25217 if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
25218 $tmpx = $this->x;
25219 }
25220 // print ordered item
25221 if ($this->rtl) {
25222 $textitem = '.'.$textitem;
25223 } else {
25224 $textitem = $textitem.'.';
25225 }
25226 $lspace += $this->GetStringWidth($textitem);
25227 if ($this->rtl) {
25228 $this->x += $lspace;
25229 } else {
25230 $this->x -= $lspace;
25231 }
25232 $this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
25233 }
25234 $this->x = $tmpx;
25235 $this->lispacer = '^';
25236 // restore colors
25237 $this->SetFillColorArray($bgcolor);
25238 $this->SetDrawColorArray($strokecolor);
25239 $this->SettextColorArray($color);
25240 }
25241
25242 /**
25243 * Returns current graphic variables as array.
25244 * @return array of graphic variables
25245 * @protected
25246 * @since 4.2.010 (2008-11-14)
25247 */
25248 protected function getGraphicVars() {
25249 $grapvars = array(
25250 'FontFamily' => $this->FontFamily,
25251 'FontStyle' => $this->FontStyle,
25252 'FontSizePt' => $this->FontSizePt,
25253 'rMargin' => $this->rMargin,
25254 'lMargin' => $this->lMargin,
25255 'cell_padding' => $this->cell_padding,
25256 'cell_margin' => $this->cell_margin,
25257 'LineWidth' => $this->LineWidth,
25258 'linestyleWidth' => $this->linestyleWidth,
25259 'linestyleCap' => $this->linestyleCap,
25260 'linestyleJoin' => $this->linestyleJoin,
25261 'linestyleDash' => $this->linestyleDash,
25262 'textrendermode' => $this->textrendermode,
25263 'textstrokewidth' => $this->textstrokewidth,
25264 'DrawColor' => $this->DrawColor,
25265 'FillColor' => $this->FillColor,
25266 'TextColor' => $this->TextColor,
25267 'ColorFlag' => $this->ColorFlag,
25268 'bgcolor' => $this->bgcolor,
25269 'fgcolor' => $this->fgcolor,
25270 'htmlvspace' => $this->htmlvspace,
25271 'listindent' => $this->listindent,
25272 'listindentlevel' => $this->listindentlevel,
25273 'listnum' => $this->listnum,
25274 'listordered' => $this->listordered,
25275 'listcount' => $this->listcount,
25276 'lispacer' => $this->lispacer,
25277 'cell_height_ratio' => $this->cell_height_ratio,
25278 'font_stretching' => $this->font_stretching,
25279 'font_spacing' => $this->font_spacing,
25280 // extended
25281 'lasth' => $this->lasth,
25282 'tMargin' => $this->tMargin,
25283 'bMargin' => $this->bMargin,
25284 'AutoPageBreak' => $this->AutoPageBreak,
25285 'PageBreakTrigger' => $this->PageBreakTrigger,
25286 'x' => $this->x,
25287 'y' => $this->y,
25288 'w' => $this->w,
25289 'h' => $this->h,
25290 'wPt' => $this->wPt,
25291 'hPt' => $this->hPt,
25292 'fwPt' => $this->fwPt,
25293 'fhPt' => $this->fhPt,
25294 'page' => $this->page,
25295 'current_column' => $this->current_column,
25296 'num_columns' => $this->num_columns
25297 );
25298 return $grapvars;
25299 }
25300
25301 /**
25302 * Set graphic variables.
25303 * @param $gvars (array) array of graphic variablesto restore
25304 * @param $extended (boolean) if true restore extended graphic variables
25305 * @protected
25306 * @since 4.2.010 (2008-11-14)
25307 */
25308 protected function setGraphicVars($gvars, $extended=false) {
25309 $this->FontFamily = $gvars['FontFamily'];
25310 $this->FontStyle = $gvars['FontStyle'];
25311 $this->FontSizePt = $gvars['FontSizePt'];
25312 $this->rMargin = $gvars['rMargin'];
25313 $this->lMargin = $gvars['lMargin'];
25314 $this->cell_padding = $gvars['cell_padding'];
25315 $this->cell_margin = $gvars['cell_margin'];
25316 $this->LineWidth = $gvars['LineWidth'];
25317 $this->linestyleWidth = $gvars['linestyleWidth'];
25318 $this->linestyleCap = $gvars['linestyleCap'];
25319 $this->linestyleJoin = $gvars['linestyleJoin'];
25320 $this->linestyleDash = $gvars['linestyleDash'];
25321 $this->textrendermode = $gvars['textrendermode'];
25322 $this->textstrokewidth = $gvars['textstrokewidth'];
25323 $this->DrawColor = $gvars['DrawColor'];
25324 $this->FillColor = $gvars['FillColor'];
25325 $this->TextColor = $gvars['TextColor'];
25326 $this->ColorFlag = $gvars['ColorFlag'];
25327 $this->bgcolor = $gvars['bgcolor'];
25328 $this->fgcolor = $gvars['fgcolor'];
25329 $this->htmlvspace = $gvars['htmlvspace'];
25330 $this->listindent = $gvars['listindent'];
25331 $this->listindentlevel = $gvars['listindentlevel'];
25332 $this->listnum = $gvars['listnum'];
25333 $this->listordered = $gvars['listordered'];
25334 $this->listcount = $gvars['listcount'];
25335 $this->lispacer = $gvars['lispacer'];
25336 $this->cell_height_ratio = $gvars['cell_height_ratio'];
25337 $this->font_stretching = $gvars['font_stretching'];
25338 $this->font_spacing = $gvars['font_spacing'];
25339 if ($extended) {
25340 // restore extended values
25341 $this->lasth = $gvars['lasth'];
25342 $this->tMargin = $gvars['tMargin'];
25343 $this->bMargin = $gvars['bMargin'];
25344 $this->AutoPageBreak = $gvars['AutoPageBreak'];
25345 $this->PageBreakTrigger = $gvars['PageBreakTrigger'];
25346 $this->x = $gvars['x'];
25347 $this->y = $gvars['y'];
25348 $this->w = $gvars['w'];
25349 $this->h = $gvars['h'];
25350 $this->wPt = $gvars['wPt'];
25351 $this->hPt = $gvars['hPt'];
25352 $this->fwPt = $gvars['fwPt'];
25353 $this->fhPt = $gvars['fhPt'];
25354 $this->page = $gvars['page'];
25355 $this->current_column = $gvars['current_column'];
25356 $this->num_columns = $gvars['num_columns'];
25357 }
25358 $this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
25359 if (!$this->empty_string($this->FontFamily)) {
25360 $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
25361 }
25362 }
25363
25364 /**
25365 * Returns a temporary filename for caching object on filesystem.
25366 * @param $name (string) prefix to add to filename
25367 * @return string filename.
25368 * @since 4.5.000 (2008-12-31)
25369 * @protected
25370 */
25371 protected function getObjFilename($name) {
25372 return tempnam(K_PATH_CACHE, $name.'_');
25373 }
25374
25375 /**
25376 * Writes data to a temporary file on filesystem.
25377 * @param $filename (string) file name
25378 * @param $data (mixed) data to write on file
25379 * @param $append (boolean) if true append data, false replace.
25380 * @since 4.5.000 (2008-12-31)
25381 * @protected
25382 */
25383 protected function writeDiskCache($filename, $data, $append=false) {
25384 if ($append) {
25385 $fmode = 'ab+';
25386 } else {
25387 $fmode = 'wb+';
25388 }
25389 $f = @fopen($filename, $fmode);
25390 if (!$f) {
25391 $this->Error('Unable to write cache file: '.$filename);
25392 } else {
25393 fwrite($f, $data);
25394 fclose($f);
25395 }
25396 // update file length (needed for transactions)
25397 if (!isset($this->cache_file_length['_'.$filename])) {
25398 $this->cache_file_length['_'.$filename] = strlen($data);
25399 } else {
25400 $this->cache_file_length['_'.$filename] += strlen($data);
25401 }
25402 }
25403
25404 /**
25405 * Read data from a temporary file on filesystem.
25406 * @param $filename (string) file name
25407 * @return mixed retrieved data
25408 * @since 4.5.000 (2008-12-31)
25409 * @protected
25410 */
25411 protected function readDiskCache($filename) {
25412 return file_get_contents($filename);
25413 }
25414
25415 /**
25416 * Set buffer content (always append data).
25417 * @param $data (string) data
25418 * @protected
25419 * @since 4.5.000 (2009-01-02)
25420 */
25421 protected function setBuffer($data) {
25422 $this->bufferlen += strlen($data);
25423 if ($this->diskcache) {
25424 if (!isset($this->buffer) OR $this->empty_string($this->buffer)) {
25425 $this->buffer = $this->getObjFilename('buffer');
25426 }
25427 $this->writeDiskCache($this->buffer, $data, true);
25428 } else {
25429 $this->buffer .= $data;
25430 }
25431 }
25432
25433 /**
25434 * Replace the buffer content
25435 * @param $data (string) data
25436 * @protected
25437 * @since 5.5.000 (2010-06-22)
25438 */
25439 protected function replaceBuffer($data) {
25440 $this->bufferlen = strlen($data);
25441 if ($this->diskcache) {
25442 if (!isset($this->buffer) OR $this->empty_string($this->buffer)) {
25443 $this->buffer = $this->getObjFilename('buffer');
25444 }
25445 $this->writeDiskCache($this->buffer, $data, false);
25446 } else {
25447 $this->buffer = $data;
25448 }
25449 }
25450
25451 /**
25452 * Get buffer content.
25453 * @return string buffer content
25454 * @protected
25455 * @since 4.5.000 (2009-01-02)
25456 */
25457 protected function getBuffer() {
25458 if ($this->diskcache) {
25459 return $this->readDiskCache($this->buffer);
25460 } else {
25461 return $this->buffer;
25462 }
25463 }
25464
25465 /**
25466 * Set page buffer content.
25467 * @param $page (int) page number
25468 * @param $data (string) page data
25469 * @param $append (boolean) if true append data, false replace.
25470 * @protected
25471 * @since 4.5.000 (2008-12-31)
25472 */
25473 protected function setPageBuffer($page, $data, $append=false) {
25474 if ($this->diskcache) {
25475 if (!isset($this->pages[$page])) {
25476 $this->pages[$page] = $this->getObjFilename('page'.$page);
25477 }
25478 $this->writeDiskCache($this->pages[$page], $data, $append);
25479 } else {
25480 if ($append) {
25481 $this->pages[$page] .= $data;
25482 } else {
25483 $this->pages[$page] = $data;
25484 }
25485 }
25486 if ($append AND isset($this->pagelen[$page])) {
25487 $this->pagelen[$page] += strlen($data);
25488 } else {
25489 $this->pagelen[$page] = strlen($data);
25490 }
25491 }
25492
25493 /**
25494 * Get page buffer content.
25495 * @param $page (int) page number
25496 * @return string page buffer content or false in case of error
25497 * @protected
25498 * @since 4.5.000 (2008-12-31)
25499 */
25500 protected function getPageBuffer($page) {
25501 if ($this->diskcache) {
25502 return $this->readDiskCache($this->pages[$page]);
25503 } elseif (isset($this->pages[$page])) {
25504 return $this->pages[$page];
25505 }
25506 return false;
25507 }
25508
25509 /**
25510 * Set image buffer content.
25511 * @param $image (string) image key
25512 * @param $data (array) image data
25513 * @protected
25514 * @since 4.5.000 (2008-12-31)
25515 */
25516 protected function setImageBuffer($image, $data) {
25517 if ($this->diskcache) {
25518 if (!isset($this->images[$image])) {
25519 $this->images[$image] = $this->getObjFilename('image'.$image);
25520 }
25521 $this->writeDiskCache($this->images[$image], serialize($data));
25522 } else {
25523 $this->images[$image] = $data;
25524 }
25525 if (!in_array($image, $this->imagekeys)) {
25526 $this->imagekeys[] = $image;
25527 ++$this->numimages;
25528 }
25529 }
25530
25531 /**
25532 * Set image buffer content for a specified sub-key.
25533 * @param $image (string) image key
25534 * @param $key (string) image sub-key
25535 * @param $data (array) image data
25536 * @protected
25537 * @since 4.5.000 (2008-12-31)
25538 */
25539 protected function setImageSubBuffer($image, $key, $data) {
25540 if (!isset($this->images[$image])) {
25541 $this->setImageBuffer($image, array());
25542 }
25543 if ($this->diskcache) {
25544 $tmpimg = $this->getImageBuffer($image);
25545 $tmpimg[$key] = $data;
25546 $this->writeDiskCache($this->images[$image], serialize($tmpimg));
25547 } else {
25548 $this->images[$image][$key] = $data;
25549 }
25550 }
25551
25552 /**
25553 * Get image buffer content.
25554 * @param $image (string) image key
25555 * @return string image buffer content or false in case of error
25556 * @protected
25557 * @since 4.5.000 (2008-12-31)
25558 */
25559 protected function getImageBuffer($image) {
25560 if ($this->diskcache AND isset($this->images[$image])) {
25561 return unserialize($this->readDiskCache($this->images[$image]));
25562 } elseif (isset($this->images[$image])) {
25563 return $this->images[$image];
25564 }
25565 return false;
25566 }
25567
25568 /**
25569 * Set font buffer content.
25570 * @param $font (string) font key
25571 * @param $data (array) font data
25572 * @protected
25573 * @since 4.5.000 (2009-01-02)
25574 */
25575 protected function setFontBuffer($font, $data) {
25576 if ($this->diskcache) {
25577 if (!isset($this->fonts[$font])) {
25578 $this->fonts[$font] = $this->getObjFilename('font');
25579 }
25580 $this->writeDiskCache($this->fonts[$font], serialize($data));
25581 } else {
25582 $this->fonts[$font] = $data;
25583 }
25584 if (!in_array($font, $this->fontkeys)) {
25585 $this->fontkeys[] = $font;
25586 // store object ID for current font
25587 ++$this->n;
25588 $this->font_obj_ids[$font] = $this->n;
25589 $this->setFontSubBuffer($font, 'n', $this->n);
25590 }
25591 }
25592
25593 /**
25594 * Set font buffer content.
25595 * @param $font (string) font key
25596 * @param $key (string) font sub-key
25597 * @param $data (array) font data
25598 * @protected
25599 * @since 4.5.000 (2009-01-02)
25600 */
25601 protected function setFontSubBuffer($font, $key, $data) {
25602 if (!isset($this->fonts[$font])) {
25603 $this->setFontBuffer($font, array());
25604 }
25605 if ($this->diskcache) {
25606 $tmpfont = $this->getFontBuffer($font);
25607 $tmpfont[$key] = $data;
25608 $this->writeDiskCache($this->fonts[$font], serialize($tmpfont));
25609 } else {
25610 $this->fonts[$font][$key] = $data;
25611 }
25612 }
25613
25614 /**
25615 * Get font buffer content.
25616 * @param $font (string) font key
25617 * @return string font buffer content or false in case of error
25618 * @protected
25619 * @since 4.5.000 (2009-01-02)
25620 */
25621 protected function getFontBuffer($font) {
25622 if ($this->diskcache AND isset($this->fonts[$font])) {
25623 return unserialize($this->readDiskCache($this->fonts[$font]));
25624 } elseif (isset($this->fonts[$font])) {
25625 return $this->fonts[$font];
25626 }
25627 return false;
25628 }
25629
25630 /**
25631 * Move a page to a previous position.
25632 * @param $frompage (int) number of the source page
25633 * @param $topage (int) number of the destination page (must be less than $frompage)
25634 * @return true in case of success, false in case of error.
25635 * @public
25636 * @since 4.5.000 (2009-01-02)
25637 */
25638 public function movePage($frompage, $topage) {
25639 if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
25640 return false;
25641 }
25642 if ($frompage == $this->page) {
25643 // close the page before moving it
25644 $this->endPage();
25645 }
25646 // move all page-related states
25647 $tmppage = $this->getPageBuffer($frompage);
25648 $tmppagedim = $this->pagedim[$frompage];
25649 $tmppagelen = $this->pagelen[$frompage];
25650 $tmpintmrk = $this->intmrk[$frompage];
25651 $tmpbordermrk = $this->bordermrk[$frompage];
25652 $tmpcntmrk = $this->cntmrk[$frompage];
25653 if (isset($this->footerpos[$frompage])) {
25654 $tmpfooterpos = $this->footerpos[$frompage];
25655 }
25656 if (isset($this->footerlen[$frompage])) {
25657 $tmpfooterlen = $this->footerlen[$frompage];
25658 }
25659 if (isset($this->transfmrk[$frompage])) {
25660 $tmptransfmrk = $this->transfmrk[$frompage];
25661 }
25662 if (isset($this->PageAnnots[$frompage])) {
25663 $tmpannots = $this->PageAnnots[$frompage];
25664 }
25665 if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
25666 for ($i = $frompage; $i > $topage; --$i) {
25667 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
25668 --$this->pagegroups[$this->newpagegroup[$i]];
25669 break;
25670 }
25671 }
25672 for ($i = $topage; $i > 0; --$i) {
25673 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
25674 ++$this->pagegroups[$this->newpagegroup[$i]];
25675 break;
25676 }
25677 }
25678 }
25679 for ($i = $frompage; $i > $topage; --$i) {
25680 $j = $i - 1;
25681 // shift pages down
25682 $this->setPageBuffer($i, $this->getPageBuffer($j));
25683 $this->pagedim[$i] = $this->pagedim[$j];
25684 $this->pagelen[$i] = $this->pagelen[$j];
25685 $this->intmrk[$i] = $this->intmrk[$j];
25686 $this->bordermrk[$i] = $this->bordermrk[$j];
25687 $this->cntmrk[$i] = $this->cntmrk[$j];
25688 if (isset($this->footerpos[$j])) {
25689 $this->footerpos[$i] = $this->footerpos[$j];
25690 } elseif (isset($this->footerpos[$i])) {
25691 unset($this->footerpos[$i]);
25692 }
25693 if (isset($this->footerlen[$j])) {
25694 $this->footerlen[$i] = $this->footerlen[$j];
25695 } elseif (isset($this->footerlen[$i])) {
25696 unset($this->footerlen[$i]);
25697 }
25698 if (isset($this->transfmrk[$j])) {
25699 $this->transfmrk[$i] = $this->transfmrk[$j];
25700 } elseif (isset($this->transfmrk[$i])) {
25701 unset($this->transfmrk[$i]);
25702 }
25703 if (isset($this->PageAnnots[$j])) {
25704 $this->PageAnnots[$i] = $this->PageAnnots[$j];
25705 } elseif (isset($this->PageAnnots[$i])) {
25706 unset($this->PageAnnots[$i]);
25707 }
25708 if (isset($this->newpagegroup[$j])) {
25709 $this->newpagegroup[$i] = $this->newpagegroup[$j];
25710 unset($this->newpagegroup[$j]);
25711 }
25712 if ($this->currpagegroup == $j) {
25713 $this->currpagegroup = $i;
25714 }
25715 }
25716 $this->setPageBuffer($topage, $tmppage);
25717 $this->pagedim[$topage] = $tmppagedim;
25718 $this->pagelen[$topage] = $tmppagelen;
25719 $this->intmrk[$topage] = $tmpintmrk;
25720 $this->bordermrk[$topage] = $tmpbordermrk;
25721 $this->cntmrk[$topage] = $tmpcntmrk;
25722 if (isset($tmpfooterpos)) {
25723 $this->footerpos[$topage] = $tmpfooterpos;
25724 } elseif (isset($this->footerpos[$topage])) {
25725 unset($this->footerpos[$topage]);
25726 }
25727 if (isset($tmpfooterlen)) {
25728 $this->footerlen[$topage] = $tmpfooterlen;
25729 } elseif (isset($this->footerlen[$topage])) {
25730 unset($this->footerlen[$topage]);
25731 }
25732 if (isset($tmptransfmrk)) {
25733 $this->transfmrk[$topage] = $tmptransfmrk;
25734 } elseif (isset($this->transfmrk[$topage])) {
25735 unset($this->transfmrk[$topage]);
25736 }
25737 if (isset($tmpannots)) {
25738 $this->PageAnnots[$topage] = $tmpannots;
25739 } elseif (isset($this->PageAnnots[$topage])) {
25740 unset($this->PageAnnots[$topage]);
25741 }
25742 // adjust outlines
25743 $tmpoutlines = $this->outlines;
25744 foreach ($tmpoutlines as $key => $outline) {
25745 if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
25746 $this->outlines[$key]['p'] = ($outline['p'] + 1);
25747 } elseif ($outline['p'] == $frompage) {
25748 $this->outlines[$key]['p'] = $topage;
25749 }
25750 }
25751 // adjust dests
25752 $tmpdests = $this->dests;
25753 foreach ($tmpdests as $key => $dest) {
25754 if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
25755 $this->dests[$key]['p'] = ($dest['p'] + 1);
25756 } elseif ($dest['p'] == $frompage) {
25757 $this->dests[$key]['p'] = $topage;
25758 }
25759 }
25760 // adjust links
25761 $tmplinks = $this->links;
25762 foreach ($tmplinks as $key => $link) {
25763 if (($link[0] >= $topage) AND ($link[0] < $frompage)) {
25764 $this->links[$key][0] = ($link[0] + 1);
25765 } elseif ($link[0] == $frompage) {
25766 $this->links[$key][0] = $topage;
25767 }
25768 }
25769 // adjust javascript
25770 $tmpjavascript = $this->javascript;
25771 global $jfrompage, $jtopage;
25772 $jfrompage = $frompage;
25773 $jtopage = $topage;
25774 $this->javascript = preg_replace_callback('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/',
25775 create_function('$matches', 'global $jfrompage, $jtopage;
25776 $pagenum = intval($matches[3]) + 1;
25777 if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
25778 $newpage = ($pagenum + 1);
25779 } elseif ($pagenum == $jfrompage) {
25780 $newpage = $jtopage;
25781 } else {
25782 $newpage = $pagenum;
25783 }
25784 --$newpage;
25785 return "this.addField(\'".$matches[1]."\',\'".$matches[2]."\',".$newpage."";'), $tmpjavascript);
25786 // return to last page
25787 $this->lastPage(true);
25788 return true;
25789 }
25790
25791 /**
25792 * Remove the specified page.
25793 * @param $page (int) page to remove
25794 * @return true in case of success, false in case of error.
25795 * @public
25796 * @since 4.6.004 (2009-04-23)
25797 */
25798 public function deletePage($page) {
25799 if (($page < 1) OR ($page > $this->numpages)) {
25800 return false;
25801 }
25802 // delete current page
25803 unset($this->pages[$page]);
25804 unset($this->pagedim[$page]);
25805 unset($this->pagelen[$page]);
25806 unset($this->intmrk[$page]);
25807 unset($this->bordermrk[$page]);
25808 unset($this->cntmrk[$page]);
25809 if (isset($this->footerpos[$page])) {
25810 unset($this->footerpos[$page]);
25811 }
25812 if (isset($this->footerlen[$page])) {
25813 unset($this->footerlen[$page]);
25814 }
25815 if (isset($this->transfmrk[$page])) {
25816 unset($this->transfmrk[$page]);
25817 }
25818 if (isset($this->PageAnnots[$page])) {
25819 unset($this->PageAnnots[$page]);
25820 }
25821 if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
25822 for ($i = $page; $i > 0; --$i) {
25823 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
25824 --$this->pagegroups[$this->newpagegroup[$i]];
25825 break;
25826 }
25827 }
25828 }
25829 if (isset($this->pageopen[$page])) {
25830 unset($this->pageopen[$page]);
25831 }
25832 if ($page < $this->numpages) {
25833 // update remaining pages
25834 for ($i = $page; $i < $this->numpages; ++$i) {
25835 $j = $i + 1;
25836 // shift pages
25837 $this->setPageBuffer($i, $this->getPageBuffer($j));
25838 $this->pagedim[$i] = $this->pagedim[$j];
25839 $this->pagelen[$i] = $this->pagelen[$j];
25840 $this->intmrk[$i] = $this->intmrk[$j];
25841 $this->bordermrk[$i] = $this->bordermrk[$j];
25842 $this->cntmrk[$i] = $this->cntmrk[$j];
25843 if (isset($this->footerpos[$j])) {
25844 $this->footerpos[$i] = $this->footerpos[$j];
25845 } elseif (isset($this->footerpos[$i])) {
25846 unset($this->footerpos[$i]);
25847 }
25848 if (isset($this->footerlen[$j])) {
25849 $this->footerlen[$i] = $this->footerlen[$j];
25850 } elseif (isset($this->footerlen[$i])) {
25851 unset($this->footerlen[$i]);
25852 }
25853 if (isset($this->transfmrk[$j])) {
25854 $this->transfmrk[$i] = $this->transfmrk[$j];
25855 } elseif (isset($this->transfmrk[$i])) {
25856 unset($this->transfmrk[$i]);
25857 }
25858 if (isset($this->PageAnnots[$j])) {
25859 $this->PageAnnots[$i] = $this->PageAnnots[$j];
25860 } elseif (isset($this->PageAnnots[$i])) {
25861 unset($this->PageAnnots[$i]);
25862 }
25863 if (isset($this->newpagegroup[$j])) {
25864 $this->newpagegroup[$i] = $this->newpagegroup[$j];
25865 unset($this->newpagegroup[$j]);
25866 }
25867 if ($this->currpagegroup == $j) {
25868 $this->currpagegroup = $i;
25869 }
25870 if (isset($this->pageopen[$j])) {
25871 $this->pageopen[$i] = $this->pageopen[$j];
25872 } elseif (isset($this->pageopen[$i])) {
25873 unset($this->pageopen[$i]);
25874 }
25875 }
25876 // remove last page
25877 unset($this->pages[$this->numpages]);
25878 unset($this->pagedim[$this->numpages]);
25879 unset($this->pagelen[$this->numpages]);
25880 unset($this->intmrk[$this->numpages]);
25881 unset($this->bordermrk[$this->numpages]);
25882 unset($this->cntmrk[$this->numpages]);
25883 if (isset($this->footerpos[$this->numpages])) {
25884 unset($this->footerpos[$this->numpages]);
25885 }
25886 if (isset($this->footerlen[$this->numpages])) {
25887 unset($this->footerlen[$this->numpages]);
25888 }
25889 if (isset($this->transfmrk[$this->numpages])) {
25890 unset($this->transfmrk[$this->numpages]);
25891 }
25892 if (isset($this->PageAnnots[$this->numpages])) {
25893 unset($this->PageAnnots[$this->numpages]);
25894 }
25895 if (isset($this->newpagegroup[$this->numpages])) {
25896 unset($this->newpagegroup[$this->numpages]);
25897 }
25898 if ($this->currpagegroup == $this->numpages) {
25899 $this->currpagegroup = ($this->numpages - 1);
25900 }
25901 if (isset($this->pagegroups[$this->numpages])) {
25902 unset($this->pagegroups[$this->numpages]);
25903 }
25904 if (isset($this->pageopen[$this->numpages])) {
25905 unset($this->pageopen[$this->numpages]);
25906 }
25907 }
25908 --$this->numpages;
25909 $this->page = $this->numpages;
25910 // adjust outlines
25911 $tmpoutlines = $this->outlines;
25912 foreach ($tmpoutlines as $key => $outline) {
25913 if ($outline['p'] > $page) {
25914 $this->outlines[$key]['p'] = $outline['p'] - 1;
25915 } elseif ($outline['p'] == $page) {
25916 unset($this->outlines[$key]);
25917 }
25918 }
25919 // adjust dests
25920 $tmpdests = $this->dests;
25921 foreach ($tmpdests as $key => $dest) {
25922 if ($dest['p'] > $page) {
25923 $this->dests[$key]['p'] = $dest['p'] - 1;
25924 } elseif ($dest['p'] == $page) {
25925 unset($this->dests[$key]);
25926 }
25927 }
25928 // adjust links
25929 $tmplinks = $this->links;
25930 foreach ($tmplinks as $key => $link) {
25931 if ($link[0] > $page) {
25932 $this->links[$key][0] = $link[0] - 1;
25933 } elseif ($link[0] == $page) {
25934 unset($this->links[$key]);
25935 }
25936 }
25937 // adjust javascript
25938 $tmpjavascript = $this->javascript;
25939 global $jpage;
25940 $jpage = $page;
25941 $this->javascript = preg_replace_callback('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/',
25942 create_function('$matches', 'global $jpage;
25943 $pagenum = intval($matches[3]) + 1;
25944 if ($pagenum >= $jpage) {
25945 $newpage = ($pagenum - 1);
25946 } elseif ($pagenum == $jpage) {
25947 $newpage = 1;
25948 } else {
25949 $newpage = $pagenum;
25950 }
25951 --$newpage;
25952 return "this.addField(\'".$matches[1]."\',\'".$matches[2]."\',".$newpage."";'), $tmpjavascript);
25953 // return to last page
25954 $this->lastPage(true);
25955 return true;
25956 }
25957
25958 /**
25959 * Clone the specified page to a new page.
25960 * @param $page (int) number of page to copy (0 = current page)
25961 * @return true in case of success, false in case of error.
25962 * @public
25963 * @since 4.9.015 (2010-04-20)
25964 */
25965 public function copyPage($page=0) {
25966 if ($page == 0) {
25967 // default value
25968 $page = $this->page;
25969 }
25970 if (($page < 1) OR ($page > $this->numpages)) {
25971 return false;
25972 }
25973 // close the last page
25974 $this->endPage();
25975 // copy all page-related states
25976 ++$this->numpages;
25977 $this->page = $this->numpages;
25978 $this->setPageBuffer($this->page, $this->getPageBuffer($page));
25979 $this->pagedim[$this->page] = $this->pagedim[$page];
25980 $this->pagelen[$this->page] = $this->pagelen[$page];
25981 $this->intmrk[$this->page] = $this->intmrk[$page];
25982 $this->bordermrk[$this->page] = $this->bordermrk[$page];
25983 $this->cntmrk[$this->page] = $this->cntmrk[$page];
25984 $this->pageopen[$this->page] = false;
25985 if (isset($this->footerpos[$page])) {
25986 $this->footerpos[$this->page] = $this->footerpos[$page];
25987 }
25988 if (isset($this->footerlen[$page])) {
25989 $this->footerlen[$this->page] = $this->footerlen[$page];
25990 }
25991 if (isset($this->transfmrk[$page])) {
25992 $this->transfmrk[$this->page] = $this->transfmrk[$page];
25993 }
25994 if (isset($this->PageAnnots[$page])) {
25995 $this->PageAnnots[$this->page] = $this->PageAnnots[$page];
25996 }
25997 if (isset($this->newpagegroup[$page])) {
25998 // start a new group
25999 $this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
26000 $this->currpagegroup = $this->newpagegroup[$this->page];
26001 $this->pagegroups[$this->currpagegroup] = 1;
26002 } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
26003 ++$this->pagegroups[$this->currpagegroup];
26004 }
26005 // copy outlines
26006 $tmpoutlines = $this->outlines;
26007 foreach ($tmpoutlines as $key => $outline) {
26008 if ($outline['p'] == $page) {
26009 $this->outlines[] = array('t' => $outline['t'], 'l' => $outline['l'], 'y' => $outline['y'], 'p' => $this->page, 's' => $outline['s'], 'c' => $outline['c']);
26010 }
26011 }
26012 // copy links
26013 $tmplinks = $this->links;
26014 foreach ($tmplinks as $key => $link) {
26015 if ($link[0] == $page) {
26016 $this->links[] = array($this->page, $link[1]);
26017 }
26018 }
26019 // return to last page
26020 $this->lastPage(true);
26021 return true;
26022 }
26023
26024 /**
26025 * Output a Table of Content Index (TOC).
26026 * This method must be called after all Bookmarks were set.
26027 * Before calling this method you have to open the page using the addTOCPage() method.
26028 * After calling this method you have to call endTOCPage() to close the TOC page.
26029 * You can override this method to achieve different styles.
26030 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
26031 * @param $numbersfont (string) set the font for page numbers (please use monospaced font for better alignment).
26032 * @param $filler (string) string used to fill the space between text and page number.
26033 * @param $toc_name (string) name to use for TOC bookmark.
26034 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
26035 * @param $color (array) RGB color array for bookmark title (values from 0 to 255).
26036 * @public
26037 * @author Nicola Asuni
26038 * @since 4.5.000 (2009-01-02)
26039 * @see addTOCPage(), endTOCPage(), addHTMLTOC()
26040 */
26041 public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
26042 $fontsize = $this->FontSizePt;
26043 $fontfamily = $this->FontFamily;
26044 $fontstyle = $this->FontStyle;
26045 $w = $this->w - $this->lMargin - $this->rMargin;
26046 $spacer = $this->GetStringWidth(chr(32)) * 4;
26047 $page_first = $this->getPage();
26048 $lmargin = $this->lMargin;
26049 $rmargin = $this->rMargin;
26050 $x_start = $this->GetX();
26051 $current_page = $this->page;
26052 $current_column = $this->current_column;
26053 if ($this->empty_string($numbersfont)) {
26054 $numbersfont = $this->default_monospaced_font;
26055 }
26056 if ($this->empty_string($filler)) {
26057 $filler = ' ';
26058 }
26059 if ($this->empty_string($page)) {
26060 $gap = ' ';
26061 } else {
26062 $gap = '';
26063 if ($page < 1) {
26064 $page = 1;
26065 }
26066 }
26067 $this->SetFont($numbersfont, $fontstyle, $fontsize);
26068 $numwidth = $this->GetStringWidth('00000');
26069 $maxpage = 0; //used for pages on attached documents
26070 foreach ($this->outlines as $key => $outline) {
26071 // check for extra pages (used for attachments)
26072 if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
26073 $outline['p'] += ($this->page - $page_first);
26074 }
26075 if ($this->rtl) {
26076 $aligntext = 'R';
26077 $alignnum = 'L';
26078 } else {
26079 $aligntext = 'L';
26080 $alignnum = 'R';
26081 }
26082 if ($outline['l'] == 0) {
26083 $this->SetFont($fontfamily, $outline['s'].'B', $fontsize);
26084 } else {
26085 $this->SetFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
26086 }
26087 $this->SetTextColorArray($outline['c']);
26088 // check for page break
26089 $this->checkPageBreak((2 * $this->FontSize * $this->cell_height_ratio));
26090 // set margins and X position
26091 if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
26092 $this->lMargin = $lmargin;
26093 $this->rMargin = $rmargin;
26094 } else {
26095 if ($this->current_column != $current_column) {
26096 if ($this->rtl) {
26097 $x_start = $this->w - $this->columns[$this->current_column]['x'];
26098 } else {
26099 $x_start = $this->columns[$this->current_column]['x'];
26100 }
26101 }
26102 $lmargin = $this->lMargin;
26103 $rmargin = $this->rMargin;
26104 $current_page = $this->page;
26105 $current_column = $this->current_column;
26106 }
26107 $this->SetX($x_start);
26108 $indent = ($spacer * $outline['l']);
26109 if ($this->rtl) {
26110 $this->x -= $indent;
26111 $this->rMargin = $this->w - $this->x;
26112 } else {
26113 $this->x += $indent;
26114 $this->lMargin = $this->x;
26115 }
26116 $link = $this->AddLink();
26117 $this->SetLink($link, $outline['y'], $outline['p']);
26118 // write the text
26119 if ($this->rtl) {
26120 $txt = ' '.$outline['t'];
26121 } else {
26122 $txt = $outline['t'].' ';
26123 }
26124 $this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
26125 if ($this->rtl) {
26126 $tw = $this->x - $this->lMargin;
26127 } else {
26128 $tw = $this->w - $this->rMargin - $this->x;
26129 }
26130 $this->SetFont($numbersfont, $fontstyle, $fontsize);
26131 if ($this->empty_string($page)) {
26132 $pagenum = $outline['p'];
26133 } else {
26134 // placemark to be replaced with the correct number
26135 $pagenum = '{#'.($outline['p']).'}';
26136 if ($this->isUnicodeFont()) {
26137 $pagenum = '{'.$pagenum.'}';
26138 }
26139 $maxpage = max($maxpage, $outline['p']);
26140 }
26141 $fw = ($tw - $this->GetStringWidth($pagenum.$filler));
26142 $numfills = floor($fw / $this->GetStringWidth($filler));
26143 if ($numfills > 0) {
26144 $rowfill = str_repeat($filler, $numfills);
26145 } else {
26146 $rowfill = '';
26147 }
26148 if ($this->rtl) {
26149 $pagenum = $pagenum.$gap.$rowfill;
26150 } else {
26151 $pagenum = $rowfill.$gap.$pagenum;
26152 }
26153 // write the number
26154 $this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
26155 }
26156 $page_last = $this->getPage();
26157 $maxpage = max($maxpage, $page_last);
26158 $numpages = $page_last - $page_first + 1;
26159 if (!$this->empty_string($page)) {
26160 for ($p = $page_first; $p <= $page_last; ++$p) {
26161 // get page data
26162 $temppage = $this->getPageBuffer($p);
26163 for ($n = 1; $n <= $maxpage; ++$n) {
26164 // update page numbers
26165 $a = '{#'.$n.'}';
26166 // get page number aliases
26167 $pnalias = $this->getInternalPageNumberAliases($a);
26168 // calculate replacement number
26169 if (($n >= $page) AND ($n <= $this->numpages)) {
26170 $np = $n + $numpages;
26171 } else {
26172 $np = $n;
26173 }
26174 $na = $this->formatTOCPageNumber(($this->starting_page_number + $np - 1));
26175 $nu = $this->UTF8ToUTF16BE($na, false);
26176 // replace aliases with numbers
26177 foreach ($pnalias['u'] as $u) {
26178 $sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
26179 if ($this->rtl) {
26180 $nr = $nu.$this->UTF8ToUTF16BE(' '.$sfill);
26181 } else {
26182 $nr = $this->UTF8ToUTF16BE($sfill.' ').$nu;
26183 }
26184 $temppage = str_replace($u, $nr, $temppage);
26185 }
26186 foreach ($pnalias['a'] as $a) {
26187 $sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
26188 if ($this->rtl) {
26189 $nr = $na.' '.$sfill;
26190 } else {
26191 $nr = $sfill.' '.$na;
26192 }
26193 $temppage = str_replace($a, $nr, $temppage);
26194 }
26195 }
26196 // save changes
26197 $this->setPageBuffer($p, $temppage);
26198 }
26199 // move pages
26200 $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
26201 for ($i = 0; $i < $numpages; ++$i) {
26202 $this->movePage($page_last, $page);
26203 }
26204 }
26205 }
26206
26207 /**
26208 * Output a Table Of Content Index (TOC) using HTML templates.
26209 * This method must be called after all Bookmarks were set.
26210 * Before calling this method you have to open the page using the addTOCPage() method.
26211 * After calling this method you have to call endTOCPage() to close the TOC page.
26212 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
26213 * @param $toc_name (string) name to use for TOC bookmark.
26214 * @param $templates (array) array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
26215 * @param $correct_align (boolean) if true correct the number alignment (numbers must be in monospaced font like courier and right aligned on LTR, or left aligned on RTL)
26216 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
26217 * @param $color (array) RGB color array for title (values from 0 to 255).
26218 * @public
26219 * @author Nicola Asuni
26220 * @since 5.0.001 (2010-05-06)
26221 * @see addTOCPage(), endTOCPage(), addTOC()
26222 */
26223 public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
26224 $filler = ' ';
26225 $prev_htmlLinkColorArray = $this->htmlLinkColorArray;
26226 $prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
26227 // set new style for link
26228 $this->htmlLinkColorArray = array();
26229 $this->htmlLinkFontStyle = '';
26230 $page_first = $this->getPage();
26231 // get the font type used for numbers in each template
26232 $current_font = $this->FontFamily;
26233 foreach ($templates as $level => $html) {
26234 $dom = $this->getHtmlDomArray($html);
26235 foreach ($dom as $key => $value) {
26236 if ($value['value'] == '#TOC_PAGE_NUMBER#') {
26237 $this->SetFont($dom[($key - 1)]['fontname']);
26238 $templates['F'.$level] = $this->isUnicodeFont();
26239 }
26240 }
26241 }
26242 $this->SetFont($current_font);
26243 $maxpage = 0; //used for pages on attached documents
26244 foreach ($this->outlines as $key => $outline) {
26245 // get HTML template
26246 $row = $templates[$outline['l']];
26247 if ($this->empty_string($page)) {
26248 $pagenum = $outline['p'];
26249 } else {
26250 // placemark to be replaced with the correct number
26251 $pagenum = '{#'.($outline['p']).'}';
26252 if ($templates['F'.$outline['l']]) {
26253 $pagenum = '{'.$pagenum.'}';
26254 }
26255 $maxpage = max($maxpage, $outline['p']);
26256 }
26257 // replace templates with current values
26258 $row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
26259 $row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
26260 // add link to page
26261 $row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
26262 // write bookmark entry
26263 $this->writeHTML($row, false, false, true, false, '');
26264 }
26265 // restore link styles
26266 $this->htmlLinkColorArray = $prev_htmlLinkColorArray;
26267 $this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
26268 // move TOC page and replace numbers
26269 $page_last = $this->getPage();
26270 $maxpage = max($maxpage, $page_last);
26271 $numpages = $page_last - $page_first + 1;
26272 if (!$this->empty_string($page)) {
26273 for ($p = $page_first; $p <= $page_last; ++$p) {
26274 // get page data
26275 $temppage = $this->getPageBuffer($p);
26276 for ($n = 1; $n <= $maxpage; ++$n) {
26277 // update page numbers
26278 $a = '{#'.$n.'}';
26279 // get page number aliases
26280 $pnalias = $this->getInternalPageNumberAliases($a);
26281 // calculate replacement number
26282 if ($n >= $page) {
26283 $np = $n + $numpages;
26284 } else {
26285 $np = $n;
26286 }
26287 $na = $this->formatTOCPageNumber(($this->starting_page_number + $np - 1));
26288 $nu = $this->UTF8ToUTF16BE($na, false);
26289 // replace aliases with numbers
26290 foreach ($pnalias['u'] as $u) {
26291 if ($correct_align) {
26292 $sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
26293 if ($this->rtl) {
26294 $nr = $nu.$this->UTF8ToUTF16BE(' '.$sfill);
26295 } else {
26296 $nr = $this->UTF8ToUTF16BE($sfill.' ').$nu;
26297 }
26298 } else {
26299 $nr = $nu;
26300 }
26301 $temppage = str_replace($u, $nr, $temppage);
26302 }
26303 foreach ($pnalias['a'] as $a) {
26304 if ($correct_align) {
26305 $sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
26306 if ($this->rtl) {
26307 $nr = $na.' '.$sfill;
26308 } else {
26309 $nr = $sfill.' '.$na;
26310 }
26311 } else {
26312 $nr = $na;
26313 }
26314 $temppage = str_replace($a, $nr, $temppage);
26315 }
26316 }
26317 // save changes
26318 $this->setPageBuffer($p, $temppage);
26319 }
26320 // move pages
26321 $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
26322 for ($i = 0; $i < $numpages; ++$i) {
26323 $this->movePage($page_last, $page);
26324 }
26325 }
26326 }
26327
26328 /**
26329 * Stores a copy of the current TCPDF object used for undo operation.
26330 * @public
26331 * @since 4.5.029 (2009-03-19)
26332 */
26333 public function startTransaction() {
26334 if (isset($this->objcopy)) {
26335 // remove previous copy
26336 $this->commitTransaction();
26337 }
26338 // record current page number and Y position
26339 $this->start_transaction_page = $this->page;
26340 $this->start_transaction_y = $this->y;
26341 // clone current object
26342 $this->objcopy = $this->objclone($this);
26343 }
26344
26345 /**
26346 * Delete the copy of the current TCPDF object used for undo operation.
26347 * @public
26348 * @since 4.5.029 (2009-03-19)
26349 */
26350 public function commitTransaction() {
26351 if (isset($this->objcopy)) {
26352 $this->objcopy->_destroy(true, true);
26353 unset($this->objcopy);
26354 }
26355 }
26356
26357 /**
26358 * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
26359 * @param $self (boolean) if true restores current class object to previous state without the need of reassignment via the returned value.
26360 * @return TCPDF object.
26361 * @public
26362 * @since 4.5.029 (2009-03-19)
26363 */
26364 public function rollbackTransaction($self=false) {
26365 if (isset($this->objcopy)) {
26366 if (isset($this->objcopy->diskcache) AND $this->objcopy->diskcache) {
26367 // truncate files to previous values
26368 foreach ($this->objcopy->cache_file_length as $file => $length) {
26369 $file = substr($file, 1);
26370 $handle = fopen($file, 'r+');
26371 ftruncate($handle, $length);
26372 }
26373 }
26374 $this->_destroy(true, true);
26375 if ($self) {
26376 $objvars = get_object_vars($this->objcopy);
26377 foreach ($objvars as $key => $value) {
26378 $this->$key = $value;
26379 }
26380 }
26381 return $this->objcopy;
26382 }
26383 return $this;
26384 }
26385
26386 /**
26387 * Creates a copy of a class object
26388 * @param $object (object) class object to be cloned
26389 * @return cloned object
26390 * @public
26391 * @since 4.5.029 (2009-03-19)
26392 */
26393 public function objclone($object) {
26394 return @clone($object);
26395 }
26396
26397 /**
26398 * Determine whether a string is empty.
26399 * @param $str (string) string to be checked
26400 * @return boolean true if string is empty
26401 * @public
26402 * @since 4.5.044 (2009-04-16)
26403 */
26404 public function empty_string($str) {
26405 return (is_null($str) OR (is_string($str) AND (strlen($str) == 0)));
26406 }
26407
26408 /**
26409 * Find position of last occurrence of a substring in a string
26410 * @param $haystack (string) The string to search in.
26411 * @param $needle (string) substring to search.
26412 * @param $offset (int) May be specified to begin searching an arbitrary number of characters into the string.
26413 * @return Returns the position where the needle exists. Returns FALSE if the needle was not found.
26414 * @public
26415 * @since 4.8.038 (2010-03-13)
26416 */
26417 public function revstrpos($haystack, $needle, $offset = 0) {
26418 $length = strlen($haystack);
26419 $offset = ($offset > 0)?($length - $offset):abs($offset);
26420 $pos = strpos(strrev($haystack), strrev($needle), $offset);
26421 return ($pos === false)?false:($length - $pos - strlen($needle));
26422 }
26423
26424 // --- MULTI COLUMNS METHODS -----------------------
26425
26426 /**
26427 * Set multiple columns of the same size
26428 * @param $numcols (int) number of columns (set to zero to disable columns mode)
26429 * @param $width (int) column width
26430 * @param $y (int) column starting Y position (leave empty for current Y position)
26431 * @public
26432 * @since 4.9.001 (2010-03-28)
26433 */
26434 public function setEqualColumns($numcols=0, $width=0, $y='') {
26435 $this->columns = array();
26436 if ($numcols < 2) {
26437 $numcols = 0;
26438 $this->columns = array();
26439 } else {
26440 // maximum column width
26441 $maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
26442 if (($width == 0) OR ($width > $maxwidth)) {
26443 $width = $maxwidth;
26444 }
26445 if ($this->empty_string($y)) {
26446 $y = $this->y;
26447 }
26448 // space between columns
26449 $space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
26450 // fill the columns array (with, space, starting Y position)
26451 for ($i = 0; $i < $numcols; ++$i) {
26452 $this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
26453 }
26454 }
26455 $this->num_columns = $numcols;
26456 $this->current_column = 0;
26457 $this->column_start_page = $this->page;
26458 $this->selectColumn(0);
26459 }
26460
26461 /**
26462 * Remove columns and reset page margins.
26463 * @public
26464 * @since 5.9.072 (2011-04-26)
26465 */
26466 public function resetColumns() {
26467 $this->lMargin = $this->original_lMargin;
26468 $this->rMargin = $this->original_rMargin;
26469 $this->setEqualColumns();
26470 }
26471
26472 /**
26473 * Set columns array.
26474 * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
26475 * @param $columns (array)
26476 * @public
26477 * @since 4.9.001 (2010-03-28)
26478 */
26479 public function setColumnsArray($columns) {
26480 $this->columns = $columns;
26481 $this->num_columns = count($columns);
26482 $this->current_column = 0;
26483 $this->column_start_page = $this->page;
26484 $this->selectColumn(0);
26485 }
26486
26487 /**
26488 * Set position at a given column
26489 * @param $col (int) column number (from 0 to getNumberOfColumns()-1); empty string = current column.
26490 * @public
26491 * @since 4.9.001 (2010-03-28)
26492 */
26493 public function selectColumn($col='') {
26494 if (is_string($col)) {
26495 $col = $this->current_column;
26496 } elseif ($col >= $this->num_columns) {
26497 $col = 0;
26498 }
26499 $xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
26500 $enable_thead = false;
26501 if ($this->num_columns > 1) {
26502 if ($col != $this->current_column) {
26503 // move Y pointer at the top of the column
26504 if ($this->column_start_page == $this->page) {
26505 $this->y = $this->columns[$col]['y'];
26506 } else {
26507 $this->y = $this->tMargin;
26508 }
26509 // Avoid to write table headers more than once
26510 if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
26511 $enable_thead = true;
26512 $this->maxselcol['page'] = $this->page;
26513 $this->maxselcol['column'] = $col;
26514 }
26515 }
26516 $xshift = $this->colxshift;
26517 // set X position of the current column by case
26518 $listindent = ($this->listindentlevel * $this->listindent);
26519 // calculate column X position
26520 $colpos = 0;
26521 for ($i = 0; $i < $col; ++$i) {
26522 $colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
26523 }
26524 if ($this->rtl) {
26525 $x = $this->w - $this->original_rMargin - $colpos;
26526 $this->rMargin = ($this->w - $x + $listindent);
26527 $this->lMargin = ($x - $this->columns[$col]['w']);
26528 $this->x = $x - $listindent;
26529 } else {
26530 $x = $this->original_lMargin + $colpos;
26531 $this->lMargin = ($x + $listindent);
26532 $this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
26533 $this->x = $x + $listindent;
26534 }
26535 $this->columns[$col]['x'] = $x;
26536 }
26537 $this->current_column = $col;
26538 // fix for HTML mode
26539 $this->newline = true;
26540 // print HTML table header (if any)
26541 if ((!$this->empty_string($this->thead)) AND (!$this->inthead)) {
26542 if ($enable_thead) {
26543 // print table header
26544 $this->writeHTML($this->thead, false, false, false, false, '');
26545 $this->y += $xshift['s']['V'];
26546 // store end of header position
26547 if (!isset($this->columns[$col]['th'])) {
26548 $this->columns[$col]['th'] = array();
26549 }
26550 $this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
26551 $this->lasth = 0;
26552 } elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
26553 $this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
26554 }
26555 }
26556 // account for an html table cell over multiple columns
26557 if ($this->rtl) {
26558 $this->rMargin += $xshift['x'];
26559 $this->x -= ($xshift['x'] + $xshift['p']['R']);
26560 } else {
26561 $this->lMargin += $xshift['x'];
26562 $this->x += $xshift['x'] + $xshift['p']['L'];
26563 }
26564 }
26565
26566 /**
26567 * Return the current column number
26568 * @return int current column number
26569 * @public
26570 * @since 5.5.011 (2010-07-08)
26571 */
26572 public function getColumn() {
26573 return $this->current_column;
26574 }
26575
26576 /**
26577 * Return the current number of columns.
26578 * @return int number of columns
26579 * @public
26580 * @since 5.8.018 (2010-08-25)
26581 */
26582 public function getNumberOfColumns() {
26583 return $this->num_columns;
26584 }
26585
26586 /**
26587 * Serialize an array of parameters to be used with TCPDF tag in HTML code.
26588 * @param $pararray (array) parameters array
26589 * @return sting containing serialized data
26590 * @public
26591 * @since 4.9.006 (2010-04-02)
26592 */
26593 public function serializeTCPDFtagParameters($pararray) {
26594 return urlencode(serialize($pararray));
26595 }
26596
26597 /**
26598 * Set Text rendering mode.
26599 * @param $stroke (int) outline size in user units (0 = disable).
26600 * @param $fill (boolean) if true fills the text (default).
26601 * @param $clip (boolean) if true activate clipping mode
26602 * @public
26603 * @since 4.9.008 (2009-04-02)
26604 */
26605 public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
26606 // Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
26607 // convert text rendering parameters
26608 if ($stroke < 0) {
26609 $stroke = 0;
26610 }
26611 if ($fill === true) {
26612 if ($stroke > 0) {
26613 if ($clip === true) {
26614 // Fill, then stroke text and add to path for clipping
26615 $textrendermode = 6;
26616 } else {
26617 // Fill, then stroke text
26618 $textrendermode = 2;
26619 }
26620 $textstrokewidth = $stroke;
26621 } else {
26622 if ($clip === true) {
26623 // Fill text and add to path for clipping
26624 $textrendermode = 4;
26625 } else {
26626 // Fill text
26627 $textrendermode = 0;
26628 }
26629 }
26630 } else {
26631 if ($stroke > 0) {
26632 if ($clip === true) {
26633 // Stroke text and add to path for clipping
26634 $textrendermode = 5;
26635 } else {
26636 // Stroke text
26637 $textrendermode = 1;
26638 }
26639 $textstrokewidth = $stroke;
26640 } else {
26641 if ($clip === true) {
26642 // Add text to path for clipping
26643 $textrendermode = 7;
26644 } else {
26645 // Neither fill nor stroke text (invisible)
26646 $textrendermode = 3;
26647 }
26648 }
26649 }
26650 $this->textrendermode = $textrendermode;
26651 $this->textstrokewidth = $stroke * $this->k;
26652 }
26653
26654 /**
26655 * Returns an array of chars containing soft hyphens.
26656 * @param $word (array) array of chars
26657 * @param $patterns (array) Array of hypenation patterns.
26658 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algoritm.
26659 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
26660 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
26661 * @param $charmin (int) Minimum word length to apply the hyphenation algoritm.
26662 * @param $charmax (int) Maximum length of broken piece of word.
26663 * @return array text with soft hyphens
26664 * @author Nicola Asuni
26665 * @since 4.9.012 (2010-04-12)
26666 * @protected
26667 */
26668 protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
26669 $hyphenword = array(); // hyphens positions
26670 $numchars = count($word);
26671 if ($numchars <= $charmin) {
26672 return $word;
26673 }
26674 $word_string = $this->UTF8ArrSubString($word);
26675 // some words will be returned as-is
26676 $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})(\]?)$/';
26677 if (preg_match($pattern, $word_string) > 0) {
26678 // email
26679 return $word;
26680 }
26681 $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})(\]?)$/';
26682 if (preg_match($pattern, $word_string) > 0) {
26683 // URL
26684 return $word;
26685 }
26686 if (isset($dictionary[$word_string])) {
26687 return $this->UTF8StringToArray($dictionary[$word_string]);
26688 }
26689 // suround word with '_' characters
26690 $tmpword = array_merge(array(95), $word, array(95));
26691 $tmpnumchars = $numchars + 2;
26692 $maxpos = $tmpnumchars - $charmin;
26693 for ($pos = 0; $pos < $maxpos; ++$pos) {
26694 $imax = min(($tmpnumchars - $pos), $charmax);
26695 for ($i = $charmin; $i <= $imax; ++$i) {
26696 $subword = strtolower($this->UTF8ArrSubString($tmpword, $pos, $pos + $i));
26697 if (isset($patterns[$subword])) {
26698 $pattern = $this->UTF8StringToArray($patterns[$subword]);
26699 $pattern_length = count($pattern);
26700 $digits = 1;
26701 for ($j = 0; $j < $pattern_length; ++$j) {
26702 // check if $pattern[$j] is a number
26703 if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
26704 if ($j == 0) {
26705 $zero = $pos - 1;
26706 } else {
26707 $zero = $pos + $j - $digits;
26708 }
26709 if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] != $pattern[$j])) {
26710 $hyphenword[$zero] = $this->unichr($pattern[$j]);
26711 }
26712 ++$digits;
26713 }
26714 }
26715 }
26716 }
26717 }
26718 $inserted = 0;
26719 $maxpos = $numchars - $rightmin;
26720 for ($i = $leftmin; $i <= $maxpos; ++$i) {
26721 if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
26722 // 173 = soft hyphen character
26723 array_splice($word, $i + $inserted, 0, 173);
26724 ++$inserted;
26725 }
26726 }
26727 return $word;
26728 }
26729
26730 /**
26731 * Returns an array of hyphenation patterns.
26732 * @param $file (string) TEX file containing hypenation patterns. TEX pattrns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
26733 * @return array of hyphenation patterns
26734 * @author Nicola Asuni
26735 * @since 4.9.012 (2010-04-12)
26736 * @public
26737 */
26738 public function getHyphenPatternsFromTEX($file) {
26739 // TEX patterns are available at:
26740 // http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
26741 $data = file_get_contents($file);
26742 $patterns = array();
26743 // remove comments
26744 $data = preg_replace('/\%[^\n]*/', '', $data);
26745 // extract the patterns part
26746 preg_match('/\\\\patterns\{([^\}]*)\}/i', $data, $matches);
26747 $data = trim(substr($matches[0], 10, -1));
26748 // extract each pattern
26749 $patterns_array = preg_split('/[\s]+/', $data);
26750 // create new language array of patterns
26751 $patterns = array();
26752 foreach($patterns_array as $val) {
26753 if (!$this->empty_string($val)) {
26754 $val = trim($val);
26755 $val = str_replace('\'', '\\\'', $val);
26756 $key = preg_replace('/[0-9]+/', '', $val);
26757 $patterns[$key] = $val;
26758 }
26759 }
26760 return $patterns;
26761 }
26762
26763 /**
26764 * Returns text with soft hyphens.
26765 * @param $text (string) text to process
26766 * @param $patterns (mixed) Array of hypenation patterns or a TEX file containing hypenation patterns. TEX patterns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
26767 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algoritm.
26768 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
26769 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
26770 * @param $charmin (int) Minimum word length to apply the hyphenation algoritm.
26771 * @param $charmax (int) Maximum length of broken piece of word.
26772 * @return array text with soft hyphens
26773 * @author Nicola Asuni
26774 * @since 4.9.012 (2010-04-12)
26775 * @public
26776 */
26777 public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
26778 $text = $this->unhtmlentities($text);
26779 $word = array(); // last word
26780 $txtarr = array(); // text to be returned
26781 $intag = false; // true if we are inside an HTML tag
26782 if (!is_array($patterns)) {
26783 $patterns = $this->getHyphenPatternsFromTEX($patterns);
26784 }
26785 // get array of characters
26786 $unichars = $this->UTF8StringToArray($text);
26787 // for each char
26788 foreach ($unichars as $char) {
26789 if ((!$intag) AND $this->unicode->uni_type[$char] == 'L') {
26790 // letter character
26791 $word[] = $char;
26792 } else {
26793 // other type of character
26794 if (!$this->empty_string($word)) {
26795 // hypenate the word
26796 $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
26797 $word = array();
26798 }
26799 $txtarr[] = $char;
26800 if (chr($char) == '<') {
26801 // we are inside an HTML tag
26802 $intag = true;
26803 } elseif ($intag AND (chr($char) == '>')) {
26804 // end of HTML tag
26805 $intag = false;
26806 }
26807 }
26808 }
26809 if (!$this->empty_string($word)) {
26810 // hypenate the word
26811 $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
26812 }
26813 // convert char array to string and return
26814 return $this->UTF8ArrSubString($txtarr);
26815 }
26816
26817 /**
26818 * Enable/disable rasterization of vector images using ImageMagick library.
26819 * @param $mode (boolean) if true enable rasterization, false otherwise.
26820 * @public
26821 * @since 5.0.000 (2010-04-27)
26822 */
26823 public function setRasterizeVectorImages($mode) {
26824 $this->rasterize_vector_images = $mode;
26825 }
26826
26827 /**
26828 * Get the Path-Painting Operators.
26829 * @param $style (string) Style of rendering. Possible values are:
26830 * <ul>
26831 * <li>S or D: Stroke the path.</li>
26832 * <li>s or d: Close and stroke the path.</li>
26833 * <li>f or F: Fill the path, using the nonzero winding number rule to determine the region to fill.</li>
26834 * <li>f* or F*: Fill the path, using the even-odd rule to determine the region to fill.</li>
26835 * <li>B or FD or DF: Fill and then stroke the path, using the nonzero winding number rule to determine the region to fill.</li>
26836 * <li>B* or F*D or DF*: Fill and then stroke the path, using the even-odd rule to determine the region to fill.</li>
26837 * <li>b or fd or df: Close, fill, and then stroke the path, using the nonzero winding number rule to determine the region to fill.</li>
26838 * <li>b or f*d or df*: Close, fill, and then stroke the path, using the even-odd rule to determine the region to fill.</li>
26839 * <li>CNZ: Clipping mode using the even-odd rule to determine which regions lie inside the clipping path.</li>
26840 * <li>CEO: Clipping mode using the nonzero winding number rule to determine which regions lie inside the clipping path</li>
26841 * <li>n: End the path object without filling or stroking it.</li>
26842 * </ul>
26843 * @param $default (string) default style
26844 * @author Nicola Asuni
26845 * @since 5.0.000 (2010-04-30)
26846 * @protected
26847 */
26848 protected function getPathPaintOperator($style, $default='S') {
26849 $op = '';
26850 switch($style) {
26851 case 'S':
26852 case 'D': {
26853 $op = 'S';
26854 break;
26855 }
26856 case 's':
26857 case 'd': {
26858 $op = 's';
26859 break;
26860 }
26861 case 'f':
26862 case 'F': {
26863 $op = 'f';
26864 break;
26865 }
26866 case 'f*':
26867 case 'F*': {
26868 $op = 'f*';
26869 break;
26870 }
26871 case 'B':
26872 case 'FD':
26873 case 'DF': {
26874 $op = 'B';
26875 break;
26876 }
26877 case 'B*':
26878 case 'F*D':
26879 case 'DF*': {
26880 $op = 'B*';
26881 break;
26882 }
26883 case 'b':
26884 case 'fd':
26885 case 'df': {
26886 $op = 'b';
26887 break;
26888 }
26889 case 'b*':
26890 case 'f*d':
26891 case 'df*': {
26892 $op = 'b*';
26893 break;
26894 }
26895 case 'CNZ': {
26896 $op = 'W n';
26897 break;
26898 }
26899 case 'CEO': {
26900 $op = 'W* n';
26901 break;
26902 }
26903 case 'n': {
26904 $op = 'n';
26905 break;
26906 }
26907 default: {
26908 if (!empty($default)) {
26909 $op = $this->getPathPaintOperator($default, '');
26910 } else {
26911 $op = '';
26912 }
26913 }
26914 }
26915 return $op;
26916 }
26917
26918 /**
26919 * Enable or disable default option for font subsetting.
26920 * @param $enable (boolean) if true enable font subsetting by default.
26921 * @author Nicola Asuni
26922 * @public
26923 * @since 5.3.002 (2010-06-07)
26924 */
26925 public function setFontSubsetting($enable=true) {
26926 if ($this->pdfa_mode) {
26927 $this->font_subsetting = false;
26928 } else {
26929 $this->font_subsetting = $enable ? true : false;
26930 }
26931 }
26932
26933 /**
26934 * Return the default option for font subsetting.
26935 * @return boolean default font subsetting state.
26936 * @author Nicola Asuni
26937 * @public
26938 * @since 5.3.002 (2010-06-07)
26939 */
26940 public function getFontSubsetting() {
26941 return $this->font_subsetting;
26942 }
26943
26944 /**
26945 * Left trim the input string
26946 * @param $str (string) string to trim
26947 * @param $replace (string) string that replace spaces.
26948 * @return left trimmed string
26949 * @author Nicola Asuni
26950 * @public
26951 * @since 5.8.000 (2010-08-11)
26952 */
26953 public function stringLeftTrim($str, $replace='') {
26954 return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
26955 }
26956
26957 /**
26958 * Right trim the input string
26959 * @param $str (string) string to trim
26960 * @param $replace (string) string that replace spaces.
26961 * @return right trimmed string
26962 * @author Nicola Asuni
26963 * @public
26964 * @since 5.8.000 (2010-08-11)
26965 */
26966 public function stringRightTrim($str, $replace='') {
26967 return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
26968 }
26969
26970 /**
26971 * Trim the input string
26972 * @param $str (string) string to trim
26973 * @param $replace (string) string that replace spaces.
26974 * @return trimmed string
26975 * @author Nicola Asuni
26976 * @public
26977 * @since 5.8.000 (2010-08-11)
26978 */
26979 public function stringTrim($str, $replace='') {
26980 $str = $this->stringLeftTrim($str, $replace);
26981 $str = $this->stringRightTrim($str, $replace);
26982 return $str;
26983 }
26984
26985 /**
26986 * Return true if the current font is unicode type.
26987 * @return true for unicode font, false otherwise.
26988 * @author Nicola Asuni
26989 * @public
26990 * @since 5.8.002 (2010-08-14)
26991 */
26992 public function isUnicodeFont() {
26993 return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
26994 }
26995
26996 /**
26997 * Return normalized font name
26998 * @param $fontfamily (string) property string containing font family names
26999 * @return string normalized font name
27000 * @author Nicola Asuni
27001 * @public
27002 * @since 5.8.004 (2010-08-17)
27003 */
27004 public function getFontFamilyName($fontfamily) {
27005 // remove spaces and symbols
27006 $fontfamily = preg_replace('/[^a-z0-9\,]/', '', strtolower($fontfamily));
27007 // extract all font names
27008 $fontslist = preg_split('/[,]/', $fontfamily);
27009 // find first valid font name
27010 foreach ($fontslist as $font) {
27011 // replace font variations
27012 $font = preg_replace('/italic$/', 'I', $font);
27013 $font = preg_replace('/oblique$/', 'I', $font);
27014 $font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
27015 // replace common family names and core fonts
27016 $pattern = array();
27017 $replacement = array();
27018 $pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
27019 $replacement[] = 'times';
27020 $pattern[] = '/^sansserif/';
27021 $replacement[] = 'helvetica';
27022 $pattern[] = '/^monospace/';
27023 $replacement[] = 'courier';
27024 $font = preg_replace($pattern, $replacement, $font);
27025 if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
27026 return $font;
27027 }
27028 }
27029 // return current font as default
27030 return $this->CurrentFont['fontkey'];
27031 }
27032
27033 /**
27034 * Start a new XObject Template.
27035 * 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).
27036 * 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.
27037 * Note: X,Y coordinates will be reset to 0,0.
27038 * @param $w (int) Template width in user units (empty string or zero = page width less margins).
27039 * @param $h (int) Template height in user units (empty string or zero = page height less margins).
27040 * @param $group (mixed) Set transparency group. Can be a boolean value or an array specifying optional parameters: 'CS' (solour space name), 'I' (boolean flag to indicate isolated group) and 'K' (boolean flag to indicate knockout group).
27041 * @return int the XObject Template ID in case of success or false in case of error.
27042 * @author Nicola Asuni
27043 * @public
27044 * @since 5.8.017 (2010-08-24)
27045 * @see endTemplate(), printTemplate()
27046 */
27047 public function startTemplate($w=0, $h=0, $group=false) {
27048 if ($this->inxobj) {
27049 // we are already inside an XObject template
27050 return false;
27051 }
27052 $this->inxobj = true;
27053 ++$this->n;
27054 // XObject ID
27055 $this->xobjid = 'XT'.$this->n;
27056 // object ID
27057 $this->xobjects[$this->xobjid] = array('n' => $this->n);
27058 // store current graphic state
27059 $this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
27060 // initialize data
27061 $this->xobjects[$this->xobjid]['intmrk'] = 0;
27062 $this->xobjects[$this->xobjid]['transfmrk'] = array();
27063 $this->xobjects[$this->xobjid]['outdata'] = '';
27064 $this->xobjects[$this->xobjid]['xobjects'] = array();
27065 $this->xobjects[$this->xobjid]['images'] = array();
27066 $this->xobjects[$this->xobjid]['fonts'] = array();
27067 $this->xobjects[$this->xobjid]['annotations'] = array();
27068 $this->xobjects[$this->xobjid]['extgstates'] = array();
27069 $this->xobjects[$this->xobjid]['gradients'] = array();
27070 $this->xobjects[$this->xobjid]['spot_colors'] = array();
27071 // set new environment
27072 $this->num_columns = 1;
27073 $this->current_column = 0;
27074 $this->SetAutoPageBreak(false);
27075 if (($w === '') OR ($w <= 0)) {
27076 $w = $this->w - $this->lMargin - $this->rMargin;
27077 }
27078 if (($h === '') OR ($h <= 0)) {
27079 $h = $this->h - $this->tMargin - $this->bMargin;
27080 }
27081 $this->xobjects[$this->xobjid]['x'] = 0;
27082 $this->xobjects[$this->xobjid]['y'] = 0;
27083 $this->xobjects[$this->xobjid]['w'] = $w;
27084 $this->xobjects[$this->xobjid]['h'] = $h;
27085 $this->w = $w;
27086 $this->h = $h;
27087 $this->wPt = $this->w * $this->k;
27088 $this->hPt = $this->h * $this->k;
27089 $this->fwPt = $this->wPt;
27090 $this->fhPt = $this->hPt;
27091 $this->x = 0;
27092 $this->y = 0;
27093 $this->lMargin = 0;
27094 $this->rMargin = 0;
27095 $this->tMargin = 0;
27096 $this->bMargin = 0;
27097 // set group mode
27098 $this->xobjects[$this->xobjid]['group'] = $group;
27099 return $this->xobjid;
27100 }
27101
27102 /**
27103 * End the current XObject Template started with startTemplate() and restore the previous graphic state.
27104 * 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).
27105 * 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.
27106 * @return int the XObject Template ID in case of success or false in case of error.
27107 * @author Nicola Asuni
27108 * @public
27109 * @since 5.8.017 (2010-08-24)
27110 * @see startTemplate(), printTemplate()
27111 */
27112 public function endTemplate() {
27113 if (!$this->inxobj) {
27114 // we are not inside a template
27115 return false;
27116 }
27117 $this->inxobj = false;
27118 // restore previous graphic state
27119 $this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
27120 return $this->xobjid;
27121 }
27122
27123 /**
27124 * Print an XObject Template.
27125 * You can print an XObject Template inside the currently opened Template.
27126 * 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).
27127 * 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.
27128 * @param $id (string) The ID of XObject Template to print.
27129 * @param $x (int) X position in user units (empty string = current x position)
27130 * @param $y (int) Y position in user units (empty string = current y position)
27131 * @param $w (int) Width in user units (zero = remaining page width)
27132 * @param $h (int) Height in user units (zero = remaining page height)
27133 * @param $align (string) Indicates the alignment of the pointer next to template insertion relative to template height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
27134 * @param $palign (string) Allows to center or align the template on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
27135 * @param $fitonpage (boolean) If true the template is resized to not exceed page dimensions.
27136 * @author Nicola Asuni
27137 * @public
27138 * @since 5.8.017 (2010-08-24)
27139 * @see startTemplate(), endTemplate()
27140 */
27141 public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
27142 if (!isset($this->xobjects[$id])) {
27143 $this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
27144 }
27145 if ($this->inxobj) {
27146 if ($id == $this->xobjid) {
27147 // close current template
27148 $this->endTemplate();
27149 } else {
27150 // use the template as resource for the template currently opened
27151 $this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
27152 }
27153 }
27154 // set default values
27155 if ($x === '') {
27156 $x = $this->x;
27157 }
27158 if ($y === '') {
27159 $y = $this->y;
27160 }
27161 // check page for no-write regions and adapt page margins if necessary
27162 list($x, $y) = $this->checkPageRegions($h, $x, $y);
27163 $ow = $this->xobjects[$id]['w'];
27164 $oh = $this->xobjects[$id]['h'];
27165 // calculate template width and height on document
27166 if (($w <= 0) AND ($h <= 0)) {
27167 $w = $ow;
27168 $h = $oh;
27169 } elseif ($w <= 0) {
27170 $w = $h * $ow / $oh;
27171 } elseif ($h <= 0) {
27172 $h = $w * $oh / $ow;
27173 }
27174 // fit the template on available space
27175 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
27176 // set page alignment
27177 $rb_y = $y + $h;
27178 // set alignment
27179 if ($this->rtl) {
27180 if ($palign == 'L') {
27181 $xt = $this->lMargin;
27182 } elseif ($palign == 'C') {
27183 $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
27184 } elseif ($palign == 'R') {
27185 $xt = $this->w - $this->rMargin - $w;
27186 } else {
27187 $xt = $x - $w;
27188 }
27189 $rb_x = $xt;
27190 } else {
27191 if ($palign == 'L') {
27192 $xt = $this->lMargin;
27193 } elseif ($palign == 'C') {
27194 $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
27195 } elseif ($palign == 'R') {
27196 $xt = $this->w - $this->rMargin - $w;
27197 } else {
27198 $xt = $x;
27199 }
27200 $rb_x = $xt + $w;
27201 }
27202 // print XObject Template + Transformation matrix
27203 $this->StartTransform();
27204 // translate and scale
27205 $sx = ($w / $this->xobjects[$id]['w']);
27206 $sy = ($h / $this->xobjects[$id]['h']);
27207 $tm = array();
27208 $tm[0] = $sx;
27209 $tm[1] = 0;
27210 $tm[2] = 0;
27211 $tm[3] = $sy;
27212 $tm[4] = $xt * $this->k;
27213 $tm[5] = ($this->h - $h - $y) * $this->k;
27214 $this->Transform($tm);
27215 // set object
27216 $this->_out('/'.$id.' Do');
27217 $this->StopTransform();
27218 // add annotations
27219 if (!empty($this->xobjects[$id]['annotations'])) {
27220 foreach ($this->xobjects[$id]['annotations'] as $annot) {
27221 // transform original coordinates
27222 $coordlt = $this->getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
27223 $ax = ($coordlt[4] / $this->k);
27224 $ay = ($this->h - $h - ($coordlt[5] / $this->k));
27225 $coordrb = $this->getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
27226 $aw = ($coordrb[4] / $this->k) - $ax;
27227 $ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
27228 $this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
27229 }
27230 }
27231 // set pointer to align the next text/objects
27232 switch($align) {
27233 case 'T': {
27234 $this->y = $y;
27235 $this->x = $rb_x;
27236 break;
27237 }
27238 case 'M': {
27239 $this->y = $y + round($h/2);
27240 $this->x = $rb_x;
27241 break;
27242 }
27243 case 'B': {
27244 $this->y = $rb_y;
27245 $this->x = $rb_x;
27246 break;
27247 }
27248 case 'N': {
27249 $this->SetY($rb_y);
27250 break;
27251 }
27252 default:{
27253 break;
27254 }
27255 }
27256 }
27257
27258 /**
27259 * Set the percentage of character stretching.
27260 * @param $perc (int) percentage of stretching (100 = no stretching)
27261 * @author Nicola Asuni
27262 * @public
27263 * @since 5.9.000 (2010-09-29)
27264 */
27265 public function setFontStretching($perc=100) {
27266 $this->font_stretching = $perc;
27267 }
27268
27269 /**
27270 * Get the percentage of character stretching.
27271 * @return float stretching value
27272 * @author Nicola Asuni
27273 * @public
27274 * @since 5.9.000 (2010-09-29)
27275 */
27276 public function getFontStretching() {
27277 return $this->font_stretching;
27278 }
27279
27280 /**
27281 * Set the amount to increase or decrease the space between characters in a text.
27282 * @param $spacing (float) amount to increase or decrease the space between characters in a text (0 = default spacing)
27283 * @author Nicola Asuni
27284 * @public
27285 * @since 5.9.000 (2010-09-29)
27286 */
27287 public function setFontSpacing($spacing=0) {
27288 $this->font_spacing = $spacing;
27289 }
27290
27291 /**
27292 * Get the amount to increase or decrease the space between characters in a text.
27293 * @return int font spacing (tracking/kerning) value
27294 * @author Nicola Asuni
27295 * @public
27296 * @since 5.9.000 (2010-09-29)
27297 */
27298 public function getFontSpacing() {
27299 return $this->font_spacing;
27300 }
27301
27302 /**
27303 * Return an array of no-write page regions
27304 * @return array of no-write page regions
27305 * @author Nicola Asuni
27306 * @public
27307 * @since 5.9.003 (2010-10-13)
27308 * @see setPageRegions(), addPageRegion()
27309 */
27310 public function getPageRegions() {
27311 return $this->page_regions;
27312 }
27313
27314 /**
27315 * Set no-write regions on page.
27316 * 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.
27317 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
27318 * You can set multiple regions for the same page.
27319 * @param $regions (array) array of no-write regions. For each region you can define an array as follow: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right). Omit this parameter to remove all regions.
27320 * @author Nicola Asuni
27321 * @public
27322 * @since 5.9.003 (2010-10-13)
27323 * @see addPageRegion(), getPageRegions()
27324 */
27325 public function setPageRegions($regions=array()) {
27326 // empty current regions array
27327 $this->page_regions = array();
27328 // add regions
27329 foreach ($regions as $data) {
27330 $this->addPageRegion($data);
27331 }
27332 }
27333
27334 /**
27335 * Add a single no-write region on selected page.
27336 * 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.
27337 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
27338 * You can set multiple regions for the same page.
27339 * @param $region (array) array of a single no-write region array: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right).
27340 * @author Nicola Asuni
27341 * @public
27342 * @since 5.9.003 (2010-10-13)
27343 * @see setPageRegions(), getPageRegions()
27344 */
27345 public function addPageRegion($region) {
27346 if (!isset($region['page']) OR empty($region['page'])) {
27347 $region['page'] = $this->page;
27348 }
27349 if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
27350 AND isset($region['yt']) AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
27351 AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
27352 $this->page_regions[] = $region;
27353 }
27354 }
27355
27356 /**
27357 * Remove a single no-write region.
27358 * @param $key (int) region key
27359 * @author Nicola Asuni
27360 * @public
27361 * @since 5.9.003 (2010-10-13)
27362 * @see setPageRegions(), getPageRegions()
27363 */
27364 public function removePageRegion($key) {
27365 if (isset($this->page_regions[$key])) {
27366 unset($this->page_regions[$key]);
27367 }
27368 }
27369
27370 /**
27371 * Check page for no-write regions and adapt current coordinates and page margins if necessary.
27372 * 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.
27373 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
27374 * @param $h (float) height of the text/image/object to print in user units
27375 * @param $x (float) current X coordinate in user units
27376 * @param $y (float) current Y coordinate in user units
27377 * @return array($x, $y)
27378 * @author Nicola Asuni
27379 * @protected
27380 * @since 5.9.003 (2010-10-13)
27381 */
27382 protected function checkPageRegions($h, $x, $y) {
27383 // set default values
27384 if ($x === '') {
27385 $x = $this->x;
27386 }
27387 if ($y === '') {
27388 $y = $this->y;
27389 }
27390 if (empty($this->page_regions)) {
27391 // no page regions defined
27392 return array($x, $y);
27393 }
27394 if (empty($h)) {
27395 $h = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
27396 }
27397 // check for page break
27398 if ($this->checkPageBreak($h, $y)) {
27399 // the content will be printed on a new page
27400 $x = $this->x;
27401 $y = $this->y;
27402 }
27403 if ($this->num_columns > 1) {
27404 if ($this->rtl) {
27405 $this->lMargin = $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w'];
27406 } else {
27407 $this->rMargin = $this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w'];
27408 }
27409 } else {
27410 if ($this->rtl) {
27411 $this->lMargin = $this->original_lMargin;
27412 } else {
27413 $this->rMargin = $this->original_rMargin;
27414 }
27415 }
27416 // adjust coordinates and page margins
27417 foreach ($this->page_regions as $regid => $regdata) {
27418 if ($regdata['page'] == $this->page) {
27419 // check region boundaries
27420 if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
27421 // Y is inside the region
27422 $minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
27423 $yt = max($y, $regdata['yt']);
27424 $yb = min(($yt + $h), $regdata['yb']);
27425 $xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
27426 $xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
27427 if ($regdata['side'] == 'L') { // left side
27428 $new_margin = max($xt, $xb);
27429 if ($this->lMargin < $new_margin) {
27430 if ($this->rtl) {
27431 // adjust left page margin
27432 $this->lMargin = $new_margin;
27433 }
27434 if ($x < $new_margin) {
27435 // adjust x position
27436 $x = $new_margin;
27437 if ($new_margin > ($this->w - $this->rMargin)) {
27438 // adjust y position
27439 $y = $regdata['yb'] - $h;
27440 }
27441 }
27442 }
27443 } elseif ($regdata['side'] == 'R') { // right side
27444 $new_margin = min($xt, $xb);
27445 if (($this->w - $this->rMargin) > $new_margin) {
27446 if (!$this->rtl) {
27447 // adjust right page margin
27448 $this->rMargin = ($this->w - $new_margin);
27449 }
27450 if ($x > $new_margin) {
27451 // adjust x position
27452 $x = $new_margin;
27453 if ($new_margin > $this->lMargin) {
27454 // adjust y position
27455 $y = $regdata['yb'] - $h;
27456 }
27457 }
27458 }
27459 }
27460 }
27461 }
27462 }
27463 return array($x, $y);
27464 }
27465
27466 // --- SVG METHODS ---------------------------------------------------------
27467
27468 /**
27469 * Embedd a Scalable Vector Graphics (SVG) image.
27470 * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
27471 * @param $file (string) Name of the SVG file or a '@' character followed by the SVG data string.
27472 * @param $x (float) Abscissa of the upper-left corner.
27473 * @param $y (float) Ordinate of the upper-left corner.
27474 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
27475 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
27476 * @param $link (mixed) URL or identifier returned by AddLink().
27477 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul> If the alignment is an empty string, then the pointer will be restored on the starting SVG position.
27478 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
27479 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
27480 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
27481 * @author Nicola Asuni
27482 * @since 5.0.000 (2010-05-02)
27483 * @public
27484 */
27485 public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
27486 if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
27487 // convert SVG to raster image using GD or ImageMagick libraries
27488 return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
27489 }
27490 if ($file{0} === '@') { // image from string
27491 $this->svgdir = '';
27492 $svgdata = substr($file, 1);
27493 } else { // SVG file
27494 $this->svgdir = dirname($file);
27495 $svgdata = file_get_contents($file);
27496 }
27497 if ($svgdata === false) {
27498 $this->Error('SVG file not found: '.$file);
27499 }
27500 if ($x === '') {
27501 $x = $this->x;
27502 }
27503 if ($y === '') {
27504 $y = $this->y;
27505 }
27506 // check page for no-write regions and adapt page margins if necessary
27507 list($x, $y) = $this->checkPageRegions($h, $x, $y);
27508 $k = $this->k;
27509 $ox = 0;
27510 $oy = 0;
27511 $ow = $w;
27512 $oh = $h;
27513 $aspect_ratio_align = 'xMidYMid';
27514 $aspect_ratio_ms = 'meet';
27515 $regs = array();
27516 // get original image width and height
27517 preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
27518 if (isset($regs[1]) AND !empty($regs[1])) {
27519 $tmp = array();
27520 if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
27521 $ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
27522 }
27523 $tmp = array();
27524 if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
27525 $oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
27526 }
27527 $tmp = array();
27528 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
27529 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
27530 }
27531 $tmp = array();
27532 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
27533 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
27534 }
27535 $tmp = array();
27536 $view_box = array();
27537 if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
27538 if (count($tmp) == 5) {
27539 array_shift($tmp);
27540 foreach ($tmp as $key => $val) {
27541 $view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
27542 }
27543 $ox = $view_box[0];
27544 $oy = $view_box[1];
27545 }
27546 // get aspect ratio
27547 $tmp = array();
27548 if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
27549 $aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
27550 switch (count($aspect_ratio)) {
27551 case 3: {
27552 $aspect_ratio_align = $aspect_ratio[1];
27553 $aspect_ratio_ms = $aspect_ratio[2];
27554 break;
27555 }
27556 case 2: {
27557 $aspect_ratio_align = $aspect_ratio[0];
27558 $aspect_ratio_ms = $aspect_ratio[1];
27559 break;
27560 }
27561 case 1: {
27562 $aspect_ratio_align = $aspect_ratio[0];
27563 $aspect_ratio_ms = 'meet';
27564 break;
27565 }
27566 }
27567 }
27568 }
27569 }
27570 // calculate image width and height on document
27571 if (($w <= 0) AND ($h <= 0)) {
27572 // convert image size to document unit
27573 $w = $ow;
27574 $h = $oh;
27575 } elseif ($w <= 0) {
27576 $w = $h * $ow / $oh;
27577 } elseif ($h <= 0) {
27578 $h = $w * $oh / $ow;
27579 }
27580 // fit the image on available space
27581 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
27582 if ($this->rasterize_vector_images) {
27583 // convert SVG to raster image using GD or ImageMagick libraries
27584 return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
27585 }
27586 // set alignment
27587 $this->img_rb_y = $y + $h;
27588 // set alignment
27589 if ($this->rtl) {
27590 if ($palign == 'L') {
27591 $ximg = $this->lMargin;
27592 } elseif ($palign == 'C') {
27593 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
27594 } elseif ($palign == 'R') {
27595 $ximg = $this->w - $this->rMargin - $w;
27596 } else {
27597 $ximg = $x - $w;
27598 }
27599 $this->img_rb_x = $ximg;
27600 } else {
27601 if ($palign == 'L') {
27602 $ximg = $this->lMargin;
27603 } elseif ($palign == 'C') {
27604 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
27605 } elseif ($palign == 'R') {
27606 $ximg = $this->w - $this->rMargin - $w;
27607 } else {
27608 $ximg = $x;
27609 }
27610 $this->img_rb_x = $ximg + $w;
27611 }
27612 // store current graphic vars
27613 $gvars = $this->getGraphicVars();
27614 // store SVG position and scale factors
27615 $svgoffset_x = ($ximg - $ox) * $this->k;
27616 $svgoffset_y = -($y - $oy) * $this->k;
27617 if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
27618 $ow = $view_box[2];
27619 $oh = $view_box[3];
27620 } else {
27621 if ($ow <= 0) {
27622 $ow = $w;
27623 }
27624 if ($oh <= 0) {
27625 $oh = $h;
27626 }
27627 }
27628 $svgscale_x = $w / $ow;
27629 $svgscale_y = $h / $oh;
27630 // scaling and alignment
27631 if ($aspect_ratio_align != 'none') {
27632 // store current scaling values
27633 $svgscale_old_x = $svgscale_x;
27634 $svgscale_old_y = $svgscale_y;
27635 // force uniform scaling
27636 if ($aspect_ratio_ms == 'slice') {
27637 // the entire viewport is covered by the viewBox
27638 if ($svgscale_x > $svgscale_y) {
27639 $svgscale_y = $svgscale_x;
27640 } elseif ($svgscale_x < $svgscale_y) {
27641 $svgscale_x = $svgscale_y;
27642 }
27643 } else { // meet
27644 // the entire viewBox is visible within the viewport
27645 if ($svgscale_x < $svgscale_y) {
27646 $svgscale_y = $svgscale_x;
27647 } elseif ($svgscale_x > $svgscale_y) {
27648 $svgscale_x = $svgscale_y;
27649 }
27650 }
27651 // correct X alignment
27652 switch (substr($aspect_ratio_align, 1, 3)) {
27653 case 'Min': {
27654 // do nothing
27655 break;
27656 }
27657 case 'Max': {
27658 $svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
27659 break;
27660 }
27661 default:
27662 case 'Mid': {
27663 $svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
27664 break;
27665 }
27666 }
27667 // correct Y alignment
27668 switch (substr($aspect_ratio_align, 5)) {
27669 case 'Min': {
27670 // do nothing
27671 break;
27672 }
27673 case 'Max': {
27674 $svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
27675 break;
27676 }
27677 default:
27678 case 'Mid': {
27679 $svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
27680 break;
27681 }
27682 }
27683 }
27684 // store current page break mode
27685 $page_break_mode = $this->AutoPageBreak;
27686 $page_break_margin = $this->getBreakMargin();
27687 $cell_padding = $this->cell_padding;
27688 $this->SetCellPadding(0);
27689 $this->SetAutoPageBreak(false);
27690 // save the current graphic state
27691 $this->_out('q'.$this->epsmarker);
27692 // set initial clipping mask
27693 $this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
27694 // scale and translate
27695 $e = $ox * $this->k * (1 - $svgscale_x);
27696 $f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
27697 $this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, $e + $svgoffset_x, $f + $svgoffset_y));
27698 // creates a new XML parser to be used by the other XML functions
27699 $this->parser = xml_parser_create('UTF-8');
27700 // the following function allows to use parser inside object
27701 xml_set_object($this->parser, $this);
27702 // disable case-folding for this XML parser
27703 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
27704 // sets the element handler functions for the XML parser
27705 xml_set_element_handler($this->parser, 'startSVGElementHandler', 'endSVGElementHandler');
27706 // sets the character data handler function for the XML parser
27707 xml_set_character_data_handler($this->parser, 'segSVGContentHandler');
27708 // start parsing an XML document
27709 if (!xml_parse($this->parser, $svgdata)) {
27710 $error_message = sprintf('SVG Error: %s at line %d', xml_error_string(xml_get_error_code($this->parser)), xml_get_current_line_number($this->parser));
27711 $this->Error($error_message);
27712 }
27713 // free this XML parser
27714 xml_parser_free($this->parser);
27715 // restore previous graphic state
27716 $this->_out($this->epsmarker.'Q');
27717 // restore graphic vars
27718 $this->setGraphicVars($gvars);
27719 $this->lasth = $gvars['lasth'];
27720 if (!empty($border)) {
27721 $bx = $this->x;
27722 $by = $this->y;
27723 $this->x = $ximg;
27724 if ($this->rtl) {
27725 $this->x += $w;
27726 }
27727 $this->y = $y;
27728 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
27729 $this->x = $bx;
27730 $this->y = $by;
27731 }
27732 if ($link) {
27733 $this->Link($ximg, $y, $w, $h, $link, 0);
27734 }
27735 // set pointer to align the next text/objects
27736 switch($align) {
27737 case 'T':{
27738 $this->y = $y;
27739 $this->x = $this->img_rb_x;
27740 break;
27741 }
27742 case 'M':{
27743 $this->y = $y + round($h/2);
27744 $this->x = $this->img_rb_x;
27745 break;
27746 }
27747 case 'B':{
27748 $this->y = $this->img_rb_y;
27749 $this->x = $this->img_rb_x;
27750 break;
27751 }
27752 case 'N':{
27753 $this->SetY($this->img_rb_y);
27754 break;
27755 }
27756 default:{
27757 // restore pointer to starting position
27758 $this->x = $gvars['x'];
27759 $this->y = $gvars['y'];
27760 $this->page = $gvars['page'];
27761 $this->current_column = $gvars['current_column'];
27762 $this->tMargin = $gvars['tMargin'];
27763 $this->bMargin = $gvars['bMargin'];
27764 $this->w = $gvars['w'];
27765 $this->h = $gvars['h'];
27766 $this->wPt = $gvars['wPt'];
27767 $this->hPt = $gvars['hPt'];
27768 $this->fwPt = $gvars['fwPt'];
27769 $this->fhPt = $gvars['fhPt'];
27770 break;
27771 }
27772 }
27773 $this->endlinex = $this->img_rb_x;
27774 // restore page break
27775 $this->SetAutoPageBreak($page_break_mode, $page_break_margin);
27776 $this->cell_padding = $cell_padding;
27777 }
27778
27779 /**
27780 * Get the tranformation matrix from SVG transform attribute
27781 * @param $attribute (string) transformation
27782 * @return array of transformations
27783 * @author Nicola Asuni
27784 * @since 5.0.000 (2010-05-02)
27785 * @protected
27786 */
27787 protected function getSVGTransformMatrix($attribute) {
27788 // identity matrix
27789 $tm = array(1, 0, 0, 1, 0, 0);
27790 $transform = array();
27791 if (preg_match_all('/(matrix|translate|scale|rotate|skewX|skewY)[\s]*\(([^\)]+)\)/si', $attribute, $transform, PREG_SET_ORDER) > 0) {
27792 foreach ($transform as $key => $data) {
27793 if (!empty($data[2])) {
27794 $a = 1;
27795 $b = 0;
27796 $c = 0;
27797 $d = 1;
27798 $e = 0;
27799 $f = 0;
27800 $regs = array();
27801 switch ($data[1]) {
27802 case 'matrix': {
27803 if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
27804 $a = $regs[1];
27805 $b = $regs[2];
27806 $c = $regs[3];
27807 $d = $regs[4];
27808 $e = $regs[5];
27809 $f = $regs[6];
27810 }
27811 break;
27812 }
27813 case 'translate': {
27814 if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
27815 $e = $regs[1];
27816 $f = $regs[2];
27817 } elseif (preg_match('/([a-z0-9\-\.]+)/si', $data[2], $regs)) {
27818 $e = $regs[1];
27819 }
27820 break;
27821 }
27822 case 'scale': {
27823 if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
27824 $a = $regs[1];
27825 $d = $regs[2];
27826 } elseif (preg_match('/([a-z0-9\-\.]+)/si', $data[2], $regs)) {
27827 $a = $regs[1];
27828 $d = $a;
27829 }
27830 break;
27831 }
27832 case 'rotate': {
27833 if (preg_match('/([0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
27834 $ang = deg2rad($regs[1]);
27835 $x = $regs[2];
27836 $y = $regs[3];
27837 $a = cos($ang);
27838 $b = sin($ang);
27839 $c = -$b;
27840 $d = $a;
27841 $e = ($x * (1 - $a)) - ($y * $c);
27842 $f = ($y * (1 - $d)) - ($x * $b);
27843 } elseif (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
27844 $ang = deg2rad($regs[1]);
27845 $a = cos($ang);
27846 $b = sin($ang);
27847 $c = -$b;
27848 $d = $a;
27849 $e = 0;
27850 $f = 0;
27851 }
27852 break;
27853 }
27854 case 'skewX': {
27855 if (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
27856 $c = tan(deg2rad($regs[1]));
27857 }
27858 break;
27859 }
27860 case 'skewY': {
27861 if (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
27862 $b = tan(deg2rad($regs[1]));
27863 }
27864 break;
27865 }
27866 }
27867 $tm = $this->getTransformationMatrixProduct($tm, array($a, $b, $c, $d, $e, $f));
27868 }
27869 }
27870 }
27871 return $tm;
27872 }
27873
27874 /**
27875 * Get the product of two SVG tranformation matrices
27876 * @param $ta (array) first SVG tranformation matrix
27877 * @param $tb (array) second SVG tranformation matrix
27878 * @return transformation array
27879 * @author Nicola Asuni
27880 * @since 5.0.000 (2010-05-02)
27881 * @protected
27882 */
27883 protected function getTransformationMatrixProduct($ta, $tb) {
27884 $tm = array();
27885 $tm[0] = ($ta[0] * $tb[0]) + ($ta[2] * $tb[1]);
27886 $tm[1] = ($ta[1] * $tb[0]) + ($ta[3] * $tb[1]);
27887 $tm[2] = ($ta[0] * $tb[2]) + ($ta[2] * $tb[3]);
27888 $tm[3] = ($ta[1] * $tb[2]) + ($ta[3] * $tb[3]);
27889 $tm[4] = ($ta[0] * $tb[4]) + ($ta[2] * $tb[5]) + $ta[4];
27890 $tm[5] = ($ta[1] * $tb[4]) + ($ta[3] * $tb[5]) + $ta[5];
27891 return $tm;
27892 }
27893
27894 /**
27895 * Convert SVG transformation matrix to PDF.
27896 * @param $tm (array) original SVG transformation matrix
27897 * @return array transformation matrix
27898 * @protected
27899 * @since 5.0.000 (2010-05-02)
27900 */
27901 protected function convertSVGtMatrix($tm) {
27902 $a = $tm[0];
27903 $b = -$tm[1];
27904 $c = -$tm[2];
27905 $d = $tm[3];
27906 $e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
27907 $f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
27908 $x = 0;
27909 $y = $this->h * $this->k;
27910 $e = ($x * (1 - $a)) - ($y * $c) + $e;
27911 $f = ($y * (1 - $d)) - ($x * $b) + $f;
27912 return array($a, $b, $c, $d, $e, $f);
27913 }
27914
27915 /**
27916 * Apply SVG graphic transformation matrix.
27917 * @param $tm (array) original SVG transformation matrix
27918 * @protected
27919 * @since 5.0.000 (2010-05-02)
27920 */
27921 protected function SVGTransform($tm) {
27922 $this->Transform($this->convertSVGtMatrix($tm));
27923 }
27924
27925 /**
27926 * Apply the requested SVG styles (*** TO BE COMPLETED ***)
27927 * @param $svgstyle (array) array of SVG styles to apply
27928 * @param $prevsvgstyle (array) array of previous SVG style
27929 * @param $x (int) X origin of the bounding box
27930 * @param $y (int) Y origin of the bounding box
27931 * @param $w (int) width of the bounding box
27932 * @param $h (int) height of the bounding box
27933 * @param $clip_function (string) clip function
27934 * @param $clip_params (array) array of parameters for clipping function
27935 * @return object style
27936 * @author Nicola Asuni
27937 * @since 5.0.000 (2010-05-02)
27938 * @protected
27939 */
27940 protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
27941 $objstyle = '';
27942 $minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
27943 if (!isset($svgstyle['opacity'])) {
27944 return $objstyle;
27945 }
27946 // clip-path
27947 $regs = array();
27948 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
27949 $clip_path = $this->svgclippaths[$regs[1]];
27950 foreach ($clip_path as $cp) {
27951 $this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
27952 }
27953 }
27954 // opacity
27955 if ($svgstyle['opacity'] != 1) {
27956 $this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
27957 }
27958 // color
27959 $fill_color = $this->convertHTMLColorToDec($svgstyle['color']);
27960 $this->SetFillColorArray($fill_color);
27961 // text color
27962 $text_color = $this->convertHTMLColorToDec($svgstyle['text-color']);
27963 $this->SetTextColorArray($text_color);
27964 // clip
27965 if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
27966 $top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
27967 $right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
27968 $bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
27969 $left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
27970 $cx = $x + $left;
27971 $cy = $y + $top;
27972 $cw = $w - $left - $right;
27973 $ch = $h - $top - $bottom;
27974 if ($svgstyle['clip-rule'] == 'evenodd') {
27975 $clip_rule = 'CNZ';
27976 } else {
27977 $clip_rule = 'CEO';
27978 }
27979 $this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
27980 }
27981 // fill
27982 $regs = array();
27983 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
27984 // gradient
27985 $gradient = $this->svggradients[$regs[1]];
27986 if (isset($gradient['xref'])) {
27987 // reference to another gradient definition
27988 $newgradient = $this->svggradients[$gradient['xref']];
27989 $newgradient['coords'] = $gradient['coords'];
27990 $newgradient['mode'] = $gradient['mode'];
27991 $newgradient['gradientUnits'] = $gradient['gradientUnits'];
27992 if (isset($gradient['gradientTransform'])) {
27993 $newgradient['gradientTransform'] = $gradient['gradientTransform'];
27994 }
27995 $gradient = $newgradient;
27996 }
27997 //save current Graphic State
27998 $this->_out('q');
27999 //set clipping area
28000 if (!empty($clip_function) AND method_exists($this, $clip_function)) {
28001 $bbox = call_user_func_array(array($this, $clip_function), $clip_params);
28002 if (is_array($bbox) AND (count($bbox) == 4)) {
28003 list($x, $y, $w, $h) = $bbox;
28004 }
28005 }
28006 if ($gradient['mode'] == 'measure') {
28007 if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
28008 $gtm = $gradient['gradientTransform'];
28009 // apply transformation matrix
28010 $xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
28011 $ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
28012 $xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
28013 $yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
28014 if (isset($gradient['coords'][4])) {
28015 $gradient['coords'][4] = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
28016 }
28017 $gradient['coords'][0] = $xa;
28018 $gradient['coords'][1] = $ya;
28019 $gradient['coords'][2] = $xb;
28020 $gradient['coords'][3] = $yb;
28021 }
28022 // convert SVG coordinates to user units
28023 $gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
28024 $gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
28025 $gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
28026 $gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
28027 if (isset($gradient['coords'][4])) {
28028 $gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
28029 }
28030 if ($w <= $minlen) {
28031 $w = $minlen;
28032 }
28033 if ($h <= $minlen) {
28034 $h = $minlen;
28035 }
28036 // shift units
28037 if ($gradient['gradientUnits'] == 'objectBoundingBox') {
28038 // convert to SVG coordinate system
28039 $gradient['coords'][0] += $x;
28040 $gradient['coords'][1] += $y;
28041 $gradient['coords'][2] += $x;
28042 $gradient['coords'][3] += $y;
28043 }
28044 // calculate percentages
28045 $gradient['coords'][0] = ($gradient['coords'][0] - $x) / $w;
28046 $gradient['coords'][1] = ($gradient['coords'][1] - $y) / $h;
28047 $gradient['coords'][2] = ($gradient['coords'][2] - $x) / $w;
28048 $gradient['coords'][3] = ($gradient['coords'][3] - $y) / $h;
28049 if (isset($gradient['coords'][4])) {
28050 $gradient['coords'][4] /= $w;
28051 }
28052 } elseif ($gradient['mode'] == 'percentage') {
28053 foreach($gradient['coords'] as $key => $val) {
28054 $gradient['coords'][$key] = (intval($val) / 100);
28055 if ($val < 0) {
28056 $gradient['coords'][$key] = 0;
28057 } elseif ($val > 1) {
28058 $gradient['coords'][$key] = 1;
28059 }
28060 }
28061 }
28062 if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
28063 // single color (no shading)
28064 $gradient['coords'][0] = 1;
28065 $gradient['coords'][1] = 0;
28066 $gradient['coords'][2] = 0.999;
28067 $gradient['coords'][3] = 0;
28068 }
28069 // swap Y coordinates
28070 $tmp = $gradient['coords'][1];
28071 $gradient['coords'][1] = $gradient['coords'][3];
28072 $gradient['coords'][3] = $tmp;
28073 // set transformation map for gradient
28074 if ($gradient['type'] == 3) {
28075 // circular gradient
28076 $cy = $this->h - $y - ($gradient['coords'][1] * ($w + $h));
28077 $this->_out(sprintf('%F 0 0 %F %F %F cm', $w*$this->k, $w*$this->k, $x*$this->k, $cy*$this->k));
28078 } else {
28079 $this->_out(sprintf('%F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k));
28080 }
28081 if (count($gradient['stops']) > 1) {
28082 $this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
28083 }
28084 } elseif ($svgstyle['fill'] != 'none') {
28085 $fill_color = $this->convertHTMLColorToDec($svgstyle['fill']);
28086 if ($svgstyle['fill-opacity'] != 1) {
28087 $this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
28088 }
28089 $this->SetFillColorArray($fill_color);
28090 if ($svgstyle['fill-rule'] == 'evenodd') {
28091 $objstyle .= 'F*';
28092 } else {
28093 $objstyle .= 'F';
28094 }
28095 }
28096 // stroke
28097 if ($svgstyle['stroke'] != 'none') {
28098 if ($svgstyle['stroke-opacity'] != 1) {
28099 $this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
28100 }
28101 $stroke_style = array(
28102 'color' => $this->convertHTMLColorToDec($svgstyle['stroke']),
28103 'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
28104 'cap' => $svgstyle['stroke-linecap'],
28105 'join' => $svgstyle['stroke-linejoin']
28106 );
28107 if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
28108 $stroke_style['dash'] = $svgstyle['stroke-dasharray'];
28109 }
28110 $this->SetLineStyle($stroke_style);
28111 $objstyle .= 'D';
28112 }
28113 // font
28114 $regs = array();
28115 if (!empty($svgstyle['font'])) {
28116 if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
28117 $font_family = $this->getFontFamilyName($regs[1]);
28118 } else {
28119 $font_family = $svgstyle['font-family'];
28120 }
28121 if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
28122 $font_size = trim($regs[1]);
28123 } else {
28124 $font_size = $svgstyle['font-size'];
28125 }
28126 if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
28127 $font_style = trim($regs[1]);
28128 } else {
28129 $font_style = $svgstyle['font-style'];
28130 }
28131 if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
28132 $font_weight = trim($regs[1]);
28133 } else {
28134 $font_weight = $svgstyle['font-weight'];
28135 }
28136 if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
28137 $font_stretch = trim($regs[1]);
28138 } else {
28139 $font_stretch = $svgstyle['font-stretch'];
28140 }
28141 if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
28142 $font_spacing = trim($regs[1]);
28143 } else {
28144 $font_spacing = $svgstyle['letter-spacing'];
28145 }
28146 } else {
28147 $font_family = $this->getFontFamilyName($svgstyle['font-family']);
28148 $font_size = $svgstyle['font-size'];
28149 $font_style = $svgstyle['font-style'];
28150 $font_weight = $svgstyle['font-weight'];
28151 $font_stretch = $svgstyle['font-stretch'];
28152 $font_spacing = $svgstyle['letter-spacing'];
28153 }
28154 $font_size = $this->getHTMLUnitToUnits($font_size, $prevsvgstyle['font-size'], $this->svgunit, false) * $this->k;
28155 $font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
28156 $font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
28157 switch ($font_style) {
28158 case 'italic': {
28159 $font_style = 'I';
28160 break;
28161 }
28162 case 'oblique': {
28163 $font_style = 'I';
28164 break;
28165 }
28166 default:
28167 case 'normal': {
28168 $font_style = '';
28169 break;
28170 }
28171 }
28172 switch ($font_weight) {
28173 case 'bold':
28174 case 'bolder': {
28175 $font_style .= 'B';
28176 break;
28177 }
28178 }
28179 switch ($svgstyle['text-decoration']) {
28180 case 'underline': {
28181 $font_style .= 'U';
28182 break;
28183 }
28184 case 'overline': {
28185 $font_style .= 'O';
28186 break;
28187 }
28188 case 'line-through': {
28189 $font_style .= 'D';
28190 break;
28191 }
28192 default:
28193 case 'none': {
28194 break;
28195 }
28196 }
28197 $this->SetFont($font_family, $font_style, $font_size);
28198 $this->setFontStretching($font_stretch);
28199 $this->setFontSpacing($font_spacing);
28200 return $objstyle;
28201 }
28202
28203 /**
28204 * Draws an SVG path
28205 * @param $d (string) attribute d of the path SVG element
28206 * @param $style (string) Style of rendering. Possible values are:
28207 * <ul>
28208 * <li>D or empty string: Draw (default).</li>
28209 * <li>F: Fill.</li>
28210 * <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
28211 * <li>DF or FD: Draw and fill.</li>
28212 * <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
28213 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
28214 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
28215 * </ul>
28216 * @return array of container box measures (x, y, w, h)
28217 * @author Nicola Asuni
28218 * @since 5.0.000 (2010-05-02)
28219 * @protected
28220 */
28221 protected function SVGPath($d, $style='') {
28222 // set fill/stroke style
28223 $op = $this->getPathPaintOperator($style, '');
28224 if (empty($op)) {
28225 return;
28226 }
28227 $paths = array();
28228 $d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
28229 preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
28230 $x = 0;
28231 $y = 0;
28232 $x1 = 0;
28233 $y1 = 0;
28234 $x2 = 0;
28235 $y2 = 0;
28236 $xmin = 2147483647;
28237 $xmax = 0;
28238 $ymin = 2147483647;
28239 $ymax = 0;
28240 $relcoord = false;
28241 $minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
28242 $firstcmd = true; // used to print first point
28243 // draw curve pieces
28244 foreach ($paths as $key => $val) {
28245 // get curve type
28246 $cmd = trim($val[1]);
28247 if (strtolower($cmd) == $cmd) {
28248 // use relative coordinated instead of absolute
28249 $relcoord = true;
28250 $xoffset = $x;
28251 $yoffset = $y;
28252 } else {
28253 $relcoord = false;
28254 $xoffset = 0;
28255 $yoffset = 0;
28256 }
28257 $params = array();
28258 if (isset($val[2])) {
28259 // get curve parameters
28260 $rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
28261 $params = array();
28262 foreach ($rawparams as $ck => $cp) {
28263 $params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
28264 if (abs($params[$ck]) < $minlen) {
28265 // aproximate little values to zero
28266 $params[$ck] = 0;
28267 }
28268 }
28269 }
28270 // store current origin point
28271 $x0 = $x;
28272 $y0 = $y;
28273 switch (strtoupper($cmd)) {
28274 case 'M': { // moveto
28275 foreach ($params as $ck => $cp) {
28276 if (($ck % 2) == 0) {
28277 $x = $cp + $xoffset;
28278 } else {
28279 $y = $cp + $yoffset;
28280 if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
28281 if ($ck == 1) {
28282 $this->_outPoint($x, $y);
28283 $firstcmd = false;
28284 } else {
28285 $this->_outLine($x, $y);
28286 }
28287 }
28288 $xmin = min($xmin, $x);
28289 $ymin = min($ymin, $y);
28290 $xmax = max($xmax, $x);
28291 $ymax = max($ymax, $y);
28292 if ($relcoord) {
28293 $xoffset = $x;
28294 $yoffset = $y;
28295 }
28296 }
28297 }
28298 break;
28299 }
28300 case 'L': { // lineto
28301 foreach ($params as $ck => $cp) {
28302 if (($ck % 2) == 0) {
28303 $x = $cp + $xoffset;
28304 } else {
28305 $y = $cp + $yoffset;
28306 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
28307 $this->_outLine($x, $y);
28308 }
28309 $xmin = min($xmin, $x);
28310 $ymin = min($ymin, $y);
28311 $xmax = max($xmax, $x);
28312 $ymax = max($ymax, $y);
28313 if ($relcoord) {
28314 $xoffset = $x;
28315 $yoffset = $y;
28316 }
28317 }
28318 }
28319 break;
28320 }
28321 case 'H': { // horizontal lineto
28322 foreach ($params as $ck => $cp) {
28323 $x = $cp + $xoffset;
28324 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
28325 $this->_outLine($x, $y);
28326 }
28327 $xmin = min($xmin, $x);
28328 $xmax = max($xmax, $x);
28329 if ($relcoord) {
28330 $xoffset = $x;
28331 }
28332 }
28333 break;
28334 }
28335 case 'V': { // vertical lineto
28336 foreach ($params as $ck => $cp) {
28337 $y = $cp + $yoffset;
28338 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
28339 $this->_outLine($x, $y);
28340 }
28341 $ymin = min($ymin, $y);
28342 $ymax = max($ymax, $y);
28343 if ($relcoord) {
28344 $yoffset = $y;
28345 }
28346 }
28347 break;
28348 }
28349 case 'C': { // curveto
28350 foreach ($params as $ck => $cp) {
28351 $params[$ck] = $cp;
28352 if ((($ck + 1) % 6) == 0) {
28353 $x1 = $params[($ck - 5)] + $xoffset;
28354 $y1 = $params[($ck - 4)] + $yoffset;
28355 $x2 = $params[($ck - 3)] + $xoffset;
28356 $y2 = $params[($ck - 2)] + $yoffset;
28357 $x = $params[($ck - 1)] + $xoffset;
28358 $y = $params[($ck)] + $yoffset;
28359 $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
28360 $xmin = min($xmin, $x, $x1, $x2);
28361 $ymin = min($ymin, $y, $y1, $y2);
28362 $xmax = max($xmax, $x, $x1, $x2);
28363 $ymax = max($ymax, $y, $y1, $y2);
28364 if ($relcoord) {
28365 $xoffset = $x;
28366 $yoffset = $y;
28367 }
28368 }
28369 }
28370 break;
28371 }
28372 case 'S': { // shorthand/smooth curveto
28373 foreach ($params as $ck => $cp) {
28374 $params[$ck] = $cp;
28375 if ((($ck + 1) % 4) == 0) {
28376 if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
28377 $x1 = (2 * $x) - $x2;
28378 $y1 = (2 * $y) - $y2;
28379 } else {
28380 $x1 = $x;
28381 $y1 = $y;
28382 }
28383 $x2 = $params[($ck - 3)] + $xoffset;
28384 $y2 = $params[($ck - 2)] + $yoffset;
28385 $x = $params[($ck - 1)] + $xoffset;
28386 $y = $params[($ck)] + $yoffset;
28387 $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
28388 $xmin = min($xmin, $x, $x1, $x2);
28389 $ymin = min($ymin, $y, $y1, $y2);
28390 $xmax = max($xmax, $x, $x1, $x2);
28391 $ymax = max($ymax, $y, $y1, $y2);
28392 if ($relcoord) {
28393 $xoffset = $x;
28394 $yoffset = $y;
28395 }
28396 }
28397 }
28398 break;
28399 }
28400 case 'Q': { // quadratic B�zier curveto
28401 foreach ($params as $ck => $cp) {
28402 $params[$ck] = $cp;
28403 if ((($ck + 1) % 4) == 0) {
28404 // convert quadratic points to cubic points
28405 $x1 = $params[($ck - 3)] + $xoffset;
28406 $y1 = $params[($ck - 2)] + $yoffset;
28407 $xa = ($x + (2 * $x1)) / 3;
28408 $ya = ($y + (2 * $y1)) / 3;
28409 $x = $params[($ck - 1)] + $xoffset;
28410 $y = $params[($ck)] + $yoffset;
28411 $xb = ($x + (2 * $x1)) / 3;
28412 $yb = ($y + (2 * $y1)) / 3;
28413 $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
28414 $xmin = min($xmin, $x, $xa, $xb);
28415 $ymin = min($ymin, $y, $ya, $yb);
28416 $xmax = max($xmax, $x, $xa, $xb);
28417 $ymax = max($ymax, $y, $ya, $yb);
28418 if ($relcoord) {
28419 $xoffset = $x;
28420 $yoffset = $y;
28421 }
28422 }
28423 }
28424 break;
28425 }
28426 case 'T': { // shorthand/smooth quadratic B�zier curveto
28427 foreach ($params as $ck => $cp) {
28428 $params[$ck] = $cp;
28429 if (($ck % 2) != 0) {
28430 if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
28431 $x1 = (2 * $x) - $x1;
28432 $y1 = (2 * $y) - $y1;
28433 } else {
28434 $x1 = $x;
28435 $y1 = $y;
28436 }
28437 // convert quadratic points to cubic points
28438 $xa = ($x + (2 * $x1)) / 3;
28439 $ya = ($y + (2 * $y1)) / 3;
28440 $x = $params[($ck - 1)] + $xoffset;
28441 $y = $params[($ck)] + $yoffset;
28442 $xb = ($x + (2 * $x1)) / 3;
28443 $yb = ($y + (2 * $y1)) / 3;
28444 $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
28445 $xmin = min($xmin, $x, $xa, $xb);
28446 $ymin = min($ymin, $y, $ya, $yb);
28447 $xmax = max($xmax, $x, $xa, $xb);
28448 $ymax = max($ymax, $y, $ya, $yb);
28449 if ($relcoord) {
28450 $xoffset = $x;
28451 $yoffset = $y;
28452 }
28453 }
28454 }
28455 break;
28456 }
28457 case 'A': { // elliptical arc
28458 foreach ($params as $ck => $cp) {
28459 $params[$ck] = $cp;
28460 if ((($ck + 1) % 7) == 0) {
28461 $x0 = $x;
28462 $y0 = $y;
28463 $rx = abs($params[($ck - 6)]);
28464 $ry = abs($params[($ck - 5)]);
28465 $ang = -$rawparams[($ck - 4)];
28466 $angle = deg2rad($ang);
28467 $fa = $rawparams[($ck - 3)]; // large-arc-flag
28468 $fs = $rawparams[($ck - 2)]; // sweep-flag
28469 $x = $params[($ck - 1)] + $xoffset;
28470 $y = $params[$ck] + $yoffset;
28471 if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
28472 // endpoints are almost identical
28473 $xmin = min($xmin, $x);
28474 $ymin = min($ymin, $y);
28475 $xmax = max($xmax, $x);
28476 $ymax = max($ymax, $y);
28477 } else {
28478 $cos_ang = cos($angle);
28479 $sin_ang = sin($angle);
28480 $a = (($x0 - $x) / 2);
28481 $b = (($y0 - $y) / 2);
28482 $xa = ($a * $cos_ang) - ($b * $sin_ang);
28483 $ya = ($a * $sin_ang) + ($b * $cos_ang);
28484 $rx2 = $rx * $rx;
28485 $ry2 = $ry * $ry;
28486 $xa2 = $xa * $xa;
28487 $ya2 = $ya * $ya;
28488 $delta = ($xa2 / $rx2) + ($ya2 / $ry2);
28489 if ($delta > 1) {
28490 $rx *= sqrt($delta);
28491 $ry *= sqrt($delta);
28492 $rx2 = $rx * $rx;
28493 $ry2 = $ry * $ry;
28494 }
28495 $numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
28496 if ($numerator < 0) {
28497 $root = 0;
28498 } else {
28499 $root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
28500 }
28501 if ($fa == $fs){
28502 $root *= -1;
28503 }
28504 $cax = $root * (($rx * $ya) / $ry);
28505 $cay = -$root * (($ry * $xa) / $rx);
28506 // coordinates of ellipse center
28507 $cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
28508 $cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
28509 // get angles
28510 $angs = $this->getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
28511 $dang = $this->getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
28512 if (($fs == 0) AND ($dang > 0)) {
28513 $dang -= (2 * M_PI);
28514 } elseif (($fs == 1) AND ($dang < 0)) {
28515 $dang += (2 * M_PI);
28516 }
28517 $angf = $angs - $dang;
28518 if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
28519 // reverse angles
28520 $tmp = $angs;
28521 $angs = $angf;
28522 $angf = $tmp;
28523 }
28524 $angs = round(rad2deg($angs), 6);
28525 $angf = round(rad2deg($angf), 6);
28526 // covent angles to positive values
28527 if (($angs < 0) AND ($angf < 0)) {
28528 $angs += 360;
28529 $angf += 360;
28530 }
28531 $pie = false;
28532 if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
28533 $pie = true;
28534 }
28535 list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
28536 $xmin = min($xmin, $x, $axmin);
28537 $ymin = min($ymin, $y, $aymin);
28538 $xmax = max($xmax, $x, $axmax);
28539 $ymax = max($ymax, $y, $aymax);
28540 }
28541 if ($relcoord) {
28542 $xoffset = $x;
28543 $yoffset = $y;
28544 }
28545 }
28546 }
28547 break;
28548 }
28549 case 'Z': {
28550 $this->_out('h');
28551 break;
28552 }
28553 }
28554 $firstcmd = false;
28555 } // end foreach
28556 if (!empty($op)) {
28557 $this->_out($op);
28558 }
28559 return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
28560 }
28561
28562 /**
28563 * Returns the angle in radiants between two vectors
28564 * @param $x1 (int) X coordinate of first vector point
28565 * @param $y1 (int) Y coordinate of first vector point
28566 * @param $x2 (int) X coordinate of second vector point
28567 * @param $y2 (int) Y coordinate of second vector point
28568 * @author Nicola Asuni
28569 * @since 5.0.000 (2010-05-04)
28570 * @protected
28571 */
28572 protected function getVectorsAngle($x1, $y1, $x2, $y2) {
28573 $dprod = ($x1 * $x2) + ($y1 * $y2);
28574 $dist1 = sqrt(($x1 * $x1) + ($y1 * $y1));
28575 $dist2 = sqrt(($x2 * $x2) + ($y2 * $y2));
28576 $angle = acos($dprod / ($dist1 * $dist2));
28577 if (is_nan($angle)) {
28578 $angle = M_PI;
28579 }
28580 if ((($x1 * $y2) - ($x2 * $y1)) < 0) {
28581 $angle *= -1;
28582 }
28583 return $angle;
28584 }
28585
28586 /**
28587 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
28588 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
28589 * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
28590 * @param $attribs (array) The third parameter, attribs, contains an associative array with the element's attributes (if any). The keys of this array are the attribute names, the values are the attribute values. Attribute names are case-folded on the same criteria as element names. Attribute values are not case-folded. The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). The first key in the array was the first attribute, and so on.
28591 * @param $ctm (array) tranformation matrix for clipping mode (starting transformation matrix).
28592 * @author Nicola Asuni
28593 * @since 5.0.000 (2010-05-02)
28594 * @protected
28595 */
28596 protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
28597 // check if we are in clip mode
28598 if ($this->svgclipmode) {
28599 $this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
28600 return;
28601 }
28602 if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
28603 if (!isset($attribs['id'])) {
28604 $attribs['id'] = 'DF_'.(count($this->svgdefs) + 1);
28605 }
28606 $this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
28607 return;
28608 }
28609 $clipping = false;
28610 if ($parser == 'clip-path') {
28611 // set clipping mode
28612 $clipping = true;
28613 }
28614 // get styling properties
28615 $prev_svgstyle = $this->svgstyles[(count($this->svgstyles) - 1)]; // previous style
28616 $svgstyle = $this->svgstyles[0]; // set default style
28617 if (isset($attribs['style']) AND !$this->empty_string($attribs['style'])) {
28618 // fix style for regular expression
28619 $attribs['style'] = ';'.$attribs['style'];
28620 }
28621 foreach ($prev_svgstyle as $key => $val) {
28622 if (in_array($key, $this->svginheritprop)) {
28623 // inherit previous value
28624 $svgstyle[$key] = $val;
28625 }
28626 if (isset($attribs[$key]) AND !$this->empty_string($attribs[$key])) {
28627 // specific attribute settings
28628 if ($attribs[$key] == 'inherit') {
28629 $svgstyle[$key] = $val;
28630 } else {
28631 $svgstyle[$key] = $attribs[$key];
28632 }
28633 } elseif (isset($attribs['style']) AND !$this->empty_string($attribs['style'])) {
28634 // CSS style syntax
28635 $attrval = array();
28636 if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
28637 if ($attrval[1] == 'inherit') {
28638 $svgstyle[$key] = $val;
28639 } else {
28640 $svgstyle[$key] = $attrval[1];
28641 }
28642 }
28643 }
28644 }
28645 // transformation matrix
28646 if (!empty($ctm)) {
28647 $tm = $ctm;
28648 } else {
28649 $tm = $this->svgstyles[(count($this->svgstyles) - 1)]['transfmatrix'];
28650 }
28651 if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
28652 $tm = $this->getTransformationMatrixProduct($tm, $this->getSVGTransformMatrix($attribs['transform']));
28653 }
28654 $svgstyle['transfmatrix'] = $tm;
28655 $invisible = false;
28656 if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
28657 // the current graphics element is invisible (nothing is painted)
28658 $invisible = true;
28659 }
28660 // process tag
28661 switch($name) {
28662 case 'defs': {
28663 $this->svgdefsmode = true;
28664 break;
28665 }
28666 // clipPath
28667 case 'clipPath': {
28668 if ($invisible) {
28669 break;
28670 }
28671 $this->svgclipmode = true;
28672 if (!isset($attribs['id'])) {
28673 $attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
28674 }
28675 $this->svgclipid = $attribs['id'];
28676 $this->svgclippaths[$this->svgclipid] = array();
28677 $this->svgcliptm[$this->svgclipid] = $tm;
28678 break;
28679 }
28680 case 'svg': {
28681 // start of SVG object
28682 break;
28683 }
28684 case 'g': {
28685 // group together related graphics elements
28686 array_push($this->svgstyles, $svgstyle);
28687 $this->StartTransform();
28688 $this->setSVGStyles($svgstyle, $prev_svgstyle);
28689 break;
28690 }
28691 case 'linearGradient': {
28692 if ($this->pdfa_mode) {
28693 break;
28694 }
28695 if (!isset($attribs['id'])) {
28696 $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
28697 }
28698 $this->svggradientid = $attribs['id'];
28699 $this->svggradients[$this->svggradientid] = array();
28700 $this->svggradients[$this->svggradientid]['type'] = 2;
28701 $this->svggradients[$this->svggradientid]['stops'] = array();
28702 if (isset($attribs['gradientUnits'])) {
28703 $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
28704 } else {
28705 $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
28706 }
28707 //$attribs['spreadMethod']
28708 $x1 = (isset($attribs['x1'])?$attribs['x1']:'0%');
28709 $y1 = (isset($attribs['y1'])?$attribs['y1']:'0%');
28710 $x2 = (isset($attribs['x2'])?$attribs['x2']:'100%');
28711 $y2 = (isset($attribs['y2'])?$attribs['y2']:'0%');
28712 if (substr($x1, -1) != '%') {
28713 $this->svggradients[$this->svggradientid]['mode'] = 'measure';
28714 } else {
28715 $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
28716 }
28717 if (isset($attribs['gradientTransform'])) {
28718 $this->svggradients[$this->svggradientid]['gradientTransform'] = $this->getSVGTransformMatrix($attribs['gradientTransform']);
28719 }
28720 $this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
28721 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
28722 // gradient is defined on another place
28723 $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
28724 }
28725 break;
28726 }
28727 case 'radialGradient': {
28728 if ($this->pdfa_mode) {
28729 break;
28730 }
28731 if (!isset($attribs['id'])) {
28732 $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
28733 }
28734 $this->svggradientid = $attribs['id'];
28735 $this->svggradients[$this->svggradientid] = array();
28736 $this->svggradients[$this->svggradientid]['type'] = 3;
28737 $this->svggradients[$this->svggradientid]['stops'] = array();
28738 if (isset($attribs['gradientUnits'])) {
28739 $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
28740 } else {
28741 $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
28742 }
28743 //$attribs['spreadMethod']
28744 $cx = (isset($attribs['cx'])?$attribs['cx']:0.5);
28745 $cy = (isset($attribs['cy'])?$attribs['cy']:0.5);
28746 $fx = (isset($attribs['fx'])?$attribs['fx']:$cx);
28747 $fy = (isset($attribs['fy'])?$attribs['fy']:$cy);
28748 $r = (isset($attribs['r'])?$attribs['r']:0.5);
28749 if (isset($attribs['cx']) AND (substr($attribs['cx'], -1) != '%')) {
28750 $this->svggradients[$this->svggradientid]['mode'] = 'measure';
28751 } else {
28752 $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
28753 }
28754 if (isset($attribs['gradientTransform'])) {
28755 $this->svggradients[$this->svggradientid]['gradientTransform'] = $this->getSVGTransformMatrix($attribs['gradientTransform']);
28756 }
28757 $this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
28758 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
28759 // gradient is defined on another place
28760 $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
28761 }
28762 break;
28763 }
28764 case 'stop': {
28765 // gradient stops
28766 if (substr($attribs['offset'], -1) == '%') {
28767 $offset = floatval(substr($attribs['offset'], -1)) / 100;
28768 } else {
28769 $offset = floatval($attribs['offset']);
28770 if ($offset > 1) {
28771 $offset /= 100;
28772 }
28773 }
28774 $stop_color = isset($svgstyle['stop-color'])?$this->convertHTMLColorToDec($svgstyle['stop-color']):'black';
28775 $opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
28776 $this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
28777 break;
28778 }
28779 // paths
28780 case 'path': {
28781 if ($invisible) {
28782 break;
28783 }
28784 if (isset($attribs['d'])) {
28785 $d = trim($attribs['d']);
28786 if (!empty($d)) {
28787 if ($clipping) {
28788 $this->SVGTransform($tm);
28789 $this->SVGPath($d, 'CNZ');
28790 } else {
28791 $this->StartTransform();
28792 $this->SVGTransform($tm);
28793 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, 0, 0, 1, 1, 'SVGPath', array($d, 'CNZ'));
28794 if (!empty($obstyle)) {
28795 $this->SVGPath($d, $obstyle);
28796 }
28797 $this->StopTransform();
28798 }
28799 }
28800 }
28801 break;
28802 }
28803 // shapes
28804 case 'rect': {
28805 if ($invisible) {
28806 break;
28807 }
28808 $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
28809 $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
28810 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
28811 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
28812 $rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
28813 $ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
28814 if ($clipping) {
28815 $this->SVGTransform($tm);
28816 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
28817 } else {
28818 $this->StartTransform();
28819 $this->SVGTransform($tm);
28820 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
28821 if (!empty($obstyle)) {
28822 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
28823 }
28824 $this->StopTransform();
28825 }
28826 break;
28827 }
28828 case 'circle': {
28829 if ($invisible) {
28830 break;
28831 }
28832 $cx = (isset($attribs['cx'])?$this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false):0);
28833 $cy = (isset($attribs['cy'])?$this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false):0);
28834 $r = (isset($attribs['r'])?$this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false):0);
28835 $x = $cx - $r;
28836 $y = $cy - $r;
28837 $w = 2 * $r;
28838 $h = $w;
28839 if ($clipping) {
28840 $this->SVGTransform($tm);
28841 $this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
28842 } else {
28843 $this->StartTransform();
28844 $this->SVGTransform($tm);
28845 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
28846 if (!empty($obstyle)) {
28847 $this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
28848 }
28849 $this->StopTransform();
28850 }
28851 break;
28852 }
28853 case 'ellipse': {
28854 if ($invisible) {
28855 break;
28856 }
28857 $cx = (isset($attribs['cx'])?$this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false):0);
28858 $cy = (isset($attribs['cy'])?$this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false):0);
28859 $rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
28860 $ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):0);
28861 $x = $cx - $rx;
28862 $y = $cy - $ry;
28863 $w = 2 * $rx;
28864 $h = 2 * $ry;
28865 if ($clipping) {
28866 $this->SVGTransform($tm);
28867 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
28868 } else {
28869 $this->StartTransform();
28870 $this->SVGTransform($tm);
28871 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
28872 if (!empty($obstyle)) {
28873 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
28874 }
28875 $this->StopTransform();
28876 }
28877 break;
28878 }
28879 case 'line': {
28880 if ($invisible) {
28881 break;
28882 }
28883 $x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
28884 $y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
28885 $x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
28886 $y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
28887 $x = $x1;
28888 $y = $y1;
28889 $w = abs($x2 - $x1);
28890 $h = abs($y2 - $y1);
28891 if (!$clipping) {
28892 $this->StartTransform();
28893 $this->SVGTransform($tm);
28894 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
28895 $this->Line($x1, $y1, $x2, $y2);
28896 $this->StopTransform();
28897 }
28898 break;
28899 }
28900 case 'polyline':
28901 case 'polygon': {
28902 if ($invisible) {
28903 break;
28904 }
28905 $points = (isset($attribs['points'])?$attribs['points']:'0 0');
28906 $points = trim($points);
28907 // note that point may use a complex syntax not covered here
28908 $points = preg_split('/[\,\s]+/si', $points);
28909 if (count($points) < 4) {
28910 break;
28911 }
28912 $p = array();
28913 $xmin = 2147483647;
28914 $xmax = 0;
28915 $ymin = 2147483647;
28916 $ymax = 0;
28917 foreach ($points as $key => $val) {
28918 $p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
28919 if (($key % 2) == 0) {
28920 // X coordinate
28921 $xmin = min($xmin, $p[$key]);
28922 $xmax = max($xmax, $p[$key]);
28923 } else {
28924 // Y coordinate
28925 $ymin = min($ymin, $p[$key]);
28926 $ymax = max($ymax, $p[$key]);
28927 }
28928 }
28929 $x = $xmin;
28930 $y = $ymin;
28931 $w = ($xmax - $xmin);
28932 $h = ($ymax - $ymin);
28933 if ($name == 'polyline') {
28934 $this->StartTransform();
28935 $this->SVGTransform($tm);
28936 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
28937 $this->PolyLine($p, 'D', array(), array());
28938 $this->StopTransform();
28939 } else { // polygon
28940 if ($clipping) {
28941 $this->SVGTransform($tm);
28942 $this->Polygon($p, 'CNZ', array(), array(), true);
28943 } else {
28944 $this->StartTransform();
28945 $this->SVGTransform($tm);
28946 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
28947 if (!empty($obstyle)) {
28948 $this->Polygon($p, $obstyle, array(), array(), true);
28949 }
28950 $this->StopTransform();
28951 }
28952 }
28953 break;
28954 }
28955 // image
28956 case 'image': {
28957 if ($invisible) {
28958 break;
28959 }
28960 if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
28961 break;
28962 }
28963 $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
28964 $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
28965 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
28966 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
28967 $img = $attribs['xlink:href'];
28968 if (!$clipping) {
28969 $this->StartTransform();
28970 $this->SVGTransform($tm);
28971 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
28972 if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
28973 // embedded image encoded as base64
28974 $img = '@'.base64_decode(substr($img, strlen($m[0])));
28975 } else {
28976 // fix image path
28977 if (!$this->empty_string($this->svgdir) AND (($img{0} == '.') OR (basename($img) == $img))) {
28978 // replace relative path with full server path
28979 $img = $this->svgdir.'/'.$img;
28980 }
28981 if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
28982 $findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
28983 if (($findroot === false) OR ($findroot > 1)) {
28984 if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
28985 $img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
28986 } else {
28987 $img = $_SERVER['DOCUMENT_ROOT'].$img;
28988 }
28989 }
28990 }
28991 $img = urldecode($img);
28992 $testscrtype = @parse_url($img);
28993 if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
28994 // convert URL to server path
28995 $img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
28996 }
28997 }
28998 $this->Image($img, $x, $y, $w, $h);
28999 $this->StopTransform();
29000 }
29001 break;
29002 }
29003 // text
29004 case 'text':
29005 case 'tspan': {
29006 $this->svgtextmode['invisible'] = $invisible;
29007 if ($invisible) {
29008 break;
29009 }
29010 array_push($this->svgstyles, $svgstyle);
29011 // only basic support - advanced features must be implemented
29012 $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):$this->x);
29013 $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):$this->y);
29014 $svgstyle['text-color'] = $svgstyle['fill'];
29015 $this->svgtext = '';
29016 if (isset($svgstyle['text-anchor'])) {
29017 $this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
29018 } else {
29019 $this->svgtextmode['text-anchor'] = 'start';
29020 }
29021 if (isset($svgstyle['direction'])) {
29022 if ($svgstyle['direction'] == 'rtl') {
29023 $this->svgtextmode['rtl'] = true;
29024 } else {
29025 $this->svgtextmode['rtl'] = false;
29026 }
29027 } else {
29028 $this->svgtextmode['rtl'] = false;
29029 }
29030 if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
29031 $this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
29032 } else {
29033 $this->svgtextmode['stroke'] = false;
29034 }
29035 $this->StartTransform();
29036 $this->SVGTransform($tm);
29037 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
29038 $this->x = $x;
29039 $this->y = $y;
29040 break;
29041 }
29042 // use
29043 case 'use': {
29044 if (isset($attribs['xlink:href'])) {
29045 $use = $this->svgdefs[substr($attribs['xlink:href'], 1)];
29046 if (isset($attribs['xlink:href'])) {
29047 unset($attribs['xlink:href']);
29048 }
29049 if (isset($attribs['id'])) {
29050 unset($attribs['id']);
29051 }
29052 $attribs = array_merge($use['attribs'], $attribs);
29053 $this->startSVGElementHandler($parser, $use['name'], $use['attribs']);
29054 }
29055 break;
29056 }
29057 default: {
29058 break;
29059 }
29060 } // end of switch
29061 }
29062
29063 /**
29064 * Sets the closing SVG element handler function for the XML parser.
29065 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
29066 * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
29067 * @author Nicola Asuni
29068 * @since 5.0.000 (2010-05-02)
29069 * @protected
29070 */
29071 protected function endSVGElementHandler($parser, $name) {
29072 switch($name) {
29073 case 'defs': {
29074 $this->svgdefsmode = false;
29075 break;
29076 }
29077 // clipPath
29078 case 'clipPath': {
29079 $this->svgclipmode = false;
29080 break;
29081 }
29082 case 'g': {
29083 // ungroup: remove last style from array
29084 array_pop($this->svgstyles);
29085 $this->StopTransform();
29086 break;
29087 }
29088 case 'text':
29089 case 'tspan': {
29090 if ($this->svgtextmode['invisible']) {
29091 // This implementation must be fixed to following the rule:
29092 // 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.
29093 break;
29094 }
29095 // print text
29096 $text = $this->svgtext;
29097 //$text = $this->stringTrim($text);
29098 $textlen = $this->GetStringWidth($text);
29099 if ($this->svgtextmode['text-anchor'] != 'start') {
29100 // check if string is RTL text
29101 if ($this->svgtextmode['text-anchor'] == 'end') {
29102 if ($this->svgtextmode['rtl']) {
29103 $this->x += $textlen;
29104 } else {
29105 $this->x -= $textlen;
29106 }
29107 } elseif ($this->svgtextmode['text-anchor'] == 'middle') {
29108 if ($this->svgtextmode['rtl']) {
29109 $this->x += ($textlen / 2);
29110 } else {
29111 $this->x -= ($textlen / 2);
29112 }
29113 }
29114 }
29115 $textrendermode = $this->textrendermode;
29116 $textstrokewidth = $this->textstrokewidth;
29117 $this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
29118 $this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
29119 // restore previous rendering mode
29120 $this->textrendermode = $textrendermode;
29121 $this->textstrokewidth = $textstrokewidth;
29122 $this->svgtext = '';
29123 $this->StopTransform();
29124 array_pop($this->svgstyles);
29125 break;
29126 }
29127 default: {
29128 break;
29129 }
29130 }
29131 }
29132
29133 /**
29134 * Sets the character data handler function for the XML parser.
29135 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
29136 * @param $data (string) The second parameter, data, contains the character data as a string.
29137 * @author Nicola Asuni
29138 * @since 5.0.000 (2010-05-02)
29139 * @protected
29140 */
29141 protected function segSVGContentHandler($parser, $data) {
29142 $this->svgtext .= $data;
29143 }
29144
29145 // --- END SVG METHODS -----------------------------------------------------
29146
29147 } // END OF TCPDF CLASS
29148
29149 //============================================================+
29150 // END OF FILE
29151 //============================================================+
29152