[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/tcpdf/ -> tcpdf.php (source)

   1  <?php
   2  //============================================================+
   3  // File name   : tcpdf.php
   4  // Version     : 6.2.12
   5  // Begin       : 2002-08-03
   6  // Last Update : 2015-06-18
   7  // Author      : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
   8  // License     : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
   9  // -------------------------------------------------------------------
  10  // Copyright (C) 2002-2015 Nicola Asuni - Tecnick.com LTD
  11  //
  12  // This file is part of TCPDF software library.
  13  //
  14  // TCPDF is free software: you can redistribute it and/or modify it
  15  // under the terms of the GNU Lesser General Public License as
  16  // published by the Free Software Foundation, either version 3 of the
  17  // License, or (at your option) any later version.
  18  //
  19  // TCPDF is distributed in the hope that it will be useful, but
  20  // WITHOUT ANY WARRANTY; without even the implied warranty of
  21  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  22  // See the GNU Lesser General Public License for more details.
  23  //
  24  // You should have received a copy of the License
  25  // along with TCPDF. If not, see
  26  // <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
  27  //
  28  // See LICENSE.TXT file for more information.
  29  // -------------------------------------------------------------------
  30  //
  31  // Description :
  32  //   This is a PHP class for generating PDF documents without requiring external extensions.
  33  //
  34  // NOTE:
  35  //   This class was originally derived in 2002 from the Public
  36  //   Domain FPDF class by Olivier Plathey (http://www.fpdf.org),
  37  //   but now is almost entirely rewritten and contains thousands of
  38  //   new lines of code and hundreds new features.
  39  //
  40  // Main features:
  41  //  * no external libraries are required for the basic functions;
  42  //  * all standard page formats, custom page formats, custom margins and units of measure;
  43  //  * UTF-8 Unicode and Right-To-Left languages;
  44  //  * TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;
  45  //  * font subsetting;
  46  //  * methods to publish some XHTML + CSS code, Javascript and Forms;
  47  //  * images, graphic (geometric figures) and transformation methods;
  48  //  * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)
  49  //  * 1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;
  50  //  * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;
  51  //  * automatic page header and footer management;
  52  //  * document encryption up to 256 bit and digital signature certifications;
  53  //  * transactions to UNDO commands;
  54  //  * PDF annotations, including links, text and file attachments;
  55  //  * text rendering modes (fill, stroke and clipping);
  56  //  * multiple columns mode;
  57  //  * no-write page regions;
  58  //  * bookmarks, named destinations and table of content;
  59  //  * text hyphenation;
  60  //  * text stretching and spacing (tracking);
  61  //  * automatic page break, line break and text alignments including justification;
  62  //  * automatic page numbering and page groups;
  63  //  * move and delete pages;
  64  //  * page compression (requires php-zlib extension);
  65  //  * XOBject Templates;
  66  //  * Layers and object visibility.
  67  //    * PDF/A-1b support
  68  //============================================================+
  69  
  70  /**
  71   * @file
  72   * This is a PHP class for generating PDF documents without requiring external extensions.<br>
  73   * TCPDF project (http://www.tcpdf.org) was originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
  74   * <h3>TCPDF main features are:</h3>
  75   * <ul>
  76   * <li>no external libraries are required for the basic functions;</li>
  77   * <li>all standard page formats, custom page formats, custom margins and units of measure;</li>
  78   * <li>UTF-8 Unicode and Right-To-Left languages;</li>
  79   * <li>TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;</li>
  80   * <li>font subsetting;</li>
  81   * <li>methods to publish some XHTML + CSS code, Javascript and Forms;</li>
  82   * <li>images, graphic (geometric figures) and transformation methods;
  83   * <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)</li>
  84   * <li>1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;</li>
  85   * <li>JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;</li>
  86   * <li>automatic page header and footer management;</li>
  87   * <li>document encryption up to 256 bit and digital signature certifications;</li>
  88   * <li>transactions to UNDO commands;</li>
  89   * <li>PDF annotations, including links, text and file attachments;</li>
  90   * <li>text rendering modes (fill, stroke and clipping);</li>
  91   * <li>multiple columns mode;</li>
  92   * <li>no-write page regions;</li>
  93   * <li>bookmarks, named destinations and table of content;</li>
  94   * <li>text hyphenation;</li>
  95   * <li>text stretching and spacing (tracking);</li>
  96   * <li>automatic page break, line break and text alignments including justification;</li>
  97   * <li>automatic page numbering and page groups;</li>
  98   * <li>move and delete pages;</li>
  99   * <li>page compression (requires php-zlib extension);</li>
 100   * <li>XOBject Templates;</li>
 101   * <li>Layers and object visibility;</li>
 102   * <li>PDF/A-1b support.</li>
 103   * </ul>
 104   * Tools to encode your unicode fonts are on fonts/utils directory.</p>
 105   * @package com.tecnick.tcpdf
 106   * @author Nicola Asuni
 107   * @version 6.2.8
 108   */
 109  
 110  // TCPDF configuration
 111  require_once(dirname(__FILE__).'/tcpdf_autoconfig.php');
 112  // TCPDF static font methods and data
 113  require_once(dirname(__FILE__).'/include/tcpdf_font_data.php');
 114  // TCPDF static font methods and data
 115  require_once(dirname(__FILE__).'/include/tcpdf_fonts.php');
 116  // TCPDF static color methods and data
 117  require_once(dirname(__FILE__).'/include/tcpdf_colors.php');
 118  // TCPDF static image methods and data
 119  require_once(dirname(__FILE__).'/include/tcpdf_images.php');
 120  // TCPDF static methods and data
 121  require_once(dirname(__FILE__).'/include/tcpdf_static.php');
 122  
 123  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 124  
 125  /**
 126   * @class TCPDF
 127   * PHP class for generating PDF documents without requiring external extensions.
 128   * TCPDF project (http://www.tcpdf.org) has been originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
 129   * @package com.tecnick.tcpdf
 130   * @brief PHP class for generating PDF documents without requiring external extensions.
 131   * @version 6.2.8
 132   * @author Nicola Asuni - info@tecnick.com
 133   */
 134  class TCPDF {
 135  
 136      // Protected properties
 137  
 138      /**
 139       * Current page number.
 140       * @protected
 141       */
 142      protected $page;
 143  
 144      /**
 145       * Current object number.
 146       * @protected
 147       */
 148      protected $n;
 149  
 150      /**
 151       * Array of object offsets.
 152       * @protected
 153       */
 154      protected $offsets = array();
 155  
 156      /**
 157       * Array of object IDs for each page.
 158       * @protected
 159       */
 160      protected $pageobjects = array();
 161  
 162      /**
 163       * Buffer holding in-memory PDF.
 164       * @protected
 165       */
 166      protected $buffer;
 167  
 168      /**
 169       * Array containing pages.
 170       * @protected
 171       */
 172      protected $pages = array();
 173  
 174      /**
 175       * Current document state.
 176       * @protected
 177       */
 178      protected $state;
 179  
 180      /**
 181       * Compression flag.
 182       * @protected
 183       */
 184      protected $compress;
 185  
 186      /**
 187       * Current page orientation (P = Portrait, L = Landscape).
 188       * @protected
 189       */
 190      protected $CurOrientation;
 191  
 192      /**
 193       * Page dimensions.
 194       * @protected
 195       */
 196      protected $pagedim = array();
 197  
 198      /**
 199       * Scale factor (number of points in user unit).
 200       * @protected
 201       */
 202      protected $k;
 203  
 204      /**
 205       * Width of page format in points.
 206       * @protected
 207       */
 208      protected $fwPt;
 209  
 210      /**
 211       * Height of page format in points.
 212       * @protected
 213       */
 214      protected $fhPt;
 215  
 216      /**
 217       * Current width of page in points.
 218       * @protected
 219       */
 220      protected $wPt;
 221  
 222      /**
 223       * Current height of page in points.
 224       * @protected
 225       */
 226      protected $hPt;
 227  
 228      /**
 229       * Current width of page in user unit.
 230       * @protected
 231       */
 232      protected $w;
 233  
 234      /**
 235       * Current height of page in user unit.
 236       * @protected
 237       */
 238      protected $h;
 239  
 240      /**
 241       * Left margin.
 242       * @protected
 243       */
 244      protected $lMargin;
 245  
 246      /**
 247       * Right margin.
 248       * @protected
 249       */
 250      protected $rMargin;
 251  
 252      /**
 253       * Cell left margin (used by regions).
 254       * @protected
 255       */
 256      protected $clMargin;
 257  
 258      /**
 259       * Cell right margin (used by regions).
 260       * @protected
 261       */
 262      protected $crMargin;
 263  
 264      /**
 265       * Top margin.
 266       * @protected
 267       */
 268      protected $tMargin;
 269  
 270      /**
 271       * Page break margin.
 272       * @protected
 273       */
 274      protected $bMargin;
 275  
 276      /**
 277       * Array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
 278       * @since 5.9.000 (2010-10-03)
 279       * @protected
 280       */
 281      protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
 282  
 283      /**
 284       * Array of cell margins ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
 285       * @since 5.9.000 (2010-10-04)
 286       * @protected
 287       */
 288      protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
 289  
 290      /**
 291       * Current horizontal position in user unit for cell positioning.
 292       * @protected
 293       */
 294      protected $x;
 295  
 296      /**
 297       * Current vertical position in user unit for cell positioning.
 298       * @protected
 299       */
 300      protected $y;
 301  
 302      /**
 303       * Height of last cell printed.
 304       * @protected
 305       */
 306      protected $lasth;
 307  
 308      /**
 309       * Line width in user unit.
 310       * @protected
 311       */
 312      protected $LineWidth;
 313  
 314      /**
 315       * Array of standard font names.
 316       * @protected
 317       */
 318      protected $CoreFonts;
 319  
 320      /**
 321       * Array of used fonts.
 322       * @protected
 323       */
 324      protected $fonts = array();
 325  
 326      /**
 327       * Array of font files.
 328       * @protected
 329       */
 330      protected $FontFiles = array();
 331  
 332      /**
 333       * Array of encoding differences.
 334       * @protected
 335       */
 336      protected $diffs = array();
 337  
 338      /**
 339       * Array of used images.
 340       * @protected
 341       */
 342      protected $images = array();
 343  
 344      /**
 345       * Depth of the svg tag, to keep track if the svg tag is a subtag or the root tag.
 346       * @protected
 347       */
 348      protected $svg_tag_depth = 0;
 349  
 350      /**
 351       * Array of Annotations in pages.
 352       * @protected
 353       */
 354      protected $PageAnnots = array();
 355  
 356      /**
 357       * Array of internal links.
 358       * @protected
 359       */
 360      protected $links = array();
 361  
 362      /**
 363       * Current font family.
 364       * @protected
 365       */
 366      protected $FontFamily;
 367  
 368      /**
 369       * Current font style.
 370       * @protected
 371       */
 372      protected $FontStyle;
 373  
 374      /**
 375       * Current font ascent (distance between font top and baseline).
 376       * @protected
 377       * @since 2.8.000 (2007-03-29)
 378       */
 379      protected $FontAscent;
 380  
 381      /**
 382       * Current font descent (distance between font bottom and baseline).
 383       * @protected
 384       * @since 2.8.000 (2007-03-29)
 385       */
 386      protected $FontDescent;
 387  
 388      /**
 389       * Underlining flag.
 390       * @protected
 391       */
 392      protected $underline;
 393  
 394      /**
 395       * Overlining flag.
 396       * @protected
 397       */
 398      protected $overline;
 399  
 400      /**
 401       * Current font info.
 402       * @protected
 403       */
 404      protected $CurrentFont;
 405  
 406      /**
 407       * Current font size in points.
 408       * @protected
 409       */
 410      protected $FontSizePt;
 411  
 412      /**
 413       * Current font size in user unit.
 414       * @protected
 415       */
 416      protected $FontSize;
 417  
 418      /**
 419       * Commands for drawing color.
 420       * @protected
 421       */
 422      protected $DrawColor;
 423  
 424      /**
 425       * Commands for filling color.
 426       * @protected
 427       */
 428      protected $FillColor;
 429  
 430      /**
 431       * Commands for text color.
 432       * @protected
 433       */
 434      protected $TextColor;
 435  
 436      /**
 437       * Indicates whether fill and text colors are different.
 438       * @protected
 439       */
 440      protected $ColorFlag;
 441  
 442      /**
 443       * Automatic page breaking.
 444       * @protected
 445       */
 446      protected $AutoPageBreak;
 447  
 448      /**
 449       * Threshold used to trigger page breaks.
 450       * @protected
 451       */
 452      protected $PageBreakTrigger;
 453  
 454      /**
 455       * Flag set when processing page header.
 456       * @protected
 457       */
 458      protected $InHeader = false;
 459  
 460      /**
 461       * Flag set when processing page footer.
 462       * @protected
 463       */
 464      protected $InFooter = false;
 465  
 466      /**
 467       * Zoom display mode.
 468       * @protected
 469       */
 470      protected $ZoomMode;
 471  
 472      /**
 473       * Layout display mode.
 474       * @protected
 475       */
 476      protected $LayoutMode;
 477  
 478      /**
 479       * If true set the document information dictionary in Unicode.
 480       * @protected
 481       */
 482      protected $docinfounicode = true;
 483  
 484      /**
 485       * Document title.
 486       * @protected
 487       */
 488      protected $title = '';
 489  
 490      /**
 491       * Document subject.
 492       * @protected
 493       */
 494      protected $subject = '';
 495  
 496      /**
 497       * Document author.
 498       * @protected
 499       */
 500      protected $author = '';
 501  
 502      /**
 503       * Document keywords.
 504       * @protected
 505       */
 506      protected $keywords = '';
 507  
 508      /**
 509       * Document creator.
 510       * @protected
 511       */
 512      protected $creator = '';
 513  
 514      /**
 515       * Starting page number.
 516       * @protected
 517       */
 518      protected $starting_page_number = 1;
 519  
 520      /**
 521       * The right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image.
 522       * @since 2002-07-31
 523       * @author Nicola Asuni
 524       * @protected
 525       */
 526      protected $img_rb_x;
 527  
 528      /**
 529       * The right-bottom corner Y coordinate of last inserted image.
 530       * @since 2002-07-31
 531       * @author Nicola Asuni
 532       * @protected
 533       */
 534      protected $img_rb_y;
 535  
 536      /**
 537       * Adjusting factor to convert pixels to user units.
 538       * @since 2004-06-14
 539       * @author Nicola Asuni
 540       * @protected
 541       */
 542      protected $imgscale = 1;
 543  
 544      /**
 545       * Boolean flag set to true when the input text is unicode (require unicode fonts).
 546       * @since 2005-01-02
 547       * @author Nicola Asuni
 548       * @protected
 549       */
 550      protected $isunicode = false;
 551  
 552      /**
 553       * PDF version.
 554       * @since 1.5.3
 555       * @protected
 556       */
 557      protected $PDFVersion = '1.7';
 558  
 559      /**
 560       * ID of the stored default header template (-1 = not set).
 561       * @protected
 562       */
 563      protected $header_xobjid = false;
 564  
 565      /**
 566       * If true reset the Header Xobject template at each page
 567       * @protected
 568       */
 569      protected $header_xobj_autoreset = false;
 570  
 571      /**
 572       * Minimum distance between header and top page margin.
 573       * @protected
 574       */
 575      protected $header_margin;
 576  
 577      /**
 578       * Minimum distance between footer and bottom page margin.
 579       * @protected
 580       */
 581      protected $footer_margin;
 582  
 583      /**
 584       * Original left margin value.
 585       * @protected
 586       * @since 1.53.0.TC013
 587       */
 588      protected $original_lMargin;
 589  
 590      /**
 591       * Original right margin value.
 592       * @protected
 593       * @since 1.53.0.TC013
 594       */
 595      protected $original_rMargin;
 596  
 597      /**
 598       * Default font used on page header.
 599       * @protected
 600       */
 601      protected $header_font;
 602  
 603      /**
 604       * Default font used on page footer.
 605       * @protected
 606       */
 607      protected $footer_font;
 608  
 609      /**
 610       * Language templates.
 611       * @protected
 612       */
 613      protected $l;
 614  
 615      /**
 616       * Barcode to print on page footer (only if set).
 617       * @protected
 618       */
 619      protected $barcode = false;
 620  
 621      /**
 622       * Boolean flag to print/hide page header.
 623       * @protected
 624       */
 625      protected $print_header = true;
 626  
 627      /**
 628       * Boolean flag to print/hide page footer.
 629       * @protected
 630       */
 631      protected $print_footer = true;
 632  
 633      /**
 634       * Header image logo.
 635       * @protected
 636       */
 637      protected $header_logo = '';
 638  
 639      /**
 640       * Width of header image logo in user units.
 641       * @protected
 642       */
 643      protected $header_logo_width = 30;
 644  
 645      /**
 646       * Title to be printed on default page header.
 647       * @protected
 648       */
 649      protected $header_title = '';
 650  
 651      /**
 652       * String to pring on page header after title.
 653       * @protected
 654       */
 655      protected $header_string = '';
 656  
 657      /**
 658       * Color for header text (RGB array).
 659       * @since 5.9.174 (2012-07-25)
 660       * @protected
 661       */
 662      protected $header_text_color = array(0,0,0);
 663  
 664      /**
 665       * Color for header line (RGB array).
 666       * @since 5.9.174 (2012-07-25)
 667       * @protected
 668       */
 669      protected $header_line_color = array(0,0,0);
 670  
 671      /**
 672       * Color for footer text (RGB array).
 673       * @since 5.9.174 (2012-07-25)
 674       * @protected
 675       */
 676      protected $footer_text_color = array(0,0,0);
 677  
 678      /**
 679       * Color for footer line (RGB array).
 680       * @since 5.9.174 (2012-07-25)
 681       * @protected
 682       */
 683      protected $footer_line_color = array(0,0,0);
 684  
 685      /**
 686       * Text shadow data array.
 687       * @since 5.9.174 (2012-07-25)
 688       * @protected
 689       */
 690      protected $txtshadow = array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal');
 691  
 692      /**
 693       * Default number of columns for html table.
 694       * @protected
 695       */
 696      protected $default_table_columns = 4;
 697  
 698      // variables for html parser
 699  
 700      /**
 701       * HTML PARSER: array to store current link and rendering styles.
 702       * @protected
 703       */
 704      protected $HREF = array();
 705  
 706      /**
 707       * List of available fonts on filesystem.
 708       * @protected
 709       */
 710      protected $fontlist = array();
 711  
 712      /**
 713       * Current foreground color.
 714       * @protected
 715       */
 716      protected $fgcolor;
 717  
 718      /**
 719       * HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
 720       * @protected
 721       */
 722      protected $listordered = array();
 723  
 724      /**
 725       * HTML PARSER: array count list items on nested lists.
 726       * @protected
 727       */
 728      protected $listcount = array();
 729  
 730      /**
 731       * HTML PARSER: current list nesting level.
 732       * @protected
 733       */
 734      protected $listnum = 0;
 735  
 736      /**
 737       * HTML PARSER: indent amount for lists.
 738       * @protected
 739       */
 740      protected $listindent = 0;
 741  
 742      /**
 743       * HTML PARSER: current list indententation level.
 744       * @protected
 745       */
 746      protected $listindentlevel = 0;
 747  
 748      /**
 749       * Current background color.
 750       * @protected
 751       */
 752      protected $bgcolor;
 753  
 754      /**
 755       * Temporary font size in points.
 756       * @protected
 757       */
 758      protected $tempfontsize = 10;
 759  
 760      /**
 761       * Spacer string for LI tags.
 762       * @protected
 763       */
 764      protected $lispacer = '';
 765  
 766      /**
 767       * Default encoding.
 768       * @protected
 769       * @since 1.53.0.TC010
 770       */
 771      protected $encoding = 'UTF-8';
 772  
 773      /**
 774       * PHP internal encoding.
 775       * @protected
 776       * @since 1.53.0.TC016
 777       */
 778      protected $internal_encoding;
 779  
 780      /**
 781       * Boolean flag to indicate if the document language is Right-To-Left.
 782       * @protected
 783       * @since 2.0.000
 784       */
 785      protected $rtl = false;
 786  
 787      /**
 788       * Boolean flag used to force RTL or LTR string direction.
 789       * @protected
 790       * @since 2.0.000
 791       */
 792      protected $tmprtl = false;
 793  
 794      // --- Variables used for document encryption:
 795  
 796      /**
 797       * IBoolean flag indicating whether document is protected.
 798       * @protected
 799       * @since 2.0.000 (2008-01-02)
 800       */
 801      protected $encrypted;
 802  
 803      /**
 804       * Array containing encryption settings.
 805       * @protected
 806       * @since 5.0.005 (2010-05-11)
 807       */
 808      protected $encryptdata = array();
 809  
 810      /**
 811       * Last RC4 key encrypted (cached for optimisation).
 812       * @protected
 813       * @since 2.0.000 (2008-01-02)
 814       */
 815      protected $last_enc_key;
 816  
 817      /**
 818       * Last RC4 computed key.
 819       * @protected
 820       * @since 2.0.000 (2008-01-02)
 821       */
 822      protected $last_enc_key_c;
 823  
 824      /**
 825       * File ID (used on document trailer).
 826       * @protected
 827       * @since 5.0.005 (2010-05-12)
 828       */
 829      protected $file_id;
 830  
 831      // --- bookmark ---
 832  
 833      /**
 834       * Outlines for bookmark.
 835       * @protected
 836       * @since 2.1.002 (2008-02-12)
 837       */
 838      protected $outlines = array();
 839  
 840      /**
 841       * Outline root for bookmark.
 842       * @protected
 843       * @since 2.1.002 (2008-02-12)
 844       */
 845      protected $OutlineRoot;
 846  
 847      // --- javascript and form ---
 848  
 849      /**
 850       * Javascript code.
 851       * @protected
 852       * @since 2.1.002 (2008-02-12)
 853       */
 854      protected $javascript = '';
 855  
 856      /**
 857       * Javascript counter.
 858       * @protected
 859       * @since 2.1.002 (2008-02-12)
 860       */
 861      protected $n_js;
 862  
 863      /**
 864       * line through state
 865       * @protected
 866       * @since 2.8.000 (2008-03-19)
 867       */
 868      protected $linethrough;
 869  
 870      /**
 871       * Array with additional document-wide usage rights for the document.
 872       * @protected
 873       * @since 5.8.014 (2010-08-23)
 874       */
 875      protected $ur = array();
 876  
 877      /**
 878       * DPI (Dot Per Inch) Document Resolution (do not change).
 879       * @protected
 880       * @since 3.0.000 (2008-03-27)
 881       */
 882      protected $dpi = 72;
 883  
 884      /**
 885       * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
 886       * @protected
 887       * @since 3.0.000 (2008-03-27)
 888       */
 889      protected $newpagegroup = array();
 890  
 891      /**
 892       * Array that contains the number of pages in each page group.
 893       * @protected
 894       * @since 3.0.000 (2008-03-27)
 895       */
 896      protected $pagegroups = array();
 897  
 898      /**
 899       * Current page group number.
 900       * @protected
 901       * @since 3.0.000 (2008-03-27)
 902       */
 903      protected $currpagegroup = 0;
 904  
 905      /**
 906       * Array of transparency objects and parameters.
 907       * @protected
 908       * @since 3.0.000 (2008-03-27)
 909       */
 910      protected $extgstates;
 911  
 912      /**
 913       * Set the default JPEG compression quality (1-100).
 914       * @protected
 915       * @since 3.0.000 (2008-03-27)
 916       */
 917      protected $jpeg_quality;
 918  
 919      /**
 920       * Default cell height ratio.
 921       * @protected
 922       * @since 3.0.014 (2008-05-23)
 923       */
 924      protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
 925  
 926      /**
 927       * PDF viewer preferences.
 928       * @protected
 929       * @since 3.1.000 (2008-06-09)
 930       */
 931      protected $viewer_preferences;
 932  
 933      /**
 934       * A name object specifying how the document should be displayed when opened.
 935       * @protected
 936       * @since 3.1.000 (2008-06-09)
 937       */
 938      protected $PageMode;
 939  
 940      /**
 941       * Array for storing gradient information.
 942       * @protected
 943       * @since 3.1.000 (2008-06-09)
 944       */
 945      protected $gradients = array();
 946  
 947      /**
 948       * Array used to store positions inside the pages buffer (keys are the page numbers).
 949       * @protected
 950       * @since 3.2.000 (2008-06-26)
 951       */
 952      protected $intmrk = array();
 953  
 954      /**
 955       * Array used to store positions inside the pages buffer (keys are the page numbers).
 956       * @protected
 957       * @since 5.7.000 (2010-08-03)
 958       */
 959      protected $bordermrk = array();
 960  
 961      /**
 962       * Array used to store page positions to track empty pages (keys are the page numbers).
 963       * @protected
 964       * @since 5.8.007 (2010-08-18)
 965       */
 966      protected $emptypagemrk = array();
 967  
 968      /**
 969       * Array used to store content positions inside the pages buffer (keys are the page numbers).
 970       * @protected
 971       * @since 4.6.021 (2009-07-20)
 972       */
 973      protected $cntmrk = array();
 974  
 975      /**
 976       * Array used to store footer positions of each page.
 977       * @protected
 978       * @since 3.2.000 (2008-07-01)
 979       */
 980      protected $footerpos = array();
 981  
 982      /**
 983       * Array used to store footer length of each page.
 984       * @protected
 985       * @since 4.0.014 (2008-07-29)
 986       */
 987      protected $footerlen = array();
 988  
 989      /**
 990       * Boolean flag to indicate if a new line is created.
 991       * @protected
 992       * @since 3.2.000 (2008-07-01)
 993       */
 994      protected $newline = true;
 995  
 996      /**
 997       * End position of the latest inserted line.
 998       * @protected
 999       * @since 3.2.000 (2008-07-01)
1000       */
1001      protected $endlinex = 0;
1002  
1003      /**
1004       * PDF string for width value of the last line.
1005       * @protected
1006       * @since 4.0.006 (2008-07-16)
1007       */
1008      protected $linestyleWidth = '';
1009  
1010      /**
1011       * PDF string for CAP value of the last line.
1012       * @protected
1013       * @since 4.0.006 (2008-07-16)
1014       */
1015      protected $linestyleCap = '0 J';
1016  
1017      /**
1018       * PDF string for join value of the last line.
1019       * @protected
1020       * @since 4.0.006 (2008-07-16)
1021       */
1022      protected $linestyleJoin = '0 j';
1023  
1024      /**
1025       * PDF string for dash value of the last line.
1026       * @protected
1027       * @since 4.0.006 (2008-07-16)
1028       */
1029      protected $linestyleDash = '[] 0 d';
1030  
1031      /**
1032       * Boolean flag to indicate if marked-content sequence is open.
1033       * @protected
1034       * @since 4.0.013 (2008-07-28)
1035       */
1036      protected $openMarkedContent = false;
1037  
1038      /**
1039       * Count the latest inserted vertical spaces on HTML.
1040       * @protected
1041       * @since 4.0.021 (2008-08-24)
1042       */
1043      protected $htmlvspace = 0;
1044  
1045      /**
1046       * Array of Spot colors.
1047       * @protected
1048       * @since 4.0.024 (2008-09-12)
1049       */
1050      protected $spot_colors = array();
1051  
1052      /**
1053       * Symbol used for HTML unordered list items.
1054       * @protected
1055       * @since 4.0.028 (2008-09-26)
1056       */
1057      protected $lisymbol = '';
1058  
1059      /**
1060       * String used to mark the beginning and end of EPS image blocks.
1061       * @protected
1062       * @since 4.1.000 (2008-10-18)
1063       */
1064      protected $epsmarker = 'x#!#EPS#!#x';
1065  
1066      /**
1067       * Array of transformation matrix.
1068       * @protected
1069       * @since 4.2.000 (2008-10-29)
1070       */
1071      protected $transfmatrix = array();
1072  
1073      /**
1074       * Current key for transformation matrix.
1075       * @protected
1076       * @since 4.8.005 (2009-09-17)
1077       */
1078      protected $transfmatrix_key = 0;
1079  
1080      /**
1081       * Booklet mode for double-sided pages.
1082       * @protected
1083       * @since 4.2.000 (2008-10-29)
1084       */
1085      protected $booklet = false;
1086  
1087      /**
1088       * Epsilon value used for float calculations.
1089       * @protected
1090       * @since 4.2.000 (2008-10-29)
1091       */
1092      protected $feps = 0.005;
1093  
1094      /**
1095       * Array used for custom vertical spaces for HTML tags.
1096       * @protected
1097       * @since 4.2.001 (2008-10-30)
1098       */
1099      protected $tagvspaces = array();
1100  
1101      /**
1102       * HTML PARSER: custom indent amount for lists. Negative value means disabled.
1103       * @protected
1104       * @since 4.2.007 (2008-11-12)
1105       */
1106      protected $customlistindent = -1;
1107  
1108      /**
1109       * Boolean flag to indicate if the border of the cell sides that cross the page should be removed.
1110       * @protected
1111       * @since 4.2.010 (2008-11-14)
1112       */
1113      protected $opencell = true;
1114  
1115      /**
1116       * Array of files to embedd.
1117       * @protected
1118       * @since 4.4.000 (2008-12-07)
1119       */
1120      protected $embeddedfiles = array();
1121  
1122      /**
1123       * Boolean flag to indicate if we are inside a PRE tag.
1124       * @protected
1125       * @since 4.4.001 (2008-12-08)
1126       */
1127      protected $premode = false;
1128  
1129      /**
1130       * Array used to store positions of graphics transformation blocks inside the page buffer.
1131       * keys are the page numbers
1132       * @protected
1133       * @since 4.4.002 (2008-12-09)
1134       */
1135      protected $transfmrk = array();
1136  
1137      /**
1138       * Default color for html links.
1139       * @protected
1140       * @since 4.4.003 (2008-12-09)
1141       */
1142      protected $htmlLinkColorArray = array(0, 0, 255);
1143  
1144      /**
1145       * Default font style to add to html links.
1146       * @protected
1147       * @since 4.4.003 (2008-12-09)
1148       */
1149      protected $htmlLinkFontStyle = 'U';
1150  
1151      /**
1152       * Counts the number of pages.
1153       * @protected
1154       * @since 4.5.000 (2008-12-31)
1155       */
1156      protected $numpages = 0;
1157  
1158      /**
1159       * Array containing page lengths in bytes.
1160       * @protected
1161       * @since 4.5.000 (2008-12-31)
1162       */
1163      protected $pagelen = array();
1164  
1165      /**
1166       * Counts the number of pages.
1167       * @protected
1168       * @since 4.5.000 (2008-12-31)
1169       */
1170      protected $numimages = 0;
1171  
1172      /**
1173       * Store the image keys.
1174       * @protected
1175       * @since 4.5.000 (2008-12-31)
1176       */
1177      protected $imagekeys = array();
1178  
1179      /**
1180       * Length of the buffer in bytes.
1181       * @protected
1182       * @since 4.5.000 (2008-12-31)
1183       */
1184      protected $bufferlen = 0;
1185  
1186      /**
1187       * Counts the number of fonts.
1188       * @protected
1189       * @since 4.5.000 (2009-01-02)
1190       */
1191      protected $numfonts = 0;
1192  
1193      /**
1194       * Store the font keys.
1195       * @protected
1196       * @since 4.5.000 (2009-01-02)
1197       */
1198      protected $fontkeys = array();
1199  
1200      /**
1201       * Store the font object IDs.
1202       * @protected
1203       * @since 4.8.001 (2009-09-09)
1204       */
1205      protected $font_obj_ids = array();
1206  
1207      /**
1208       * Store the fage status (true when opened, false when closed).
1209       * @protected
1210       * @since 4.5.000 (2009-01-02)
1211       */
1212      protected $pageopen = array();
1213  
1214      /**
1215       * Default monospace font.
1216       * @protected
1217       * @since 4.5.025 (2009-03-10)
1218       */
1219      protected $default_monospaced_font = 'courier';
1220  
1221      /**
1222       * Cloned copy of the current class object.
1223       * @protected
1224       * @since 4.5.029 (2009-03-19)
1225       */
1226      protected $objcopy;
1227  
1228      /**
1229       * Array used to store the lengths of cache files.
1230       * @protected
1231       * @since 4.5.029 (2009-03-19)
1232       */
1233      protected $cache_file_length = array();
1234  
1235      /**
1236       * Table header content to be repeated on each new page.
1237       * @protected
1238       * @since 4.5.030 (2009-03-20)
1239       */
1240      protected $thead = '';
1241  
1242      /**
1243       * Margins used for table header.
1244       * @protected
1245       * @since 4.5.030 (2009-03-20)
1246       */
1247      protected $theadMargins = array();
1248  
1249      /**
1250       * Boolean flag to enable document digital signature.
1251       * @protected
1252       * @since 4.6.005 (2009-04-24)
1253       */
1254      protected $sign = false;
1255  
1256      /**
1257       * Digital signature data.
1258       * @protected
1259       * @since 4.6.005 (2009-04-24)
1260       */
1261      protected $signature_data = array();
1262  
1263      /**
1264       * Digital signature max length.
1265       * @protected
1266       * @since 4.6.005 (2009-04-24)
1267       */
1268      protected $signature_max_length = 11742;
1269  
1270      /**
1271       * Data for digital signature appearance.
1272       * @protected
1273       * @since 5.3.011 (2010-06-16)
1274       */
1275      protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1276  
1277      /**
1278       * Array of empty digital signature appearances.
1279       * @protected
1280       * @since 5.9.101 (2011-07-06)
1281       */
1282      protected $empty_signature_appearance = array();
1283  
1284      /**
1285       * Boolean flag to enable document timestamping with TSA.
1286       * @protected
1287       * @since 6.0.085 (2014-06-19)
1288       */
1289      protected $tsa_timestamp = false;
1290  
1291      /**
1292       * Timestamping data.
1293       * @protected
1294       * @since 6.0.085 (2014-06-19)
1295       */
1296      protected $tsa_data = array();
1297  
1298      /**
1299       * Regular expression used to find blank characters (required for word-wrapping).
1300       * @protected
1301       * @since 4.6.006 (2009-04-28)
1302       */
1303      protected $re_spaces = '/[^\S\xa0]/';
1304  
1305      /**
1306       * Array of $re_spaces parts.
1307       * @protected
1308       * @since 5.5.011 (2010-07-09)
1309       */
1310      protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1311  
1312      /**
1313       * Digital signature object ID.
1314       * @protected
1315       * @since 4.6.022 (2009-06-23)
1316       */
1317      protected $sig_obj_id = 0;
1318  
1319      /**
1320       * ID of page objects.
1321       * @protected
1322       * @since 4.7.000 (2009-08-29)
1323       */
1324      protected $page_obj_id = array();
1325  
1326      /**
1327       * List of form annotations IDs.
1328       * @protected
1329       * @since 4.8.000 (2009-09-07)
1330       */
1331      protected $form_obj_id = array();
1332  
1333      /**
1334       * Deafult Javascript field properties. Possible values are described on official Javascript for Acrobat API reference. Annotation options can be directly specified using the 'aopt' entry.
1335       * @protected
1336       * @since 4.8.000 (2009-09-07)
1337       */
1338      protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1339  
1340      /**
1341       * Javascript objects array.
1342       * @protected
1343       * @since 4.8.000 (2009-09-07)
1344       */
1345      protected $js_objects = array();
1346  
1347      /**
1348       * Current form action (used during XHTML rendering).
1349       * @protected
1350       * @since 4.8.000 (2009-09-07)
1351       */
1352      protected $form_action = '';
1353  
1354      /**
1355       * Current form encryption type (used during XHTML rendering).
1356       * @protected
1357       * @since 4.8.000 (2009-09-07)
1358       */
1359      protected $form_enctype = 'application/x-www-form-urlencoded';
1360  
1361      /**
1362       * Current method to submit forms.
1363       * @protected
1364       * @since 4.8.000 (2009-09-07)
1365       */
1366      protected $form_mode = 'post';
1367  
1368      /**
1369       * List of fonts used on form fields (fontname => fontkey).
1370       * @protected
1371       * @since 4.8.001 (2009-09-09)
1372       */
1373      protected $annotation_fonts = array();
1374  
1375      /**
1376       * List of radio buttons parent objects.
1377       * @protected
1378       * @since 4.8.001 (2009-09-09)
1379       */
1380      protected $radiobutton_groups = array();
1381  
1382      /**
1383       * List of radio group objects IDs.
1384       * @protected
1385       * @since 4.8.001 (2009-09-09)
1386       */
1387      protected $radio_groups = array();
1388  
1389      /**
1390       * Text indentation value (used for text-indent CSS attribute).
1391       * @protected
1392       * @since 4.8.006 (2009-09-23)
1393       */
1394      protected $textindent = 0;
1395  
1396      /**
1397       * Store page number when startTransaction() is called.
1398       * @protected
1399       * @since 4.8.006 (2009-09-23)
1400       */
1401      protected $start_transaction_page = 0;
1402  
1403      /**
1404       * Store Y position when startTransaction() is called.
1405       * @protected
1406       * @since 4.9.001 (2010-03-28)
1407       */
1408      protected $start_transaction_y = 0;
1409  
1410      /**
1411       * True when we are printing the thead section on a new page.
1412       * @protected
1413       * @since 4.8.027 (2010-01-25)
1414       */
1415      protected $inthead = false;
1416  
1417      /**
1418       * Array of column measures (width, space, starting Y position).
1419       * @protected
1420       * @since 4.9.001 (2010-03-28)
1421       */
1422      protected $columns = array();
1423  
1424      /**
1425       * Number of colums.
1426       * @protected
1427       * @since 4.9.001 (2010-03-28)
1428       */
1429      protected $num_columns = 1;
1430  
1431      /**
1432       * Current column number.
1433       * @protected
1434       * @since 4.9.001 (2010-03-28)
1435       */
1436      protected $current_column = 0;
1437  
1438      /**
1439       * Starting page for columns.
1440       * @protected
1441       * @since 4.9.001 (2010-03-28)
1442       */
1443      protected $column_start_page = 0;
1444  
1445      /**
1446       * Maximum page and column selected.
1447       * @protected
1448       * @since 5.8.000 (2010-08-11)
1449       */
1450      protected $maxselcol = array('page' => 0, 'column' => 0);
1451  
1452      /**
1453       * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
1454       * @protected
1455       * @since 5.8.000 (2010-08-11)
1456       */
1457      protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1458  
1459      /**
1460       * Text rendering mode: 0 = Fill text; 1 = Stroke text; 2 = Fill, then stroke text; 3 = Neither fill nor stroke text (invisible); 4 = Fill text and add to path for clipping; 5 = Stroke text and add to path for clipping; 6 = Fill, then stroke text and add to path for clipping; 7 = Add text to path for clipping.
1461       * @protected
1462       * @since 4.9.008 (2010-04-03)
1463       */
1464      protected $textrendermode = 0;
1465  
1466      /**
1467       * Text stroke width in doc units.
1468       * @protected
1469       * @since 4.9.008 (2010-04-03)
1470       */
1471      protected $textstrokewidth = 0;
1472  
1473      /**
1474       * Current stroke color.
1475       * @protected
1476       * @since 4.9.008 (2010-04-03)
1477       */
1478      protected $strokecolor;
1479  
1480      /**
1481       * Default unit of measure for document.
1482       * @protected
1483       * @since 5.0.000 (2010-04-22)
1484       */
1485      protected $pdfunit = 'mm';
1486  
1487      /**
1488       * Boolean flag true when we are on TOC (Table Of Content) page.
1489       * @protected
1490       */
1491      protected $tocpage = false;
1492  
1493      /**
1494       * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1495       * @protected
1496       * @since 5.0.000 (2010-04-26)
1497       */
1498      protected $rasterize_vector_images = false;
1499  
1500      /**
1501       * Boolean flag: if true enables font subsetting by default.
1502       * @protected
1503       * @since 5.3.002 (2010-06-07)
1504       */
1505      protected $font_subsetting = true;
1506  
1507      /**
1508       * Array of default graphic settings.
1509       * @protected
1510       * @since 5.5.008 (2010-07-02)
1511       */
1512      protected $default_graphic_vars = array();
1513  
1514      /**
1515       * Array of XObjects.
1516       * @protected
1517       * @since 5.8.014 (2010-08-23)
1518       */
1519      protected $xobjects = array();
1520  
1521      /**
1522       * Boolean value true when we are inside an XObject.
1523       * @protected
1524       * @since 5.8.017 (2010-08-24)
1525       */
1526      protected $inxobj = false;
1527  
1528      /**
1529       * Current XObject ID.
1530       * @protected
1531       * @since 5.8.017 (2010-08-24)
1532       */
1533      protected $xobjid = '';
1534  
1535      /**
1536       * Percentage of character stretching.
1537       * @protected
1538       * @since 5.9.000 (2010-09-29)
1539       */
1540      protected $font_stretching = 100;
1541  
1542      /**
1543       * Increases or decreases the space between characters in a text by the specified amount (tracking).
1544       * @protected
1545       * @since 5.9.000 (2010-09-29)
1546       */
1547      protected $font_spacing = 0;
1548  
1549      /**
1550       * Array of no-write regions.
1551       * ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right)
1552       * @protected
1553       * @since 5.9.003 (2010-10-14)
1554       */
1555      protected $page_regions = array();
1556  
1557      /**
1558       * Boolean value true when page region check is active.
1559       * @protected
1560       */
1561      protected $check_page_regions = true;
1562  
1563      /**
1564       * Array of PDF layers data.
1565       * @protected
1566       * @since 5.9.102 (2011-07-13)
1567       */
1568      protected $pdflayers = array();
1569  
1570      /**
1571       * A dictionary of names and corresponding destinations (Dests key on document Catalog).
1572       * @protected
1573       * @since 5.9.097 (2011-06-23)
1574       */
1575      protected $dests = array();
1576  
1577      /**
1578       * Object ID for Named Destinations
1579       * @protected
1580       * @since 5.9.097 (2011-06-23)
1581       */
1582      protected $n_dests;
1583  
1584      /**
1585       * Embedded Files Names
1586       * @protected
1587       * @since 5.9.204 (2013-01-23)
1588       */
1589      protected $efnames = array();
1590  
1591      /**
1592       * Directory used for the last SVG image.
1593       * @protected
1594       * @since 5.0.000 (2010-05-05)
1595       */
1596      protected $svgdir = '';
1597  
1598      /**
1599       *  Deafult unit of measure for SVG.
1600       * @protected
1601       * @since 5.0.000 (2010-05-02)
1602       */
1603      protected $svgunit = 'px';
1604  
1605      /**
1606       * Array of SVG gradients.
1607       * @protected
1608       * @since 5.0.000 (2010-05-02)
1609       */
1610      protected $svggradients = array();
1611  
1612      /**
1613       * ID of last SVG gradient.
1614       * @protected
1615       * @since 5.0.000 (2010-05-02)
1616       */
1617      protected $svggradientid = 0;
1618  
1619      /**
1620       * Boolean value true when in SVG defs group.
1621       * @protected
1622       * @since 5.0.000 (2010-05-02)
1623       */
1624      protected $svgdefsmode = false;
1625  
1626      /**
1627       * Array of SVG defs.
1628       * @protected
1629       * @since 5.0.000 (2010-05-02)
1630       */
1631      protected $svgdefs = array();
1632  
1633      /**
1634       * Boolean value true when in SVG clipPath tag.
1635       * @protected
1636       * @since 5.0.000 (2010-04-26)
1637       */
1638      protected $svgclipmode = false;
1639  
1640      /**
1641       * Array of SVG clipPath commands.
1642       * @protected
1643       * @since 5.0.000 (2010-05-02)
1644       */
1645      protected $svgclippaths = array();
1646  
1647      /**
1648       * Array of SVG clipPath tranformation matrix.
1649       * @protected
1650       * @since 5.8.022 (2010-08-31)
1651       */
1652      protected $svgcliptm = array();
1653  
1654      /**
1655       * ID of last SVG clipPath.
1656       * @protected
1657       * @since 5.0.000 (2010-05-02)
1658       */
1659      protected $svgclipid = 0;
1660  
1661      /**
1662       * SVG text.
1663       * @protected
1664       * @since 5.0.000 (2010-05-02)
1665       */
1666      protected $svgtext = '';
1667  
1668      /**
1669       * SVG text properties.
1670       * @protected
1671       * @since 5.8.013 (2010-08-23)
1672       */
1673      protected $svgtextmode = array();
1674  
1675      /**
1676       * Array of SVG properties.
1677       * @protected
1678       * @since 5.0.000 (2010-05-02)
1679       */
1680      protected $svgstyles = array(array(
1681          'alignment-baseline' => 'auto',
1682          'baseline-shift' => 'baseline',
1683          'clip' => 'auto',
1684          'clip-path' => 'none',
1685          'clip-rule' => 'nonzero',
1686          'color' => 'black',
1687          'color-interpolation' => 'sRGB',
1688          'color-interpolation-filters' => 'linearRGB',
1689          'color-profile' => 'auto',
1690          'color-rendering' => 'auto',
1691          'cursor' => 'auto',
1692          'direction' => 'ltr',
1693          'display' => 'inline',
1694          'dominant-baseline' => 'auto',
1695          'enable-background' => 'accumulate',
1696          'fill' => 'black',
1697          'fill-opacity' => 1,
1698          'fill-rule' => 'nonzero',
1699          'filter' => 'none',
1700          'flood-color' => 'black',
1701          'flood-opacity' => 1,
1702          'font' => '',
1703          'font-family' => 'helvetica',
1704          'font-size' => 'medium',
1705          'font-size-adjust' => 'none',
1706          'font-stretch' => 'normal',
1707          'font-style' => 'normal',
1708          'font-variant' => 'normal',
1709          'font-weight' => 'normal',
1710          'glyph-orientation-horizontal' => '0deg',
1711          'glyph-orientation-vertical' => 'auto',
1712          'image-rendering' => 'auto',
1713          'kerning' => 'auto',
1714          'letter-spacing' => 'normal',
1715          'lighting-color' => 'white',
1716          'marker' => '',
1717          'marker-end' => 'none',
1718          'marker-mid' => 'none',
1719          'marker-start' => 'none',
1720          'mask' => 'none',
1721          'opacity' => 1,
1722          'overflow' => 'auto',
1723          'pointer-events' => 'visiblePainted',
1724          'shape-rendering' => 'auto',
1725          'stop-color' => 'black',
1726          'stop-opacity' => 1,
1727          'stroke' => 'none',
1728          'stroke-dasharray' => 'none',
1729          'stroke-dashoffset' => 0,
1730          'stroke-linecap' => 'butt',
1731          'stroke-linejoin' => 'miter',
1732          'stroke-miterlimit' => 4,
1733          'stroke-opacity' => 1,
1734          'stroke-width' => 1,
1735          'text-anchor' => 'start',
1736          'text-decoration' => 'none',
1737          'text-rendering' => 'auto',
1738          'unicode-bidi' => 'normal',
1739          'visibility' => 'visible',
1740          'word-spacing' => 'normal',
1741          'writing-mode' => 'lr-tb',
1742          'text-color' => 'black',
1743          'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1744          ));
1745  
1746      /**
1747       * If true force sRGB color profile for all document.
1748       * @protected
1749       * @since 5.9.121 (2011-09-28)
1750       */
1751      protected $force_srgb = false;
1752  
1753      /**
1754       * If true set the document to PDF/A mode.
1755       * @protected
1756       * @since 5.9.121 (2011-09-27)
1757       */
1758      protected $pdfa_mode = false;
1759  
1760      /**
1761       * Document creation date-time
1762       * @protected
1763       * @since 5.9.152 (2012-03-22)
1764       */
1765      protected $doc_creation_timestamp;
1766  
1767      /**
1768       * Document modification date-time
1769       * @protected
1770       * @since 5.9.152 (2012-03-22)
1771       */
1772      protected $doc_modification_timestamp;
1773  
1774      /**
1775       * Custom XMP data.
1776       * @protected
1777       * @since 5.9.128 (2011-10-06)
1778       */
1779      protected $custom_xmp = '';
1780  
1781      /**
1782       * Overprint mode array.
1783       * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1784       * @protected
1785       * @since 5.9.152 (2012-03-23)
1786       */
1787      protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1788  
1789      /**
1790       * Alpha mode array.
1791       * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1792       * @protected
1793       * @since 5.9.152 (2012-03-23)
1794       */
1795      protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1796  
1797      /**
1798       * Define the page boundaries boxes to be set on document.
1799       * @protected
1800       * @since 5.9.152 (2012-03-23)
1801       */
1802      protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1803  
1804      /**
1805       * If true print TCPDF meta link.
1806       * @protected
1807       * @since 5.9.152 (2012-03-23)
1808       */
1809      protected $tcpdflink = true;
1810  
1811      /**
1812       * Cache array for computed GD gamma values.
1813       * @protected
1814       * @since 5.9.1632 (2012-06-05)
1815       */
1816      protected $gdgammacache = array();
1817  
1818      //------------------------------------------------------------
1819      // METHODS
1820      //------------------------------------------------------------
1821  
1822      /**
1823       * This is the class constructor.
1824       * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1825       * 
1826       * IMPORTANT: Please note that this method sets the mb_internal_encoding to ASCII, so if you are using the mbstring module functions with TCPDF you need to correctly set/unset the mb_internal_encoding when needed.
1827       * 
1828       * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
1829       * @param $unit (string) User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
1830       * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
1831       * @param $unicode (boolean) TRUE means that the input text is unicode (default = true)
1832       * @param $encoding (string) Charset encoding (used only when converting back html entities); default is UTF-8.
1833       * @param $diskcache (boolean) DEPRECATED FEATURE
1834       * @param $pdfa (boolean) If TRUE set the document to PDF/A mode.
1835       * @public
1836       * @see getPageSizeFromFormat(), setPageFormat()
1837       */
1838  	public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
1839          /* Set internal character encoding to ASCII */
1840          if (function_exists('mb_internal_encoding') AND mb_internal_encoding()) {
1841              $this->internal_encoding = mb_internal_encoding();
1842              mb_internal_encoding('ASCII');
1843          }
1844          // set file ID for trailer
1845          $serformat = (is_array($format) ? json_encode($format) : $format);
1846          $this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
1847          $this->font_obj_ids = array();
1848          $this->page_obj_id = array();
1849          $this->form_obj_id = array();
1850          // set pdf/a mode
1851          $this->pdfa_mode = $pdfa;
1852          $this->force_srgb = false;
1853          // set language direction
1854          $this->rtl = false;
1855          $this->tmprtl = false;
1856          // some checks
1857          $this->_dochecks();
1858          // initialization of properties
1859          $this->isunicode = $unicode;
1860          $this->page = 0;
1861          $this->transfmrk[0] = array();
1862          $this->pagedim = array();
1863          $this->n = 2;
1864          $this->buffer = '';
1865          $this->pages = array();
1866          $this->state = 0;
1867          $this->fonts = array();
1868          $this->FontFiles = array();
1869          $this->diffs = array();
1870          $this->images = array();
1871          $this->links = array();
1872          $this->gradients = array();
1873          $this->InFooter = false;
1874          $this->lasth = 0;
1875          $this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
1876          $this->FontStyle = '';
1877          $this->FontSizePt = 12;
1878          $this->underline = false;
1879          $this->overline = false;
1880          $this->linethrough = false;
1881          $this->DrawColor = '0 G';
1882          $this->FillColor = '0 g';
1883          $this->TextColor = '0 g';
1884          $this->ColorFlag = false;
1885          $this->pdflayers = array();
1886          // encryption values
1887          $this->encrypted = false;
1888          $this->last_enc_key = '';
1889          // standard Unicode fonts
1890          $this->CoreFonts = array(
1891              'courier'=>'Courier',
1892              'courierB'=>'Courier-Bold',
1893              'courierI'=>'Courier-Oblique',
1894              'courierBI'=>'Courier-BoldOblique',
1895              'helvetica'=>'Helvetica',
1896              'helveticaB'=>'Helvetica-Bold',
1897              'helveticaI'=>'Helvetica-Oblique',
1898              'helveticaBI'=>'Helvetica-BoldOblique',
1899              'times'=>'Times-Roman',
1900              'timesB'=>'Times-Bold',
1901              'timesI'=>'Times-Italic',
1902              'timesBI'=>'Times-BoldItalic',
1903              'symbol'=>'Symbol',
1904              'zapfdingbats'=>'ZapfDingbats'
1905          );
1906          // set scale factor
1907          $this->setPageUnit($unit);
1908          // set page format and orientation
1909          $this->setPageFormat($format, $orientation);
1910          // page margins (1 cm)
1911          $margin = 28.35 / $this->k;
1912          $this->SetMargins($margin, $margin);
1913          $this->clMargin = $this->lMargin;
1914          $this->crMargin = $this->rMargin;
1915          // internal cell padding
1916          $cpadding = $margin / 10;
1917          $this->setCellPaddings($cpadding, 0, $cpadding, 0);
1918          // cell margins
1919          $this->setCellMargins(0, 0, 0, 0);
1920          // line width (0.2 mm)
1921          $this->LineWidth = 0.57 / $this->k;
1922          $this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
1923          $this->linestyleCap = '0 J';
1924          $this->linestyleJoin = '0 j';
1925          $this->linestyleDash = '[] 0 d';
1926          // automatic page break
1927          $this->SetAutoPageBreak(true, (2 * $margin));
1928          // full width display mode
1929          $this->SetDisplayMode('fullwidth');
1930          // compression
1931          $this->SetCompression();
1932          // set default PDF version number
1933          $this->setPDFVersion();
1934          $this->tcpdflink = true;
1935          $this->encoding = $encoding;
1936          $this->HREF = array();
1937          $this->getFontsList();
1938          $this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
1939          $this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
1940          $this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
1941          $this->extgstates = array();
1942          $this->setTextShadow();
1943          // signature
1944          $this->sign = false;
1945          $this->tsa_timestamp = false;
1946          $this->tsa_data = array();
1947          $this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
1948          $this->empty_signature_appearance = array();
1949          // user's rights
1950          $this->ur['enabled'] = false;
1951          $this->ur['document'] = '/FullSave';
1952          $this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
1953          $this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
1954          $this->ur['signature'] = '/Modify';
1955          $this->ur['ef'] = '/Create/Delete/Modify/Import';
1956          $this->ur['formex'] = '';
1957          // set default JPEG quality
1958          $this->jpeg_quality = 75;
1959          // initialize some settings
1960          TCPDF_FONTS::utf8Bidi(array(''), '', false, $this->isunicode, $this->CurrentFont);
1961          // set default font
1962          $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
1963          $this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1964          $this->setFooterFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1965          // check if PCRE Unicode support is enabled
1966          if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
1967              // PCRE unicode support is turned ON
1968              // \s     : any whitespace character
1969              // \p{Z}  : any separator
1970              // \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
1971              // \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
1972              //$this->setSpacesRE('/(?!\xa0)[\s\p{Z}\p{Lo}]/u');
1973              $this->setSpacesRE('/(?!\xa0)[\s\p{Z}]/u');
1974          } else {
1975              // PCRE unicode support is turned OFF
1976              $this->setSpacesRE('/[^\S\xa0]/');
1977          }
1978          $this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1979          // set document creation and modification timestamp
1980          $this->doc_creation_timestamp = time();
1981          $this->doc_modification_timestamp = $this->doc_creation_timestamp;
1982          // get default graphic vars
1983          $this->default_graphic_vars = $this->getGraphicVars();
1984          $this->header_xobj_autoreset = false;
1985          $this->custom_xmp = '';
1986          // Call cleanup method after script execution finishes or exit() is called.
1987          // NOTE: This will not be executed if the process is killed with a SIGTERM or SIGKILL signal.
1988          register_shutdown_function(array($this, '_destroy'), true);
1989      }
1990  
1991      /**
1992       * Default destructor.
1993       * @public
1994       * @since 1.53.0.TC016
1995       */
1996  	public function __destruct() {
1997          // restore internal encoding
1998          if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) {
1999              mb_internal_encoding($this->internal_encoding);
2000          }
2001          // cleanup
2002          $this->_destroy(true);
2003      }
2004  
2005      /**
2006       * Set the units of measure for the document.
2007       * @param $unit (string) User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
2008       * @public
2009       * @since 3.0.015 (2008-06-06)
2010       */
2011  	public function setPageUnit($unit) {
2012          $unit = strtolower($unit);
2013          //Set scale factor
2014          switch ($unit) {
2015              // points
2016              case 'px':
2017              case 'pt': {
2018                  $this->k = 1;
2019                  break;
2020              }
2021              // millimeters
2022              case 'mm': {
2023                  $this->k = $this->dpi / 25.4;
2024                  break;
2025              }
2026              // centimeters
2027              case 'cm': {
2028                  $this->k = $this->dpi / 2.54;
2029                  break;
2030              }
2031              // inches
2032              case 'in': {
2033                  $this->k = $this->dpi;
2034                  break;
2035              }
2036              // unsupported unit
2037              default : {
2038                  $this->Error('Incorrect unit: '.$unit);
2039                  break;
2040              }
2041          }
2042          $this->pdfunit = $unit;
2043          if (isset($this->CurOrientation)) {
2044              $this->setPageOrientation($this->CurOrientation);
2045          }
2046      }
2047  
2048      /**
2049       * Change the format of the current page
2050       * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() documentation or an array of two numbers (width, height) or an array containing the following measures and options:<ul>
2051       * <li>['format'] = page format name (one of the above);</li>
2052       * <li>['Rotate'] : The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.</li>
2053       * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2054       * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2055       * <li>['MediaBox']['llx'] : lower-left x coordinate</li>
2056       * <li>['MediaBox']['lly'] : lower-left y coordinate</li>
2057       * <li>['MediaBox']['urx'] : upper-right x coordinate</li>
2058       * <li>['MediaBox']['ury'] : upper-right y coordinate</li>
2059       * <li>['CropBox'] : the visible region of default user space:</li>
2060       * <li>['CropBox']['llx'] : lower-left x coordinate</li>
2061       * <li>['CropBox']['lly'] : lower-left y coordinate</li>
2062       * <li>['CropBox']['urx'] : upper-right x coordinate</li>
2063       * <li>['CropBox']['ury'] : upper-right y coordinate</li>
2064       * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2065       * <li>['BleedBox']['llx'] : lower-left x coordinate</li>
2066       * <li>['BleedBox']['lly'] : lower-left y coordinate</li>
2067       * <li>['BleedBox']['urx'] : upper-right x coordinate</li>
2068       * <li>['BleedBox']['ury'] : upper-right y coordinate</li>
2069       * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2070       * <li>['TrimBox']['llx'] : lower-left x coordinate</li>
2071       * <li>['TrimBox']['lly'] : lower-left y coordinate</li>
2072       * <li>['TrimBox']['urx'] : upper-right x coordinate</li>
2073       * <li>['TrimBox']['ury'] : upper-right y coordinate</li>
2074       * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2075       * <li>['ArtBox']['llx'] : lower-left x coordinate</li>
2076       * <li>['ArtBox']['lly'] : lower-left y coordinate</li>
2077       * <li>['ArtBox']['urx'] : upper-right x coordinate</li>
2078       * <li>['ArtBox']['ury'] : upper-right y coordinate</li>
2079       * <li>['BoxColorInfo'] :specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for each of the possible page boundaries other than the MediaBox:</li>
2080       * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2081       * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2082       * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2083       * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2084       * <li>['trans'] : the style and duration of the visual transition to use when moving from another page to the given page during a presentation</li>
2085       * <li>['trans']['Dur'] : The page's display duration (also called its advance timing): the maximum length of time, in seconds, that the page shall be displayed during presentations before the viewer application shall automatically advance to the next page.</li>
2086       * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2087       * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2088       * <li>['trans']['Dm'] : (Split and Blinds transition styles only) The dimension in which the specified transition effect shall occur: H = Horizontal, V = Vertical. Default value: H.</li>
2089       * <li>['trans']['M'] : (Split, Box and Fly transition styles only) The direction of motion for the specified transition effect: I = Inward from the edges of the page, O = Outward from the center of the pageDefault value: I.</li>
2090       * <li>['trans']['Di'] : (Wipe, Glitter, Fly, Cover, Uncover and Push transition styles only) The direction in which the specified transition effect shall moves, expressed in degrees counterclockwise starting from a left-to-right direction. If the value is a number, it shall be one of: 0 = Left to right, 90 = Bottom to top (Wipe only), 180 = Right to left (Wipe only), 270 = Top to bottom, 315 = Top-left to bottom-right (Glitter only). If the value is a name, it shall be None, which is relevant only for the Fly transition when the value of SS is not 1.0. Default value: 0.</li>
2091       * <li>['trans']['SS'] : (Fly transition style only) The starting or ending scale at which the changes shall be drawn. If M specifies an inward transition, the scale of the changes drawn shall progress from SS to 1.0 over the course of the transition. If M specifies an outward transition, the scale of the changes drawn shall progress from 1.0 to SS over the course of the transition. Default: 1.0.</li>
2092       * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2093       * </ul>
2094       * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul>
2095       * <li>P or Portrait (default)</li>
2096       * <li>L or Landscape</li>
2097       * <li>'' (empty string) for automatic orientation</li>
2098       * </ul>
2099       * @protected
2100       * @since 3.0.015 (2008-06-06)
2101       * @see getPageSizeFromFormat()
2102       */
2103  	protected function setPageFormat($format, $orientation='P') {
2104          if (!empty($format) AND isset($this->pagedim[$this->page])) {
2105              // remove inherited values
2106              unset($this->pagedim[$this->page]);
2107          }
2108          if (is_string($format)) {
2109              // get page measures from format name
2110              $pf = TCPDF_STATIC::getPageSizeFromFormat($format);
2111              $this->fwPt = $pf[0];
2112              $this->fhPt = $pf[1];
2113          } else {
2114              // the boundaries of the physical medium on which the page shall be displayed or printed
2115              if (isset($format['MediaBox'])) {
2116                  $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', $format['MediaBox']['llx'], $format['MediaBox']['lly'], $format['MediaBox']['urx'], $format['MediaBox']['ury'], false, $this->k, $this->pagedim);
2117                  $this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2118                  $this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2119              } else {
2120                  if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2121                      $pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2122                  } else {
2123                      if (!isset($format['format'])) {
2124                          // default value
2125                          $format['format'] = 'A4';
2126                      }
2127                      $pf = TCPDF_STATIC::getPageSizeFromFormat($format['format']);
2128                  }
2129                  $this->fwPt = $pf[0];
2130                  $this->fhPt = $pf[1];
2131                  $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2132              }
2133              // the visible region of default user space
2134              if (isset($format['CropBox'])) {
2135                  $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'CropBox', $format['CropBox']['llx'], $format['CropBox']['lly'], $format['CropBox']['urx'], $format['CropBox']['ury'], false, $this->k, $this->pagedim);
2136              }
2137              // the region to which the contents of the page shall be clipped when output in a production environment
2138              if (isset($format['BleedBox'])) {
2139                  $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'BleedBox', $format['BleedBox']['llx'], $format['BleedBox']['lly'], $format['BleedBox']['urx'], $format['BleedBox']['ury'], false, $this->k, $this->pagedim);
2140              }
2141              // the intended dimensions of the finished page after trimming
2142              if (isset($format['TrimBox'])) {
2143                  $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'TrimBox', $format['TrimBox']['llx'], $format['TrimBox']['lly'], $format['TrimBox']['urx'], $format['TrimBox']['ury'], false, $this->k, $this->pagedim);
2144              }
2145              // the page's meaningful content (including potential white space)
2146              if (isset($format['ArtBox'])) {
2147                  $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'ArtBox', $format['ArtBox']['llx'], $format['ArtBox']['lly'], $format['ArtBox']['urx'], $format['ArtBox']['ury'], false, $this->k, $this->pagedim);
2148              }
2149              // specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2150              if (isset($format['BoxColorInfo'])) {
2151                  $this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2152              }
2153              if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2154                  // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2155                  $this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2156              }
2157              if (isset($format['PZ'])) {
2158                  // The page's preferred zoom (magnification) factor
2159                  $this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2160              }
2161              if (isset($format['trans'])) {
2162                  // The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2163                  if (isset($format['trans']['Dur'])) {
2164                      // The page's display duration
2165                      $this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2166                  }
2167                  $stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2168                  if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2169                      // The transition style that shall be used when moving to this page from another during a presentation
2170                      $this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2171                      $valid_effect = array('Split', 'Blinds');
2172                      $valid_vals = array('H', 'V');
2173                      if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2174                          $this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2175                      }
2176                      $valid_effect = array('Split', 'Box', 'Fly');
2177                      $valid_vals = array('I', 'O');
2178                      if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2179                          $this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2180                      }
2181                      $valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2182                      if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2183                          if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2184                              OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2185                              OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2186                              $this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2187                          }
2188                      }
2189                      if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2190                          $this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2191                      }
2192                      if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2193                          $this->pagedim[$this->page]['trans']['B'] = 'true';
2194                      }
2195                  } else {
2196                      $this->pagedim[$this->page]['trans']['S'] = 'R';
2197                  }
2198                  if (isset($format['trans']['D'])) {
2199                      // The duration of the transition effect, in seconds
2200                      $this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2201                  } else {
2202                      $this->pagedim[$this->page]['trans']['D'] = 1;
2203                  }
2204              }
2205          }
2206          $this->setPageOrientation($orientation);
2207      }
2208  
2209      /**
2210       * Set page orientation.
2211       * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
2212       * @param $autopagebreak (boolean) Boolean indicating if auto-page-break mode should be on or off.
2213       * @param $bottommargin (float) bottom margin of the page.
2214       * @public
2215       * @since 3.0.015 (2008-06-06)
2216       */
2217  	public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
2218          if (!isset($this->pagedim[$this->page]['MediaBox'])) {
2219              // the boundaries of the physical medium on which the page shall be displayed or printed
2220              $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2221          }
2222          if (!isset($this->pagedim[$this->page]['CropBox'])) {
2223              // the visible region of default user space
2224              $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'CropBox', $this->pagedim[$this->page]['MediaBox']['llx'], $this->pagedim[$this->page]['MediaBox']['lly'], $this->pagedim[$this->page]['MediaBox']['urx'], $this->pagedim[$this->page]['MediaBox']['ury'], true, $this->k, $this->pagedim);
2225          }
2226          if (!isset($this->pagedim[$this->page]['BleedBox'])) {
2227              // the region to which the contents of the page shall be clipped when output in a production environment
2228              $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'BleedBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
2229          }
2230          if (!isset($this->pagedim[$this->page]['TrimBox'])) {
2231              // the intended dimensions of the finished page after trimming
2232              $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'TrimBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
2233          }
2234          if (!isset($this->pagedim[$this->page]['ArtBox'])) {
2235              // the page's meaningful content (including potential white space)
2236              $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'ArtBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
2237          }
2238          if (!isset($this->pagedim[$this->page]['Rotate'])) {
2239              // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2240              $this->pagedim[$this->page]['Rotate'] = 0;
2241          }
2242          if (!isset($this->pagedim[$this->page]['PZ'])) {
2243              // The page's preferred zoom (magnification) factor
2244              $this->pagedim[$this->page]['PZ'] = 1;
2245          }
2246          if ($this->fwPt > $this->fhPt) {
2247              // landscape
2248              $default_orientation = 'L';
2249          } else {
2250              // portrait
2251              $default_orientation = 'P';
2252          }
2253          $valid_orientations = array('P', 'L');
2254          if (empty($orientation)) {
2255              $orientation = $default_orientation;
2256          } else {
2257              $orientation = strtoupper($orientation[0]);
2258          }
2259          if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2260              $this->CurOrientation = $orientation;
2261              $this->wPt = $this->fhPt;
2262              $this->hPt = $this->fwPt;
2263          } else {
2264              $this->CurOrientation = $default_orientation;
2265              $this->wPt = $this->fwPt;
2266              $this->hPt = $this->fhPt;
2267          }
2268          if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
2269              // swap X and Y coordinates (change page orientation)
2270              $this->pagedim = TCPDF_STATIC::swapPageBoxCoordinates($this->page, $this->pagedim);
2271          }
2272          $this->w = ($this->wPt / $this->k);
2273          $this->h = ($this->hPt / $this->k);
2274          if (TCPDF_STATIC::empty_string($autopagebreak)) {
2275              if (isset($this->AutoPageBreak)) {
2276                  $autopagebreak = $this->AutoPageBreak;
2277              } else {
2278                  $autopagebreak = true;
2279              }
2280          }
2281          if (TCPDF_STATIC::empty_string($bottommargin)) {
2282              if (isset($this->bMargin)) {
2283                  $bottommargin = $this->bMargin;
2284              } else {
2285                  // default value = 2 cm
2286                  $bottommargin = 2 * 28.35 / $this->k;
2287              }
2288          }
2289          $this->SetAutoPageBreak($autopagebreak, $bottommargin);
2290          // store page dimensions
2291          $this->pagedim[$this->page]['w'] = $this->wPt;
2292          $this->pagedim[$this->page]['h'] = $this->hPt;
2293          $this->pagedim[$this->page]['wk'] = $this->w;
2294          $this->pagedim[$this->page]['hk'] = $this->h;
2295          $this->pagedim[$this->page]['tm'] = $this->tMargin;
2296          $this->pagedim[$this->page]['bm'] = $bottommargin;
2297          $this->pagedim[$this->page]['lm'] = $this->lMargin;
2298          $this->pagedim[$this->page]['rm'] = $this->rMargin;
2299          $this->pagedim[$this->page]['pb'] = $autopagebreak;
2300          $this->pagedim[$this->page]['or'] = $this->CurOrientation;
2301          $this->pagedim[$this->page]['olm'] = $this->original_lMargin;
2302          $this->pagedim[$this->page]['orm'] = $this->original_rMargin;
2303      }
2304  
2305      /**
2306       * Set regular expression to detect withespaces or word separators.
2307       * The pattern delimiter must be the forward-slash character "/".
2308       * Some example patterns are:
2309       * <pre>
2310       * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
2311       * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
2312       * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
2313       * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
2314       *      \s     : any whitespace character
2315       *      \p{Z}  : any separator
2316       *      \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2317       *      \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2318       * </pre>
2319       * @param $re (string) regular expression (leave empty for default).
2320       * @public
2321       * @since 4.6.016 (2009-06-15)
2322       */
2323  	public function setSpacesRE($re='/[^\S\xa0]/') {
2324          $this->re_spaces = $re;
2325          $re_parts = explode('/', $re);
2326          // get pattern parts
2327          $this->re_space = array();
2328          if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2329              $this->re_space['p'] = $re_parts[1];
2330          } else {
2331              $this->re_space['p'] = '[\s]';
2332          }
2333          // set pattern modifiers
2334          if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2335              $this->re_space['m'] = $re_parts[2];
2336          } else {
2337              $this->re_space['m'] = '';
2338          }
2339      }
2340  
2341      /**
2342       * Enable or disable Right-To-Left language mode
2343       * @param $enable (Boolean) if true enable Right-To-Left language mode.
2344       * @param $resetx (Boolean) if true reset the X position on direction change.
2345       * @public
2346       * @since 2.0.000 (2008-01-03)
2347       */
2348  	public function setRTL($enable, $resetx=true) {
2349          $enable = $enable ? true : false;
2350          $resetx = ($resetx AND ($enable != $this->rtl));
2351          $this->rtl = $enable;
2352          $this->tmprtl = false;
2353          if ($resetx) {
2354              $this->Ln(0);
2355          }
2356      }
2357  
2358      /**
2359       * Return the RTL status
2360       * @return boolean
2361       * @public
2362       * @since 4.0.012 (2008-07-24)
2363       */
2364  	public function getRTL() {
2365          return $this->rtl;
2366      }
2367  
2368      /**
2369       * Force temporary RTL language direction
2370       * @param $mode (mixed) can be false, 'L' for LTR or 'R' for RTL
2371       * @public
2372       * @since 2.1.000 (2008-01-09)
2373       */
2374  	public function setTempRTL($mode) {
2375          $newmode = false;
2376          switch (strtoupper($mode)) {
2377              case 'LTR':
2378              case 'L': {
2379                  if ($this->rtl) {
2380                      $newmode = 'L';
2381                  }
2382                  break;
2383              }
2384              case 'RTL':
2385              case 'R': {
2386                  if (!$this->rtl) {
2387                      $newmode = 'R';
2388                  }
2389                  break;
2390              }
2391              case false:
2392              default: {
2393                  $newmode = false;
2394                  break;
2395              }
2396          }
2397          $this->tmprtl = $newmode;
2398      }
2399  
2400      /**
2401       * Return the current temporary RTL status
2402       * @return boolean
2403       * @public
2404       * @since 4.8.014 (2009-11-04)
2405       */
2406  	public function isRTLTextDir() {
2407          return ($this->rtl OR ($this->tmprtl == 'R'));
2408      }
2409  
2410      /**
2411       * Set the last cell height.
2412       * @param $h (float) cell height.
2413       * @author Nicola Asuni
2414       * @public
2415       * @since 1.53.0.TC034
2416       */
2417  	public function setLastH($h) {
2418          $this->lasth = $h;
2419      }
2420  
2421      /**
2422       * Return the cell height
2423       * @param $fontsize (int) Font size in internal units
2424       * @param $padding (boolean) If true add cell padding
2425       * @public
2426       */
2427  	public function getCellHeight($fontsize, $padding=TRUE) {
2428          $height = ($fontsize * $this->cell_height_ratio);
2429          if ($padding) {
2430              $height += ($this->cell_padding['T'] + $this->cell_padding['B']);
2431          }
2432          return round($height, 6);
2433      }
2434  
2435      /**
2436       * Reset the last cell height.
2437       * @public
2438       * @since 5.9.000 (2010-10-03)
2439       */
2440  	public function resetLastH() {
2441          $this->lasth = $this->getCellHeight($this->FontSize);
2442      }
2443  
2444      /**
2445       * Get the last cell height.
2446       * @return last cell height
2447       * @public
2448       * @since 4.0.017 (2008-08-05)
2449       */
2450  	public function getLastH() {
2451          return $this->lasth;
2452      }
2453  
2454      /**
2455       * Set the adjusting factor to convert pixels to user units.
2456       * @param $scale (float) adjusting factor to convert pixels to user units.
2457       * @author Nicola Asuni
2458       * @public
2459       * @since 1.5.2
2460       */
2461  	public function setImageScale($scale) {
2462          $this->imgscale = $scale;
2463      }
2464  
2465      /**
2466       * Returns the adjusting factor to convert pixels to user units.
2467       * @return float adjusting factor to convert pixels to user units.
2468       * @author Nicola Asuni
2469       * @public
2470       * @since 1.5.2
2471       */
2472  	public function getImageScale() {
2473          return $this->imgscale;
2474      }
2475  
2476      /**
2477       * Returns an array of page dimensions:
2478       * <ul><li>$this->pagedim[$this->page]['w'] = page width in points</li><li>$this->pagedim[$this->page]['h'] = height in points</li><li>$this->pagedim[$this->page]['wk'] = page width in user units</li><li>$this->pagedim[$this->page]['hk'] = page height in user units</li><li>$this->pagedim[$this->page]['tm'] = top margin</li><li>$this->pagedim[$this->page]['bm'] = bottom margin</li><li>$this->pagedim[$this->page]['lm'] = left margin</li><li>$this->pagedim[$this->page]['rm'] = right margin</li><li>$this->pagedim[$this->page]['pb'] = auto page break</li><li>$this->pagedim[$this->page]['or'] = page orientation</li><li>$this->pagedim[$this->page]['olm'] = original left margin</li><li>$this->pagedim[$this->page]['orm'] = original right margin</li><li>$this->pagedim[$this->page]['Rotate'] = The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.</li><li>$this->pagedim[$this->page]['PZ'] = The page's preferred zoom (magnification) factor.</li><li>$this->pagedim[$this->page]['trans'] : the style and duration of the visual transition to use when moving from another page to the given page during a presentation<ul><li>$this->pagedim[$this->page]['trans']['Dur'] = The page's display duration (also called its advance timing): the maximum length of time, in seconds, that the page shall be displayed during presentations before the viewer application shall automatically advance to the next page.</li><li>$this->pagedim[$this->page]['trans']['S'] = transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li><li>$this->pagedim[$this->page]['trans']['D'] = The duration of the transition effect, in seconds.</li><li>$this->pagedim[$this->page]['trans']['Dm'] = (Split and Blinds transition styles only) The dimension in which the specified transition effect shall occur: H = Horizontal, V = Vertical. Default value: H.</li><li>$this->pagedim[$this->page]['trans']['M'] = (Split, Box and Fly transition styles only) The direction of motion for the specified transition effect: I = Inward from the edges of the page, O = Outward from the center of the pageDefault value: I.</li><li>$this->pagedim[$this->page]['trans']['Di'] = (Wipe, Glitter, Fly, Cover, Uncover and Push transition styles only) The direction in which the specified transition effect shall moves, expressed in degrees counterclockwise starting from a left-to-right direction. If the value is a number, it shall be one of: 0 = Left to right, 90 = Bottom to top (Wipe only), 180 = Right to left (Wipe only), 270 = Top to bottom, 315 = Top-left to bottom-right (Glitter only). If the value is a name, it shall be None, which is relevant only for the Fly transition when the value of SS is not 1.0. Default value: 0.</li><li>$this->pagedim[$this->page]['trans']['SS'] = (Fly transition style only) The starting or ending scale at which the changes shall be drawn. If M specifies an inward transition, the scale of the changes drawn shall progress from SS to 1.0 over the course of the transition. If M specifies an outward transition, the scale of the changes drawn shall progress from 1.0 to SS over the course of the transition. Default: 1.0. </li><li>$this->pagedim[$this->page]['trans']['B'] = (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li></ul></li><li>$this->pagedim[$this->page]['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed<ul><li>$this->pagedim[$this->page]['MediaBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['MediaBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['MediaBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['MediaBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['CropBox'] : the visible region of default user space<ul><li>$this->pagedim[$this->page]['CropBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['CropBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['CropBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['CropBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment<ul><li>$this->pagedim[$this->page]['BleedBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['BleedBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['BleedBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['BleedBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['TrimBox'] : the intended dimensions of the finished page after trimming<ul><li>$this->pagedim[$this->page]['TrimBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['TrimBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['TrimBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['TrimBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['ArtBox'] : the extent of the page's meaningful content<ul><li>$this->pagedim[$this->page]['ArtBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['ArtBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['ArtBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['ArtBox']['ury'] = upper-right y coordinate in points</li></ul></li></ul>
2479       * @param $pagenum (int) page number (empty = current page)
2480       * @return array of page dimensions.
2481       * @author Nicola Asuni
2482       * @public
2483       * @since 4.5.027 (2009-03-16)
2484       */
2485  	public function getPageDimensions($pagenum='') {
2486          if (empty($pagenum)) {
2487              $pagenum = $this->page;
2488          }
2489          return $this->pagedim[$pagenum];
2490      }
2491  
2492      /**
2493       * Returns the page width in units.
2494       * @param $pagenum (int) page number (empty = current page)
2495       * @return int page width.
2496       * @author Nicola Asuni
2497       * @public
2498       * @since 1.5.2
2499       * @see getPageDimensions()
2500       */
2501  	public function getPageWidth($pagenum='') {
2502          if (empty($pagenum)) {
2503              return $this->w;
2504          }
2505          return $this->pagedim[$pagenum]['w'];
2506      }
2507  
2508      /**
2509       * Returns the page height in units.
2510       * @param $pagenum (int) page number (empty = current page)
2511       * @return int page height.
2512       * @author Nicola Asuni
2513       * @public
2514       * @since 1.5.2
2515       * @see getPageDimensions()
2516       */
2517  	public function getPageHeight($pagenum='') {
2518          if (empty($pagenum)) {
2519              return $this->h;
2520          }
2521          return $this->pagedim[$pagenum]['h'];
2522      }
2523  
2524      /**
2525       * Returns the page break margin.
2526       * @param $pagenum (int) page number (empty = current page)
2527       * @return int page break margin.
2528       * @author Nicola Asuni
2529       * @public
2530       * @since 1.5.2
2531       * @see getPageDimensions()
2532       */
2533  	public function getBreakMargin($pagenum='') {
2534          if (empty($pagenum)) {
2535              return $this->bMargin;
2536          }
2537          return $this->pagedim[$pagenum]['bm'];
2538      }
2539  
2540      /**
2541       * Returns the scale factor (number of points in user unit).
2542       * @return int scale factor.
2543       * @author Nicola Asuni
2544       * @public
2545       * @since 1.5.2
2546       */
2547  	public function getScaleFactor() {
2548          return $this->k;
2549      }
2550  
2551      /**
2552       * Defines the left, top and right margins.
2553       * @param $left (float) Left margin.
2554       * @param $top (float) Top margin.
2555       * @param $right (float) Right margin. Default value is the left one.
2556       * @param $keepmargins (boolean) if true overwrites the default page margins
2557       * @public
2558       * @since 1.0
2559       * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
2560       */
2561  	public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
2562          //Set left, top and right margins
2563          $this->lMargin = $left;
2564          $this->tMargin = $top;
2565          if ($right == -1) {
2566              $right = $left;
2567          }
2568          $this->rMargin = $right;
2569          if ($keepmargins) {
2570              // overwrite original values
2571              $this->original_lMargin = $this->lMargin;
2572              $this->original_rMargin = $this->rMargin;
2573          }
2574      }
2575  
2576      /**
2577       * Defines the left margin. The method can be called before creating the first page. If the current abscissa gets out of page, it is brought back to the margin.
2578       * @param $margin (float) The margin.
2579       * @public
2580       * @since 1.4
2581       * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2582       */
2583  	public function SetLeftMargin($margin) {
2584          //Set left margin
2585          $this->lMargin = $margin;
2586          if (($this->page > 0) AND ($this->x < $margin)) {
2587              $this->x = $margin;
2588          }
2589      }
2590  
2591      /**
2592       * Defines the top margin. The method can be called before creating the first page.
2593       * @param $margin (float) The margin.
2594       * @public
2595       * @since 1.5
2596       * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2597       */
2598  	public function SetTopMargin($margin) {
2599          //Set top margin
2600          $this->tMargin = $margin;
2601          if (($this->page > 0) AND ($this->y < $margin)) {
2602              $this->y = $margin;
2603          }
2604      }
2605  
2606      /**
2607       * Defines the right margin. The method can be called before creating the first page.
2608       * @param $margin (float) The margin.
2609       * @public
2610       * @since 1.5
2611       * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
2612       */
2613  	public function SetRightMargin($margin) {
2614          $this->rMargin = $margin;
2615          if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
2616              $this->x = $this->w - $margin;
2617          }
2618      }
2619  
2620      /**
2621       * Set the same internal Cell padding for top, right, bottom, left-
2622       * @param $pad (float) internal padding.
2623       * @public
2624       * @since 2.1.000 (2008-01-09)
2625       * @see getCellPaddings(), setCellPaddings()
2626       */
2627  	public function SetCellPadding($pad) {
2628          if ($pad >= 0) {
2629              $this->cell_padding['L'] = $pad;
2630              $this->cell_padding['T'] = $pad;
2631              $this->cell_padding['R'] = $pad;
2632              $this->cell_padding['B'] = $pad;
2633          }
2634      }
2635  
2636      /**
2637       * Set the internal Cell paddings.
2638       * @param $left (float) left padding
2639       * @param $top (float) top padding
2640       * @param $right (float) right padding
2641       * @param $bottom (float) bottom padding
2642       * @public
2643       * @since 5.9.000 (2010-10-03)
2644       * @see getCellPaddings(), SetCellPadding()
2645       */
2646  	public function setCellPaddings($left='', $top='', $right='', $bottom='') {
2647          if (($left !== '') AND ($left >= 0)) {
2648              $this->cell_padding['L'] = $left;
2649          }
2650          if (($top !== '') AND ($top >= 0)) {
2651              $this->cell_padding['T'] = $top;
2652          }
2653          if (($right !== '') AND ($right >= 0)) {
2654              $this->cell_padding['R'] = $right;
2655          }
2656          if (($bottom !== '') AND ($bottom >= 0)) {
2657              $this->cell_padding['B'] = $bottom;
2658          }
2659      }
2660  
2661      /**
2662       * Get the internal Cell padding array.
2663       * @return array of padding values
2664       * @public
2665       * @since 5.9.000 (2010-10-03)
2666       * @see setCellPaddings(), SetCellPadding()
2667       */
2668  	public function getCellPaddings() {
2669          return $this->cell_padding;
2670      }
2671  
2672      /**
2673       * Set the internal Cell margins.
2674       * @param $left (float) left margin
2675       * @param $top (float) top margin
2676       * @param $right (float) right margin
2677       * @param $bottom (float) bottom margin
2678       * @public
2679       * @since 5.9.000 (2010-10-03)
2680       * @see getCellMargins()
2681       */
2682  	public function setCellMargins($left='', $top='', $right='', $bottom='') {
2683          if (($left !== '') AND ($left >= 0)) {
2684              $this->cell_margin['L'] = $left;
2685          }
2686          if (($top !== '') AND ($top >= 0)) {
2687              $this->cell_margin['T'] = $top;
2688          }
2689          if (($right !== '') AND ($right >= 0)) {
2690              $this->cell_margin['R'] = $right;
2691          }
2692          if (($bottom !== '') AND ($bottom >= 0)) {
2693              $this->cell_margin['B'] = $bottom;
2694          }
2695      }
2696  
2697      /**
2698       * Get the internal Cell margin array.
2699       * @return array of margin values
2700       * @public
2701       * @since 5.9.000 (2010-10-03)
2702       * @see setCellMargins()
2703       */
2704  	public function getCellMargins() {
2705          return $this->cell_margin;
2706      }
2707  
2708      /**
2709       * Adjust the internal Cell padding array to take account of the line width.
2710       * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
2711       * @return array of adjustments
2712       * @public
2713       * @since 5.9.000 (2010-10-03)
2714       */
2715  	protected function adjustCellPadding($brd=0) {
2716          if (empty($brd)) {
2717              return;
2718          }
2719          if (is_string($brd)) {
2720              // convert string to array
2721              $slen = strlen($brd);
2722              $newbrd = array();
2723              for ($i = 0; $i < $slen; ++$i) {
2724                  $newbrd[$brd[$i]] = true;
2725              }
2726              $brd = $newbrd;
2727          } elseif (($brd === 1) OR ($brd === true) OR (is_numeric($brd) AND (intval($brd) > 0))) {
2728              $brd = array('LRTB' => true);
2729          }
2730          if (!is_array($brd)) {
2731              return;
2732          }
2733          // store current cell padding
2734          $cp = $this->cell_padding;
2735          // select border mode
2736          if (isset($brd['mode'])) {
2737              $mode = $brd['mode'];
2738              unset($brd['mode']);
2739          } else {
2740              $mode = 'normal';
2741          }
2742          // process borders
2743          foreach ($brd as $border => $style) {
2744              $line_width = $this->LineWidth;
2745              if (is_array($style) AND isset($style['width'])) {
2746                  // get border width
2747                  $line_width = $style['width'];
2748              }
2749              $adj = 0; // line width inside the cell
2750              switch ($mode) {
2751                  case 'ext': {
2752                      $adj = 0;
2753                      break;
2754                  }
2755                  case 'int': {
2756                      $adj = $line_width;
2757                      break;
2758                  }
2759                  case 'normal':
2760                  default: {
2761                      $adj = ($line_width / 2);
2762                      break;
2763                  }
2764              }
2765              // correct internal cell padding if required to avoid overlap between text and lines
2766              if ((strpos($border,'T') !== false) AND ($this->cell_padding['T'] < $adj)) {
2767                  $this->cell_padding['T'] = $adj;
2768              }
2769              if ((strpos($border,'R') !== false) AND ($this->cell_padding['R'] < $adj)) {
2770                  $this->cell_padding['R'] = $adj;
2771              }
2772              if ((strpos($border,'B') !== false) AND ($this->cell_padding['B'] < $adj)) {
2773                  $this->cell_padding['B'] = $adj;
2774              }
2775              if ((strpos($border,'L') !== false) AND ($this->cell_padding['L'] < $adj)) {
2776                  $this->cell_padding['L'] = $adj;
2777              }
2778          }
2779          return array('T' => ($this->cell_padding['T'] - $cp['T']), 'R' => ($this->cell_padding['R'] - $cp['R']), 'B' => ($this->cell_padding['B'] - $cp['B']), 'L' => ($this->cell_padding['L'] - $cp['L']));
2780      }
2781  
2782      /**
2783       * Enables or disables the automatic page breaking mode. When enabling, the second parameter is the distance from the bottom of the page that defines the triggering limit. By default, the mode is on and the margin is 2 cm.
2784       * @param $auto (boolean) Boolean indicating if mode should be on or off.
2785       * @param $margin (float) Distance from the bottom of the page.
2786       * @public
2787       * @since 1.0
2788       * @see Cell(), MultiCell(), AcceptPageBreak()
2789       */
2790  	public function SetAutoPageBreak($auto, $margin=0) {
2791          $this->AutoPageBreak = $auto ? true : false;
2792          $this->bMargin = $margin;
2793          $this->PageBreakTrigger = $this->h - $margin;
2794      }
2795  
2796      /**
2797       * Return the auto-page-break mode (true or false).
2798       * @return boolean auto-page-break mode
2799       * @public
2800       * @since 5.9.088
2801       */
2802  	public function getAutoPageBreak() {
2803          return $this->AutoPageBreak;
2804      }
2805  
2806      /**
2807       * Defines the way the document is to be displayed by the viewer.
2808       * @param $zoom (mixed) The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. <ul><li>fullpage: displays the entire page on screen </li><li>fullwidth: uses maximum width of window</li><li>real: uses real size (equivalent to 100% zoom)</li><li>default: uses viewer default mode</li></ul>
2809       * @param $layout (string) The page layout. Possible values are:<ul><li>SinglePage Display one page at a time</li><li>OneColumn Display the pages in one column</li><li>TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left</li><li>TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right</li><li>TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left</li><li>TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right</li></ul>
2810       * @param $mode (string) A name object specifying how the document should be displayed when opened:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible</li><li>UseOC (PDF 1.5) Optional content group panel visible</li><li>UseAttachments (PDF 1.6) Attachments panel visible</li></ul>
2811       * @public
2812       * @since 1.2
2813       */
2814  	public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
2815          if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
2816              $this->ZoomMode = $zoom;
2817          } else {
2818              $this->Error('Incorrect zoom display mode: '.$zoom);
2819          }
2820          $this->LayoutMode = TCPDF_STATIC::getPageLayoutMode($layout);
2821          $this->PageMode = TCPDF_STATIC::getPageMode($mode);
2822      }
2823  
2824      /**
2825       * Activates or deactivates page compression. When activated, the internal representation of each page is compressed, which leads to a compression ratio of about 2 for the resulting document. Compression is on by default.
2826       * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
2827       * @param $compress (boolean) Boolean indicating if compression must be enabled.
2828       * @public
2829       * @since 1.4
2830       */
2831  	public function SetCompression($compress=true) {
2832          if (function_exists('gzcompress')) {
2833              $this->compress = $compress ? true : false;
2834          } else {
2835              $this->compress = false;
2836          }
2837      }
2838  
2839      /**
2840       * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
2841       * @param $mode (boolean) If true force sRGB output intent.
2842       * @public
2843       * @since 5.9.121 (2011-09-28)
2844       */
2845  	public function setSRGBmode($mode=false) {
2846          $this->force_srgb = $mode ? true : false;
2847      }
2848  
2849      /**
2850       * Turn on/off Unicode mode for document information dictionary (meta tags).
2851       * This has effect only when unicode mode is set to false.
2852       * @param $unicode (boolean) if true set the meta information in Unicode
2853       * @since 5.9.027 (2010-12-01)
2854       * @public
2855       */
2856  	public function SetDocInfoUnicode($unicode=true) {
2857          $this->docinfounicode = $unicode ? true : false;
2858      }
2859  
2860      /**
2861       * Defines the title of the document.
2862       * @param $title (string) The title.
2863       * @public
2864       * @since 1.2
2865       * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
2866       */
2867  	public function SetTitle($title) {
2868          $this->title = $title;
2869      }
2870  
2871      /**
2872       * Defines the subject of the document.
2873       * @param $subject (string) The subject.
2874       * @public
2875       * @since 1.2
2876       * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
2877       */
2878  	public function SetSubject($subject) {
2879          $this->subject = $subject;
2880      }
2881  
2882      /**
2883       * Defines the author of the document.
2884       * @param $author (string) The name of the author.
2885       * @public
2886       * @since 1.2
2887       * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
2888       */
2889  	public function SetAuthor($author) {
2890          $this->author = $author;
2891      }
2892  
2893      /**
2894       * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
2895       * @param $keywords (string) The list of keywords.
2896       * @public
2897       * @since 1.2
2898       * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
2899       */
2900  	public function SetKeywords($keywords) {
2901          $this->keywords = $keywords;
2902      }
2903  
2904      /**
2905       * Defines the creator of the document. This is typically the name of the application that generates the PDF.
2906       * @param $creator (string) The name of the creator.
2907       * @public
2908       * @since 1.2
2909       * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
2910       */
2911  	public function SetCreator($creator) {
2912          $this->creator = $creator;
2913      }
2914  
2915      /**
2916       * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
2917       * @param $msg (string) The error message
2918       * @public
2919       * @since 1.0
2920       */
2921  	public function Error($msg) {
2922          // unset all class variables
2923          $this->_destroy(true);
2924          if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
2925              die('<strong>TCPDF ERROR: </strong>'.$msg);
2926          } else {
2927              throw new Exception('TCPDF ERROR: '.$msg);
2928          }
2929      }
2930  
2931      /**
2932       * This method begins the generation of the PDF document.
2933       * It is not necessary to call it explicitly because AddPage() does it automatically.
2934       * Note: no page is created by this method
2935       * @public
2936       * @since 1.0
2937       * @see AddPage(), Close()
2938       */
2939  	public function Open() {
2940          $this->state = 1;
2941      }
2942  
2943      /**
2944       * Terminates the PDF document.
2945       * It is not necessary to call this method explicitly because Output() does it automatically.
2946       * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
2947       * @public
2948       * @since 1.0
2949       * @see Open(), Output()
2950       */
2951  	public function Close() {
2952          if ($this->state == 3) {
2953              return;
2954          }
2955          if ($this->page == 0) {
2956              $this->AddPage();
2957          }
2958          $this->endLayer();
2959          if ($this->tcpdflink) {
2960              // save current graphic settings
2961              $gvars = $this->getGraphicVars();
2962              $this->setEqualColumns();
2963              $this->lastpage(true);
2964              $this->SetAutoPageBreak(false);
2965              $this->x = 0;
2966              $this->y = $this->h - (1 / $this->k);
2967              $this->lMargin = 0;
2968              $this->_outSaveGraphicsState();
2969              $font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
2970              $this->SetFont($font, '', 1);
2971              $this->setTextRenderingMode(0, false, false);
2972              $msg = "\x50\x6f\x77\x65\x72\x65\x64\x20\x62\x79\x20\x54\x43\x50\x44\x46\x20\x28\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67\x29";
2973              $lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
2974              $this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
2975              $this->_outRestoreGraphicsState();
2976              // restore graphic settings
2977              $this->setGraphicVars($gvars);
2978          }
2979          // close page
2980          $this->endPage();
2981          // close document
2982          $this->_enddoc();
2983          // unset all class variables (except critical ones)
2984          $this->_destroy(false);
2985      }
2986  
2987      /**
2988       * Move pointer at the specified document page and update page dimensions.
2989       * @param $pnum (int) page number (1 ... numpages)
2990       * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
2991       * @public
2992       * @since 2.1.000 (2008-01-07)
2993       * @see getPage(), lastpage(), getNumPages()
2994       */
2995  	public function setPage($pnum, $resetmargins=false) {
2996          if (($pnum == $this->page) AND ($this->state == 2)) {
2997              return;
2998          }
2999          if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3000              $this->state = 2;
3001              // save current graphic settings
3002              //$gvars = $this->getGraphicVars();
3003              $oldpage = $this->page;
3004              $this->page = $pnum;
3005              $this->wPt = $this->pagedim[$this->page]['w'];
3006              $this->hPt = $this->pagedim[$this->page]['h'];
3007              $this->w = $this->pagedim[$this->page]['wk'];
3008              $this->h = $this->pagedim[$this->page]['hk'];
3009              $this->tMargin = $this->pagedim[$this->page]['tm'];
3010              $this->bMargin = $this->pagedim[$this->page]['bm'];
3011              $this->original_lMargin = $this->pagedim[$this->page]['olm'];
3012              $this->original_rMargin = $this->pagedim[$this->page]['orm'];
3013              $this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3014              $this->CurOrientation = $this->pagedim[$this->page]['or'];
3015              $this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3016              // restore graphic settings
3017              //$this->setGraphicVars($gvars);
3018              if ($resetmargins) {
3019                  $this->lMargin = $this->pagedim[$this->page]['olm'];
3020                  $this->rMargin = $this->pagedim[$this->page]['orm'];
3021                  $this->SetY($this->tMargin);
3022              } else {
3023                  // account for booklet mode
3024                  if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3025                      $deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3026                      $this->lMargin += $deltam;
3027                      $this->rMargin -= $deltam;
3028                  }
3029              }
3030          } else {
3031              $this->Error('Wrong page number on setPage() function: '.$pnum);
3032          }
3033      }
3034  
3035      /**
3036       * Reset pointer to the last document page.
3037       * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
3038       * @public
3039       * @since 2.0.000 (2008-01-04)
3040       * @see setPage(), getPage(), getNumPages()
3041       */
3042  	public function lastPage($resetmargins=false) {
3043          $this->setPage($this->getNumPages(), $resetmargins);
3044      }
3045  
3046      /**
3047       * Get current document page number.
3048       * @return int page number
3049       * @public
3050       * @since 2.1.000 (2008-01-07)
3051       * @see setPage(), lastpage(), getNumPages()
3052       */
3053  	public function getPage() {
3054          return $this->page;
3055      }
3056  
3057      /**
3058       * Get the total number of insered pages.
3059       * @return int number of pages
3060       * @public
3061       * @since 2.1.000 (2008-01-07)
3062       * @see setPage(), getPage(), lastpage()
3063       */
3064  	public function getNumPages() {
3065          return $this->numpages;
3066      }
3067  
3068      /**
3069       * Adds a new TOC (Table Of Content) page to the document.
3070       * @param $orientation (string) page orientation.
3071       * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3072       * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3073       * @public
3074       * @since 5.0.001 (2010-05-06)
3075       * @see AddPage(), startPage(), endPage(), endTOCPage()
3076       */
3077  	public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3078          $this->AddPage($orientation, $format, $keepmargins, true);
3079      }
3080  
3081      /**
3082       * Terminate the current TOC (Table Of Content) page
3083       * @public
3084       * @since 5.0.001 (2010-05-06)
3085       * @see AddPage(), startPage(), endPage(), addTOCPage()
3086       */
3087  	public function endTOCPage() {
3088          $this->endPage(true);
3089      }
3090  
3091      /**
3092       * Adds a new page to the document. If a page is already present, the Footer() method is called first to output the footer (if enabled). Then the page is added, the current position set to the top-left corner according to the left and top margins (or top-right if in RTL mode), and Header() is called to display the header (if enabled).
3093       * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3094       * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3095       * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3096       * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3097       * @param $tocpage (boolean) if true set the tocpage state to true (the added page will be used to display Table Of Content).
3098       * @public
3099       * @since 1.0
3100       * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3101       */
3102  	public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3103          if ($this->inxobj) {
3104              // we are inside an XObject template
3105              return;
3106          }
3107          if (!isset($this->original_lMargin) OR $keepmargins) {
3108              $this->original_lMargin = $this->lMargin;
3109          }
3110          if (!isset($this->original_rMargin) OR $keepmargins) {
3111              $this->original_rMargin = $this->rMargin;
3112          }
3113          // terminate previous page
3114          $this->endPage();
3115          // start new page
3116          $this->startPage($orientation, $format, $tocpage);
3117      }
3118  
3119      /**
3120       * Terminate the current page
3121       * @param $tocpage (boolean) if true set the tocpage state to false (end the page used to display Table Of Content).
3122       * @public
3123       * @since 4.2.010 (2008-11-14)
3124       * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3125       */
3126  	public function endPage($tocpage=false) {
3127          // check if page is already closed
3128          if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3129              return;
3130          }
3131          // print page footer
3132          $this->setFooter();
3133          // close page
3134          $this->_endpage();
3135          // mark page as closed
3136          $this->pageopen[$this->page] = false;
3137          if ($tocpage) {
3138              $this->tocpage = false;
3139          }
3140      }
3141  
3142      /**
3143       * Starts a new page to the document. The page must be closed using the endPage() function.
3144       * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3145       * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3146       * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3147       * @param $tocpage (boolean) if true the page is designated to contain the Table-Of-Content.
3148       * @since 4.2.010 (2008-11-14)
3149       * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3150       * @public
3151       */
3152  	public function startPage($orientation='', $format='', $tocpage=false) {
3153          if ($tocpage) {
3154              $this->tocpage = true;
3155          }
3156          // move page numbers of documents to be attached
3157          if ($this->tocpage) {
3158              // move reference to unexistent pages (used for page attachments)
3159              // adjust outlines
3160              $tmpoutlines = $this->outlines;
3161              foreach ($tmpoutlines as $key => $outline) {
3162                  if (!$outline['f'] AND ($outline['p'] > $this->numpages)) {
3163                      $this->outlines[$key]['p'] = ($outline['p'] + 1);
3164                  }
3165              }
3166              // adjust dests
3167              $tmpdests = $this->dests;
3168              foreach ($tmpdests as $key => $dest) {
3169                  if (!$dest['f'] AND ($dest['p'] > $this->numpages)) {
3170                      $this->dests[$key]['p'] = ($dest['p'] + 1);
3171                  }
3172              }
3173              // adjust links
3174              $tmplinks = $this->links;
3175              foreach ($tmplinks as $key => $link) {
3176                  if (!$link['f'] AND ($link['p'] > $this->numpages)) {
3177                      $this->links[$key]['p'] = ($link['p'] + 1);
3178                  }
3179              }
3180          }
3181          if ($this->numpages > $this->page) {
3182              // this page has been already added
3183              $this->setPage($this->page + 1);
3184              $this->SetY($this->tMargin);
3185              return;
3186          }
3187          // start a new page
3188          if ($this->state == 0) {
3189              $this->Open();
3190          }
3191          ++$this->numpages;
3192          $this->swapMargins($this->booklet);
3193          // save current graphic settings
3194          $gvars = $this->getGraphicVars();
3195          // start new page
3196          $this->_beginpage($orientation, $format);
3197          // mark page as open
3198          $this->pageopen[$this->page] = true;
3199          // restore graphic settings
3200          $this->setGraphicVars($gvars);
3201          // mark this point
3202          $this->setPageMark();
3203          // print page header
3204          $this->setHeader();
3205          // restore graphic settings
3206          $this->setGraphicVars($gvars);
3207          // mark this point
3208          $this->setPageMark();
3209          // print table header (if any)
3210          $this->setTableHeader();
3211          // set mark for empty page check
3212          $this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
3213      }
3214  
3215      /**
3216       * Set start-writing mark on current page stream used to put borders and fills.
3217       * Borders and fills are always created after content and inserted on the position marked by this method.
3218       * This function must be called after calling Image() function for a background image.
3219       * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
3220       * @public
3221       * @since 4.0.016 (2008-07-30)
3222       */
3223  	public function setPageMark() {
3224          $this->intmrk[$this->page] = $this->pagelen[$this->page];
3225          $this->bordermrk[$this->page] = $this->intmrk[$this->page];
3226          $this->setContentMark();
3227      }
3228  
3229      /**
3230       * Set start-writing mark on selected page.
3231       * Borders and fills are always created after content and inserted on the position marked by this method.
3232       * @param $page (int) page number (default is the current page)
3233       * @protected
3234       * @since 4.6.021 (2009-07-20)
3235       */
3236  	protected function setContentMark($page=0) {
3237          if ($page <= 0) {
3238              $page = $this->page;
3239          }
3240          if (isset($this->footerlen[$page])) {
3241              $this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3242          } else {
3243              $this->cntmrk[$page] = $this->pagelen[$page];
3244          }
3245      }
3246  
3247      /**
3248       * Set header data.
3249       * @param $ln (string) header image logo
3250       * @param $lw (string) header image logo width in mm
3251       * @param $ht (string) string to print as title on document header
3252       * @param $hs (string) string to print on document header
3253       * @param $tc (array) RGB array color for text.
3254       * @param $lc (array) RGB array color for line.
3255       * @public
3256       */
3257  	public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
3258          $this->header_logo = $ln;
3259          $this->header_logo_width = $lw;
3260          $this->header_title = $ht;
3261          $this->header_string = $hs;
3262          $this->header_text_color = $tc;
3263          $this->header_line_color = $lc;
3264      }
3265  
3266      /**
3267       * Set footer data.
3268       * @param $tc (array) RGB array color for text.
3269       * @param $lc (array) RGB array color for line.
3270       * @public
3271       */
3272  	public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
3273          $this->footer_text_color = $tc;
3274          $this->footer_line_color = $lc;
3275      }
3276  
3277      /**
3278       * Returns header data:
3279       * <ul><li>$ret['logo'] = logo image</li><li>$ret['logo_width'] = width of the image logo in user units</li><li>$ret['title'] = header title</li><li>$ret['string'] = header description string</li></ul>
3280       * @return array()
3281       * @public
3282       * @since 4.0.012 (2008-07-24)
3283       */
3284  	public function getHeaderData() {
3285          $ret = array();
3286          $ret['logo'] = $this->header_logo;
3287          $ret['logo_width'] = $this->header_logo_width;
3288          $ret['title'] = $this->header_title;
3289          $ret['string'] = $this->header_string;
3290          $ret['text_color'] = $this->header_text_color;
3291          $ret['line_color'] = $this->header_line_color;
3292          return $ret;
3293      }
3294  
3295      /**
3296       * Set header margin.
3297       * (minimum distance between header and top page margin)
3298       * @param $hm (int) distance in user units
3299       * @public
3300       */
3301  	public function setHeaderMargin($hm=10) {
3302          $this->header_margin = $hm;
3303      }
3304  
3305      /**
3306       * Returns header margin in user units.
3307       * @return float
3308       * @since 4.0.012 (2008-07-24)
3309       * @public
3310       */
3311  	public function getHeaderMargin() {
3312          return $this->header_margin;
3313      }
3314  
3315      /**
3316       * Set footer margin.
3317       * (minimum distance between footer and bottom page margin)
3318       * @param $fm (int) distance in user units
3319       * @public
3320       */
3321  	public function setFooterMargin($fm=10) {
3322          $this->footer_margin = $fm;
3323      }
3324  
3325      /**
3326       * Returns footer margin in user units.
3327       * @return float
3328       * @since 4.0.012 (2008-07-24)
3329       * @public
3330       */
3331  	public function getFooterMargin() {
3332          return $this->footer_margin;
3333      }
3334      /**
3335       * Set a flag to print page header.
3336       * @param $val (boolean) set to true to print the page header (default), false otherwise.
3337       * @public
3338       */
3339  	public function setPrintHeader($val=true) {
3340          $this->print_header = $val ? true : false;
3341      }
3342  
3343      /**
3344       * Set a flag to print page footer.
3345       * @param $val (boolean) set to true to print the page footer (default), false otherwise.
3346       * @public
3347       */
3348  	public function setPrintFooter($val=true) {
3349          $this->print_footer = $val ? true : false;
3350      }
3351  
3352      /**
3353       * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
3354       * @return float
3355       * @public
3356       */
3357  	public function getImageRBX() {
3358          return $this->img_rb_x;
3359      }
3360  
3361      /**
3362       * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
3363       * @return float
3364       * @public
3365       */
3366  	public function getImageRBY() {
3367          return $this->img_rb_y;
3368      }
3369  
3370      /**
3371       * Reset the xobject template used by Header() method.
3372       * @public
3373       */
3374  	public function resetHeaderTemplate() {
3375          $this->header_xobjid = false;
3376      }
3377  
3378      /**
3379       * Set a flag to automatically reset the xobject template used by Header() method at each page.
3380       * @param $val (boolean) set to true to reset Header xobject template at each page, false otherwise.
3381       * @public
3382       */
3383  	public function setHeaderTemplateAutoreset($val=true) {
3384          $this->header_xobj_autoreset = $val ? true : false;
3385      }
3386  
3387      /**
3388       * This method is used to render the page header.
3389       * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3390       * @public
3391       */
3392  	public function Header() {
3393          if ($this->header_xobjid === false) {
3394              // start a new XObject Template
3395              $this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
3396              $headerfont = $this->getHeaderFont();
3397              $headerdata = $this->getHeaderData();
3398              $this->y = $this->header_margin;
3399              if ($this->rtl) {
3400                  $this->x = $this->w - $this->original_rMargin;
3401              } else {
3402                  $this->x = $this->original_lMargin;
3403              }
3404              if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
3405                  $imgtype = TCPDF_IMAGES::getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
3406                  if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
3407                      $this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3408                  } elseif ($imgtype == 'svg') {
3409                      $this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3410                  } else {
3411                      $this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3412                  }
3413                  $imgy = $this->getImageRBY();
3414              } else {
3415                  $imgy = $this->y;
3416              }
3417              $cell_height = $this->getCellHeight($headerfont[2] / $this->k);
3418              // set starting margin for text data cell
3419              if ($this->getRTL()) {
3420                  $header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
3421              } else {
3422                  $header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
3423              }
3424              $cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
3425              $this->SetTextColorArray($this->header_text_color);
3426              // header title
3427              $this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
3428              $this->SetX($header_x);
3429              $this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
3430              // header string
3431              $this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
3432              $this->SetX($header_x);
3433              $this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
3434              // print an ending header line
3435              $this->SetLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
3436              $this->SetY((2.835 / $this->k) + max($imgy, $this->y));
3437              if ($this->rtl) {
3438                  $this->SetX($this->original_rMargin);
3439              } else {
3440                  $this->SetX($this->original_lMargin);
3441              }
3442              $this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
3443              $this->endTemplate();
3444          }
3445          // print header template
3446          $x = 0;
3447          $dx = 0;
3448          if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
3449              // adjust margins for booklet mode
3450              $dx = ($this->original_lMargin - $this->original_rMargin);
3451          }
3452          if ($this->rtl) {
3453              $x = $this->w + $dx;
3454          } else {
3455              $x = 0 + $dx;
3456          }
3457          $this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
3458          if ($this->header_xobj_autoreset) {
3459              // reset header xobject template at each page
3460              $this->header_xobjid = false;
3461          }
3462      }
3463  
3464      /**
3465       * This method is used to render the page footer.
3466       * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3467       * @public
3468       */
3469  	public function Footer() {
3470          $cur_y = $this->y;
3471          $this->SetTextColorArray($this->footer_text_color);
3472          //set style for cell border
3473          $line_width = (0.85 / $this->k);
3474          $this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
3475          //print document barcode
3476          $barcode = $this->getBarcode();
3477          if (!empty($barcode)) {
3478              $this->Ln($line_width);
3479              $barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
3480              $style = array(
3481                  'position' => $this->rtl?'R':'L',
3482                  'align' => $this->rtl?'R':'L',
3483                  'stretch' => false,
3484                  'fitwidth' => true,
3485                  'cellfitalign' => '',
3486                  'border' => false,
3487                  'padding' => 0,
3488                  'fgcolor' => array(0,0,0),
3489                  'bgcolor' => false,
3490                  'text' => false
3491              );
3492              $this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
3493          }
3494          $w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
3495          if (empty($this->pagegroups)) {
3496              $pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
3497          } else {
3498              $pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
3499          }
3500          $this->SetY($cur_y);
3501          //Print page number
3502          if ($this->getRTL()) {
3503              $this->SetX($this->original_rMargin);
3504              $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
3505          } else {
3506              $this->SetX($this->original_lMargin);
3507              $this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
3508          }
3509      }
3510  
3511      /**
3512       * This method is used to render the page header.
3513       * @protected
3514       * @since 4.0.012 (2008-07-24)
3515       */
3516  	protected function setHeader() {
3517          if (!$this->print_header OR ($this->state != 2)) {
3518              return;
3519          }
3520          $this->InHeader = true;
3521          $this->setGraphicVars($this->default_graphic_vars);
3522          $temp_thead = $this->thead;
3523          $temp_theadMargins = $this->theadMargins;
3524          $lasth = $this->lasth;
3525          $newline = $this->newline;
3526          $this->_outSaveGraphicsState();
3527          $this->rMargin = $this->original_rMargin;
3528          $this->lMargin = $this->original_lMargin;
3529          $this->SetCellPadding(0);
3530          //set current position
3531          if ($this->rtl) {
3532              $this->SetXY($this->original_rMargin, $this->header_margin);
3533          } else {
3534              $this->SetXY($this->original_lMargin, $this->header_margin);
3535          }
3536          $this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
3537          $this->Header();
3538          //restore position
3539          if ($this->rtl) {
3540              $this->SetXY($this->original_rMargin, $this->tMargin);
3541          } else {
3542              $this->SetXY($this->original_lMargin, $this->tMargin);
3543          }
3544          $this->_outRestoreGraphicsState();
3545          $this->lasth = $lasth;
3546          $this->thead = $temp_thead;
3547          $this->theadMargins = $temp_theadMargins;
3548          $this->newline = $newline;
3549          $this->InHeader = false;
3550      }
3551  
3552      /**
3553       * This method is used to render the page footer.
3554       * @protected
3555       * @since 4.0.012 (2008-07-24)
3556       */
3557  	protected function setFooter() {
3558          if ($this->state != 2) {
3559              return;
3560          }
3561          $this->InFooter = true;
3562          // save current graphic settings
3563          $gvars = $this->getGraphicVars();
3564          // mark this point
3565          $this->footerpos[$this->page] = $this->pagelen[$this->page];
3566          $this->_out("\n");
3567          if ($this->print_footer) {
3568              $this->setGraphicVars($this->default_graphic_vars);
3569              $this->current_column = 0;
3570              $this->num_columns = 1;
3571              $temp_thead = $this->thead;
3572              $temp_theadMargins = $this->theadMargins;
3573              $lasth = $this->lasth;
3574              $this->_outSaveGraphicsState();
3575              $this->rMargin = $this->original_rMargin;
3576              $this->lMargin = $this->original_lMargin;
3577              $this->SetCellPadding(0);
3578              //set current position
3579              $footer_y = $this->h - $this->footer_margin;
3580              if ($this->rtl) {
3581                  $this->SetXY($this->original_rMargin, $footer_y);
3582              } else {
3583                  $this->SetXY($this->original_lMargin, $footer_y);
3584              }
3585              $this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
3586              $this->Footer();
3587              //restore position
3588              if ($this->rtl) {
3589                  $this->SetXY($this->original_rMargin, $this->tMargin);
3590              } else {
3591                  $this->SetXY($this->original_lMargin, $this->tMargin);
3592              }
3593              $this->_outRestoreGraphicsState();
3594              $this->lasth = $lasth;
3595              $this->thead = $temp_thead;
3596              $this->theadMargins = $temp_theadMargins;
3597          }
3598          // restore graphic settings
3599          $this->setGraphicVars($gvars);
3600          $this->current_column = $gvars['current_column'];
3601          $this->num_columns = $gvars['num_columns'];
3602          // calculate footer length
3603          $this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
3604          $this->InFooter = false;
3605      }
3606  
3607      /**
3608       * Check if we are on the page body (excluding page header and footer).
3609       * @return true if we are not in page header nor in page footer, false otherwise.
3610       * @protected
3611       * @since 5.9.091 (2011-06-15)
3612       */
3613  	protected function inPageBody() {
3614          return (($this->InHeader === false) AND ($this->InFooter === false));
3615      }
3616  
3617      /**
3618       * This method is used to render the table header on new page (if any).
3619       * @protected
3620       * @since 4.5.030 (2009-03-25)
3621       */
3622  	protected function setTableHeader() {
3623          if ($this->num_columns > 1) {
3624              // multi column mode
3625              return;
3626          }
3627          if (isset($this->theadMargins['top'])) {
3628              // restore the original top-margin
3629              $this->tMargin = $this->theadMargins['top'];
3630              $this->pagedim[$this->page]['tm'] = $this->tMargin;
3631              $this->y = $this->tMargin;
3632          }
3633          if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
3634              // set margins
3635              $prev_lMargin = $this->lMargin;
3636              $prev_rMargin = $this->rMargin;
3637              $prev_cell_padding = $this->cell_padding;
3638              $this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
3639              $this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
3640              $this->cell_padding = $this->theadMargins['cell_padding'];
3641              if ($this->rtl) {
3642                  $this->x = $this->w - $this->rMargin;
3643              } else {
3644                  $this->x = $this->lMargin;
3645              }
3646              // account for special "cell" mode
3647              if ($this->theadMargins['cell']) {
3648                  if ($this->rtl) {
3649                      $this->x -= $this->cell_padding['R'];
3650                  } else {
3651                      $this->x += $this->cell_padding['L'];
3652                  }
3653              }
3654              $gvars = $this->getGraphicVars();
3655              if (!empty($this->theadMargins['gvars'])) {
3656                  // set the correct graphic style
3657                  $this->setGraphicVars($this->theadMargins['gvars']);
3658                  $this->rMargin = $gvars['rMargin'];
3659                  $this->lMargin = $gvars['lMargin'];
3660              }
3661              // print table header
3662              $this->writeHTML($this->thead, false, false, false, false, '');
3663              $this->setGraphicVars($gvars);
3664              // set new top margin to skip the table headers
3665              if (!isset($this->theadMargins['top'])) {
3666                  $this->theadMargins['top'] = $this->tMargin;
3667              }
3668              // store end of header position
3669              if (!isset($this->columns[0]['th'])) {
3670                  $this->columns[0]['th'] = array();
3671              }
3672              $this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
3673              $this->tMargin = $this->y;
3674              $this->pagedim[$this->page]['tm'] = $this->tMargin;
3675              $this->lasth = 0;
3676              $this->lMargin = $prev_lMargin;
3677              $this->rMargin = $prev_rMargin;
3678              $this->cell_padding = $prev_cell_padding;
3679          }
3680      }
3681  
3682      /**
3683       * Returns the current page number.
3684       * @return int page number
3685       * @public
3686       * @since 1.0
3687       * @see getAliasNbPages()
3688       */
3689  	public function PageNo() {
3690          return $this->page;
3691      }
3692  
3693      /**
3694       * Returns the array of spot colors.
3695       * @return (array) Spot colors array.
3696       * @public
3697       * @since 6.0.038 (2013-09-30)
3698       */
3699  	public function getAllSpotColors() {
3700          return $this->spot_colors;
3701      }
3702  
3703      /**
3704       * Defines a new spot color.
3705       * It can be expressed in RGB components or gray scale.
3706       * The method can be called before the first page is created and the value is retained from page to page.
3707       * @param $name (string) Full name of the spot color.
3708       * @param $c (float) Cyan color for CMYK. Value between 0 and 100.
3709       * @param $m (float) Magenta color for CMYK. Value between 0 and 100.
3710       * @param $y (float) Yellow color for CMYK. Value between 0 and 100.
3711       * @param $k (float) Key (Black) color for CMYK. Value between 0 and 100.
3712       * @public
3713       * @since 4.0.024 (2008-09-12)
3714       * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3715       */
3716  	public function AddSpotColor($name, $c, $m, $y, $k) {
3717          if (!isset($this->spot_colors[$name])) {
3718              $i = (1 + count($this->spot_colors));
3719              $this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
3720          }
3721      }
3722  
3723      /**
3724       * Set the spot color for the specified type ('draw', 'fill', 'text').
3725       * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3726       * @param $name (string) Name of the spot color.
3727       * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3728       * @return (string) PDF color command.
3729       * @public
3730       * @since 5.9.125 (2011-10-03)
3731       */
3732  	public function setSpotColor($type, $name, $tint=100) {
3733          $spotcolor = TCPDF_COLORS::getSpotColor($name, $this->spot_colors);
3734          if ($spotcolor === false) {
3735              $this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
3736          }
3737          $tint = (max(0, min(100, $tint)) / 100);
3738          $pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
3739          switch ($type) {
3740              case 'draw': {
3741                  $pdfcolor .= sprintf('CS %F SCN', $tint);
3742                  $this->DrawColor = $pdfcolor;
3743                  $this->strokecolor = $spotcolor;
3744                  break;
3745              }
3746              case 'fill': {
3747                  $pdfcolor .= sprintf('cs %F scn', $tint);
3748                  $this->FillColor = $pdfcolor;
3749                  $this->bgcolor = $spotcolor;
3750                  break;
3751              }
3752              case 'text': {
3753                  $pdfcolor .= sprintf('cs %F scn', $tint);
3754                  $this->TextColor = $pdfcolor;
3755                  $this->fgcolor = $spotcolor;
3756                  break;
3757              }
3758          }
3759          $this->ColorFlag = ($this->FillColor != $this->TextColor);
3760          if ($this->state == 2) {
3761              $this->_out($pdfcolor);
3762          }
3763          if ($this->inxobj) {
3764              // we are inside an XObject template
3765              $this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
3766          }
3767          return $pdfcolor;
3768      }
3769  
3770      /**
3771       * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
3772       * @param $name (string) Name of the spot color.
3773       * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3774       * @public
3775       * @since 4.0.024 (2008-09-12)
3776       * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3777       */
3778  	public function SetDrawSpotColor($name, $tint=100) {
3779          $this->setSpotColor('draw', $name, $tint);
3780      }
3781  
3782      /**
3783       * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
3784       * @param $name (string) Name of the spot color.
3785       * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3786       * @public
3787       * @since 4.0.024 (2008-09-12)
3788       * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
3789       */
3790  	public function SetFillSpotColor($name, $tint=100) {
3791          $this->setSpotColor('fill', $name, $tint);
3792      }
3793  
3794      /**
3795       * Defines the spot color used for text.
3796       * @param $name (string) Name of the spot color.
3797       * @param $tint (int) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3798       * @public
3799       * @since 4.0.024 (2008-09-12)
3800       * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
3801       */
3802  	public function SetTextSpotColor($name, $tint=100) {
3803          $this->setSpotColor('text', $name, $tint);
3804      }
3805  
3806      /**
3807       * Set the color array for the specified type ('draw', 'fill', 'text').
3808       * It can be expressed in RGB, CMYK or GRAY SCALE components.
3809       * The method can be called before the first page is created and the value is retained from page to page.
3810       * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3811       * @param $color (array) Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
3812       * @param $ret (boolean) If true do not send the PDF command.
3813       * @return (string) The PDF command or empty string.
3814       * @public
3815       * @since 3.1.000 (2008-06-11)
3816       */
3817  	public function setColorArray($type, $color, $ret=false) {
3818          if (is_array($color)) {
3819              $color = array_values($color);
3820              // component: grey, RGB red or CMYK cyan
3821              $c = isset($color[0]) ? $color[0] : -1;
3822              // component: RGB green or CMYK magenta
3823              $m = isset($color[1]) ? $color[1] : -1;
3824              // component: RGB blue or CMYK yellow
3825              $y = isset($color[2]) ? $color[2] : -1;
3826              // component: CMYK black
3827              $k = isset($color[3]) ? $color[3] : -1;
3828              // color name
3829              $name = isset($color[4]) ? $color[4] : '';
3830              if ($c >= 0) {
3831                  return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
3832              }
3833          }
3834          return '';
3835      }
3836  
3837      /**
3838       * Defines the color used for all drawing operations (lines, rectangles and cell borders).
3839       * It can be expressed in RGB, CMYK or GRAY SCALE components.
3840       * The method can be called before the first page is created and the value is retained from page to page.
3841       * @param $color (array) Array of colors (1, 3 or 4 values).
3842       * @param $ret (boolean) If true do not send the PDF command.
3843       * @return string the PDF command
3844       * @public
3845       * @since 3.1.000 (2008-06-11)
3846       * @see SetDrawColor()
3847       */
3848  	public function SetDrawColorArray($color, $ret=false) {
3849          return $this->setColorArray('draw', $color, $ret);
3850      }
3851  
3852      /**
3853       * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
3854       * It can be expressed in RGB, CMYK or GRAY SCALE components.
3855       * The method can be called before the first page is created and the value is retained from page to page.
3856       * @param $color (array) Array of colors (1, 3 or 4 values).
3857       * @param $ret (boolean) If true do not send the PDF command.
3858       * @public
3859       * @since 3.1.000 (2008-6-11)
3860       * @see SetFillColor()
3861       */
3862  	public function SetFillColorArray($color, $ret=false) {
3863          return $this->setColorArray('fill', $color, $ret);
3864      }
3865  
3866      /**
3867       * Defines the color used for text. It can be expressed in RGB components or gray scale.
3868       * The method can be called before the first page is created and the value is retained from page to page.
3869       * @param $color (array) Array of colors (1, 3 or 4 values).
3870       * @param $ret (boolean) If true do not send the PDF command.
3871       * @public
3872       * @since 3.1.000 (2008-6-11)
3873       * @see SetFillColor()
3874       */
3875  	public function SetTextColorArray($color, $ret=false) {
3876          return $this->setColorArray('text', $color, $ret);
3877      }
3878  
3879      /**
3880       * Defines the color used by the specified type ('draw', 'fill', 'text').
3881       * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3882       * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3883       * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3884       * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3885       * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3886       * @param $ret (boolean) If true do not send the command.
3887       * @param $name (string) spot color name (if any)
3888       * @return (string) The PDF command or empty string.
3889       * @public
3890       * @since 5.9.125 (2011-10-03)
3891       */
3892  	public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3893          // set default values
3894          if (!is_numeric($col1)) {
3895              $col1 = 0;
3896          }
3897          if (!is_numeric($col2)) {
3898              $col2 = -1;
3899          }
3900          if (!is_numeric($col3)) {
3901              $col3 = -1;
3902          }
3903          if (!is_numeric($col4)) {
3904              $col4 = -1;
3905          }
3906          // set color by case
3907          $suffix = '';
3908          if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
3909              // Grey scale
3910              $col1 = max(0, min(255, $col1));
3911              $intcolor = array('G' => $col1);
3912              $pdfcolor = sprintf('%F ', ($col1 / 255));
3913              $suffix = 'g';
3914          } elseif ($col4 == -1) {
3915              // RGB
3916              $col1 = max(0, min(255, $col1));
3917              $col2 = max(0, min(255, $col2));
3918              $col3 = max(0, min(255, $col3));
3919              $intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
3920              $pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
3921              $suffix = 'rg';
3922          } else {
3923              $col1 = max(0, min(100, $col1));
3924              $col2 = max(0, min(100, $col2));
3925              $col3 = max(0, min(100, $col3));
3926              $col4 = max(0, min(100, $col4));
3927              if (empty($name)) {
3928                  // CMYK
3929                  $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
3930                  $pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
3931                  $suffix = 'k';
3932              } else {
3933                  // SPOT COLOR
3934                  $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
3935                  $this->AddSpotColor($name, $col1, $col2, $col3, $col4);
3936                  $pdfcolor = $this->setSpotColor($type, $name, 100);
3937              }
3938          }
3939          switch ($type) {
3940              case 'draw': {
3941                  $pdfcolor .= strtoupper($suffix);
3942                  $this->DrawColor = $pdfcolor;
3943                  $this->strokecolor = $intcolor;
3944                  break;
3945              }
3946              case 'fill': {
3947                  $pdfcolor .= $suffix;
3948                  $this->FillColor = $pdfcolor;
3949                  $this->bgcolor = $intcolor;
3950                  break;
3951              }
3952              case 'text': {
3953                  $pdfcolor .= $suffix;
3954                  $this->TextColor = $pdfcolor;
3955                  $this->fgcolor = $intcolor;
3956                  break;
3957              }
3958          }
3959          $this->ColorFlag = ($this->FillColor != $this->TextColor);
3960          if (($type != 'text') AND ($this->state == 2)) {
3961              if (!$ret) {
3962                  $this->_out($pdfcolor);
3963              }
3964              return $pdfcolor;
3965          }
3966          return '';
3967      }
3968  
3969      /**
3970       * Defines the color used for all drawing operations (lines, rectangles and cell borders). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
3971       * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3972       * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3973       * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3974       * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3975       * @param $ret (boolean) If true do not send the command.
3976       * @param $name (string) spot color name (if any)
3977       * @return string the PDF command
3978       * @public
3979       * @since 1.3
3980       * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
3981       */
3982  	public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3983          return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
3984      }
3985  
3986      /**
3987       * Defines the color used for all filling operations (filled rectangles and cell backgrounds). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
3988       * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3989       * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3990       * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3991       * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3992       * @param $ret (boolean) If true do not send the command.
3993       * @param $name (string) Spot color name (if any).
3994       * @return (string) The PDF command.
3995       * @public
3996       * @since 1.3
3997       * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
3998       */
3999  	public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4000          return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
4001      }
4002  
4003      /**
4004       * Defines the color used for text. It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
4005       * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4006       * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4007       * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4008       * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4009       * @param $ret (boolean) If true do not send the command.
4010       * @param $name (string) Spot color name (if any).
4011       * @return (string) Empty string.
4012       * @public
4013       * @since 1.3
4014       * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4015       */
4016  	public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4017          return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4018      }
4019  
4020      /**
4021       * Returns the length of a string in user unit. A font must be selected.<br>
4022       * @param $s (string) The string whose length is to be computed
4023       * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4024       * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line-through</li><li>O: overline</li></ul> or any combination. The default value is regular.
4025       * @param $fontsize (float) Font size in points. The default value is the current size.
4026       * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4027       * @return mixed int total string length or array of characted widths
4028       * @author Nicola Asuni
4029       * @public
4030       * @since 1.2
4031       */
4032  	public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4033          return $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont), $s, $this->tmprtl, $this->isunicode, $this->CurrentFont), $fontname, $fontstyle, $fontsize, $getarray);
4034      }
4035  
4036      /**
4037       * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4038       * @param $sa (string) The array of chars whose total length is to be computed
4039       * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4040       * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular.
4041       * @param $fontsize (float) Font size in points. The default value is the current size.
4042       * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4043       * @return mixed int total string length or array of characted widths
4044       * @author Nicola Asuni
4045       * @public
4046       * @since 2.4.000 (2008-03-06)
4047       */
4048  	public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4049          // store current values
4050          if (!TCPDF_STATIC::empty_string($fontname)) {
4051              $prev_FontFamily = $this->FontFamily;
4052              $prev_FontStyle = $this->FontStyle;
4053              $prev_FontSizePt = $this->FontSizePt;
4054              $this->SetFont($fontname, $fontstyle, $fontsize, '', 'default', false);
4055          }
4056          // convert UTF-8 array to Latin1 if required
4057          if ($this->isunicode AND (!$this->isUnicodeFont())) {
4058              $sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
4059          }
4060          $w = 0; // total width
4061          $wa = array(); // array of characters widths
4062          foreach ($sa as $ck => $char) {
4063              // character width
4064              $cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4065              $wa[] = $cw;
4066              $w += $cw;
4067          }
4068          // restore previous values
4069          if (!TCPDF_STATIC::empty_string($fontname)) {
4070              $this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4071          }
4072          if ($getarray) {
4073              return $wa;
4074          }
4075          return $w;
4076      }
4077  
4078      /**
4079       * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
4080       * @param $char (int) The char code whose length is to be returned
4081       * @param $notlast (boolean) If false ignore the font-spacing.
4082       * @return float char width
4083       * @author Nicola Asuni
4084       * @public
4085       * @since 2.4.000 (2008-03-06)
4086       */
4087  	public function GetCharWidth($char, $notlast=true) {
4088          // get raw width
4089          $chw = $this->getRawCharWidth($char);
4090          if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
4091              // increase/decrease font spacing
4092              $chw += $this->font_spacing;
4093          }
4094          if ($this->font_stretching != 100) {
4095              // fixed stretching mode
4096              $chw *= ($this->font_stretching / 100);
4097          }
4098          return $chw;
4099      }
4100  
4101      /**
4102       * Returns the length of the char in user unit for the current font.
4103       * @param $char (int) The char code whose length is to be returned
4104       * @return float char width
4105       * @author Nicola Asuni
4106       * @public
4107       * @since 5.9.000 (2010-09-28)
4108       */
4109  	public function getRawCharWidth($char) {
4110          if ($char == 173) {
4111              // SHY character will not be printed
4112              return (0);
4113          }
4114          if (isset($this->CurrentFont['cw'][$char])) {
4115              $w = $this->CurrentFont['cw'][$char];
4116          } elseif (isset($this->CurrentFont['dw'])) {
4117              // default width
4118              $w = $this->CurrentFont['dw'];
4119          } elseif (isset($this->CurrentFont['cw'][32])) {
4120              // default width
4121              $w = $this->CurrentFont['cw'][32];
4122          } else {
4123              $w = 600;
4124          }
4125          return $this->getAbsFontMeasure($w);
4126      }
4127  
4128      /**
4129       * Returns the numbero of characters in a string.
4130       * @param $s (string) The input string.
4131       * @return int number of characters
4132       * @public
4133       * @since 2.0.0001 (2008-01-07)
4134       */
4135  	public function GetNumChars($s) {
4136          if ($this->isUnicodeFont()) {
4137              return count(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont));
4138          }
4139          return strlen($s);
4140      }
4141  
4142      /**
4143       * Fill the list of available fonts ($this->fontlist).
4144       * @protected
4145       * @since 4.0.013 (2008-07-28)
4146       */
4147  	protected function getFontsList() {
4148          if (($fontsdir = opendir(TCPDF_FONTS::_getfontpath())) !== false) {
4149              while (($file = readdir($fontsdir)) !== false) {
4150                  if (substr($file, -4) == '.php') {
4151                      array_push($this->fontlist, strtolower(basename($file, '.php')));
4152                  }
4153              }
4154              closedir($fontsdir);
4155          }
4156      }
4157  
4158      /**
4159       * Imports a TrueType, Type1, core, or CID0 font and makes it available.
4160       * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
4161       * The definition file (and the font file itself when embedding) must be present either in the current directory or in the one indicated by K_PATH_FONTS if the constant is defined. If it could not be found, the error "Could not include font definition file" is generated.
4162       * @param $family (string) Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
4163       * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular (default)</li><li>B: bold</li><li>I: italic</li><li>BI or IB: bold italic</li></ul>
4164       * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4165       * @return array containing the font data, or false in case of error.
4166       * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
4167       * @public
4168       * @since 1.5
4169       * @see SetFont(), setFontSubsetting()
4170       */
4171  	public function AddFont($family, $style='', $fontfile='', $subset='default') {
4172          if ($subset === 'default') {
4173              $subset = $this->font_subsetting;
4174          }
4175          if ($this->pdfa_mode) {
4176              $subset = false;
4177          }
4178          if (TCPDF_STATIC::empty_string($family)) {
4179              if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
4180                  $family = $this->FontFamily;
4181              } else {
4182                  $this->Error('Empty font family');
4183              }
4184          }
4185          // move embedded styles on $style
4186          if (substr($family, -1) == 'I') {
4187              $style .= 'I';
4188              $family = substr($family, 0, -1);
4189          }
4190          if (substr($family, -1) == 'B') {
4191              $style .= 'B';
4192              $family = substr($family, 0, -1);
4193          }
4194          // normalize family name
4195          $family = strtolower($family);
4196          if ((!$this->isunicode) AND ($family == 'arial')) {
4197              $family = 'helvetica';
4198          }
4199          if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4200              $style = '';
4201          }
4202          if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
4203              // all fonts must be embedded
4204              $family = 'pdfa'.$family;
4205          }
4206          $tempstyle = strtoupper($style);
4207          $style = '';
4208          // underline
4209          if (strpos($tempstyle, 'U') !== false) {
4210              $this->underline = true;
4211          } else {
4212              $this->underline = false;
4213          }
4214          // line-through (deleted)
4215          if (strpos($tempstyle, 'D') !== false) {
4216              $this->linethrough = true;
4217          } else {
4218              $this->linethrough = false;
4219          }
4220          // overline
4221          if (strpos($tempstyle, 'O') !== false) {
4222              $this->overline = true;
4223          } else {
4224              $this->overline = false;
4225          }
4226          // bold
4227          if (strpos($tempstyle, 'B') !== false) {
4228              $style .= 'B';
4229          }
4230          // oblique
4231          if (strpos($tempstyle, 'I') !== false) {
4232              $style .= 'I';
4233          }
4234          $bistyle = $style;
4235          $fontkey = $family.$style;
4236          $font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
4237          $fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4238          // check if the font has been already added
4239          $fb = $this->getFontBuffer($fontkey);
4240          if ($fb !== false) {
4241              if ($this->inxobj) {
4242                  // we are inside an XObject template
4243                  $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
4244              }
4245              return $fontdata;
4246          }
4247          // get specified font directory (if any)
4248          $fontdir = false;
4249          if (!TCPDF_STATIC::empty_string($fontfile)) {
4250              $fontdir = dirname($fontfile);
4251              if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
4252                  $fontdir = '';
4253              } else {
4254                  $fontdir .= '/';
4255              }
4256          }
4257          // true when the font style variation is missing
4258          $missing_style = false;
4259          // search and include font file
4260          if (TCPDF_STATIC::empty_string($fontfile) OR (!@file_exists($fontfile))) {
4261              // build a standard filenames for specified font
4262              $tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
4263              $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4264              if (TCPDF_STATIC::empty_string($fontfile)) {
4265                  $missing_style = true;
4266                  // try to remove the style part
4267                  $tmp_fontfile = str_replace(' ', '', $family).'.php';
4268                  $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4269              }
4270          }
4271          // include font file
4272          if (!TCPDF_STATIC::empty_string($fontfile) AND (@file_exists($fontfile))) {
4273              include($fontfile);
4274          } else {
4275              $this->Error('Could not include font definition file: '.$family.'');
4276          }
4277          // check font parameters
4278          if ((!isset($type)) OR (!isset($cw))) {
4279              $this->Error('The font definition file has a bad format: '.$fontfile.'');
4280          }
4281          // SET default parameters
4282          if (!isset($file) OR TCPDF_STATIC::empty_string($file)) {
4283              $file = '';
4284          }
4285          if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
4286              $enc = '';
4287          }
4288          if (!isset($cidinfo) OR TCPDF_STATIC::empty_string($cidinfo)) {
4289              $cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
4290              $cidinfo['uni2cid'] = array();
4291          }
4292          if (!isset($ctg) OR TCPDF_STATIC::empty_string($ctg)) {
4293              $ctg = '';
4294          }
4295          if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
4296              $desc = array();
4297          }
4298          if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
4299              $up = -100;
4300          }
4301          if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
4302              $ut = 50;
4303          }
4304          if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
4305              $cw = array();
4306          }
4307          if (!isset($dw) OR TCPDF_STATIC::empty_string($dw)) {
4308              // set default width
4309              if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4310                  $dw = $desc['MissingWidth'];
4311              } elseif (isset($cw[32])) {
4312                  $dw = $cw[32];
4313              } else {
4314                  $dw = 600;
4315              }
4316          }
4317          ++$this->numfonts;
4318          if ($type == 'core') {
4319              $name = $this->CoreFonts[$fontkey];
4320              $subset = false;
4321          } elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4322              $subset = false;
4323          } elseif ($type == 'TrueTypeUnicode') {
4324              $enc = 'Identity-H';
4325          } elseif ($type == 'cidfont0') {
4326              if ($this->pdfa_mode) {
4327                  $this->Error('All fonts must be embedded in PDF/A mode!');
4328              }
4329          } else {
4330              $this->Error('Unknow font type: '.$type.'');
4331          }
4332          // set name if unset
4333          if (!isset($name) OR empty($name)) {
4334              $name = $fontkey;
4335          }
4336          // create artificial font style variations if missing (only works with non-embedded fonts)
4337          if (($type != 'core') AND $missing_style) {
4338              // style variations
4339              $styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4340              $name .= $styles[$bistyle];
4341              // artificial bold
4342              if (strpos($bistyle, 'B') !== false) {
4343                  if (isset($desc['StemV'])) {
4344                      // from normal to bold
4345                      $desc['StemV'] = round($desc['StemV'] * 1.75);
4346                  } else {
4347                      // bold
4348                      $desc['StemV'] = 123;
4349                  }
4350              }
4351              // artificial italic
4352              if (strpos($bistyle, 'I') !== false) {
4353                  if (isset($desc['ItalicAngle'])) {
4354                      $desc['ItalicAngle'] -= 11;
4355                  } else {
4356                      $desc['ItalicAngle'] = -11;
4357                  }
4358                  if (isset($desc['Flags'])) {
4359                      $desc['Flags'] |= 64; //bit 7
4360                  } else {
4361                      $desc['Flags'] = 64;
4362                  }
4363              }
4364          }
4365          // check if the array of characters bounding boxes is defined
4366          if (!isset($cbbox)) {
4367              $cbbox = array();
4368          }
4369          // initialize subsetchars
4370          $subsetchars = array_fill(0, 255, true);
4371          $this->setFontBuffer($fontkey, array('fontkey' => $fontkey, 'i' => $this->numfonts, 'type' => $type, 'name' => $name, 'desc' => $desc, 'up' => $up, 'ut' => $ut, 'cw' => $cw, 'cbbox' => $cbbox, 'dw' => $dw, 'enc' => $enc, 'cidinfo' => $cidinfo, 'file' => $file, 'ctg' => $ctg, 'subset' => $subset, 'subsetchars' => $subsetchars));
4372          if ($this->inxobj) {
4373              // we are inside an XObject template
4374              $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
4375          }
4376          if (isset($diff) AND (!empty($diff))) {
4377              //Search existing encodings
4378              $d = 0;
4379              $nb = count($this->diffs);
4380              for ($i=1; $i <= $nb; ++$i) {
4381                  if ($this->diffs[$i] == $diff) {
4382                      $d = $i;
4383                      break;
4384                  }
4385              }
4386              if ($d == 0) {
4387                  $d = $nb + 1;
4388                  $this->diffs[$d] = $diff;
4389              }
4390              $this->setFontSubBuffer($fontkey, 'diff', $d);
4391          }
4392          if (!TCPDF_STATIC::empty_string($file)) {
4393              if (!isset($this->FontFiles[$file])) {
4394                  if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4395                      $this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4396                  } elseif ($type != 'core') {
4397                      $this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4398                  }
4399              } else {
4400                  // update fontkeys that are sharing this font file
4401                  $this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
4402                  if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
4403                      $this->FontFiles[$file]['fontkeys'][] = $fontkey;
4404                  }
4405              }
4406          }
4407          return $fontdata;
4408      }
4409  
4410      /**
4411       * Sets the font used to print character strings.
4412       * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
4413       * The method can be called before the first page is created and the font is retained from page to page.
4414       * If you just wish to change the current font size, it is simpler to call SetFontSize().
4415       * Note: for the standard fonts, the font metric files must be accessible. There are three possibilities for this:<ul><li>They are in the current directory (the one where the running script lies)</li><li>They are in one of the directories defined by the include_path parameter</li><li>They are in the directory defined by the K_PATH_FONTS constant</li></ul><br />
4416       * @param $family (string) Family font. It can be either a name defined by AddFont() or one of the standard Type1 families (case insensitive):<ul><li>times (Times-Roman)</li><li>timesb (Times-Bold)</li><li>timesi (Times-Italic)</li><li>timesbi (Times-BoldItalic)</li><li>helvetica (Helvetica)</li><li>helveticab (Helvetica-Bold)</li><li>helveticai (Helvetica-Oblique)</li><li>helveticabi (Helvetica-BoldOblique)</li><li>courier (Courier)</li><li>courierb (Courier-Bold)</li><li>courieri (Courier-Oblique)</li><li>courierbi (Courier-BoldOblique)</li><li>symbol (Symbol)</li><li>zapfdingbats (ZapfDingbats)</li></ul> It is also possible to pass an empty string. In that case, the current family is retained.
4417       * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats basic fonts or other fonts when not defined.
4418       * @param $size (float) Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12
4419       * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4420       * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
4421       * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4422       * @author Nicola Asuni
4423       * @public
4424       * @since 1.0
4425       * @see AddFont(), SetFontSize()
4426       */
4427  	public function SetFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
4428          //Select a font; size given in points
4429          if ($size === null) {
4430              $size = $this->FontSizePt;
4431          }
4432          if ($size < 0) {
4433              $size = 0;
4434          }
4435          // try to add font (if not already added)
4436          $fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4437          $this->FontFamily = $fontdata['family'];
4438          $this->FontStyle = $fontdata['style'];
4439          if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
4440              // save subset chars of the previous font
4441              $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
4442          }
4443          $this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
4444          $this->SetFontSize($size, $out);
4445      }
4446  
4447      /**
4448       * Defines the size of the current font.
4449       * @param $size (float) The font size in points.
4450       * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4451       * @public
4452       * @since 1.0
4453       * @see SetFont()
4454       */
4455  	public function SetFontSize($size, $out=true) {
4456          // font size in points
4457          $this->FontSizePt = $size;
4458          // font size in user units
4459          $this->FontSize = $size / $this->k;
4460          // calculate some font metrics
4461          if (isset($this->CurrentFont['desc']['FontBBox'])) {
4462              $bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4463              $font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4464          } else {
4465              $font_height = $size * 1.219;
4466          }
4467          if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
4468              $font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
4469          }
4470          if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
4471              $font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
4472          }
4473          if (!isset($font_ascent) AND !isset($font_descent)) {
4474              // core font
4475              $font_ascent = 0.76 * $font_height;
4476              $font_descent = $font_height - $font_ascent;
4477          } elseif (!isset($font_descent)) {
4478              $font_descent = $font_height - $font_ascent;
4479          } elseif (!isset($font_ascent)) {
4480              $font_ascent = $font_height - $font_descent;
4481          }
4482          $this->FontAscent = ($font_ascent / $this->k);
4483          $this->FontDescent = ($font_descent / $this->k);
4484          if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
4485              $this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4486          }
4487      }
4488  
4489      /**
4490       * Returns the bounding box of the current font in user units.
4491       * @return array
4492       * @public
4493       * @since 5.9.152 (2012-03-23)
4494       */
4495  	public function getFontBBox() {
4496          $fbbox = array();
4497          if (isset($this->CurrentFont['desc']['FontBBox'])) {
4498              $tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4499              $fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
4500          } else {
4501              // Find max width
4502              if (isset($this->CurrentFont['desc']['MaxWidth'])) {
4503                  $maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
4504              } else {
4505                  $maxw = 0;
4506                  if (isset($this->CurrentFont['desc']['MissingWidth'])) {
4507                      $maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
4508                  }
4509                  if (isset($this->CurrentFont['desc']['AvgWidth'])) {
4510                      $maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
4511                  }
4512                  if (isset($this->CurrentFont['dw'])) {
4513                      $maxw = max($maxw, $this->CurrentFont['dw']);
4514                  }
4515                  foreach ($this->CurrentFont['cw'] as $char => $w) {
4516                      $maxw = max($maxw, $w);
4517                  }
4518                  if ($maxw == 0) {
4519                      $maxw = 600;
4520                  }
4521                  $maxw = $this->getAbsFontMeasure($maxw);
4522              }
4523              $fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
4524          }
4525          return $fbbox;
4526      }
4527  
4528      /**
4529       * Convert a relative font measure into absolute value.
4530       * @param $s (int) Font measure.
4531       * @return float Absolute measure.
4532       * @since 5.9.186 (2012-09-13)
4533       */
4534  	public function getAbsFontMeasure($s) {
4535          return ($s * $this->FontSize / 1000);
4536      }
4537  
4538      /**
4539       * Returns the glyph bounding box of the specified character in the current font in user units.
4540       * @param $char (int) Input character code.
4541       * @return mixed array(xMin, yMin, xMax, yMax) or FALSE if not defined.
4542       * @since 5.9.186 (2012-09-13)
4543       */
4544  	public function getCharBBox($char) {
4545          $c = intval($char);
4546          if (isset($this->CurrentFont['cw'][$c])) {
4547              // glyph is defined ... use zero width & height for glyphs without outlines
4548              $result = array(0,0,0,0);
4549              if (isset($this->CurrentFont['cbbox'][$c])) {
4550                  $result = $this->CurrentFont['cbbox'][$c];
4551              }
4552              return array_map(array($this,'getAbsFontMeasure'), $result);
4553          }
4554          return false;
4555      }
4556  
4557      /**
4558       * Return the font descent value
4559       * @param $font (string) font name
4560       * @param $style (string) font style
4561       * @param $size (float) The size (in points)
4562       * @return int font descent
4563       * @public
4564       * @author Nicola Asuni
4565       * @since 4.9.003 (2010-03-30)
4566       */
4567  	public function getFontDescent($font, $style='', $size=0) {
4568          $fontdata = $this->AddFont($font, $style);
4569          $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4570          if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
4571              $descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
4572          } else {
4573              $descent = (1.219 * 0.24 * $size);
4574          }
4575          return ($descent / $this->k);
4576      }
4577  
4578      /**
4579       * Return the font ascent value.
4580       * @param $font (string) font name
4581       * @param $style (string) font style
4582       * @param $size (float) The size (in points)
4583       * @return int font ascent
4584       * @public
4585       * @author Nicola Asuni
4586       * @since 4.9.003 (2010-03-30)
4587       */
4588  	public function getFontAscent($font, $style='', $size=0) {
4589          $fontdata = $this->AddFont($font, $style);
4590          $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4591          if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
4592              $ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
4593          } else {
4594              $ascent = 1.219 * 0.76 * $size;
4595          }
4596          return ($ascent / $this->k);
4597      }
4598  
4599      /**
4600       * Return true in the character is present in the specified font.
4601       * @param $char (mixed) Character to check (integer value or string)
4602       * @param $font (string) Font name (family name).
4603       * @param $style (string) Font style.
4604       * @return (boolean) true if the char is defined, false otherwise.
4605       * @public
4606       * @since 5.9.153 (2012-03-28)
4607       */
4608  	public function isCharDefined($char, $font='', $style='') {
4609          if (is_string($char)) {
4610              // get character code
4611              $char = TCPDF_FONTS::UTF8StringToArray($char, $this->isunicode, $this->CurrentFont);
4612              $char = $char[0];
4613          }
4614          if (TCPDF_STATIC::empty_string($font)) {
4615              if (TCPDF_STATIC::empty_string($style)) {
4616                  return (isset($this->CurrentFont['cw'][intval($char)]));
4617              }
4618              $font = $this->FontFamily;
4619          }
4620          $fontdata = $this->AddFont($font, $style);
4621          $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4622          return (isset($fontinfo['cw'][intval($char)]));
4623      }
4624  
4625      /**
4626       * Replace missing font characters on selected font with specified substitutions.
4627       * @param $text (string) Text to process.
4628       * @param $font (string) Font name (family name).
4629       * @param $style (string) Font style.
4630       * @param $subs (array) Array of possible character substitutions. The key is the character to check (integer value) and the value is a single intege value or an array of possible substitutes.
4631       * @return (string) Processed text.
4632       * @public
4633       * @since 5.9.153 (2012-03-28)
4634       */
4635  	public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
4636          if (empty($subs)) {
4637              return $text;
4638          }
4639          if (TCPDF_STATIC::empty_string($font)) {
4640              $font = $this->FontFamily;
4641          }
4642          $fontdata = $this->AddFont($font, $style);
4643          $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4644          $uniarr = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
4645          foreach ($uniarr as $k => $chr) {
4646              if (!isset($fontinfo['cw'][$chr])) {
4647                  // this character is missing on the selected font
4648                  if (isset($subs[$chr])) {
4649                      // we have available substitutions
4650                      if (is_array($subs[$chr])) {
4651                          foreach($subs[$chr] as $s) {
4652                              if (isset($fontinfo['cw'][$s])) {
4653                                  $uniarr[$k] = $s;
4654                                  break;
4655                              }
4656                          }
4657                      } elseif (isset($fontinfo['cw'][$subs[$chr]])) {
4658                          $uniarr[$k] = $subs[$chr];
4659                      }
4660                  }
4661              }
4662          }
4663          return TCPDF_FONTS::UniArrSubString(TCPDF_FONTS::UTF8ArrayToUniArray($uniarr, $this->isunicode));
4664      }
4665  
4666      /**
4667       * Defines the default monospaced font.
4668       * @param $font (string) Font name.
4669       * @public
4670       * @since 4.5.025
4671       */
4672  	public function SetDefaultMonospacedFont($font) {
4673          $this->default_monospaced_font = $font;
4674      }
4675  
4676      /**
4677       * Creates a new internal link and returns its identifier. An internal link is a clickable area which directs to another place within the document.<br />
4678       * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
4679       * @public
4680       * @since 1.5
4681       * @see Cell(), Write(), Image(), Link(), SetLink()
4682       */
4683  	public function AddLink() {
4684          // create a new internal link
4685          $n = count($this->links) + 1;
4686          $this->links[$n] = array('p' => 0, 'y' => 0, 'f' => false);
4687          return $n;
4688      }
4689  
4690      /**
4691       * Defines the page and position a link points to.
4692       * @param $link (int) The link identifier returned by AddLink()
4693       * @param $y (float) Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
4694       * @param $page (int) Number of target page; -1 indicates the current page (default value). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages. 
4695       * @public
4696       * @since 1.5
4697       * @see AddLink()
4698       */
4699  	public function SetLink($link, $y=0, $page=-1) {
4700          $fixed = false;
4701          if (!empty($page) AND ($page[0] == '*')) {
4702              $page = intval(substr($page, 1));
4703              // this page number will not be changed when moving/add/deleting pages
4704              $fixed = true;
4705          }
4706          if ($page < 0) {
4707              $page = $this->page;
4708          }
4709          if ($y == -1) {
4710              $y = $this->y;
4711          }
4712          $this->links[$link] = array('p' => $page, 'y' => $y, 'f' => $fixed);
4713      }
4714  
4715      /**
4716       * Puts a link on a rectangular area of the page.
4717       * Text or image links are generally put via Cell(), Write() or Image(), but this method can be useful for instance to define a clickable area inside an image.
4718       * @param $x (float) Abscissa of the upper-left corner of the rectangle
4719       * @param $y (float) Ordinate of the upper-left corner of the rectangle
4720       * @param $w (float) Width of the rectangle
4721       * @param $h (float) Height of the rectangle
4722       * @param $link (mixed) URL or identifier returned by AddLink()
4723       * @param $spaces (int) number of spaces on the text to link
4724       * @public
4725       * @since 1.5
4726       * @see AddLink(), Annotation(), Cell(), Write(), Image()
4727       */
4728  	public function Link($x, $y, $w, $h, $link, $spaces=0) {
4729          $this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
4730      }
4731  
4732      /**
4733       * Puts a markup annotation on a rectangular area of the page.
4734       * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
4735       * @param $x (float) Abscissa of the upper-left corner of the rectangle
4736       * @param $y (float) Ordinate of the upper-left corner of the rectangle
4737       * @param $w (float) Width of the rectangle
4738       * @param $h (float) Height of the rectangle
4739       * @param $text (string) annotation text or alternate content
4740       * @param $opt (array) array of options (see section 8.4 of PDF reference 1.7).
4741       * @param $spaces (int) number of spaces on the text to link
4742       * @public
4743       * @since 4.0.018 (2008-08-06)
4744       */
4745  	public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
4746          if ($this->inxobj) {
4747              // store parameters for later use on template
4748              $this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
4749              return;
4750          }
4751          if ($x === '') {
4752              $x = $this->x;
4753          }
4754          if ($y === '') {
4755              $y = $this->y;
4756          }
4757          // check page for no-write regions and adapt page margins if necessary
4758          list($x, $y) = $this->checkPageRegions($h, $x, $y);
4759          // recalculate coordinates to account for graphic transformations
4760          if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
4761              for ($i=$this->transfmatrix_key; $i > 0; --$i) {
4762                  $maxid = count($this->transfmatrix[$i]) - 1;
4763                  for ($j=$maxid; $j >= 0; --$j) {
4764                      $ctm = $this->transfmatrix[$i][$j];
4765                      if (isset($ctm['a'])) {
4766                          $x = $x * $this->k;
4767                          $y = ($this->h - $y) * $this->k;
4768                          $w = $w * $this->k;
4769                          $h = $h * $this->k;
4770                          // top left
4771                          $xt = $x;
4772                          $yt = $y;
4773                          $x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4774                          $y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4775                          // top right
4776                          $xt = $x + $w;
4777                          $yt = $y;
4778                          $x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4779                          $y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4780                          // bottom left
4781                          $xt = $x;
4782                          $yt = $y - $h;
4783                          $x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4784                          $y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4785                          // bottom right
4786                          $xt = $x + $w;
4787                          $yt = $y - $h;
4788                          $x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4789                          $y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4790                          // new coordinates (rectangle area)
4791                          $x = min($x1, $x2, $x3, $x4);
4792                          $y = max($y1, $y2, $y3, $y4);
4793                          $w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
4794                          $h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
4795                          $x = $x / $this->k;
4796                          $y = $this->h - ($y / $this->k);
4797                      }
4798                  }
4799              }
4800          }
4801          if ($this->page <= 0) {
4802              $page = 1;
4803          } else {
4804              $page = $this->page;
4805          }
4806          if (!isset($this->PageAnnots[$page])) {
4807              $this->PageAnnots[$page] = array();
4808          }
4809          $this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
4810          if (!$this->pdfa_mode) {
4811              if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
4812                  AND (@file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
4813                  AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
4814                  $this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
4815              }
4816          }
4817          // Add widgets annotation's icons
4818          if (isset($opt['mk']['i']) AND @file_exists($opt['mk']['i'])) {
4819              $this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
4820          }
4821          if (isset($opt['mk']['ri']) AND @file_exists($opt['mk']['ri'])) {
4822              $this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4823          }
4824          if (isset($opt['mk']['ix']) AND @file_exists($opt['mk']['ix'])) {
4825              $this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4826          }
4827      }
4828  
4829      /**
4830       * Embedd the attached files.
4831       * @since 4.4.000 (2008-12-07)
4832       * @protected
4833       * @see Annotation()
4834       */
4835  	protected function _putEmbeddedFiles() {
4836          if ($this->pdfa_mode) {
4837              // embedded files are not allowed in PDF/A mode
4838              return;
4839          }
4840          reset($this->embeddedfiles);
4841          foreach ($this->embeddedfiles as $filename => $filedata) {
4842              $data = TCPDF_STATIC::fileGetContents($filedata['file']);
4843              if ($data !== FALSE) {
4844                  $rawsize = strlen($data);
4845                  if ($rawsize > 0) {
4846                      // update name tree
4847                      $this->efnames[$filename] = $filedata['f'].' 0 R';
4848                      // embedded file specification object
4849                      $out = $this->_getobj($filedata['f'])."\n";
4850                      $out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']).' /EF <</F '.$filedata['n'].' 0 R>> >>';
4851                      $out .= "\n".'endobj';
4852                      $this->_out($out);
4853                      // embedded file object
4854                      $filter = '';
4855                      if ($this->compress) {
4856                          $data = gzcompress($data);
4857                          $filter = ' /Filter /FlateDecode';
4858                      }
4859                      $stream = $this->_getrawstream($data, $filedata['n']);
4860                      $out = $this->_getobj($filedata['n'])."\n";
4861                      $out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
4862                      $out .= ' stream'."\n".$stream."\n".'endstream';
4863                      $out .= "\n".'endobj';
4864                      $this->_out($out);
4865                  }
4866              }
4867          }
4868      }
4869  
4870      /**
4871       * Prints a text cell at the specified position.
4872       * This method allows to place a string precisely on the page.
4873       * @param $x (float) Abscissa of the cell origin
4874       * @param $y (float) Ordinate of the cell origin
4875       * @param $txt (string) String to print
4876       * @param $fstroke (int) outline size in user units (false = disable)
4877       * @param $fclip (boolean) if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
4878       * @param $ffill (boolean) if true fills the text
4879       * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
4880       * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
4881       * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
4882       * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
4883       * @param $link (mixed) URL or identifier returned by AddLink().
4884       * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
4885       * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
4886       * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li><li>B : cell bottom</li></ul>
4887       * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
4888       * @param $rtloff (boolean) if true uses the page top-left corner as origin of axis for $x and $y initial position.
4889       * @public
4890       * @since 1.0
4891       * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
4892       */
4893  	public function Text($x, $y, $txt, $fstroke=false, $fclip=false, $ffill=true, $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M', $rtloff=false) {
4894          $textrendermode = $this->textrendermode;
4895          $textstrokewidth = $this->textstrokewidth;
4896          $this->setTextRenderingMode($fstroke, $ffill, $fclip);
4897          $this->SetXY($x, $y, $rtloff);
4898          $this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
4899          // restore previous rendering mode
4900          $this->textrendermode = $textrendermode;
4901          $this->textstrokewidth = $textstrokewidth;
4902      }
4903  
4904      /**
4905       * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
4906       * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
4907       * This method is called automatically and should not be called directly by the application.
4908       * @return boolean
4909       * @public
4910       * @since 1.4
4911       * @see SetAutoPageBreak()
4912       */
4913  	public function AcceptPageBreak() {
4914          if ($this->num_columns > 1) {
4915              // multi column mode
4916              if ($this->current_column < ($this->num_columns - 1)) {
4917                  // go to next column
4918                  $this->selectColumn($this->current_column + 1);
4919              } elseif ($this->AutoPageBreak) {
4920                  // add a new page
4921                  $this->AddPage();
4922                  // set first column
4923                  $this->selectColumn(0);
4924              }
4925              // avoid page breaking from checkPageBreak()
4926              return false;
4927          }
4928          return $this->AutoPageBreak;
4929      }
4930  
4931      /**
4932       * Add page if needed.
4933       * @param $h (float) Cell height. Default value: 0.
4934       * @param $y (mixed) starting y position, leave empty for current position.
4935       * @param $addpage (boolean) if true add a page, otherwise only return the true/false state
4936       * @return boolean true in case of page break, false otherwise.
4937       * @since 3.2.000 (2008-07-01)
4938       * @protected
4939       */
4940  	protected function checkPageBreak($h=0, $y='', $addpage=true) {
4941          if (TCPDF_STATIC::empty_string($y)) {
4942              $y = $this->y;
4943          }
4944          $current_page = $this->page;
4945          if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
4946              if ($addpage) {
4947                  //Automatic page break
4948                  $x = $this->x;
4949                  $this->AddPage($this->CurOrientation);
4950                  $this->y = $this->tMargin;
4951                  $oldpage = $this->page - 1;
4952                  if ($this->rtl) {
4953                      if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
4954                          $this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
4955                      } else {
4956                          $this->x = $x;
4957                      }
4958                  } else {
4959                      if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
4960                          $this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
4961                      } else {
4962                          $this->x = $x;
4963                      }
4964                  }
4965              }
4966              return true;
4967          }
4968          if ($current_page != $this->page) {
4969              // account for columns mode
4970              return true;
4971          }
4972          return false;
4973      }
4974  
4975      /**
4976       * Prints a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.<br />
4977       * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
4978       * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
4979       * @param $h (float) Cell height. Default value: 0.
4980       * @param $txt (string) String to print. Default value: empty string.
4981       * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
4982       * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul> Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
4983       * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
4984       * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
4985       * @param $link (mixed) URL or identifier returned by AddLink().
4986       * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
4987       * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
4988       * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
4989       * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
4990       * @public
4991       * @since 1.0
4992       * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
4993       */
4994  	public function Cell($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M') {
4995          $prev_cell_margin = $this->cell_margin;
4996          $prev_cell_padding = $this->cell_padding;
4997          $this->adjustCellPadding($border);
4998          if (!$ignore_min_height) {
4999              $min_cell_height = $this->getCellHeight($this->FontSize);
5000              if ($h < $min_cell_height) {
5001                  $h = $min_cell_height;
5002              }
5003          }
5004          $this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5005          // apply text shadow if enabled
5006          if ($this->txtshadow['enabled']) {
5007              // save data
5008              $x = $this->x;
5009              $y = $this->y;
5010              $bc = $this->bgcolor;
5011              $fc = $this->fgcolor;
5012              $sc = $this->strokecolor;
5013              $alpha = $this->alpha;
5014              // print shadow
5015              $this->x += $this->txtshadow['depth_w'];
5016              $this->y += $this->txtshadow['depth_h'];
5017              $this->SetFillColorArray($this->txtshadow['color']);
5018              $this->SetTextColorArray($this->txtshadow['color']);
5019              $this->SetDrawColorArray($this->txtshadow['color']);
5020              if ($this->txtshadow['opacity'] != $alpha['CA']) {
5021                  $this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
5022              }
5023              if ($this->state == 2) {
5024                  $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5025              }
5026              //restore data
5027              $this->x = $x;
5028              $this->y = $y;
5029              $this->SetFillColorArray($bc);
5030              $this->SetTextColorArray($fc);
5031              $this->SetDrawColorArray($sc);
5032              if ($this->txtshadow['opacity'] != $alpha['CA']) {
5033                  $this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
5034              }
5035          }
5036          if ($this->state == 2) {
5037              $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5038          }
5039          $this->cell_padding = $prev_cell_padding;
5040          $this->cell_margin = $prev_cell_margin;
5041      }
5042  
5043      /**
5044       * Returns the PDF string code to print a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.<br />
5045       * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5046       * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
5047       * @param $h (float) Cell height. Default value: 0.
5048       * @param $txt (string) String to print. Default value: empty string.
5049       * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5050       * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5051       * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5052       * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5053       * @param $link (mixed) URL or identifier returned by AddLink().
5054       * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5055       * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5056       * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5057       * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>M : middle</li><li>B : bottom</li></ul>
5058       * @return string containing cell code
5059       * @protected
5060       * @since 1.0
5061       * @see Cell()
5062       */
5063  	protected function getCellCode($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M') {
5064          // replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
5065          $txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
5066          $prev_cell_margin = $this->cell_margin;
5067          $prev_cell_padding = $this->cell_padding;
5068          $txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
5069          $rs = ''; //string to be returned
5070          $this->adjustCellPadding($border);
5071          if (!$ignore_min_height) {
5072              $min_cell_height = $this->getCellHeight($this->FontSize);
5073              if ($h < $min_cell_height) {
5074                  $h = $min_cell_height;
5075              }
5076          }
5077          $k = $this->k;
5078          // check page for no-write regions and adapt page margins if necessary
5079          list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
5080          if ($this->rtl) {
5081              $x = $this->x - $this->cell_margin['R'];
5082          } else {
5083              $x = $this->x + $this->cell_margin['L'];
5084          }
5085          $y = $this->y + $this->cell_margin['T'];
5086          $prev_font_stretching = $this->font_stretching;
5087          $prev_font_spacing = $this->font_spacing;
5088          // cell vertical alignment
5089          switch ($calign) {
5090              case 'A': {
5091                  // font top
5092                  switch ($valign) {
5093                      case 'T': {
5094                          // top
5095                          $y -= $this->cell_padding['T'];
5096                          break;
5097                      }
5098                      case 'B': {
5099                          // bottom
5100                          $y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5101                          break;
5102                      }
5103                      default:
5104                      case 'C':
5105                      case 'M': {
5106                          // center
5107                          $y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5108                          break;
5109                      }
5110                  }
5111                  break;
5112              }
5113              case 'L': {
5114                  // font baseline
5115                  switch ($valign) {
5116                      case 'T': {
5117                          // top
5118                          $y -= ($this->cell_padding['T'] + $this->FontAscent);
5119                          break;
5120                      }
5121                      case 'B': {
5122                          // bottom
5123                          $y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5124                          break;
5125                      }
5126                      default:
5127                      case 'C':
5128                      case 'M': {
5129                          // center
5130                          $y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5131                          break;
5132                      }
5133                  }
5134                  break;
5135              }
5136              case 'D': {
5137                  // font bottom
5138                  switch ($valign) {
5139                      case 'T': {
5140                          // top
5141                          $y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5142                          break;
5143                      }
5144                      case 'B': {
5145                          // bottom
5146                          $y -= ($h - $this->cell_padding['B']);
5147                          break;
5148                      }
5149                      default:
5150                      case 'C':
5151                      case 'M': {
5152                          // center
5153                          $y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5154                          break;
5155                      }
5156                  }
5157                  break;
5158              }
5159              case 'B': {
5160                  // cell bottom
5161                  $y -= $h;
5162                  break;
5163              }
5164              case 'C':
5165              case 'M': {
5166                  // cell center
5167                  $y -= ($h / 2);
5168                  break;
5169              }
5170              default:
5171              case 'T': {
5172                  // cell top
5173                  break;
5174              }
5175          }
5176          // text vertical alignment
5177          switch ($valign) {
5178              case 'T': {
5179                  // top
5180                  $yt = $y + $this->cell_padding['T'];
5181                  break;
5182              }
5183              case 'B': {
5184                  // bottom
5185                  $yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5186                  break;
5187              }
5188              default:
5189              case 'C':
5190              case 'M': {
5191                  // center
5192                  $yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5193                  break;
5194              }
5195          }
5196          $basefonty = $yt + $this->FontAscent;
5197          if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5198              if ($this->rtl) {
5199                  $w = $x - $this->lMargin;
5200              } else {
5201                  $w = $this->w - $this->rMargin - $x;
5202              }
5203          }
5204          $s = '';
5205          // fill and borders
5206          if (is_string($border) AND (strlen($border) == 4)) {
5207              // full border
5208              $border = 1;
5209          }
5210          if ($fill OR ($border == 1)) {
5211              if ($fill) {
5212                  $op = ($border == 1) ? 'B' : 'f';
5213              } else {
5214                  $op = 'S';
5215              }
5216              if ($this->rtl) {
5217                  $xk = (($x - $w) * $k);
5218              } else {
5219                  $xk = ($x * $k);
5220              }
5221              $s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5222          }
5223          // draw borders
5224          $s .= $this->getCellBorder($x, $y, $w, $h, $border);
5225          if ($txt != '') {
5226              $txt2 = $txt;
5227              if ($this->isunicode) {
5228                  if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
5229                      $txt2 = TCPDF_FONTS::UTF8ToLatin1($txt2, $this->isunicode, $this->CurrentFont);
5230                  } else {
5231                      $unicode = TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont); // array of UTF-8 unicode values
5232                      $unicode = TCPDF_FONTS::utf8Bidi($unicode, '', $this->tmprtl, $this->isunicode, $this->CurrentFont);
5233                      // replace thai chars (if any)
5234                      if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
5235                          // number of chars
5236                          $numchars = count($unicode);
5237                          // po pla, for far, for fan
5238                          $longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
5239                          // do chada, to patak
5240                          $lowtail = array(0x0e0e, 0x0e0f);
5241                          // mai hun arkad, sara i, sara ii, sara ue, sara uee
5242                          $upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
5243                          // mai ek, mai tho, mai tri, mai chattawa, karan
5244                          $tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
5245                          // sara u, sara uu, pinthu
5246                          $lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
5247                          $output = array();
5248                          for ($i = 0; $i < $numchars; $i++) {
5249                              if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
5250                                  $ch0 = $unicode[$i];
5251                                  $ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
5252                                  $ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
5253                                  $chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
5254                                  if (in_array($ch0, $tonemark)) {
5255                                      if ($chn == 0x0e33) {
5256                                          // sara um
5257                                          if (in_array($ch1, $longtail)) {
5258                                              // tonemark at upper left
5259                                              $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5260                                          } else {
5261                                              // tonemark at upper right (normal position)
5262                                              $output[] = $ch0;
5263                                          }
5264                                      } elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
5265                                          // tonemark at lower left
5266                                          $output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
5267                                      } elseif (in_array($ch1, $upvowel)) {
5268                                          if (in_array($ch2, $longtail)) {
5269                                              // tonemark at upper left
5270                                              $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5271                                          } else {
5272                                              // tonemark at upper right (normal position)
5273                                              $output[] = $ch0;
5274                                          }
5275                                      } else {
5276                                          // tonemark at lower right
5277                                          $output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
5278                                      }
5279                                  } elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
5280                                      // add lower left nikhahit and sara aa
5281                                      if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
5282                                          $output[] = 0xf711;
5283                                          $this->CurrentFont['subsetchars'][0xf711] = true;
5284                                          $output[] = 0x0e32;
5285                                          $this->CurrentFont['subsetchars'][0x0e32] = true;
5286                                      } else {
5287                                          $output[] = $ch0;
5288                                      }
5289                                  } elseif (in_array($ch1, $longtail)) {
5290                                      if ($ch0 == 0x0e31) {
5291                                          // lower left mai hun arkad
5292                                          $output[] = $this->replaceChar($ch0, 0xf710);
5293                                      } elseif (in_array($ch0, $upvowel)) {
5294                                          // lower left
5295                                          $output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
5296                                      } elseif ($ch0 == 0x0e47) {
5297                                          // lower left mai tai koo
5298                                          $output[] = $this->replaceChar($ch0, 0xf712);
5299                                      } else {
5300                                          // normal character
5301                                          $output[] = $ch0;
5302                                      }
5303                                  } elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
5304                                      // lower vowel
5305                                      $output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
5306                                  } elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
5307                                      // yo ying without lower part
5308                                      $output[] = $this->replaceChar($ch0, 0xf70f);
5309                                  } elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
5310                                      // tho santan without lower part
5311                                      $output[] = $this->replaceChar($ch0, 0xf700);
5312                                  } else {
5313                                      $output[] = $ch0;
5314                                  }
5315                              } else {
5316                                  // non-thai character
5317                                  $output[] = $unicode[$i];
5318                              }
5319                          }
5320                          $unicode = $output;
5321                          // update font subsetchars
5322                          $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
5323                      } // end of K_THAI_TOPCHARS
5324                      $txt2 = TCPDF_FONTS::arrUTF8ToUTF16BE($unicode, false);
5325                  }
5326              }
5327              $txt2 = TCPDF_STATIC::_escape($txt2);
5328              // get current text width (considering general font stretching and spacing)
5329              $txwidth = $this->GetStringWidth($txt);
5330              $width = $txwidth;
5331              // check for stretch mode
5332              if ($stretch > 0) {
5333                  // calculate ratio between cell width and text width
5334                  if ($width <= 0) {
5335                      $ratio = 1;
5336                  } else {
5337                      $ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
5338                  }
5339                  // check if stretching is required
5340                  if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
5341                      // the text will be stretched to fit cell width
5342                      if ($stretch > 2) {
5343                          // set new character spacing
5344                          $this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
5345                      } else {
5346                          // set new horizontal stretching
5347                          $this->font_stretching *= $ratio;
5348                      }
5349                      // recalculate text width (the text fills the entire cell)
5350                      $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5351                      // reset alignment
5352                      $align = '';
5353                  }
5354              }
5355              if ($this->font_stretching != 100) {
5356                  // apply font stretching
5357                  $rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
5358              }
5359              if ($this->font_spacing != 0) {
5360                  // increase/decrease font spacing
5361                  $rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
5362              }
5363              if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5364                  $s .= 'q '.$this->TextColor.' ';
5365              }
5366              // rendering mode
5367              $s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
5368              // count number of spaces
5369              $ns = substr_count($txt, chr(32));
5370              // Justification
5371              $spacewidth = 0;
5372              if (($align == 'J') AND ($ns > 0)) {
5373                  if ($this->isUnicodeFont()) {
5374                      // get string width without spaces
5375                      $width = $this->GetStringWidth(str_replace(' ', '', $txt));
5376                      // calculate average space width
5377                      $spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / ($this->FontSize?$this->FontSize:1);
5378                      if ($this->font_stretching != 100) {
5379                          // word spacing is affected by stretching
5380                          $spacewidth /= ($this->font_stretching / 100);
5381                      }
5382                      // set word position to be used with TJ operator
5383                      $txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
5384                      $unicode_justification = true;
5385                  } else {
5386                      // get string width
5387                      $width = $txwidth;
5388                      // new space width
5389                      $spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
5390                      if ($this->font_stretching != 100) {
5391                          // word spacing (Tw) is affected by stretching
5392                          $spacewidth /= ($this->font_stretching / 100);
5393                      }
5394                      // set word spacing
5395                      $rs .= sprintf('BT %F Tw ET ', $spacewidth);
5396                  }
5397                  $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5398              }
5399              // replace carriage return characters
5400              $txt2 = str_replace("\r", ' ', $txt2);
5401              switch ($align) {
5402                  case 'C': {
5403                      $dx = ($w - $width) / 2;
5404                      break;
5405                  }
5406                  case 'R': {
5407                      if ($this->rtl) {
5408                          $dx = $this->cell_padding['R'];
5409                      } else {
5410                          $dx = $w - $width - $this->cell_padding['R'];
5411                      }
5412                      break;
5413                  }
5414                  case 'L': {
5415                      if ($this->rtl) {
5416                          $dx = $w - $width - $this->cell_padding['L'];
5417                      } else {
5418                          $dx = $this->cell_padding['L'];
5419                      }
5420                      break;
5421                  }
5422                  case 'J':
5423                  default: {
5424                      if ($this->rtl) {
5425                          $dx = $this->cell_padding['R'];
5426                      } else {
5427                          $dx = $this->cell_padding['L'];
5428                      }
5429                      break;
5430                  }
5431              }
5432              if ($this->rtl) {
5433                  $xdx = $x - $dx - $width;
5434              } else {
5435                  $xdx = $x + $dx;
5436              }
5437              $xdk = $xdx * $k;
5438              // print text
5439              $s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
5440              if (isset($uniblock)) {
5441                  // print overlapping characters as separate string
5442                  $xshift = 0; // horizontal shift
5443                  $ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
5444                  $spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
5445                  foreach ($uniblock as $uk => $uniarr) {
5446                      if (($uk % 2) == 0) {
5447                          // x space to skip
5448                          if ($spacewidth != 0) {
5449                              // justification shift
5450                              $xshift += (count(array_keys($uniarr, 32)) * $spw);
5451                          }
5452                          $xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5453                      } else {
5454                          // character to print
5455                          $topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
5456                          $topchr = TCPDF_STATIC::_escape($topchr);
5457                          $s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
5458                      }
5459                  }
5460              }
5461              if ($this->underline) {
5462                  $s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5463              }
5464              if ($this->linethrough) {
5465                  $s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5466              }
5467              if ($this->overline) {
5468                  $s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5469              }
5470              if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5471                  $s .= ' Q';
5472              }
5473              if ($link) {
5474                  $this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5475              }
5476          }
5477          // output cell
5478          if ($s) {
5479              // output cell
5480              $rs .= $s;
5481              if ($this->font_spacing != 0) {
5482                  // reset font spacing mode
5483                  $rs .= ' BT 0 Tc ET';
5484              }
5485              if ($this->font_stretching != 100) {
5486                  // reset font stretching mode
5487                  $rs .= ' BT 100 Tz ET';
5488              }
5489          }
5490          // reset word spacing
5491          if (!$this->isUnicodeFont() AND ($align == 'J')) {
5492              $rs .= ' BT 0 Tw ET';
5493          }
5494          // reset stretching and spacing
5495          $this->font_stretching = $prev_font_stretching;
5496          $this->font_spacing = $prev_font_spacing;
5497          $this->lasth = $h;
5498          if ($ln > 0) {
5499              //Go to the beginning of the next line
5500              $this->y = $y + $h + $this->cell_margin['B'];
5501              if ($ln == 1) {
5502                  if ($this->rtl) {
5503                      $this->x = $this->w - $this->rMargin;
5504                  } else {
5505                      $this->x = $this->lMargin;
5506                  }
5507              }
5508          } else {
5509              // go left or right by case
5510              if ($this->rtl) {
5511                  $this->x = $x - $w - $this->cell_margin['L'];
5512              } else {
5513                  $this->x = $x + $w + $this->cell_margin['R'];
5514              }
5515          }
5516          $gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5517          $rs = $gstyles.$rs;
5518          $this->cell_padding = $prev_cell_padding;
5519          $this->cell_margin = $prev_cell_margin;
5520          return $rs;
5521      }
5522  
5523      /**
5524       * Replace a char if is defined on the current font.
5525       * @param $oldchar (int) Integer code (unicode) of the character to replace.
5526       * @param $newchar (int) Integer code (unicode) of the new character.
5527       * @return int the replaced char or the old char in case the new char i not defined
5528       * @protected
5529       * @since 5.9.167 (2012-06-22)
5530       */
5531  	protected function replaceChar($oldchar, $newchar) {
5532          if ($this->isCharDefined($newchar)) {
5533              // add the new char on the subset list
5534              $this->CurrentFont['subsetchars'][$newchar] = true;
5535              // return the new character
5536              return $newchar;
5537          }
5538          // return the old char
5539          return $oldchar;
5540      }
5541  
5542      /**
5543       * Returns the code to draw the cell border
5544       * @param $x (float) X coordinate.
5545       * @param $y (float) Y coordinate.
5546       * @param $w (float) Cell width.
5547       * @param $h (float) Cell height.
5548       * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5549       * @return string containing cell border code
5550       * @protected
5551       * @see SetLineStyle()
5552       * @since 5.7.000 (2010-08-02)
5553       */
5554  	protected function getCellBorder($x, $y, $w, $h, $brd) {
5555          $s = ''; // string to be returned
5556          if (empty($brd)) {
5557              return $s;
5558          }
5559          if ($brd == 1) {
5560              $brd = array('LRTB' => true);
5561          }
5562          // calculate coordinates for border
5563          $k = $this->k;
5564          if ($this->rtl) {
5565              $xeL = ($x - $w) * $k;
5566              $xeR = $x * $k;
5567          } else {
5568              $xeL = $x * $k;
5569              $xeR = ($x + $w) * $k;
5570          }
5571          $yeL = (($this->h - ($y + $h)) * $k);
5572          $yeT = (($this->h - $y) * $k);
5573          $xeT = $xeL;
5574          $xeB = $xeR;
5575          $yeR = $yeT;
5576          $yeB = $yeL;
5577          if (is_string($brd)) {
5578              // convert string to array
5579              $slen = strlen($brd);
5580              $newbrd = array();
5581              for ($i = 0; $i < $slen; ++$i) {
5582                  $newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
5583              }
5584              $brd = $newbrd;
5585          }
5586          if (isset($brd['mode'])) {
5587              $mode = $brd['mode'];
5588              unset($brd['mode']);
5589          } else {
5590              $mode = 'normal';
5591          }
5592          foreach ($brd as $border => $style) {
5593              if (is_array($style) AND !empty($style)) {
5594                  // apply border style
5595                  $prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
5596                  $s .= $this->SetLineStyle($style, true)."\n";
5597              }
5598              switch ($mode) {
5599                  case 'ext': {
5600                      $off = (($this->LineWidth / 2) * $k);
5601                      $xL = $xeL - $off;
5602                      $xR = $xeR + $off;
5603                      $yT = $yeT + $off;
5604                      $yL = $yeL - $off;
5605                      $xT = $xL;
5606                      $xB = $xR;
5607                      $yR = $yT;
5608                      $yB = $yL;
5609                      $w += $this->LineWidth;
5610                      $h += $this->LineWidth;
5611                      break;
5612                  }
5613                  case 'int': {
5614                      $off = ($this->LineWidth / 2) * $k;
5615                      $xL = $xeL + $off;
5616                      $xR = $xeR - $off;
5617                      $yT = $yeT - $off;
5618                      $yL = $yeL + $off;
5619                      $xT = $xL;
5620                      $xB = $xR;
5621                      $yR = $yT;
5622                      $yB = $yL;
5623                      $w -= $this->LineWidth;
5624                      $h -= $this->LineWidth;
5625                      break;
5626                  }
5627                  case 'normal':
5628                  default: {
5629                      $xL = $xeL;
5630                      $xT = $xeT;
5631                      $xB = $xeB;
5632                      $xR = $xeR;
5633                      $yL = $yeL;
5634                      $yT = $yeT;
5635                      $yB = $yeB;
5636                      $yR = $yeR;
5637                      break;
5638                  }
5639              }
5640              // draw borders by case
5641              if (strlen($border) == 4) {
5642                  $s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5643              } elseif (strlen($border) == 3) {
5644                  if (strpos($border,'B') === false) { // LTR
5645                      $s .= sprintf('%F %F m ', $xL, $yL);
5646                      $s .= sprintf('%F %F l ', $xT, $yT);
5647                      $s .= sprintf('%F %F l ', $xR, $yR);
5648                      $s .= sprintf('%F %F l ', $xB, $yB);
5649                      $s .= 'S ';
5650                  } elseif (strpos($border,'L') === false) { // TRB
5651                      $s .= sprintf('%F %F m ', $xT, $yT);
5652                      $s .= sprintf('%F %F l ', $xR, $yR);
5653                      $s .= sprintf('%F %F l ', $xB, $yB);
5654                      $s .= sprintf('%F %F l ', $xL, $yL);
5655                      $s .= 'S ';
5656                  } elseif (strpos($border,'T') === false) { // RBL
5657                      $s .= sprintf('%F %F m ', $xR, $yR);
5658                      $s .= sprintf('%F %F l ', $xB, $yB);
5659                      $s .= sprintf('%F %F l ', $xL, $yL);
5660                      $s .= sprintf('%F %F l ', $xT, $yT);
5661                      $s .= 'S ';
5662                  } elseif (strpos($border,'R') === false) { // BLT
5663                      $s .= sprintf('%F %F m ', $xB, $yB);
5664                      $s .= sprintf('%F %F l ', $xL, $yL);
5665                      $s .= sprintf('%F %F l ', $xT, $yT);
5666                      $s .= sprintf('%F %F l ', $xR, $yR);
5667                      $s .= 'S ';
5668                  }
5669              } elseif (strlen($border) == 2) {
5670                  if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5671                      $s .= sprintf('%F %F m ', $xL, $yL);
5672                      $s .= sprintf('%F %F l ', $xT, $yT);
5673                      $s .= sprintf('%F %F l ', $xR, $yR);
5674                      $s .= 'S ';
5675                  } elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5676                      $s .= sprintf('%F %F m ', $xT, $yT);
5677                      $s .= sprintf('%F %F l ', $xR, $yR);
5678                      $s .= sprintf('%F %F l ', $xB, $yB);
5679                      $s .= 'S ';
5680                  } elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5681                      $s .= sprintf('%F %F m ', $xR, $yR);
5682                      $s .= sprintf('%F %F l ', $xB, $yB);
5683                      $s .= sprintf('%F %F l ', $xL, $yL);
5684                      $s .= 'S ';
5685                  } elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5686                      $s .= sprintf('%F %F m ', $xB, $yB);
5687                      $s .= sprintf('%F %F l ', $xL, $yL);
5688                      $s .= sprintf('%F %F l ', $xT, $yT);
5689                      $s .= 'S ';
5690                  } elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5691                      $s .= sprintf('%F %F m ', $xL, $yL);
5692                      $s .= sprintf('%F %F l ', $xT, $yT);
5693                      $s .= 'S ';
5694                      $s .= sprintf('%F %F m ', $xR, $yR);
5695                      $s .= sprintf('%F %F l ', $xB, $yB);
5696                      $s .= 'S ';
5697                  } elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5698                      $s .= sprintf('%F %F m ', $xT, $yT);
5699                      $s .= sprintf('%F %F l ', $xR, $yR);
5700                      $s .= 'S ';
5701                      $s .= sprintf('%F %F m ', $xB, $yB);
5702                      $s .= sprintf('%F %F l ', $xL, $yL);
5703                      $s .= 'S ';
5704                  }
5705              } else { // strlen($border) == 1
5706                  if (strpos($border,'L') !== false) { // L
5707                      $s .= sprintf('%F %F m ', $xL, $yL);
5708                      $s .= sprintf('%F %F l ', $xT, $yT);
5709                      $s .= 'S ';
5710                  } elseif (strpos($border,'T') !== false) { // T
5711                      $s .= sprintf('%F %F m ', $xT, $yT);
5712                      $s .= sprintf('%F %F l ', $xR, $yR);
5713                      $s .= 'S ';
5714                  } elseif (strpos($border,'R') !== false) { // R
5715                      $s .= sprintf('%F %F m ', $xR, $yR);
5716                      $s .= sprintf('%F %F l ', $xB, $yB);
5717                      $s .= 'S ';
5718                  } elseif (strpos($border,'B') !== false) { // B
5719                      $s .= sprintf('%F %F m ', $xB, $yB);
5720                      $s .= sprintf('%F %F l ', $xL, $yL);
5721                      $s .= 'S ';
5722                  }
5723              }
5724              if (is_array($style) AND !empty($style)) {
5725                  // reset border style to previous value
5726                  $s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
5727              }
5728          }
5729          return $s;
5730      }
5731  
5732      /**
5733       * This method allows printing text with line breaks.
5734       * They can be automatic (as soon as the text reaches the right border of the cell) or explicit (via the \n character). As many cells as necessary are output, one below the other.<br />
5735       * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
5736       * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
5737       * @param $h (float) Cell minimum height. The cell extends automatically if needed.
5738       * @param $txt (string) String to print
5739       * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5740       * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align</li><li>C: center</li><li>R: right align</li><li>J: justification (default value when $ishtml=false)</li></ul>
5741       * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5742       * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line [DEFAULT]</li><li>2: below</li></ul>
5743       * @param $x (float) x position in user units
5744       * @param $y (float) y position in user units
5745       * @param $reseth (boolean) if true reset the last cell height (default true).
5746       * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5747       * @param $ishtml (boolean) INTERNAL USE ONLY -- set to true if $txt is HTML content (default = false). Never set this parameter to true, use instead writeHTMLCell() or writeHTML() methods.
5748       * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
5749       * @param $maxh (float) maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. This feature works only when $ishtml=false.
5750       * @param $valign (string) Vertical alignment of text (requires $maxh = $h > 0). Possible values are:<ul><li>T: TOP</li><li>M: middle</li><li>B: bottom</li></ul>. This feature works only when $ishtml=false and the cell must fit in a single page.
5751       * @param $fitcell (boolean) if true attempt to fit all the text within the cell by reducing the font size (do not work in HTML mode). $maxh must be greater than 0 and equal to $h.
5752       * @return int Return the number of cells or 1 for html mode.
5753       * @public
5754       * @since 1.3
5755       * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
5756       */
5757  	public function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0, $valign='T', $fitcell=false) {
5758          $prev_cell_margin = $this->cell_margin;
5759          $prev_cell_padding = $this->cell_padding;
5760          // adjust internal padding
5761          $this->adjustCellPadding($border);
5762          $mc_padding = $this->cell_padding;
5763          $mc_margin = $this->cell_margin;
5764          $this->cell_padding['T'] = 0;
5765          $this->cell_padding['B'] = 0;
5766          $this->setCellMargins(0, 0, 0, 0);
5767          if (TCPDF_STATIC::empty_string($this->lasth) OR $reseth) {
5768              // reset row height
5769              $this->resetLastH();
5770          }
5771          if (!TCPDF_STATIC::empty_string($y)) {
5772              $this->SetY($y);
5773          } else {
5774              $y = $this->GetY();
5775          }
5776          $resth = 0;
5777          if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
5778              // spit cell in more pages/columns
5779              $newh = ($this->PageBreakTrigger - $y);
5780              $resth = ($h - $newh); // cell to be printed on the next page/column
5781              $h = $newh;
5782          }
5783          // get current page number
5784          $startpage = $this->page;
5785          // get current column
5786          $startcolumn = $this->current_column;
5787          if (!TCPDF_STATIC::empty_string($x)) {
5788              $this->SetX($x);
5789          } else {
5790              $x = $this->GetX();
5791          }
5792          // check page for no-write regions and adapt page margins if necessary
5793          list($x, $y) = $this->checkPageRegions(0, $x, $y);
5794          // apply margins
5795          $oy = $y + $mc_margin['T'];
5796          if ($this->rtl) {
5797              $ox = ($this->w - $x - $mc_margin['R']);
5798          } else {
5799              $ox = ($x + $mc_margin['L']);
5800          }
5801          $this->x = $ox;
5802          $this->y = $oy;
5803          // set width
5804          if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5805              if ($this->rtl) {
5806                  $w = ($this->x - $this->lMargin - $mc_margin['L']);
5807              } else {
5808                  $w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
5809              }
5810          }
5811          // store original margin values
5812          $lMargin = $this->lMargin;
5813          $rMargin = $this->rMargin;
5814          if ($this->rtl) {
5815              $this->rMargin = ($this->w - $this->x);
5816              $this->lMargin = ($this->x - $w);
5817          } else {
5818              $this->lMargin = ($this->x);
5819              $this->rMargin = ($this->w - $this->x - $w);
5820          }
5821          $this->clMargin = $this->lMargin;
5822          $this->crMargin = $this->rMargin;
5823          if ($autopadding) {
5824              // add top padding
5825              $this->y += $mc_padding['T'];
5826          }
5827          if ($ishtml) { // ******* Write HTML text
5828              $this->writeHTML($txt, true, false, $reseth, true, $align);
5829              $nl = 1;
5830          } else { // ******* Write simple text
5831              $prev_FontSizePt = $this->FontSizePt;
5832              if ($fitcell) {
5833                  // ajust height values
5834                  $tobottom = ($this->h - $this->y - $this->bMargin - $this->cell_padding['T'] - $this->cell_padding['B']);
5835                  $h = $maxh = max(min($h, $tobottom), min($maxh, $tobottom));
5836              }
5837              // vertical alignment
5838              if ($maxh > 0) {
5839                  // get text height
5840                  $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5841                  if ($fitcell AND ($text_height > $maxh) AND ($this->FontSizePt > 1)) {
5842                      // try to reduce font size to fit text on cell (use a quick search algorithm)
5843                      $fmin = 1;
5844                      $fmax = $this->FontSizePt;
5845                      $diff_epsilon = (1 / $this->k); // one point (min resolution)
5846                      $maxit = (2 * min(100, max(10, intval($fmax)))); // max number of iterations
5847                      while ($maxit >= 0) {
5848                          $fmid = (($fmax + $fmin) / 2);
5849                          $this->SetFontSize($fmid, false);
5850                          $this->resetLastH();
5851                          $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5852                          $diff = ($maxh - $text_height);
5853                          if ($diff >= 0) {
5854                              if ($diff <= $diff_epsilon) {
5855                                  break;
5856                              }
5857                              $fmin = $fmid;
5858                          } else {
5859                              $fmax = $fmid;
5860                          }
5861                          --$maxit;
5862                      }
5863                      if ($maxit < 0) {
5864                          // premature exit, we get the minimum font value to fit the cell
5865                          $this->SetFontSize($fmin);
5866                          $this->resetLastH();
5867                          $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5868                      } else {
5869                          $this->SetFontSize($fmid);
5870                          $this->resetLastH();
5871                      }
5872                  }
5873                  if ($text_height < $maxh) {
5874                      if ($valign == 'M') {
5875                          // text vertically centered
5876                          $this->y += (($maxh - $text_height) / 2);
5877                      } elseif ($valign == 'B') {
5878                          // text vertically aligned on bottom
5879                          $this->y += ($maxh - $text_height);
5880                      }
5881                  }
5882              }
5883              $nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
5884              if ($fitcell) {
5885                  // restore font size
5886                  $this->SetFontSize($prev_FontSizePt);
5887              }
5888          }
5889          if ($autopadding) {
5890              // add bottom padding
5891              $this->y += $mc_padding['B'];
5892          }
5893          // Get end-of-text Y position
5894          $currentY = $this->y;
5895          // get latest page number
5896          $endpage = $this->page;
5897          if ($resth > 0) {
5898              $skip = ($endpage - $startpage);
5899              $tmpresth = $resth;
5900              while ($tmpresth > 0) {
5901                  if ($skip <= 0) {
5902                      // add a page (or trig AcceptPageBreak() for multicolumn mode)
5903                      $this->checkPageBreak($this->PageBreakTrigger + 1);
5904                  }
5905                  if ($this->num_columns > 1) {
5906                      $tmpresth -= ($this->h - $this->y - $this->bMargin);
5907                  } else {
5908                      $tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
5909                  }
5910                  --$skip;
5911              }
5912              $currentY = $this->y;
5913              $endpage = $this->page;
5914          }
5915          // get latest column
5916          $endcolumn = $this->current_column;
5917          if ($this->num_columns == 0) {
5918              $this->num_columns = 1;
5919          }
5920          // disable page regions check
5921          $check_page_regions = $this->check_page_regions;
5922          $this->check_page_regions = false;
5923          // get border modes
5924          $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
5925          $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
5926          $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
5927          // design borders around HTML cells.
5928          for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
5929              $ccode = '';
5930              $this->setPage($page);
5931              if ($this->num_columns < 2) {
5932                  // single-column mode
5933                  $this->SetX($x);
5934                  $this->y = $this->tMargin;
5935              }
5936              // account for margin changes
5937              if ($page > $startpage) {
5938                  if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
5939                      $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
5940                  } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
5941                      $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
5942                  }
5943              }
5944              if ($startpage == $endpage) {
5945                  // single page
5946                  for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
5947                      $this->selectColumn($column);
5948                      if ($this->rtl) {
5949                          $this->x -= $mc_margin['R'];
5950                      } else {
5951                          $this->x += $mc_margin['L'];
5952                      }
5953                      if ($startcolumn == $endcolumn) { // single column
5954                          $cborder = $border;
5955                          $h = max($h, ($currentY - $oy));
5956                          $this->y = $oy;
5957                      } elseif ($column == $startcolumn) { // first column
5958                          $cborder = $border_start;
5959                          $this->y = $oy;
5960                          $h = $this->h - $this->y - $this->bMargin;
5961                      } elseif ($column == $endcolumn) { // end column
5962                          $cborder = $border_end;
5963                          $h = $currentY - $this->y;
5964                          if ($resth > $h) {
5965                              $h = $resth;
5966                          }
5967                      } else { // middle column
5968                          $cborder = $border_middle;
5969                          $h = $this->h - $this->y - $this->bMargin;
5970                          $resth -= $h;
5971                      }
5972                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
5973                  } // end for each column
5974              } elseif ($page == $startpage) { // first page
5975                  for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
5976                      $this->selectColumn($column);
5977                      if ($this->rtl) {
5978                          $this->x -= $mc_margin['R'];
5979                      } else {
5980                          $this->x += $mc_margin['L'];
5981                      }
5982                      if ($column == $startcolumn) { // first column
5983                          $cborder = $border_start;
5984                          $this->y = $oy;
5985                          $h = $this->h - $this->y - $this->bMargin;
5986                      } else { // middle column
5987                          $cborder = $border_middle;
5988                          $h = $this->h - $this->y - $this->bMargin;
5989                          $resth -= $h;
5990                      }
5991                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
5992                  } // end for each column
5993              } elseif ($page == $endpage) { // last page
5994                  for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
5995                      $this->selectColumn($column);
5996                      if ($this->rtl) {
5997                          $this->x -= $mc_margin['R'];
5998                      } else {
5999                          $this->x += $mc_margin['L'];
6000                      }
6001                      if ($column == $endcolumn) {
6002                          // end column
6003                          $cborder = $border_end;
6004                          $h = $currentY - $this->y;
6005                          if ($resth > $h) {
6006                              $h = $resth;
6007                          }
6008                      } else {
6009                          // middle column
6010                          $cborder = $border_middle;
6011                          $h = $this->h - $this->y - $this->bMargin;
6012                          $resth -= $h;
6013                      }
6014                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6015                  } // end for each column
6016              } else { // middle page
6017                  for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6018                      $this->selectColumn($column);
6019                      if ($this->rtl) {
6020                          $this->x -= $mc_margin['R'];
6021                      } else {
6022                          $this->x += $mc_margin['L'];
6023                      }
6024                      $cborder = $border_middle;
6025                      $h = $this->h - $this->y - $this->bMargin;
6026                      $resth -= $h;
6027                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6028                  } // end for each column
6029              }
6030              if ($cborder OR $fill) {
6031                  $offsetlen = strlen($ccode);
6032                  // draw border and fill
6033                  if ($this->inxobj) {
6034                      // we are inside an XObject template
6035                      if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6036                          $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6037                          $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6038                          $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
6039                      } else {
6040                          $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
6041                          $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
6042                      }
6043                      $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6044                      $pstart = substr($pagebuff, 0, $pagemark);
6045                      $pend = substr($pagebuff, $pagemark);
6046                      $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6047                  } else {
6048                      if (end($this->transfmrk[$this->page]) !== false) {
6049                          $pagemarkkey = key($this->transfmrk[$this->page]);
6050                          $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
6051                          $this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
6052                      } elseif ($this->InFooter) {
6053                          $pagemark = $this->footerpos[$this->page];
6054                          $this->footerpos[$this->page] += $offsetlen;
6055                      } else {
6056                          $pagemark = $this->intmrk[$this->page];
6057                          $this->intmrk[$this->page] += $offsetlen;
6058                      }
6059                      $pagebuff = $this->getPageBuffer($this->page);
6060                      $pstart = substr($pagebuff, 0, $pagemark);
6061                      $pend = substr($pagebuff, $pagemark);
6062                      $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6063                  }
6064              }
6065          } // end for each page
6066          // restore page regions check
6067          $this->check_page_regions = $check_page_regions;
6068          // Get end-of-cell Y position
6069          $currentY = $this->GetY();
6070          // restore previous values
6071          if ($this->num_columns > 1) {
6072              $this->selectColumn();
6073          } else {
6074              // restore original margins
6075              $this->lMargin = $lMargin;
6076              $this->rMargin = $rMargin;
6077              if ($this->page > $startpage) {
6078                  // check for margin variations between pages (i.e. booklet mode)
6079                  $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
6080                  $dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
6081                  if (($dl != 0) OR ($dr != 0)) {
6082                      $this->lMargin += $dl;
6083                      $this->rMargin += $dr;
6084                  }
6085              }
6086          }
6087          if ($ln > 0) {
6088              //Go to the beginning of the next line
6089              $this->SetY($currentY + $mc_margin['B']);
6090              if ($ln == 2) {
6091                  $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6092              }
6093          } else {
6094              // go left or right by case
6095              $this->setPage($startpage);
6096              $this->y = $y;
6097              $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6098          }
6099          $this->setContentMark();
6100          $this->cell_padding = $prev_cell_padding;
6101          $this->cell_margin = $prev_cell_margin;
6102          $this->clMargin = $this->lMargin;
6103          $this->crMargin = $this->rMargin;
6104          return $nl;
6105      }
6106  
6107      /**
6108       * This method return the estimated number of lines for print a simple text string using Multicell() method.
6109       * @param $txt (string) String for calculating his height
6110       * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6111       * @param $reseth (boolean) if true reset the last cell height (default false).
6112       * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6113       * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6114       * @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)))
6115       * @return float Return the minimal height needed for multicell method for printing the $txt param.
6116       * @author Alexander Escalona Fern\E1ndez, Nicola Asuni
6117       * @public
6118       * @since 4.5.011
6119       */
6120  	public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6121          if ($txt === NULL) {
6122              return 0;
6123          }
6124          if ($txt === '') {
6125              // empty string
6126              return 1;
6127          }
6128          // adjust internal padding
6129          $prev_cell_padding = $this->cell_padding;
6130          $prev_lasth = $this->lasth;
6131          if (is_array($cellpadding)) {
6132              $this->cell_padding = $cellpadding;
6133          }
6134          $this->adjustCellPadding($border);
6135          if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
6136              if ($this->rtl) {
6137                  $w = $this->x - $this->lMargin;
6138              } else {
6139                  $w = $this->w - $this->rMargin - $this->x;
6140              }
6141          }
6142          $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6143          if ($reseth) {
6144              // reset row height
6145              $this->resetLastH();
6146          }
6147          $lines = 1;
6148          $sum = 0;
6149          $chars = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont), $txt, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6150          $charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6151          $length = count($chars);
6152          $lastSeparator = -1;
6153          for ($i = 0; $i < $length; ++$i) {
6154              $c = $chars[$i];
6155              $charWidth = $charsWidth[$i];
6156              if (($c != 160)
6157                      AND (($c == 173)
6158                          OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6159                          OR (($c == 45)
6160                              AND ($i > 0) AND ($i < ($length - 1))
6161                              AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i - 1)], $this->isunicode))
6162                              AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6163                          )
6164                      )
6165                  ) {
6166                  $lastSeparator = $i;
6167              }
6168              if ((($sum + $charWidth) > $wmax) OR ($c == 10)) {
6169                  ++$lines;
6170                  if ($c == 10) {
6171                      $lastSeparator = -1;
6172                      $sum = 0;
6173                  } elseif ($lastSeparator != -1) {
6174                      $i = $lastSeparator;
6175                      $lastSeparator = -1;
6176                      $sum = 0;
6177                  } else {
6178                      $sum = $charWidth;
6179                  }
6180              } else {
6181                  $sum += $charWidth;
6182              }
6183          }
6184          if ($chars[($length - 1)] == 10) {
6185              --$lines;
6186          }
6187          $this->cell_padding = $prev_cell_padding;
6188          $this->lasth = $prev_lasth;
6189          return $lines;
6190      }
6191  
6192      /**
6193       * This method return the estimated height needed for printing a simple text string using the Multicell() method.
6194       * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6195       * @pre
6196       *  // store current object
6197       *  $pdf->startTransaction();
6198       *  // store starting values
6199       *  $start_y = $pdf->GetY();
6200       *  $start_page = $pdf->getPage();
6201       *  // call your printing functions with your parameters
6202       *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6203       *  $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);
6204       *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6205       *  // get the new Y
6206       *  $end_y = $pdf->GetY();
6207       *  $end_page = $pdf->getPage();
6208       *  // calculate height
6209       *  $height = 0;
6210       *  if ($end_page == $start_page) {
6211       *      $height = $end_y - $start_y;
6212       *  } else {
6213       *      for ($page=$start_page; $page <= $end_page; ++$page) {
6214       *          $this->setPage($page);
6215       *          if ($page == $start_page) {
6216       *              // first page
6217       *              $height = $this->h - $start_y - $this->bMargin;
6218       *          } elseif ($page == $end_page) {
6219       *              // last page
6220       *              $height = $end_y - $this->tMargin;
6221       *          } else {
6222       *              $height = $this->h - $this->tMargin - $this->bMargin;
6223       *          }
6224       *      }
6225       *  }
6226       *  // restore previous object
6227       *  $pdf = $pdf->rollbackTransaction();
6228       *
6229       * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6230       * @param $txt (string) String for calculating his height
6231       * @param $reseth (boolean) if true reset the last cell height (default false).
6232       * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6233       * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6234       * @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)))
6235       * @return float Return the minimal height needed for multicell method for printing the $txt param.
6236       * @author Nicola Asuni, Alexander Escalona Fern\E1ndez
6237       * @public
6238       */
6239  	public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6240          // adjust internal padding
6241          $prev_cell_padding = $this->cell_padding;
6242          $prev_lasth = $this->lasth;
6243          if (is_array($cellpadding)) {
6244              $this->cell_padding = $cellpadding;
6245          }
6246          $this->adjustCellPadding($border);
6247          $lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6248          $height = $this->getCellHeight(($lines * $this->FontSize), $autopadding);
6249          $this->cell_padding = $prev_cell_padding;
6250          $this->lasth = $prev_lasth;
6251          return $height;
6252      }
6253  
6254      /**
6255       * This method prints text from the current position.<br />
6256       * @param $h (float) Line height
6257       * @param $txt (string) String to print
6258       * @param $link (mixed) URL or identifier returned by AddLink()
6259       * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
6260       * @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>
6261       * @param $ln (boolean) if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
6262       * @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.
6263       * @param $firstline (boolean) if true prints only the first line and return the remaining string.
6264       * @param $firstblock (boolean) if true the string is the starting of a line.
6265       * @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.
6266       * @param $wadj (float) first line width will be reduced by this amount (used in HTML mode).
6267       * @param $margin (array) margin array of the parent container
6268       * @return mixed Return the number of cells or the remaining string if $firstline = true.
6269       * @public
6270       * @since 1.5
6271       */
6272  	public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
6273          // check page for no-write regions and adapt page margins if necessary
6274          list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
6275          if (strlen($txt) == 0) {
6276              // fix empty text
6277              $txt = ' ';
6278          }
6279          if ($margin === '') {
6280              // set default margins
6281              $margin = $this->cell_margin;
6282          }
6283          // remove carriage returns
6284          $s = str_replace("\r", '', $txt);
6285          // check if string contains arabic text
6286          if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $s)) {
6287              $arabic = true;
6288          } else {
6289              $arabic = false;
6290          }
6291          // check if string contains RTL text
6292          if ($arabic OR ($this->tmprtl == 'R') OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $s)) {
6293              $rtlmode = true;
6294          } else {
6295              $rtlmode = false;
6296          }
6297          // get a char width
6298          $chrwidth = $this->GetCharWidth(46); // dot character
6299          // get array of unicode values
6300          $chars = TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont);
6301          // calculate maximum width for a single character on string
6302          $chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
6303          array_walk($chrw, array($this, 'getRawCharWidth'));
6304          $maxchwidth = max($chrw);
6305          // get array of chars
6306          $uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
6307          // get the number of characters
6308          $nb = count($chars);
6309          // replacement for SHY character (minus symbol)
6310          $shy_replacement = 45;
6311          $shy_replacement_char = TCPDF_FONTS::unichr($shy_replacement, $this->isunicode);
6312          // widht for SHY replacement
6313          $shy_replacement_width = $this->GetCharWidth($shy_replacement);
6314          // page width
6315          $pw = $w = $this->w - $this->lMargin - $this->rMargin;
6316          // calculate remaining line width ($w)
6317          if ($this->rtl) {
6318              $w = $this->x - $this->lMargin;
6319          } else {
6320              $w = $this->w - $this->rMargin - $this->x;
6321          }
6322          // max column width
6323          $wmax = ($w - $wadj);
6324          if (!$firstline) {
6325              $wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
6326          }
6327          if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
6328              // the maximum width character do not fit on column
6329              return '';
6330          }
6331          // minimum row height
6332          $row_height = max($h, $this->getCellHeight($this->FontSize));
6333          // max Y
6334          $maxy = $this->y + $maxh - max($row_height, $h);
6335          $start_page = $this->page;
6336          $i = 0; // character position
6337          $j = 0; // current starting position
6338          $sep = -1; // position of the last blank space
6339          $prevsep = $sep; // previous separator
6340          $shy = false; // true if the last blank is a soft hypen (SHY)
6341          $prevshy = $shy; // previous shy mode
6342          $l = 0; // current string length
6343          $nl = 0; //number of lines
6344          $linebreak = false;
6345          $pc = 0; // previous character
6346          // for each character
6347          while ($i < $nb) {
6348              if (($maxh > 0) AND ($this->y > $maxy) ) {
6349                  break;
6350              }
6351              //Get the current character
6352              $c = $chars[$i];
6353              if ($c == 10) { // 10 = "\n" = new line
6354                  //Explicit line break
6355                  if ($align == 'J') {
6356                      if ($this->rtl) {
6357                          $talign = 'R';
6358                      } else {
6359                          $talign = 'L';
6360                      }
6361                  } else {
6362                      $talign = $align;
6363                  }
6364                  $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6365                  if ($firstline) {
6366                      $startx = $this->x;
6367                      $tmparr = array_slice($chars, $j, ($i - $j));
6368                      if ($rtlmode) {
6369                          $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6370                      }
6371                      $linew = $this->GetArrStringWidth($tmparr);
6372                      unset($tmparr);
6373                      if ($this->rtl) {
6374                          $this->endlinex = $startx - $linew;
6375                      } else {
6376                          $this->endlinex = $startx + $linew;
6377                      }
6378                      $w = $linew;
6379                      $tmpcellpadding = $this->cell_padding;
6380                      if ($maxh == 0) {
6381                          $this->SetCellPadding(0);
6382                      }
6383                  }
6384                  if ($firstblock AND $this->isRTLTextDir()) {
6385                      $tmpstr = $this->stringRightTrim($tmpstr);
6386                  }
6387                  // Skip newlines at the beginning of a page or column
6388                  if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
6389                      $this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6390                  }
6391                  unset($tmpstr);
6392                  if ($firstline) {
6393                      $this->cell_padding = $tmpcellpadding;
6394                      return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6395                  }
6396                  ++$nl;
6397                  $j = $i + 1;
6398                  $l = 0;
6399                  $sep = -1;
6400                  $prevsep = $sep;
6401                  $shy = false;
6402                  // account for margin changes
6403                  if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6404                      $this->AcceptPageBreak();
6405                      if ($this->rtl) {
6406                          $this->x -= $margin['R'];
6407                      } else {
6408                          $this->x += $margin['L'];
6409                      }
6410                      $this->lMargin += $margin['L'];
6411                      $this->rMargin += $margin['R'];
6412                  }
6413                  $w = $this->getRemainingWidth();
6414                  $wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
6415              } else {
6416                  // 160 is the non-breaking space.
6417                  // 173 is SHY (Soft Hypen).
6418                  // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6419                  // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6420                  // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6421                  if (($c != 160)
6422                      AND (($c == 173)
6423                          OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6424                          OR (($c == 45)
6425                              AND ($i < ($nb - 1))
6426                              AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($pc, $this->isunicode))
6427                              AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6428                          )
6429                      )
6430                  ) {
6431                      // update last blank space position
6432                      $prevsep = $sep;
6433                      $sep = $i;
6434                      // check if is a SHY
6435                      if (($c == 173) OR ($c == 45)) {
6436                          $prevshy = $shy;
6437                          $shy = true;
6438                          if ($pc == 45) {
6439                              $tmp_shy_replacement_width = 0;
6440                              $tmp_shy_replacement_char = '';
6441                          } else {
6442                              $tmp_shy_replacement_width = $shy_replacement_width;
6443                              $tmp_shy_replacement_char = $shy_replacement_char;
6444                          }
6445                      } else {
6446                          $shy = false;
6447                      }
6448                  }
6449                  // update string length
6450                  if ($this->isUnicodeFont() AND ($arabic)) {
6451                      // with bidirectional algorithm some chars may be changed affecting the line length
6452                      // *** very slow ***
6453                      $l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont));
6454                  } else {
6455                      $l += $this->GetCharWidth($c);
6456                  }
6457                  if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) >= $wmax))) {
6458                      if (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) {
6459                          $sep = $prevsep;
6460                          $shy = $prevshy;
6461                      }
6462                      // we have reached the end of column
6463                      if ($sep == -1) {
6464                          // check if the line was already started
6465                          if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
6466                              OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
6467                              // print a void cell and go to next line
6468                              $this->Cell($w, $h, '', 0, 1);
6469                              $linebreak = true;
6470                              if ($firstline) {
6471                                  return (TCPDF_FONTS::UniArrSubString($uchars, $j));
6472                              }
6473                          } else {
6474                              // truncate the word because do not fit on column
6475                              $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6476                              if ($firstline) {
6477                                  $startx = $this->x;
6478                                  $tmparr = array_slice($chars, $j, ($i - $j));
6479                                  if ($rtlmode) {
6480                                      $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6481                                  }
6482                                  $linew = $this->GetArrStringWidth($tmparr);
6483                                  unset($tmparr);
6484                                  if ($this->rtl) {
6485                                      $this->endlinex = $startx - $linew;
6486                                  } else {
6487                                      $this->endlinex = $startx + $linew;
6488                                  }
6489                                  $w = $linew;
6490                                  $tmpcellpadding = $this->cell_padding;
6491                                  if ($maxh == 0) {
6492                                      $this->SetCellPadding(0);
6493                                  }
6494                              }
6495                              if ($firstblock AND $this->isRTLTextDir()) {
6496                                  $tmpstr = $this->stringRightTrim($tmpstr);
6497                              }
6498                              $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6499                              unset($tmpstr);
6500                              if ($firstline) {
6501                                  $this->cell_padding = $tmpcellpadding;
6502                                  return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6503                              }
6504                              $j = $i;
6505                              --$i;
6506                          }
6507                      } else {
6508                          // word wrapping
6509                          if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6510                              $endspace = 1;
6511                          } else {
6512                              $endspace = 0;
6513                          }
6514                          // check the length of the next string
6515                          $strrest = TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace));
6516                          $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $this->stringTrim($strrest));
6517                          if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
6518                              // truncate the word because do not fit on a full page width
6519                              $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6520                              if ($firstline) {
6521                                  $startx = $this->x;
6522                                  $tmparr = array_slice($chars, $j, ($i - $j));
6523                                  if ($rtlmode) {
6524                                      $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6525                                  }
6526                                  $linew = $this->GetArrStringWidth($tmparr);
6527                                  unset($tmparr);
6528                                  if ($this->rtl) {
6529                                      $this->endlinex = ($startx - $linew);
6530                                  } else {
6531                                      $this->endlinex = ($startx + $linew);
6532                                  }
6533                                  $w = $linew;
6534                                  $tmpcellpadding = $this->cell_padding;
6535                                  if ($maxh == 0) {
6536                                      $this->SetCellPadding(0);
6537                                  }
6538                              }
6539                              if ($firstblock AND $this->isRTLTextDir()) {
6540                                  $tmpstr = $this->stringRightTrim($tmpstr);
6541                              }
6542                              $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6543                              unset($tmpstr);
6544                              if ($firstline) {
6545                                  $this->cell_padding = $tmpcellpadding;
6546                                  return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6547                              }
6548                              $j = $i;
6549                              --$i;
6550                          } else {
6551                              // word wrapping
6552                              if ($shy) {
6553                                  // add hypen (minus symbol) at the end of the line
6554                                  $shy_width = $tmp_shy_replacement_width;
6555                                  if ($this->rtl) {
6556                                      $shy_char_left = $tmp_shy_replacement_char;
6557                                      $shy_char_right = '';
6558                                  } else {
6559                                      $shy_char_left = '';
6560                                      $shy_char_right = $tmp_shy_replacement_char;
6561                                  }
6562                              } else {
6563                                  $shy_width = 0;
6564                                  $shy_char_left = '';
6565                                  $shy_char_right = '';
6566                              }
6567                              $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
6568                              if ($firstline) {
6569                                  $startx = $this->x;
6570                                  $tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6571                                  if ($rtlmode) {
6572                                      $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6573                                  }
6574                                  $linew = $this->GetArrStringWidth($tmparr);
6575                                  unset($tmparr);
6576                                  if ($this->rtl) {
6577                                      $this->endlinex = $startx - $linew - $shy_width;
6578                                  } else {
6579                                      $this->endlinex = $startx + $linew + $shy_width;
6580                                  }
6581                                  $w = $linew;
6582                                  $tmpcellpadding = $this->cell_padding;
6583                                  if ($maxh == 0) {
6584                                      $this->SetCellPadding(0);
6585                                  }
6586                              }
6587                              // print the line
6588                              if ($firstblock AND $this->isRTLTextDir()) {
6589                                  $tmpstr = $this->stringRightTrim($tmpstr);
6590                              }
6591                              $this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6592                              unset($tmpstr);
6593                              if ($firstline) {
6594                                  if ($chars[$sep] == 45) {
6595                                      $endspace += 1;
6596                                  }
6597                                  // return the remaining text
6598                                  $this->cell_padding = $tmpcellpadding;
6599                                  return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
6600                              }
6601                              $i = $sep;
6602                              $sep = -1;
6603                              $shy = false;
6604                              $j = ($i + 1);
6605                          }
6606                      }
6607                      // account for margin changes
6608                      if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6609                          $this->AcceptPageBreak();
6610                          if ($this->rtl) {
6611                              $this->x -= $margin['R'];
6612                          } else {
6613                              $this->x += $margin['L'];
6614                          }
6615                          $this->lMargin += $margin['L'];
6616                          $this->rMargin += $margin['R'];
6617                      }
6618                      $w = $this->getRemainingWidth();
6619                      $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6620                      if ($linebreak) {
6621                          $linebreak = false;
6622                      } else {
6623                          ++$nl;
6624                          $l = 0;
6625                      }
6626                  }
6627              }
6628              // save last character
6629              $pc = $c;
6630              ++$i;
6631          } // end while i < nb
6632          // print last substring (if any)
6633          if ($l > 0) {
6634              switch ($align) {
6635                  case 'J':
6636                  case 'C': {
6637                      $w = $w;
6638                      break;
6639                  }
6640                  case 'L': {
6641                      if ($this->rtl) {
6642                          $w = $w;
6643                      } else {
6644                          $w = $l;
6645                      }
6646                      break;
6647                  }
6648                  case 'R': {
6649                      if ($this->rtl) {
6650                          $w = $l;
6651                      } else {
6652                          $w = $w;
6653                      }
6654                      break;
6655                  }
6656                  default: {
6657                      $w = $l;
6658                      break;
6659                  }
6660              }
6661              $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
6662              if ($firstline) {
6663                  $startx = $this->x;
6664                  $tmparr = array_slice($chars, $j, ($nb - $j));
6665                  if ($rtlmode) {
6666                      $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6667                  }
6668                  $linew = $this->GetArrStringWidth($tmparr);
6669                  unset($tmparr);
6670                  if ($this->rtl) {
6671                      $this->endlinex = $startx - $linew;
6672                  } else {
6673                      $this->endlinex = $startx + $linew;
6674                  }
6675                  $w = $linew;
6676                  $tmpcellpadding = $this->cell_padding;
6677                  if ($maxh == 0) {
6678                      $this->SetCellPadding(0);
6679                  }
6680              }
6681              if ($firstblock AND $this->isRTLTextDir()) {
6682                  $tmpstr = $this->stringRightTrim($tmpstr);
6683              }
6684              $this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6685              unset($tmpstr);
6686              if ($firstline) {
6687                  $this->cell_padding = $tmpcellpadding;
6688                  return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
6689              }
6690              ++$nl;
6691          }
6692          if ($firstline) {
6693              return '';
6694          }
6695          return $nl;
6696      }
6697  
6698      /**
6699       * Returns the remaining width between the current position and margins.
6700       * @return int Return the remaining width
6701       * @protected
6702       */
6703  	protected function getRemainingWidth() {
6704          list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
6705          if ($this->rtl) {
6706              return ($this->x - $this->lMargin);
6707          } else {
6708              return ($this->w - $this->rMargin - $this->x);
6709          }
6710      }
6711  
6712      /**
6713       * Set the block dimensions accounting for page breaks and page/column fitting
6714       * @param $w (float) width
6715       * @param $h (float) height
6716       * @param $x (float) X coordinate
6717       * @param $y (float) Y coodiante
6718       * @param $fitonpage (boolean) if true the block is resized to not exceed page dimensions.
6719       * @return array($w, $h, $x, $y)
6720       * @protected
6721       * @since 5.5.009 (2010-07-05)
6722       */
6723  	protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
6724          if ($w <= 0) {
6725              // set maximum width
6726              $w = ($this->w - $this->lMargin - $this->rMargin);
6727              if ($w <= 0) {
6728                  $w = 1;
6729              }
6730          }
6731          if ($h <= 0) {
6732              // set maximum height
6733              $h = ($this->PageBreakTrigger - $this->tMargin);
6734              if ($h <= 0) {
6735                  $h = 1;
6736              }
6737          }
6738          // resize the block to be vertically contained on a single page or single column
6739          if ($fitonpage OR $this->AutoPageBreak) {
6740              $ratio_wh = ($w / $h);
6741              if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
6742                  $h = $this->PageBreakTrigger - $this->tMargin;
6743                  $w = ($h * $ratio_wh);
6744              }
6745              // resize the block to be horizontally contained on a single page or single column
6746              if ($fitonpage) {
6747                  $maxw = ($this->w - $this->lMargin - $this->rMargin);
6748                  if ($w > $maxw) {
6749                      $w = $maxw;
6750                      $h = ($w / $ratio_wh);
6751                  }
6752              }
6753          }
6754          // Check whether we need a new page or new column first as this does not fit
6755          $prev_x = $this->x;
6756          $prev_y = $this->y;
6757          if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
6758              $y = $this->y;
6759              if ($this->rtl) {
6760                  $x += ($prev_x - $this->x);
6761              } else {
6762                  $x += ($this->x - $prev_x);
6763              }
6764              $this->newline = true;
6765          }
6766          // resize the block to be contained on the remaining available page or column space
6767          if ($fitonpage) {
6768              $ratio_wh = ($w / $h);
6769              if (($y + $h) > $this->PageBreakTrigger) {
6770                  $h = $this->PageBreakTrigger - $y;
6771                  $w = ($h * $ratio_wh);
6772              }
6773              if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
6774                  $w = $this->w - $this->rMargin - $x;
6775                  $h = ($w / $ratio_wh);
6776              } elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
6777                  $w = $x - $this->lMargin;
6778                  $h = ($w / $ratio_wh);
6779              }
6780          }
6781          return array($w, $h, $x, $y);
6782      }
6783  
6784      /**
6785       * Puts an image in the page.
6786       * The upper-left corner must be given.
6787       * The dimensions can be specified in different ways:<ul>
6788       * <li>explicit width and height (expressed in user unit)</li>
6789       * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
6790       * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
6791       * 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;
6792       * The format can be specified explicitly or inferred from the file extension.<br />
6793       * It is possible to put a link on the image.<br />
6794       * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
6795       * @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').
6796       * @param $x (float) Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
6797       * @param $y (float) Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
6798       * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
6799       * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
6800       * @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.
6801       * @param $link (mixed) URL or identifier returned by AddLink().
6802       * @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>
6803       * @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).
6804       * @param $dpi (int) dot-per-inch resolution used on resize
6805       * @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>
6806       * @param $ismask (boolean) true if this image is a mask, false otherwise
6807       * @param $imgmask (mixed) image object returned by this function or false
6808       * @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)))
6809       * @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).
6810       * @param $hidden (boolean) If true do not display the image.
6811       * @param $fitonpage (boolean) If true the image is resized to not exceed page dimensions.
6812       * @param $alt (boolean) If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
6813       * @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.
6814       * @return image information
6815       * @public
6816       * @since 1.1
6817       */
6818  	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()) {
6819          if ($this->state != 2) {
6820              return;
6821          }
6822          if (strcmp($x, '') === 0) {
6823              $x = $this->x;
6824          }
6825          if (strcmp($y, '') === 0) {
6826              $y = $this->y;
6827          }
6828          // check page for no-write regions and adapt page margins if necessary
6829          list($x, $y) = $this->checkPageRegions($h, $x, $y);
6830          $exurl = ''; // external streams
6831          $imsize = FALSE;
6832          // check if we are passing an image as file or string
6833          if ($file[0] === '@') {
6834              // image from string
6835              $imgdata = substr($file, 1);
6836          } else { // image file
6837              if ($file[0] === '*') {
6838                  // image as external stream
6839                  $file = substr($file, 1);
6840                  $exurl = $file;
6841              }
6842              // check if is a local file
6843              if (!@file_exists($file)) {
6844                  // try to encode spaces on filename
6845                  $tfile = str_replace(' ', '%20', $file);
6846                  if (@file_exists($tfile)) {
6847                      $file = $tfile;
6848                  }
6849              }
6850              if (($imsize = @getimagesize($file)) === FALSE) {
6851                  if (in_array($file, $this->imagekeys)) {
6852                      // get existing image data
6853                      $info = $this->getImageBuffer($file);
6854                      $imsize = array($info['w'], $info['h']);
6855                  } elseif (strpos($file, '__tcpdf_'.$this->file_id.'_img') === FALSE) {
6856                      $imgdata = TCPDF_STATIC::fileGetContents($file);
6857                  }
6858              }
6859          }
6860          if (!empty($imgdata)) {
6861              // copy image to cache
6862              $original_file = $file;
6863              $file = TCPDF_STATIC::getObjFilename('img', $this->file_id);
6864              $fp = TCPDF_STATIC::fopenLocal($file, 'w');
6865              if (!$fp) {
6866                  $this->Error('Unable to write file: '.$file);
6867              }
6868              fwrite($fp, $imgdata);
6869              fclose($fp);
6870              unset($imgdata);
6871              $imsize = @getimagesize($file);
6872              if ($imsize === FALSE) {
6873                  unlink($file);
6874                  $file = $original_file;
6875              }
6876          }
6877          if ($imsize === FALSE) {
6878              if (($w > 0) AND ($h > 0)) {
6879                  // get measures from specified data
6880                  $pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6881                  $ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6882                  $imsize = array($pw, $ph);
6883              } else {
6884                  $this->Error('[Image] Unable to get the size of the image: '.$file);
6885              }
6886          }
6887          // file hash
6888          $filehash = md5($file);
6889          // get original image width and height in pixels
6890          list($pixw, $pixh) = $imsize;
6891          // calculate image width and height on document
6892          if (($w <= 0) AND ($h <= 0)) {
6893              // convert image size to document unit
6894              $w = $this->pixelsToUnits($pixw);
6895              $h = $this->pixelsToUnits($pixh);
6896          } elseif ($w <= 0) {
6897              $w = $h * $pixw / $pixh;
6898          } elseif ($h <= 0) {
6899              $h = $w * $pixh / $pixw;
6900          } elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
6901              if (strlen($fitbox) !== 2) {
6902                  // set default alignment
6903                  $fitbox = '--';
6904              }
6905              // scale image dimensions proportionally to fit within the ($w, $h) box
6906              if ((($w * $pixh) / ($h * $pixw)) < 1) {
6907                  // store current height
6908                  $oldh = $h;
6909                  // calculate new height
6910                  $h = $w * $pixh / $pixw;
6911                  // height difference
6912                  $hdiff = ($oldh - $h);
6913                  // vertical alignment
6914                  switch (strtoupper($fitbox[1])) {
6915                      case 'T': {
6916                          break;
6917                      }
6918                      case 'M': {
6919                          $y += ($hdiff / 2);
6920                          break;
6921                      }
6922                      case 'B': {
6923                          $y += $hdiff;
6924                          break;
6925                      }
6926                  }
6927              } else {
6928                  // store current width
6929                  $oldw = $w;
6930                  // calculate new width
6931                  $w = $h * $pixw / $pixh;
6932                  // width difference
6933                  $wdiff = ($oldw - $w);
6934                  // horizontal alignment
6935                  switch (strtoupper($fitbox[0])) {
6936                      case 'L': {
6937                          if ($this->rtl) {
6938                              $x -= $wdiff;
6939                          }
6940                          break;
6941                      }
6942                      case 'C': {
6943                          if ($this->rtl) {
6944                              $x -= ($wdiff / 2);
6945                          } else {
6946                              $x += ($wdiff / 2);
6947                          }
6948                          break;
6949                      }
6950                      case 'R': {
6951                          if (!$this->rtl) {
6952                              $x += $wdiff;
6953                          }
6954                          break;
6955                      }
6956                  }
6957              }
6958          }
6959          // fit the image on available space
6960          list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
6961          // calculate new minimum dimensions in pixels
6962          $neww = round($w * $this->k * $dpi / $this->dpi);
6963          $newh = round($h * $this->k * $dpi / $this->dpi);
6964          // check if resize is necessary (resize is used only to reduce the image)
6965          $newsize = ($neww * $newh);
6966          $pixsize = ($pixw * $pixh);
6967          if (intval($resize) == 2) {
6968              $resize = true;
6969          } elseif ($newsize >= $pixsize) {
6970              $resize = false;
6971          }
6972          // check if image has been already added on document
6973          $newimage = true;
6974          if (in_array($file, $this->imagekeys)) {
6975              $newimage = false;
6976              // get existing image data
6977              $info = $this->getImageBuffer($file);
6978              if (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE) {
6979                  // check if the newer image is larger
6980                  $oldsize = ($info['w'] * $info['h']);
6981                  if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
6982                      $newimage = true;
6983                  }
6984              }
6985          } elseif (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)) {
6986              // create temp image file (without alpha channel)
6987              $tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
6988              // create temp alpha file
6989              $tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
6990              // check for cached images
6991              if (in_array($tempfile_plain, $this->imagekeys)) {
6992                  // get existing image data
6993                  $info = $this->getImageBuffer($tempfile_plain);
6994                  // check if the newer image is larger
6995                  $oldsize = ($info['w'] * $info['h']);
6996                  if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
6997                      $newimage = true;
6998                  } else {
6999                      $newimage = false;
7000                      // embed mask image
7001                      $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7002                      // embed image, masked with previously embedded mask
7003                      return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7004                  }
7005              }
7006          }
7007          if ($newimage) {
7008              //First use of image, get info
7009              $type = strtolower($type);
7010              if ($type == '') {
7011                  $type = TCPDF_IMAGES::getImageFileType($file, $imsize);
7012              } elseif ($type == 'jpg') {
7013                  $type = 'jpeg';
7014              }
7015              $mqr = TCPDF_STATIC::get_mqr();
7016              TCPDF_STATIC::set_mqr(false);
7017              // Specific image handlers (defined on TCPDF_IMAGES CLASS)
7018              $mtd = '_parse'.$type;
7019              // GD image handler function
7020              $gdfunction = 'imagecreatefrom'.$type;
7021              $info = false;
7022              if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
7023                  // TCPDF image functions
7024                  $info = TCPDF_IMAGES::$mtd($file);
7025                  if (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)
7026                      AND (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns'])))) {
7027                      return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
7028                  }
7029              }
7030              if (($info === false) AND function_exists($gdfunction)) {
7031                  try {
7032                      // GD library
7033                      $img = $gdfunction($file);
7034                      if ($img !== false) {
7035                          if ($resize) {
7036                              $imgr = imagecreatetruecolor($neww, $newh);
7037                              if (($type == 'gif') OR ($type == 'png')) {
7038                                  $imgr = TCPDF_IMAGES::setGDImageTransparency($imgr, $img);
7039                              }
7040                              imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7041                              $img = $imgr;
7042                          }
7043                          if (($type == 'gif') OR ($type == 'png')) {
7044                              $info = TCPDF_IMAGES::_toPNG($img, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7045                          } else {
7046                              $info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7047                          }
7048                      }
7049                  } catch(Exception $e) {
7050                      $info = false;
7051                  }
7052              }
7053              if (($info === false) AND extension_loaded('imagick')) {
7054                  try {
7055                      // ImageMagick library
7056                      $img = new Imagick();
7057                      if ($type == 'svg') {
7058                          if ($file[0] === '@') {
7059                              // image from string
7060                              $svgimg = substr($file, 1);
7061                          } else {
7062                              // get SVG file content
7063                              $svgimg = TCPDF_STATIC::fileGetContents($file);
7064                          }
7065                          if ($svgimg !== FALSE) {
7066                              // get width and height
7067                              $regs = array();
7068                              if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7069                                  $svgtag = $regs[1];
7070                                  $tmp = array();
7071                                  if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7072                                      $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7073                                      $owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
7074                                      $svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7075                                  } else {
7076                                      $ow = $w;
7077                                  }
7078                                  $tmp = array();
7079                                  if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7080                                      $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7081                                      $ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
7082                                      $svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7083                                  } else {
7084                                      $oh = $h;
7085                                  }
7086                                  $tmp = array();
7087                                  if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7088                                      $vbw = ($ow * $this->imgscale * $this->k);
7089                                      $vbh = ($oh * $this->imgscale * $this->k);
7090                                      $vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
7091                                      $svgtag = $vbox.$svgtag;
7092                                  }
7093                                  $svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7094                              }
7095                              $img->readImageBlob($svgimg);
7096                          }
7097                      } else {
7098                          $img->readImage($file);
7099                      }
7100                      if ($resize) {
7101                          $img->resizeImage($neww, $newh, 10, 1, false);
7102                      }
7103                      $img->setCompressionQuality($this->jpeg_quality);
7104                      $img->setImageFormat('jpeg');
7105                      $tempname = TCPDF_STATIC::getObjFilename('img', $this->file_id);
7106                      $img->writeImage($tempname);
7107                      $info = TCPDF_IMAGES::_parsejpeg($tempname);
7108                      unlink($tempname);
7109                      $img->destroy();
7110                  } catch(Exception $e) {
7111                      $info = false;
7112                  }
7113              }
7114              if ($info === false) {
7115                  // unable to process image
7116                  return;
7117              }
7118              TCPDF_STATIC::set_mqr($mqr);
7119              if ($ismask) {
7120                  // force grayscale
7121                  $info['cs'] = 'DeviceGray';
7122              }
7123              if ($imgmask !== false) {
7124                  $info['masked'] = $imgmask;
7125              }
7126              if (!empty($exurl)) {
7127                  $info['exurl'] = $exurl;
7128              }
7129              // array of alternative images
7130              $info['altimgs'] = $altimgs;
7131              // add image to document
7132              $info['i'] = $this->setImageBuffer($file, $info);
7133          }
7134          // set alignment
7135          $this->img_rb_y = $y + $h;
7136          // set alignment
7137          if ($this->rtl) {
7138              if ($palign == 'L') {
7139                  $ximg = $this->lMargin;
7140              } elseif ($palign == 'C') {
7141                  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7142              } elseif ($palign == 'R') {
7143                  $ximg = $this->w - $this->rMargin - $w;
7144              } else {
7145                  $ximg = $x - $w;
7146              }
7147              $this->img_rb_x = $ximg;
7148          } else {
7149              if ($palign == 'L') {
7150                  $ximg = $this->lMargin;
7151              } elseif ($palign == 'C') {
7152                  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7153              } elseif ($palign == 'R') {
7154                  $ximg = $this->w - $this->rMargin - $w;
7155              } else {
7156                  $ximg = $x;
7157              }
7158              $this->img_rb_x = $ximg + $w;
7159          }
7160          if ($ismask OR $hidden) {
7161              // image is not displayed
7162              return $info['i'];
7163          }
7164          $xkimg = $ximg * $this->k;
7165          if (!$alt) {
7166              // only non-alternative immages will be set
7167              $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']));
7168          }
7169          if (!empty($border)) {
7170              $bx = $this->x;
7171              $by = $this->y;
7172              $this->x = $ximg;
7173              if ($this->rtl) {
7174                  $this->x += $w;
7175              }
7176              $this->y = $y;
7177              $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7178              $this->x = $bx;
7179              $this->y = $by;
7180          }
7181          if ($link) {
7182              $this->Link($ximg, $y, $w, $h, $link, 0);
7183          }
7184          // set pointer to align the next text/objects
7185          switch($align) {
7186              case 'T': {
7187                  $this->y = $y;
7188                  $this->x = $this->img_rb_x;
7189                  break;
7190              }
7191              case 'M': {
7192                  $this->y = $y + round($h/2);
7193                  $this->x = $this->img_rb_x;
7194                  break;
7195              }
7196              case 'B': {
7197                  $this->y = $this->img_rb_y;
7198                  $this->x = $this->img_rb_x;
7199                  break;
7200              }
7201              case 'N': {
7202                  $this->SetY($this->img_rb_y);
7203                  break;
7204              }
7205              default:{
7206                  break;
7207              }
7208          }
7209          $this->endlinex = $this->img_rb_x;
7210          if ($this->inxobj) {
7211              // we are inside an XObject template
7212              $this->xobjects[$this->xobjid]['images'][] = $info['i'];
7213          }
7214          return $info['i'];
7215      }
7216  
7217      /**
7218       * Extract info from a PNG image with alpha channel using the Imagick or GD library.
7219       * @param $file (string) Name of the file containing the image.
7220       * @param $x (float) Abscissa of the upper-left corner.
7221       * @param $y (float) Ordinate of the upper-left corner.
7222       * @param $wpx (float) Original width of the image in pixels.
7223       * @param $hpx (float) original height of the image in pixels.
7224       * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7225       * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7226       * @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.
7227       * @param $link (mixed) URL or identifier returned by AddLink().
7228       * @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>
7229       * @param $resize (boolean) If true resize (reduce) the image to fit $w and $h (requires GD library).
7230       * @param $dpi (int) dot-per-inch resolution used on resize
7231       * @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>
7232       * @param $filehash (string) File hash used to build unique file names.
7233       * @author Nicola Asuni
7234       * @protected
7235       * @since 4.3.007 (2008-12-04)
7236       * @see Image()
7237       */
7238  	protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
7239          // create temp images
7240          if (empty($filehash)) {
7241              $filehash = md5($file);
7242          }
7243          // create temp image file (without alpha channel)
7244          $tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
7245          // create temp alpha file
7246          $tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
7247          $parsed = false;
7248          $parse_error = '';
7249          // ImageMagick extension
7250          if (($parsed === false) AND extension_loaded('imagick')) {
7251              try {
7252                  // ImageMagick library
7253                  $img = new Imagick();
7254                  $img->readImage($file);
7255                  // clone image object
7256                  $imga = TCPDF_STATIC::objclone($img);
7257                  // extract alpha channel
7258                  if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
7259                      $img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
7260                  } else {
7261                      $img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7262                      $img->negateImage(true);
7263                  }
7264                  $img->setImageFormat('png');
7265                  $img->writeImage($tempfile_alpha);
7266                  // remove alpha channel
7267                  if (method_exists($imga, 'setImageMatte')) {
7268                      $imga->setImageMatte(false);
7269                  } else {
7270                      $imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7271                  }
7272                  $imga->setImageFormat('png');
7273                  $imga->writeImage($tempfile_plain);
7274                  $parsed = true;
7275              } catch (Exception $e) {
7276                  // Imagemagick fails, try with GD
7277                  $parse_error = 'Imagick library error: '.$e->getMessage();
7278              }
7279          }
7280          // GD extension
7281          if (($parsed === false) AND function_exists('imagecreatefrompng')) {
7282              try {
7283                  // generate images
7284                  $img = imagecreatefrompng($file);
7285                  $imgalpha = imagecreate($wpx, $hpx);
7286                  // generate gray scale palette (0 -> 255)
7287                  for ($c = 0; $c < 256; ++$c) {
7288                      ImageColorAllocate($imgalpha, $c, $c, $c);
7289                  }
7290                  // extract alpha channel
7291                  for ($xpx = 0; $xpx < $wpx; ++$xpx) {
7292                      for ($ypx = 0; $ypx < $hpx; ++$ypx) {
7293                          $color = imagecolorat($img, $xpx, $ypx);
7294                          // get and correct gamma color
7295                          $alpha = $this->getGDgamma($img, $color);
7296                          imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
7297                      }
7298                  }
7299                  imagepng($imgalpha, $tempfile_alpha);
7300                  imagedestroy($imgalpha);
7301                  // extract image without alpha channel
7302                  $imgplain = imagecreatetruecolor($wpx, $hpx);
7303                  imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7304                  imagepng($imgplain, $tempfile_plain);
7305                  imagedestroy($imgplain);
7306                  $parsed = true;
7307              } catch (Exception $e) {
7308                  // GD fails
7309                  $parse_error = 'GD library error: '.$e->getMessage();
7310              }
7311          }
7312          if ($parsed === false) {
7313              if (empty($parse_error)) {
7314                  $this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
7315              } else {
7316                  $this->Error($parse_error);
7317              }
7318          }
7319          // embed mask image
7320          $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7321          // embed image, masked with previously embedded mask
7322          $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7323      }
7324  
7325      /**
7326       * Get the GD-corrected PNG gamma value from alpha color
7327       * @param $img (int) GD image Resource ID.
7328       * @param $c (int) alpha color
7329       * @protected
7330       * @since 4.3.007 (2008-12-04)
7331       */
7332  	protected function getGDgamma($img, $c) {
7333          if (!isset($this->gdgammacache['#'.$c])) {
7334              $colors = imagecolorsforindex($img, $c);
7335              // GD alpha is only 7 bit (0 -> 127)
7336              $this->gdgammacache['#'.$c] = (((127 - $colors['alpha']) / 127) * 255);
7337              // correct gamma
7338              $this->gdgammacache['#'.$c] = (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255);
7339              // store the latest values on cache to improve performances
7340              if (count($this->gdgammacache) > 8) {
7341                  // remove one element from the cache array
7342                  array_shift($this->gdgammacache);
7343              }
7344          }
7345          return $this->gdgammacache['#'.$c];
7346      }
7347  
7348      /**
7349       * Performs a line break.
7350       * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
7351       * @param $h (float) The height of the break. By default, the value equals the height of the last printed cell.
7352       * @param $cell (boolean) if true add the current left (or right o for RTL) padding to the X coordinate
7353       * @public
7354       * @since 1.0
7355       * @see Cell()
7356       */
7357      public function Ln($h='', $cell=false) {
7358          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'])) {
7359              // revove vertical space from the top of the column
7360              return;
7361          }
7362          if ($cell) {
7363              if ($this->rtl) {
7364                  $cellpadding = $this->cell_padding['R'];
7365              } else {
7366                  $cellpadding = $this->cell_padding['L'];
7367              }
7368          } else {
7369              $cellpadding = 0;
7370          }
7371          if ($this->rtl) {
7372              $this->x = $this->w - $this->rMargin - $cellpadding;
7373          } else {
7374              $this->x = $this->lMargin + $cellpadding;
7375          }
7376          if (is_string($h)) {
7377              $h = $this->lasth;
7378          }
7379          $this->y += $h;
7380          $this->newline = true;
7381      }
7382  
7383      /**
7384       * Returns the relative X value of current position.
7385       * The value is relative to the left border for LTR languages and to the right border for RTL languages.
7386       * @return float
7387       * @public
7388       * @since 1.2
7389       * @see SetX(), GetY(), SetY()
7390       */
7391  	public function GetX() {
7392          //Get x position
7393          if ($this->rtl) {
7394              return ($this->w - $this->x);
7395          } else {
7396              return $this->x;
7397          }
7398      }
7399  
7400      /**
7401       * Returns the absolute X value of current position.
7402       * @return float
7403       * @public
7404       * @since 1.2
7405       * @see SetX(), GetY(), SetY()
7406       */
7407  	public function GetAbsX() {
7408          return $this->x;
7409      }
7410  
7411      /**
7412       * Returns the ordinate of the current position.
7413       * @return float
7414       * @public
7415       * @since 1.0
7416       * @see SetY(), GetX(), SetX()
7417       */
7418  	public function GetY() {
7419          return $this->y;
7420      }
7421  
7422      /**
7423       * Defines the abscissa of the current position.
7424       * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
7425       * @param $x (float) The value of the abscissa in user units.
7426       * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7427       * @public
7428       * @since 1.2
7429       * @see GetX(), GetY(), SetY(), SetXY()
7430       */
7431  	public function SetX($x, $rtloff=false) {
7432          $x = floatval($x);
7433          if (!$rtloff AND $this->rtl) {
7434              if ($x >= 0) {
7435                  $this->x = $this->w - $x;
7436              } else {
7437                  $this->x = abs($x);
7438              }
7439          } else {
7440              if ($x >= 0) {
7441                  $this->x = $x;
7442              } else {
7443                  $this->x = $this->w + $x;
7444              }
7445          }
7446          if ($this->x < 0) {
7447              $this->x = 0;
7448          }
7449          if ($this->x > $this->w) {
7450              $this->x = $this->w;
7451          }
7452      }
7453  
7454      /**
7455       * Moves the current abscissa back to the left margin and sets the ordinate.
7456       * If the passed value is negative, it is relative to the bottom of the page.
7457       * @param $y (float) The value of the ordinate in user units.
7458       * @param $resetx (bool) if true (default) reset the X position.
7459       * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7460       * @public
7461       * @since 1.0
7462       * @see GetX(), GetY(), SetY(), SetXY()
7463       */
7464  	public function SetY($y, $resetx=true, $rtloff=false) {
7465          $y = floatval($y);
7466          if ($resetx) {
7467              //reset x
7468              if (!$rtloff AND $this->rtl) {
7469                  $this->x = $this->w - $this->rMargin;
7470              } else {
7471                  $this->x = $this->lMargin;
7472              }
7473          }
7474          if ($y >= 0) {
7475              $this->y = $y;
7476          } else {
7477              $this->y = $this->h + $y;
7478          }
7479          if ($this->y < 0) {
7480              $this->y = 0;
7481          }
7482          if ($this->y > $this->h) {
7483              $this->y = $this->h;
7484          }
7485      }
7486  
7487      /**
7488       * Defines the abscissa and ordinate of the current position.
7489       * If the passed values are negative, they are relative respectively to the right and bottom of the page.
7490       * @param $x (float) The value of the abscissa.
7491       * @param $y (float) The value of the ordinate.
7492       * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7493       * @public
7494       * @since 1.2
7495       * @see SetX(), SetY()
7496       */
7497  	public function SetXY($x, $y, $rtloff=false) {
7498          $this->SetY($y, false, $rtloff);
7499          $this->SetX($x, $rtloff);
7500      }
7501  
7502      /**
7503       * Set the absolute X coordinate of the current pointer.
7504       * @param $x (float) The value of the abscissa in user units.
7505       * @public
7506       * @since 5.9.186 (2012-09-13)
7507       * @see setAbsX(), setAbsY(), SetAbsXY()
7508       */
7509  	public function SetAbsX($x) {
7510          $this->x = floatval($x);
7511      }
7512  
7513      /**
7514       * Set the absolute Y coordinate of the current pointer.
7515       * @param $y (float) (float) The value of the ordinate in user units.
7516       * @public
7517       * @since 5.9.186 (2012-09-13)
7518       * @see setAbsX(), setAbsY(), SetAbsXY()
7519       */
7520  	public function SetAbsY($y) {
7521          $this->y = floatval($y);
7522      }
7523  
7524      /**
7525       * Set the absolute X and Y coordinates of the current pointer.
7526       * @param $x (float) The value of the abscissa in user units.
7527       * @param $y (float) (float) The value of the ordinate in user units.
7528       * @public
7529       * @since 5.9.186 (2012-09-13)
7530       * @see setAbsX(), setAbsY(), SetAbsXY()
7531       */
7532  	public function SetAbsXY($x, $y) {
7533          $this->SetAbsX($x);
7534          $this->SetAbsY($y);
7535      }
7536  
7537      /**
7538       * Send the document to a given destination: string, local file or browser.
7539       * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
7540       * The method first calls Close() if necessary to terminate the document.
7541       * @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.
7542       * @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>
7543       * @return string
7544       * @public
7545       * @since 1.0
7546       * @see Close()
7547       */
7548  	public function Output($name='doc.pdf', $dest='I') {
7549          //Output PDF to some destination
7550          //Finish document if necessary
7551          if ($this->state < 3) {
7552              $this->Close();
7553          }
7554          //Normalize parameters
7555          if (is_bool($dest)) {
7556              $dest = $dest ? 'D' : 'F';
7557          }
7558          $dest = strtoupper($dest);
7559          if ($dest[0] != 'F') {
7560              $name = preg_replace('/[\s]+/', '_', $name);
7561              $name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
7562          }
7563          if ($this->sign) {
7564              // *** apply digital signature to the document ***
7565              // get the document content
7566              $pdfdoc = $this->getBuffer();
7567              // remove last newline
7568              $pdfdoc = substr($pdfdoc, 0, -1);
7569              // remove filler space
7570              $byterange_string_len = strlen(TCPDF_STATIC::$byterange_string);
7571              // define the ByteRange
7572              $byte_range = array();
7573              $byte_range[0] = 0;
7574              $byte_range[1] = strpos($pdfdoc, TCPDF_STATIC::$byterange_string) + $byterange_string_len + 10;
7575              $byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
7576              $byte_range[3] = strlen($pdfdoc) - $byte_range[2];
7577              $pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
7578              // replace the ByteRange
7579              $byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
7580              $byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
7581              $pdfdoc = str_replace(TCPDF_STATIC::$byterange_string, $byterange, $pdfdoc);
7582              // write the document to a temporary folder
7583              $tempdoc = TCPDF_STATIC::getObjFilename('doc', $this->file_id);
7584              $f = TCPDF_STATIC::fopenLocal($tempdoc, 'wb');
7585              if (!$f) {
7586                  $this->Error('Unable to create temporary file: '.$tempdoc);
7587              }
7588              $pdfdoc_length = strlen($pdfdoc);
7589              fwrite($f, $pdfdoc, $pdfdoc_length);
7590              fclose($f);
7591              // get digital signature via openssl library
7592              $tempsign = TCPDF_STATIC::getObjFilename('sig', $this->file_id);
7593              if (empty($this->signature_data['extracerts'])) {
7594                  openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
7595              } else {
7596                  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']);
7597              }
7598              // read signature
7599              $signature = file_get_contents($tempsign);
7600              // extract signature
7601              $signature = substr($signature, $pdfdoc_length);
7602              $signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
7603              $tmparr = explode("\n\n", $signature);
7604              $signature = $tmparr[1];
7605              // decode signature
7606              $signature = base64_decode(trim($signature));
7607              // add TSA timestamp to signature
7608              $signature = $this->applyTSA($signature);
7609              // convert signature to hex
7610              $signature = current(unpack('H*', $signature));
7611              $signature = str_pad($signature, $this->signature_max_length, '0');
7612              // Add signature to the document
7613              $this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
7614              $this->bufferlen = strlen($this->buffer);
7615          }
7616          switch($dest) {
7617              case 'I': {
7618                  // Send PDF to the standard output
7619                  if (ob_get_contents()) {
7620                      $this->Error('Some data has already been output, can\'t send PDF file');
7621                  }
7622                  if (php_sapi_name() != 'cli') {
7623                      // send output to a browser
7624                      header('Content-Type: application/pdf');
7625                      if (headers_sent()) {
7626                          $this->Error('Some data has already been output to browser, can\'t send PDF file');
7627                      }
7628                      header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7629                      //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7630                      header('Pragma: public');
7631                      header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7632                      header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7633                      header('Content-Disposition: inline; filename="'.basename($name).'"');
7634                      TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7635                  } else {
7636                      echo $this->getBuffer();
7637                  }
7638                  break;
7639              }
7640              case 'D': {
7641                  // download PDF as file
7642                  if (ob_get_contents()) {
7643                      $this->Error('Some data has already been output, can\'t send PDF file');
7644                  }
7645                  header('Content-Description: File Transfer');
7646                  if (headers_sent()) {
7647                      $this->Error('Some data has already been output to browser, can\'t send PDF file');
7648                  }
7649                  header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7650                  //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7651                  header('Pragma: public');
7652                  header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7653                  header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7654                  // force download dialog
7655                  if (strpos(php_sapi_name(), 'cgi') === false) {
7656                      header('Content-Type: application/force-download');
7657                      header('Content-Type: application/octet-stream', false);
7658                      header('Content-Type: application/download', false);
7659                      header('Content-Type: application/pdf', false);
7660                  } else {
7661                      header('Content-Type: application/pdf');
7662                  }
7663                  // use the Content-Disposition header to supply a recommended filename
7664                  header('Content-Disposition: attachment; filename="'.basename($name).'"');
7665                  header('Content-Transfer-Encoding: binary');
7666                  TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7667                  break;
7668              }
7669              case 'F':
7670              case 'FI':
7671              case 'FD': {
7672                  // save PDF to a local file
7673                  $f = TCPDF_STATIC::fopenLocal($name, 'wb');
7674                  if (!$f) {
7675                      $this->Error('Unable to create output file: '.$name);
7676                  }
7677                  fwrite($f, $this->getBuffer(), $this->bufferlen);
7678                  fclose($f);
7679                  if ($dest == 'FI') {
7680                      // send headers to browser
7681                      header('Content-Type: application/pdf');
7682                      header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7683                      //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7684                      header('Pragma: public');
7685                      header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7686                      header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7687                      header('Content-Disposition: inline; filename="'.basename($name).'"');
7688                      TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7689                  } elseif ($dest == 'FD') {
7690                      // send headers to browser
7691                      if (ob_get_contents()) {
7692                          $this->Error('Some data has already been output, can\'t send PDF file');
7693                      }
7694                      header('Content-Description: File Transfer');
7695                      if (headers_sent()) {
7696                          $this->Error('Some data has already been output to browser, can\'t send PDF file');
7697                      }
7698                      header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7699                      header('Pragma: public');
7700                      header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7701                      header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7702                      // force download dialog
7703                      if (strpos(php_sapi_name(), 'cgi') === false) {
7704                          header('Content-Type: application/force-download');
7705                          header('Content-Type: application/octet-stream', false);
7706                          header('Content-Type: application/download', false);
7707                          header('Content-Type: application/pdf', false);
7708                      } else {
7709                          header('Content-Type: application/pdf');
7710                      }
7711                      // use the Content-Disposition header to supply a recommended filename
7712                      header('Content-Disposition: attachment; filename="'.basename($name).'"');
7713                      header('Content-Transfer-Encoding: binary');
7714                      TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7715                  }
7716                  break;
7717              }
7718              case 'E': {
7719                  // return PDF as base64 mime multi-part email attachment (RFC 2045)
7720                  $retval = 'Content-Type: application/pdf;'."\r\n";
7721                  $retval .= ' name="'.$name.'"'."\r\n";
7722                  $retval .= 'Content-Transfer-Encoding: base64'."\r\n";
7723                  $retval .= 'Content-Disposition: attachment;'."\r\n";
7724                  $retval .= ' filename="'.$name.'"'."\r\n\r\n";
7725                  $retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
7726                  return $retval;
7727              }
7728              case 'S': {
7729                  // returns PDF as a string
7730                  return $this->getBuffer();
7731              }
7732              default: {
7733                  $this->Error('Incorrect output destination: '.$dest);
7734              }
7735          }
7736          return '';
7737      }
7738  
7739      /**
7740       * Unset all class variables except the following critical variables.
7741       * @param $destroyall (boolean) if true destroys all class variables, otherwise preserves critical variables.
7742       * @param $preserve_objcopy (boolean) if true preserves the objcopy variable
7743       * @public
7744       * @since 4.5.016 (2009-02-24)
7745       */
7746  	public function _destroy($destroyall=false, $preserve_objcopy=false) {
7747          if ($destroyall AND !$preserve_objcopy) {
7748              // remove all temporary files
7749              $tmpfiles = glob(K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_*');
7750              if (!empty($tmpfiles)) {
7751                  array_map('unlink', $tmpfiles);
7752              }
7753          }
7754          $preserve = array(
7755              'file_id',
7756              'internal_encoding',
7757              'state',
7758              'bufferlen',
7759              'buffer',
7760              'cached_files',
7761              'sign',
7762              'signature_data',
7763              'signature_max_length',
7764              'byterange_string',
7765              'tsa_timestamp',
7766              'tsa_data'
7767          );
7768          foreach (array_keys(get_object_vars($this)) as $val) {
7769              if ($destroyall OR !in_array($val, $preserve)) {
7770                  if ((!$preserve_objcopy OR ($val != 'objcopy')) AND ($val != 'file_id') AND isset($this->$val)) {
7771                      unset($this->$val);
7772                  }
7773              }
7774          }
7775      }
7776  
7777      /**
7778       * Check for locale-related bug
7779       * @protected
7780       */
7781  	protected function _dochecks() {
7782          //Check for locale-related bug
7783          if (1.1 == 1) {
7784              $this->Error('Don\'t alter the locale before including class file');
7785          }
7786          //Check for decimal separator
7787          if (sprintf('%.1F', 1.0) != '1.0') {
7788              setlocale(LC_NUMERIC, 'C');
7789          }
7790      }
7791  
7792      /**
7793       * Return an array containing variations for the basic page number alias.
7794       * @param $a (string) Base alias.
7795       * @return array of page number aliases
7796       * @protected
7797       */
7798  	protected function getInternalPageNumberAliases($a= '') {
7799          $alias = array();
7800          // build array of Unicode + ASCII variants (the order is important)
7801          $alias = array('u' => array(), 'a' => array());
7802          $u = '{'.$a.'}';
7803          $alias['u'][] = TCPDF_STATIC::_escape($u);
7804          if ($this->isunicode) {
7805              $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($u, $this->isunicode, $this->CurrentFont));
7806              $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($u, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7807              $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($a, $this->isunicode, $this->CurrentFont));
7808              $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($a, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7809          }
7810          $alias['a'][] = TCPDF_STATIC::_escape($a);
7811          return $alias;
7812      }
7813  
7814      /**
7815       * Return an array containing all internal page aliases.
7816       * @return array of page number aliases
7817       * @protected
7818       */
7819  	protected function getAllInternalPageNumberAliases() {
7820          $basic_alias = array(TCPDF_STATIC::$alias_tot_pages, TCPDF_STATIC::$alias_num_page, TCPDF_STATIC::$alias_group_tot_pages, TCPDF_STATIC::$alias_group_num_page, TCPDF_STATIC::$alias_right_shift);
7821          $pnalias = array();
7822          foreach($basic_alias as $k => $a) {
7823              $pnalias[$k] = $this->getInternalPageNumberAliases($a);
7824          }
7825          return $pnalias;
7826      }
7827  
7828      /**
7829       * Replace right shift page number aliases with spaces to correct right alignment.
7830       * This works perfectly only when using monospaced fonts.
7831       * @param $page (string) Page content.
7832       * @param $aliases (array) Array of page aliases.
7833       * @param $diff (int) initial difference to add.
7834       * @return replaced page content.
7835       * @protected
7836       */
7837  	protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
7838          foreach ($aliases as $type => $alias) {
7839              foreach ($alias as $a) {
7840                  // find position of compensation factor
7841                  $startnum = (strpos($a, ':') + 1);
7842                  $a = substr($a, 0, $startnum);
7843                  if (($pos = strpos($page, $a)) !== false) {
7844                      // end of alias
7845                      $endnum = strpos($page, '}', $pos);
7846                      // string to be replaced
7847                      $aa = substr($page, $pos, ($endnum - $pos + 1));
7848                      // get compensation factor
7849                      $ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
7850                      $ratio = preg_replace('/[^0-9\.]/', '', $ratio);
7851                      $ratio = floatval($ratio);
7852                      if ($type == 'u') {
7853                          $chrdiff = floor(($diff + 12) * $ratio);
7854                          $shift = str_repeat(' ', $chrdiff);
7855                          $shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
7856                      } else {
7857                          $chrdiff = floor(($diff + 11) * $ratio);
7858                          $shift = str_repeat(' ', $chrdiff);
7859                      }
7860                      $page = str_replace($aa, $shift, $page);
7861                  }
7862              }
7863          }
7864          return $page;
7865      }
7866  
7867      /**
7868       * Set page boxes to be included on page descriptions.
7869       * @param $boxes (array) Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
7870       * @protected
7871       */
7872  	protected function setPageBoxTypes($boxes) {
7873          $this->page_boxes = array();
7874          foreach ($boxes as $box) {
7875              if (in_array($box, TCPDF_STATIC::$pageboxes)) {
7876                  $this->page_boxes[] = $box;
7877              }
7878          }
7879      }
7880  
7881      /**
7882       * Output pages (and replace page number aliases).
7883       * @protected
7884       */
7885  	protected function _putpages() {
7886          $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
7887          // get internal aliases for page numbers
7888          $pnalias = $this->getAllInternalPageNumberAliases();
7889          $num_pages = $this->numpages;
7890          $ptpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $num_pages - 1));
7891          $ptpu = TCPDF_FONTS::UTF8ToUTF16BE($ptpa, false, $this->isunicode, $this->CurrentFont);
7892          $ptp_num_chars = $this->GetNumChars($ptpa);
7893          $pagegroupnum = 0;
7894          $groupnum = 0;
7895          $ptgu = 1;
7896          $ptga = 1;
7897          $ptg_num_chars = 1;
7898          for ($n = 1; $n <= $num_pages; ++$n) {
7899              // get current page
7900              $temppage = $this->getPageBuffer($n);
7901              $pagelen = strlen($temppage);
7902              // set replacements for total pages number
7903              $pnpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $n - 1));
7904              $pnpu = TCPDF_FONTS::UTF8ToUTF16BE($pnpa, false, $this->isunicode, $this->CurrentFont);
7905              $pnp_num_chars = $this->GetNumChars($pnpa);
7906              $pdiff = 0; // difference used for right shift alignment of page numbers
7907              $gdiff = 0; // difference used for right shift alignment of page group numbers
7908              if (!empty($this->pagegroups)) {
7909                  if (isset($this->newpagegroup[$n])) {
7910                      $pagegroupnum = 0;
7911                      ++$groupnum;
7912                      $ptga = TCPDF_STATIC::formatPageNumber($this->pagegroups[$groupnum]);
7913                      $ptgu = TCPDF_FONTS::UTF8ToUTF16BE($ptga, false, $this->isunicode, $this->CurrentFont);
7914                      $ptg_num_chars = $this->GetNumChars($ptga);
7915                  }
7916                  ++$pagegroupnum;
7917                  $pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
7918                  $pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
7919                  $png_num_chars = $this->GetNumChars($pnga);
7920                  // replace page numbers
7921                  $replace = array();
7922                  $replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
7923                  $replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
7924                  $replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
7925                  $replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
7926                  list($temppage, $gdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $gdiff);
7927              }
7928              // replace page numbers
7929              $replace = array();
7930              $replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
7931              $replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
7932              $replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
7933              $replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
7934              list($temppage, $pdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $pdiff);
7935              // replace right shift alias
7936              $temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
7937              // replace EPS marker
7938              $temppage = str_replace($this->epsmarker, '', $temppage);
7939              //Page
7940              $this->page_obj_id[$n] = $this->_newobj();
7941              $out = '<<';
7942              $out .= ' /Type /Page';
7943              $out .= ' /Parent 1 0 R';
7944              if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
7945                  $out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
7946              }
7947              $out .= ' /Resources 2 0 R';
7948              foreach ($this->page_boxes as $box) {
7949                  $out .= ' /'.$box;
7950                  $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']);
7951              }
7952              if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
7953                  $out .= ' /BoxColorInfo <<';
7954                  foreach ($this->page_boxes as $box) {
7955                      if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
7956                          $out .= ' /'.$box.' <<';
7957                          if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
7958                              $color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
7959                              $out .= ' /C [';
7960                              $out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
7961                              $out .= ' ]';
7962                          }
7963                          if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
7964                              $out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
7965                          }
7966                          if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
7967                              $out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
7968                          }
7969                          if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
7970                              $dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
7971                              $out .= ' /D [';
7972                              foreach ($dashes as $dash) {
7973                                  $out .= sprintf(' %F', ($dash * $this->k));
7974                              }
7975                              $out .= ' ]';
7976                          }
7977                          $out .= ' >>';
7978                      }
7979                  }
7980                  $out .= ' >>';
7981              }
7982              $out .= ' /Contents '.($this->n + 1).' 0 R';
7983              $out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
7984              if (!$this->pdfa_mode) {
7985                  $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
7986              }
7987              if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
7988                  // page transitions
7989                  if (isset($this->pagedim[$n]['trans']['Dur'])) {
7990                      $out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
7991                  }
7992                  $out .= ' /Trans <<';
7993                  $out .= ' /Type /Trans';
7994                  if (isset($this->pagedim[$n]['trans']['S'])) {
7995                      $out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
7996                  }
7997                  if (isset($this->pagedim[$n]['trans']['D'])) {
7998                      $out .= ' /D '.$this->pagedim[$n]['trans']['D'];
7999                  }
8000                  if (isset($this->pagedim[$n]['trans']['Dm'])) {
8001                      $out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
8002                  }
8003                  if (isset($this->pagedim[$n]['trans']['M'])) {
8004                      $out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
8005                  }
8006                  if (isset($this->pagedim[$n]['trans']['Di'])) {
8007                      $out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
8008                  }
8009                  if (isset($this->pagedim[$n]['trans']['SS'])) {
8010                      $out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
8011                  }
8012                  if (isset($this->pagedim[$n]['trans']['B'])) {
8013                      $out .= ' /B '.$this->pagedim[$n]['trans']['B'];
8014                  }
8015                  $out .= ' >>';
8016              }
8017              $out .= $this->_getannotsrefs($n);
8018              $out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8019              $out .= ' >>';
8020              $out .= "\n".'endobj';
8021              $this->_out($out);
8022              //Page content
8023              $p = ($this->compress) ? gzcompress($temppage) : $temppage;
8024              $this->_newobj();
8025              $p = $this->_getrawstream($p);
8026              $this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8027          }
8028          //Pages root
8029          $out = $this->_getobj(1)."\n";
8030          $out .= '<< /Type /Pages /Kids [';
8031          foreach($this->page_obj_id as $page_obj) {
8032              $out .= ' '.$page_obj.' 0 R';
8033          }
8034          $out .= ' ] /Count '.$num_pages.' >>';
8035          $out .= "\n".'endobj';
8036          $this->_out($out);
8037      }
8038  
8039      /**
8040       * Get references to page annotations.
8041       * @param $n (int) page number
8042       * @return string
8043       * @protected
8044       * @author Nicola Asuni
8045       * @since 5.0.010 (2010-05-17)
8046       */
8047  	protected function _getannotsrefs($n) {
8048          if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
8049              return '';
8050          }
8051          $out = ' /Annots [';
8052          if (isset($this->PageAnnots[$n])) {
8053              foreach ($this->PageAnnots[$n] as $key => $val) {
8054                  if (!in_array($val['n'], $this->radio_groups)) {
8055                      $out .= ' '.$val['n'].' 0 R';
8056                  }
8057              }
8058              // add radiobutton groups
8059              if (isset($this->radiobutton_groups[$n])) {
8060                  foreach ($this->radiobutton_groups[$n] as $key => $data) {
8061                      if (isset($data['n'])) {
8062                          $out .= ' '.$data['n'].' 0 R';
8063                      }
8064                  }
8065              }
8066          }
8067          if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
8068              // set reference for signature object
8069              $out .= ' '.$this->sig_obj_id.' 0 R';
8070          }
8071          if (!empty($this->empty_signature_appearance)) {
8072              foreach ($this->empty_signature_appearance as $esa) {
8073                  if ($esa['page'] == $n) {
8074                      // set reference for empty signature objects
8075                      $out .= ' '.$esa['objid'].' 0 R';
8076                  }
8077              }
8078          }
8079          $out .= ' ]';
8080          return $out;
8081      }
8082  
8083      /**
8084       * Output annotations objects for all pages.
8085       * !!! THIS METHOD IS NOT YET COMPLETED !!!
8086       * See section 12.5 of PDF 32000_2008 reference.
8087       * @protected
8088       * @author Nicola Asuni
8089       * @since 4.0.018 (2008-08-06)
8090       */
8091  	protected function _putannotsobjs() {
8092          // reset object counter
8093          for ($n=1; $n <= $this->numpages; ++$n) {
8094              if (isset($this->PageAnnots[$n])) {
8095                  // set page annotations
8096                  foreach ($this->PageAnnots[$n] as $key => $pl) {
8097                      $annot_obj_id = $this->PageAnnots[$n][$key]['n'];
8098                      // create annotation object for grouping radiobuttons
8099                      if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
8100                          $radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
8101                          $annots = '<<';
8102                          $annots .= ' /Type /Annot';
8103                          $annots .= ' /Subtype /Widget';
8104                          $annots .= ' /Rect [0 0 0 0]';
8105                          if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
8106                              // read only
8107                              $annots .= ' /F 68';
8108                              $annots .= ' /Ff 49153';
8109                          } else {
8110                              $annots .= ' /F 4'; // default print for PDF/A
8111                              $annots .= ' /Ff 49152';
8112                          }
8113                          $annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8114                          if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8115                              $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
8116                          }
8117                          $annots .= ' /FT /Btn';
8118                          $annots .= ' /Kids [';
8119                          $defval = '';
8120                          foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
8121                              if (isset($data['kid'])) {
8122                                  $annots .= ' '.$data['kid'].' 0 R';
8123                                  if ($data['def'] !== 'Off') {
8124                                      $defval = $data['def'];
8125                                  }
8126                              }
8127                          }
8128                          $annots .= ' ]';
8129                          if (!empty($defval)) {
8130                              $annots .= ' /V /'.$defval;
8131                          }
8132                          $annots .= ' >>';
8133                          $this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8134                          $this->form_obj_id[] = $radio_button_obj_id;
8135                          // store object id to be used on Parent entry of Kids
8136                          $this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
8137                      }
8138                      $formfield = false;
8139                      $pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
8140                      $a = $pl['x'] * $this->k;
8141                      $b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
8142                      $c = $pl['w'] * $this->k;
8143                      $d = $pl['h'] * $this->k;
8144                      $rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
8145                      // create new annotation object
8146                      $annots = '<</Type /Annot';
8147                      $annots .= ' /Subtype /'.$pl['opt']['subtype'];
8148                      $annots .= ' /Rect ['.$rect.']';
8149                      $ft = array('Btn', 'Tx', 'Ch', 'Sig');
8150                      if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8151                          $annots .= ' /FT /'.$pl['opt']['ft'];
8152                          $formfield = true;
8153                      }
8154                      $annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8155                      $annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
8156                      $annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8157                      $annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
8158                      if (isset($pl['opt']['f'])) {
8159                          $fval = 0;
8160                          if (is_array($pl['opt']['f'])) {
8161                              foreach ($pl['opt']['f'] as $f) {
8162                                  switch (strtolower($f)) {
8163                                      case 'invisible': {
8164                                          $fval += 1 << 0;
8165                                          break;
8166                                      }
8167                                      case 'hidden': {
8168                                          $fval += 1 << 1;
8169                                          break;
8170                                      }
8171                                      case 'print': {
8172                                          $fval += 1 << 2;
8173                                          break;
8174                                      }
8175                                      case 'nozoom': {
8176                                          $fval += 1 << 3;
8177                                          break;
8178                                      }
8179                                      case 'norotate': {
8180                                          $fval += 1 << 4;
8181                                          break;
8182                                      }
8183                                      case 'noview': {
8184                                          $fval += 1 << 5;
8185                                          break;
8186                                      }
8187                                      case 'readonly': {
8188                                          $fval += 1 << 6;
8189                                          break;
8190                                      }
8191                                      case 'locked': {
8192                                          $fval += 1 << 8;
8193                                          break;
8194                                      }
8195                                      case 'togglenoview': {
8196                                          $fval += 1 << 9;
8197                                          break;
8198                                      }
8199                                      case 'lockedcontents': {
8200                                          $fval += 1 << 10;
8201                                          break;
8202                                      }
8203                                      default: {
8204                                          break;
8205                                      }
8206                                  }
8207                              }
8208                          } else {
8209                              $fval = intval($pl['opt']['f']);
8210                          }
8211                      } else {
8212                          $fval = 4;
8213                      }
8214                      if ($this->pdfa_mode) {
8215                          // force print flag for PDF/A mode
8216                          $fval |= 4;
8217                      }
8218                      $annots .= ' /F '.intval($fval);
8219                      if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8220                          $annots .= ' /AS /'.$pl['opt']['as'];
8221                      }
8222                      if (isset($pl['opt']['ap'])) {
8223                          // appearance stream
8224                          $annots .= ' /AP <<';
8225                          if (is_array($pl['opt']['ap'])) {
8226                              foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8227                                  // $apmode can be: n = normal; r = rollover; d = down;
8228                                  $annots .= ' /'.strtoupper($apmode);
8229                                  if (is_array($apdef)) {
8230                                      $annots .= ' <<';
8231                                      foreach ($apdef as $apstate => $stream) {
8232                                          // reference to XObject that define the appearance for this mode-state
8233                                          $apsobjid = $this->_putAPXObject($c, $d, $stream);
8234                                          $annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8235                                      }
8236                                      $annots .= ' >>';
8237                                  } else {
8238                                      // reference to XObject that define the appearance for this mode
8239                                      $apsobjid = $this->_putAPXObject($c, $d, $apdef);
8240                                      $annots .= ' '.$apsobjid.' 0 R';
8241                                  }
8242                              }
8243                          } else {
8244                              $annots .= $pl['opt']['ap'];
8245                          }
8246                          $annots .= ' >>';
8247                      }
8248                      if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8249                          $annots .= ' /BS <<';
8250                          $annots .= ' /Type /Border';
8251                          if (isset($pl['opt']['bs']['w'])) {
8252                              $annots .= ' /W '.intval($pl['opt']['bs']['w']);
8253                          }
8254                          $bstyles = array('S', 'D', 'B', 'I', 'U');
8255                          if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8256                              $annots .= ' /S /'.$pl['opt']['bs']['s'];
8257                          }
8258                          if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8259                              $annots .= ' /D [';
8260                              foreach ($pl['opt']['bs']['d'] as $cord) {
8261                                  $annots .= ' '.intval($cord);
8262                              }
8263                              $annots .= ']';
8264                          }
8265                          $annots .= ' >>';
8266                      } else {
8267                          $annots .= ' /Border [';
8268                          if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8269                              $annots .= intval($pl['opt']['border'][0]).' ';
8270                              $annots .= intval($pl['opt']['border'][1]).' ';
8271                              $annots .= intval($pl['opt']['border'][2]);
8272                              if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8273                                  $annots .= ' [';
8274                                  foreach ($pl['opt']['border'][3] as $dash) {
8275                                      $annots .= intval($dash).' ';
8276                                  }
8277                                  $annots .= ']';
8278                              }
8279                          } else {
8280                              $annots .= '0 0 0';
8281                          }
8282                          $annots .= ']';
8283                      }
8284                      if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8285                          $annots .= ' /BE <<';
8286                          $bstyles = array('S', 'C');
8287                          if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
8288                              $annots .= ' /S /'.$pl['opt']['bs']['s'];
8289                          } else {
8290                              $annots .= ' /S /S';
8291                          }
8292                          if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8293                              $annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
8294                          }
8295                          $annots .= '>>';
8296                      }
8297                      if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8298                          $annots .= ' /C '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['c']);
8299                      }
8300                      //$annots .= ' /StructParent ';
8301                      //$annots .= ' /OC ';
8302                      $markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8303                      if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8304                          // this is a markup type
8305                          if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8306                              $annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8307                          }
8308                          //$annots .= ' /Popup ';
8309                          if (isset($pl['opt']['ca'])) {
8310                              $annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
8311                          }
8312                          if (isset($pl['opt']['rc'])) {
8313                              $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8314                          }
8315                          $annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
8316                          //$annots .= ' /IRT ';
8317                          if (isset($pl['opt']['subj'])) {
8318                              $annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8319                          }
8320                          //$annots .= ' /RT ';
8321                          //$annots .= ' /IT ';
8322                          //$annots .= ' /ExData ';
8323                      }
8324                      $lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8325                      // Annotation types
8326                      switch (strtolower($pl['opt']['subtype'])) {
8327                          case 'text': {
8328                              if (isset($pl['opt']['open'])) {
8329                                  $annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
8330                              }
8331                              $iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8332                              if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8333                                  $annots .= ' /Name /'.$pl['opt']['name'];
8334                              } else {
8335                                  $annots .= ' /Name /Note';
8336                              }
8337                              $statemodels = array('Marked', 'Review');
8338                              if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
8339                                  $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8340                              } else {
8341                                  $pl['opt']['statemodel'] = 'Marked';
8342                                  $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8343                              }
8344                              if ($pl['opt']['statemodel'] == 'Marked') {
8345                                  $states = array('Accepted', 'Unmarked');
8346                              } else {
8347                                  $states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8348                              }
8349                              if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
8350                                  $annots .= ' /State /'.$pl['opt']['state'];
8351                              } else {
8352                                  if ($pl['opt']['statemodel'] == 'Marked') {
8353                                      $annots .= ' /State /Unmarked';
8354                                  } else {
8355                                      $annots .= ' /State /None';
8356                                  }
8357                              }
8358                              break;
8359                          }
8360                          case 'link': {
8361                              if (is_string($pl['txt'])) {
8362                                  if ($pl['txt'][0] == '#') {
8363                                      // internal destination
8364                                      $annots .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1));
8365                                  } elseif ($pl['txt'][0] == '%') {
8366                                      // embedded PDF file
8367                                      $filename = basename(substr($pl['txt'], 1));
8368                                      $annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
8369                                  } elseif ($pl['txt'][0] == '*') {
8370                                      // embedded generic file
8371                                      $filename = basename(substr($pl['txt'], 1));
8372                                      $jsa = 'var D=event.target.doc;var MyData=D.dataObjects;for (var i in MyData) if (MyData[i].path=="'.$filename.'") D.exportDataObject( { cName : MyData[i].name, nLaunch : 2});';
8373                                      $annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
8374                                  } else {
8375                                      $parsedUrl = parse_url($pl['txt']);
8376                                      if (empty($parsedUrl['scheme']) AND (strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
8377                                          // relative link to a PDF file
8378                                          $dest = '[0 /Fit]'; // default page 0
8379                                          if (!empty($parsedUrl['fragment'])) {
8380                                              // check for named destination
8381                                              $tmp = explode('=', $parsedUrl['fragment']);
8382                                              $dest = '('.((count($tmp) == 2) ? $tmp[1] : $tmp[0]).')';
8383                                          }
8384                                          $annots .= ' /A <</S /GoToR /D '.$dest.' /F '.$this->_datastring($this->unhtmlentities($parsedUrl['path']), $annot_obj_id).' /NewWindow true>>';
8385                                      } else {
8386                                          // external URI link
8387                                          $annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8388                                      }
8389                                  }
8390                              } elseif (isset($this->links[$pl['txt']])) {
8391                                  // internal link ID
8392                                  $l = $this->links[$pl['txt']];
8393                                  if (isset($this->page_obj_id[($l['p'])])) {
8394                                      $annots .= sprintf(' /Dest [%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($l['p'])], ($this->pagedim[$l['p']]['h'] - ($l['y'] * $this->k)));
8395                                  }
8396                              }
8397                              $hmodes = array('N', 'I', 'O', 'P');
8398                              if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8399                                  $annots .= ' /H /'.$pl['opt']['h'];
8400                              } else {
8401                                  $annots .= ' /H /I';
8402                              }
8403                              //$annots .= ' /PA ';
8404                              //$annots .= ' /Quadpoints ';
8405                              break;
8406                          }
8407                          case 'freetext': {
8408                              if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8409                                  $annots .= ' /DA ('.$pl['opt']['da'].')';
8410                              }
8411                              if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8412                                  $annots .= ' /Q '.intval($pl['opt']['q']);
8413                              }
8414                              if (isset($pl['opt']['rc'])) {
8415                                  $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8416                              }
8417                              if (isset($pl['opt']['ds'])) {
8418                                  $annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8419                              }
8420                              if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8421                                  $annots .= ' /CL [';
8422                                  foreach ($pl['opt']['cl'] as $cl) {
8423                                      $annots .= sprintf('%F ', $cl * $this->k);
8424                                  }
8425                                  $annots .= ']';
8426                              }
8427                              $tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8428                              if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8429                                  $annots .= ' /IT /'.$pl['opt']['it'];
8430                              }
8431                              if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8432                                  $l = $pl['opt']['rd'][0] * $this->k;
8433                                  $r = $pl['opt']['rd'][1] * $this->k;
8434                                  $t = $pl['opt']['rd'][2] * $this->k;
8435                                  $b = $pl['opt']['rd'][3] * $this->k;
8436                                  $annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
8437                              }
8438                              if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8439                                  $annots .= ' /LE /'.$pl['opt']['le'];
8440                              }
8441                              break;
8442                          }
8443                          case 'line': {
8444                              break;
8445                          }
8446                          case 'square': {
8447                              break;
8448                          }
8449                          case 'circle': {
8450                              break;
8451                          }
8452                          case 'polygon': {
8453                              break;
8454                          }
8455                          case 'polyline': {
8456                              break;
8457                          }
8458                          case 'highlight': {
8459                              break;
8460                          }
8461                          case 'underline': {
8462                              break;
8463                          }
8464                          case 'squiggly': {
8465                              break;
8466                          }
8467                          case 'strikeout': {
8468                              break;
8469                          }
8470                          case 'stamp': {
8471                              break;
8472                          }
8473                          case 'caret': {
8474                              break;
8475                          }
8476                          case 'ink': {
8477                              break;
8478                          }
8479                          case 'popup': {
8480                              break;
8481                          }
8482                          case 'fileattachment': {
8483                              if ($this->pdfa_mode) {
8484                                  // embedded files are not allowed in PDF/A mode
8485                                  break;
8486                              }
8487                              if (!isset($pl['opt']['fs'])) {
8488                                  break;
8489                              }
8490                              $filename = basename($pl['opt']['fs']);
8491                              if (isset($this->embeddedfiles[$filename]['f'])) {
8492                                  $annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
8493                                  $iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8494                                  if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8495                                      $annots .= ' /Name /'.$pl['opt']['name'];
8496                                  } else {
8497                                      $annots .= ' /Name /PushPin';
8498                                  }
8499                                  // index (zero-based) of the annotation in the Annots array of this page
8500                                  $this->embeddedfiles[$filename]['a'] = $key;
8501                              }
8502                              break;
8503                          }
8504                          case 'sound': {
8505                              if (!isset($pl['opt']['fs'])) {
8506                                  break;
8507                              }
8508                              $filename = basename($pl['opt']['fs']);
8509                              if (isset($this->embeddedfiles[$filename]['f'])) {
8510                                  // ... TO BE COMPLETED ...
8511                                  // /R /C /B /E /CO /CP
8512                                  $annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
8513                                  $iconsapp = array('Speaker', 'Mic');
8514                                  if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8515                                      $annots .= ' /Name /'.$pl['opt']['name'];
8516                                  } else {
8517                                      $annots .= ' /Name /Speaker';
8518                                  }
8519                              }
8520                              break;
8521                          }
8522                          case 'movie': {
8523                              break;
8524                          }
8525                          case 'widget': {
8526                              $hmode = array('N', 'I', 'O', 'P', 'T');
8527                              if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8528                                  $annots .= ' /H /'.$pl['opt']['h'];
8529                              }
8530                              if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8531                                  $annots .= ' /MK <<';
8532                                  if (isset($pl['opt']['mk']['r'])) {
8533                                      $annots .= ' /R '.$pl['opt']['mk']['r'];
8534                                  }
8535                                  if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8536                                      $annots .= ' /BC '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bc']);
8537                                  }
8538                                  if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8539                                      $annots .= ' /BG '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bg']);
8540                                  }
8541                                  if (isset($pl['opt']['mk']['ca'])) {
8542                                      $annots .= ' /CA '.$pl['opt']['mk']['ca'];
8543                                  }
8544                                  if (isset($pl['opt']['mk']['rc'])) {
8545                                      $annots .= ' /RC '.$pl['opt']['mk']['rc'];
8546                                  }
8547                                  if (isset($pl['opt']['mk']['ac'])) {
8548                                      $annots .= ' /AC '.$pl['opt']['mk']['ac'];
8549                                  }
8550                                  if (isset($pl['opt']['mk']['i'])) {
8551                                      $info = $this->getImageBuffer($pl['opt']['mk']['i']);
8552                                      if ($info !== false) {
8553                                          $annots .= ' /I '.$info['n'].' 0 R';
8554                                      }
8555                                  }
8556                                  if (isset($pl['opt']['mk']['ri'])) {
8557                                      $info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8558                                      if ($info !== false) {
8559                                          $annots .= ' /RI '.$info['n'].' 0 R';
8560                                      }
8561                                  }
8562                                  if (isset($pl['opt']['mk']['ix'])) {
8563                                      $info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8564                                      if ($info !== false) {
8565                                          $annots .= ' /IX '.$info['n'].' 0 R';
8566                                      }
8567                                  }
8568                                  if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8569                                      $annots .= ' /IF <<';
8570                                      $if_sw = array('A', 'B', 'S', 'N');
8571                                      if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8572                                          $annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8573                                      }
8574                                      $if_s = array('A', 'P');
8575                                      if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8576                                          $annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8577                                      }
8578                                      if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8579                                          $annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8580                                      }
8581                                      if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8582                                          $annots .= ' /FB true';
8583                                      }
8584                                      $annots .= '>>';
8585                                  }
8586                                  if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8587                                      $annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8588                                  }
8589                                  $annots .= '>>';
8590                              } // end MK
8591                              // --- Entries for field dictionaries ---
8592                              if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8593                                  // set parent
8594                                  $annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
8595                              }
8596                              if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8597                                  $annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8598                              }
8599                              if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8600                                  $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8601                              }
8602                              if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8603                                  $annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8604                              }
8605                              if (isset($pl['opt']['ff'])) {
8606                                  if (is_array($pl['opt']['ff'])) {
8607                                      // array of bit settings
8608                                      $flag = 0;
8609                                      foreach($pl['opt']['ff'] as $val) {
8610                                          $flag += 1 << ($val - 1);
8611                                      }
8612                                  } else {
8613                                      $flag = intval($pl['opt']['ff']);
8614                                  }
8615                                  $annots .= ' /Ff '.$flag;
8616                              }
8617                              if (isset($pl['opt']['maxlen'])) {
8618                                  $annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
8619                              }
8620                              if (isset($pl['opt']['v'])) {
8621                                  $annots .= ' /V';
8622                                  if (is_array($pl['opt']['v'])) {
8623                                      foreach ($pl['opt']['v'] AS $optval) {
8624                                          if (is_float($optval)) {
8625                                              $optval = sprintf('%F', $optval);
8626                                          }
8627                                          $annots .= ' '.$optval;
8628                                      }
8629                                  } else {
8630                                      $annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8631                                  }
8632                              }
8633                              if (isset($pl['opt']['dv'])) {
8634                                  $annots .= ' /DV';
8635                                  if (is_array($pl['opt']['dv'])) {
8636                                      foreach ($pl['opt']['dv'] AS $optval) {
8637                                          if (is_float($optval)) {
8638                                              $optval = sprintf('%F', $optval);
8639                                          }
8640                                          $annots .= ' '.$optval;
8641                                      }
8642                                  } else {
8643                                      $annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8644                                  }
8645                              }
8646                              if (isset($pl['opt']['rv'])) {
8647                                  $annots .= ' /RV';
8648                                  if (is_array($pl['opt']['rv'])) {
8649                                      foreach ($pl['opt']['rv'] AS $optval) {
8650                                          if (is_float($optval)) {
8651                                              $optval = sprintf('%F', $optval);
8652                                          }
8653                                          $annots .= ' '.$optval;
8654                                      }
8655                                  } else {
8656                                      $annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
8657                                  }
8658                              }
8659                              if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
8660                                  $annots .= ' /A << '.$pl['opt']['a'].' >>';
8661                              }
8662                              if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
8663                                  $annots .= ' /AA << '.$pl['opt']['aa'].' >>';
8664                              }
8665                              if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8666                                  $annots .= ' /DA ('.$pl['opt']['da'].')';
8667                              }
8668                              if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8669                                  $annots .= ' /Q '.intval($pl['opt']['q']);
8670                              }
8671                              if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
8672                                  $annots .= ' /Opt [';
8673                                  foreach($pl['opt']['opt'] AS $copt) {
8674                                      if (is_array($copt)) {
8675                                          $annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
8676                                      } else {
8677                                          $annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8678                                      }
8679                                  }
8680                                  $annots .= ']';
8681                              }
8682                              if (isset($pl['opt']['ti'])) {
8683                                  $annots .= ' /TI '.intval($pl['opt']['ti']);
8684                              }
8685                              if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
8686                                  $annots .= ' /I [';
8687                                  foreach($pl['opt']['i'] AS $copt) {
8688                                      $annots .= intval($copt).' ';
8689                                  }
8690                                  $annots .= ']';
8691                              }
8692                              break;
8693                          }
8694                          case 'screen': {
8695                              break;
8696                          }
8697                          case 'printermark': {
8698                              break;
8699                          }
8700                          case 'trapnet': {
8701                              break;
8702                          }
8703                          case 'watermark': {
8704                              break;
8705                          }
8706                          case '3d': {
8707                              break;
8708                          }
8709                          default: {
8710                              break;
8711                          }
8712                      }
8713                      $annots .= '>>';
8714                      // create new annotation object
8715                      $this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
8716                      if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
8717                          // store reference of form object
8718                          $this->form_obj_id[] = $annot_obj_id;
8719                      }
8720                  }
8721              }
8722          } // end for each page
8723      }
8724  
8725      /**
8726       * Put appearance streams XObject used to define annotation's appearance states.
8727       * @param $w (int) annotation width
8728       * @param $h (int) annotation height
8729       * @param $stream (string) appearance stream
8730       * @return int object ID
8731       * @protected
8732       * @since 4.8.001 (2009-09-09)
8733       */
8734  	protected function _putAPXObject($w=0, $h=0, $stream='') {
8735          $stream = trim($stream);
8736          $out = $this->_getobj()."\n";
8737          $this->xobjects['AX'.$this->n] = array('n' => $this->n);
8738          $out .= '<<';
8739          $out .= ' /Type /XObject';
8740          $out .= ' /Subtype /Form';
8741          $out .= ' /FormType 1';
8742          if ($this->compress) {
8743              $stream = gzcompress($stream);
8744              $out .= ' /Filter /FlateDecode';
8745          }
8746          $rect = sprintf('%F %F', $w, $h);
8747          $out .= ' /BBox [0 0 '.$rect.']';
8748          $out .= ' /Matrix [1 0 0 1 0 0]';
8749          $out .= ' /Resources 2 0 R';
8750          $stream = $this->_getrawstream($stream);
8751          $out .= ' /Length '.strlen($stream);
8752          $out .= ' >>';
8753          $out .= ' stream'."\n".$stream."\n".'endstream';
8754          $out .= "\n".'endobj';
8755          $this->_out($out);
8756          return $this->n;
8757      }
8758  
8759      /**
8760       * Output fonts.
8761       * @author Nicola Asuni
8762       * @protected
8763       */
8764  	protected function _putfonts() {
8765          $nf = $this->n;
8766          foreach ($this->diffs as $diff) {
8767              //Encodings
8768              $this->_newobj();
8769              $this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
8770          }
8771          $mqr = TCPDF_STATIC::get_mqr();
8772          TCPDF_STATIC::set_mqr(false);
8773          foreach ($this->FontFiles as $file => $info) {
8774              // search and get font file to embedd
8775              $fontfile = TCPDF_FONTS::getFontFullPath($file, $info['fontdir']);
8776              if (!TCPDF_STATIC::empty_string($fontfile)) {
8777                  $font = file_get_contents($fontfile);
8778                  $compressed = (substr($file, -2) == '.z');
8779                  if ((!$compressed) AND (isset($info['length2']))) {
8780                      $header = (ord($font[0]) == 128);
8781                      if ($header) {
8782                          // strip first binary header
8783                          $font = substr($font, 6);
8784                      }
8785                      if ($header AND (ord($font[$info['length1']]) == 128)) {
8786                          // strip second binary header
8787                          $font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
8788                      }
8789                  } elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
8790                      if ($compressed) {
8791                          // uncompress font
8792                          $font = gzuncompress($font);
8793                      }
8794                      // merge subset characters
8795                      $subsetchars = array(); // used chars
8796                      foreach ($info['fontkeys'] as $fontkey) {
8797                          $fontinfo = $this->getFontBuffer($fontkey);
8798                          $subsetchars += $fontinfo['subsetchars'];
8799                      }
8800                      // rebuild a font subset
8801                      $font = TCPDF_FONTS::_getTrueTypeFontSubset($font, $subsetchars);
8802                      // calculate new font length
8803                      $info['length1'] = strlen($font);
8804                      if ($compressed) {
8805                          // recompress font
8806                          $font = gzcompress($font);
8807                      }
8808                  }
8809                  $this->_newobj();
8810                  $this->FontFiles[$file]['n'] = $this->n;
8811                  $stream = $this->_getrawstream($font);
8812                  $out = '<< /Length '.strlen($stream);
8813                  if ($compressed) {
8814                      $out .= ' /Filter /FlateDecode';
8815                  }
8816                  $out .= ' /Length1 '.$info['length1'];
8817                  if (isset($info['length2'])) {
8818                      $out .= ' /Length2 '.$info['length2'].' /Length3 0';
8819                  }
8820                  $out .= ' >>';
8821                  $out .= ' stream'."\n".$stream."\n".'endstream';
8822                  $out .= "\n".'endobj';
8823                  $this->_out($out);
8824              }
8825          }
8826          TCPDF_STATIC::set_mqr($mqr);
8827          foreach ($this->fontkeys as $k) {
8828              //Font objects
8829              $font = $this->getFontBuffer($k);
8830              $type = $font['type'];
8831              $name = $font['name'];
8832              if ($type == 'core') {
8833                  // standard core font
8834                  $out = $this->_getobj($this->font_obj_ids[$k])."\n";
8835                  $out .= '<</Type /Font';
8836                  $out .= ' /Subtype /Type1';
8837                  $out .= ' /BaseFont /'.$name;
8838                  $out .= ' /Name /F'.$font['i'];
8839                  if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
8840                      $out .= ' /Encoding /WinAnsiEncoding';
8841                  }
8842                  if ($k == 'helvetica') {
8843                      // add default font for annotations
8844                      $this->annotation_fonts[$k] = $font['i'];
8845                  }
8846                  $out .= ' >>';
8847                  $out .= "\n".'endobj';
8848                  $this->_out($out);
8849              } elseif (($type == 'Type1') OR ($type == 'TrueType')) {
8850                  // additional Type1 or TrueType font
8851                  $out = $this->_getobj($this->font_obj_ids[$k])."\n";
8852                  $out .= '<</Type /Font';
8853                  $out .= ' /Subtype /'.$type;
8854                  $out .= ' /BaseFont /'.$name;
8855                  $out .= ' /Name /F'.$font['i'];
8856                  $out .= ' /FirstChar 32 /LastChar 255';
8857                  $out .= ' /Widths '.($this->n + 1).' 0 R';
8858                  $out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
8859                  if ($font['enc']) {
8860                      if (isset($font['diff'])) {
8861                          $out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
8862                      } else {
8863                          $out .= ' /Encoding /WinAnsiEncoding';
8864                      }
8865                  }
8866                  $out .= ' >>';
8867                  $out .= "\n".'endobj';
8868                  $this->_out($out);
8869                  // Widths
8870                  $this->_newobj();
8871                  $s = '[';
8872                  for ($i = 32; $i < 256; ++$i) {
8873                      if (isset($font['cw'][$i])) {
8874                          $s .= $font['cw'][$i].' ';
8875                      } else {
8876                          $s .= $font['dw'].' ';
8877                      }
8878                  }
8879                  $s .= ']';
8880                  $s .= "\n".'endobj';
8881                  $this->_out($s);
8882                  //Descriptor
8883                  $this->_newobj();
8884                  $s = '<</Type /FontDescriptor /FontName /'.$name;
8885                  foreach ($font['desc'] as $fdk => $fdv) {
8886                      if (is_float($fdv)) {
8887                          $fdv = sprintf('%F', $fdv);
8888                      }
8889                      $s .= ' /'.$fdk.' '.$fdv.'';
8890                  }
8891                  if (!TCPDF_STATIC::empty_string($font['file'])) {
8892                      $s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
8893                  }
8894                  $s .= '>>';
8895                  $s .= "\n".'endobj';
8896                  $this->_out($s);
8897              } else {
8898                  // additional types
8899                  $mtd = '_put'.strtolower($type);
8900                  if (!method_exists($this, $mtd)) {
8901                      $this->Error('Unsupported font type: '.$type);
8902                  }
8903                  $this->$mtd($font);
8904              }
8905          }
8906      }
8907  
8908      /**
8909       * Adds unicode fonts.<br>
8910       * Based on PDF Reference 1.3 (section 5)
8911       * @param $font (array) font data
8912       * @protected
8913       * @author Nicola Asuni
8914       * @since 1.52.0.TC005 (2005-01-05)
8915       */
8916  	protected function _puttruetypeunicode($font) {
8917          $fontname = '';
8918          if ($font['subset']) {
8919              // change name for font subsetting
8920              $subtag = sprintf('%06u', $font['i']);
8921              $subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
8922              $fontname .= $subtag.'+';
8923          }
8924          $fontname .= $font['name'];
8925          // Type0 Font
8926          // A composite font composed of other fonts, organized hierarchically
8927          $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
8928          $out .= '<< /Type /Font';
8929          $out .= ' /Subtype /Type0';
8930          $out .= ' /BaseFont /'.$fontname;
8931          $out .= ' /Name /F'.$font['i'];
8932          $out .= ' /Encoding /'.$font['enc'];
8933          $out .= ' /ToUnicode '.($this->n + 1).' 0 R';
8934          $out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
8935          $out .= ' >>';
8936          $out .= "\n".'endobj';
8937          $this->_out($out);
8938          // ToUnicode map for Identity-H
8939          $stream = TCPDF_FONT_DATA::$uni_identity_h;
8940          // ToUnicode Object
8941          $this->_newobj();
8942          $stream = ($this->compress) ? gzcompress($stream) : $stream;
8943          $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
8944          $stream = $this->_getrawstream($stream);
8945          $this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
8946          // CIDFontType2
8947          // A CIDFont whose glyph descriptions are based on TrueType font technology
8948          $oid = $this->_newobj();
8949          $out = '<< /Type /Font';
8950          $out .= ' /Subtype /CIDFontType2';
8951          $out .= ' /BaseFont /'.$fontname;
8952          // A dictionary containing entries that define the character collection of the CIDFont.
8953          $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
8954          $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
8955          $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
8956          $out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
8957          $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
8958          $out .= ' /DW '.$font['dw']; // default width
8959          $out .= "\n".TCPDF_FONTS::_putfontwidths($font, 0);
8960          if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
8961              $out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
8962          }
8963          $out .= ' >>';
8964          $out .= "\n".'endobj';
8965          $this->_out($out);
8966          // Font descriptor
8967          // A font descriptor describing the CIDFont default metrics other than its glyph widths
8968          $this->_newobj();
8969          $out = '<< /Type /FontDescriptor';
8970          $out .= ' /FontName /'.$fontname;
8971          foreach ($font['desc'] as $key => $value) {
8972              if (is_float($value)) {
8973                  $value = sprintf('%F', $value);
8974              }
8975              $out .= ' /'.$key.' '.$value;
8976          }
8977          $fontdir = false;
8978          if (!TCPDF_STATIC::empty_string($font['file'])) {
8979              // A stream containing a TrueType font
8980              $out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
8981              $fontdir = $this->FontFiles[$font['file']]['fontdir'];
8982          }
8983          $out .= ' >>';
8984          $out .= "\n".'endobj';
8985          $this->_out($out);
8986          if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
8987              $this->_newobj();
8988              // Embed CIDToGIDMap
8989              // A specification of the mapping from CIDs to glyph indices
8990              // search and get CTG font file to embedd
8991              $ctgfile = strtolower($font['ctg']);
8992              // search and get ctg font file to embedd
8993              $fontfile = TCPDF_FONTS::getFontFullPath($ctgfile, $fontdir);
8994              if (TCPDF_STATIC::empty_string($fontfile)) {
8995                  $this->Error('Font file not found: '.$ctgfile);
8996              }
8997              $stream = $this->_getrawstream(file_get_contents($fontfile));
8998              $out = '<< /Length '.strlen($stream).'';
8999              if (substr($fontfile, -2) == '.z') { // check file extension
9000                  // Decompresses data encoded using the public-domain
9001                  // zlib/deflate compression method, reproducing the
9002                  // original text or binary data
9003                  $out .= ' /Filter /FlateDecode';
9004              }
9005              $out .= ' >>';
9006              $out .= ' stream'."\n".$stream."\n".'endstream';
9007              $out .= "\n".'endobj';
9008              $this->_out($out);
9009          }
9010      }
9011  
9012      /**
9013       * Output CID-0 fonts.
9014       * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
9015       * @param $font (array) font data
9016       * @protected
9017       * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
9018       * @since 3.2.000 (2008-06-23)
9019       */
9020  	protected function _putcidfont0($font) {
9021          $cidoffset = 0;
9022          if (!isset($font['cw'][1])) {
9023              $cidoffset = 31;
9024          }
9025          if (isset($font['cidinfo']['uni2cid'])) {
9026              // convert unicode to cid.
9027              $uni2cid = $font['cidinfo']['uni2cid'];
9028              $cw = array();
9029              foreach ($font['cw'] as $uni => $width) {
9030                  if (isset($uni2cid[$uni])) {
9031                      $cw[($uni2cid[$uni] + $cidoffset)] = $width;
9032                  } elseif ($uni < 256) {
9033                      $cw[$uni] = $width;
9034                  } // else unknown character
9035              }
9036              $font = array_merge($font, array('cw' => $cw));
9037          }
9038          $name = $font['name'];
9039          $enc = $font['enc'];
9040          if ($enc) {
9041              $longname = $name.'-'.$enc;
9042          } else {
9043              $longname = $name;
9044          }
9045          $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9046          $out .= '<</Type /Font';
9047          $out .= ' /Subtype /Type0';
9048          $out .= ' /BaseFont /'.$longname;
9049          $out .= ' /Name /F'.$font['i'];
9050          if ($enc) {
9051              $out .= ' /Encoding /'.$enc;
9052          }
9053          $out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
9054          $out .= ' >>';
9055          $out .= "\n".'endobj';
9056          $this->_out($out);
9057          $oid = $this->_newobj();
9058          $out = '<</Type /Font';
9059          $out .= ' /Subtype /CIDFontType0';
9060          $out .= ' /BaseFont /'.$name;
9061          $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9062          $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9063          $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9064          $out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
9065          $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9066          $out .= ' /DW '.$font['dw'];
9067          $out .= "\n".TCPDF_FONTS::_putfontwidths($font, $cidoffset);
9068          $out .= ' >>';
9069          $out .= "\n".'endobj';
9070          $this->_out($out);
9071          $this->_newobj();
9072          $s = '<</Type /FontDescriptor /FontName /'.$name;
9073          foreach ($font['desc'] as $k => $v) {
9074              if ($k != 'Style') {
9075                  if (is_float($v)) {
9076                      $v = sprintf('%F', $v);
9077                  }
9078                  $s .= ' /'.$k.' '.$v.'';
9079              }
9080          }
9081          $s .= '>>';
9082          $s .= "\n".'endobj';
9083          $this->_out($s);
9084      }
9085  
9086      /**
9087       * Output images.
9088       * @protected
9089       */
9090  	protected function _putimages() {
9091          $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9092          foreach ($this->imagekeys as $file) {
9093              $info = $this->getImageBuffer($file);
9094              // set object for alternate images array
9095              if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
9096                  $altoid = $this->_newobj();
9097                  $out = '[';
9098                  foreach ($info['altimgs'] as $altimage) {
9099                      if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
9100                          $out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
9101                          $out .= ' /DefaultForPrinting';
9102                          if ($altimage[1] === true) {
9103                              $out .= ' true';
9104                          } else {
9105                              $out .= ' false';
9106                          }
9107                          $out .= ' >>';
9108                      }
9109                  }
9110                  $out .= ' ]';
9111                  $out .= "\n".'endobj';
9112                  $this->_out($out);
9113              }
9114              // set image object
9115              $oid = $this->_newobj();
9116              $this->xobjects['I'.$info['i']] = array('n' => $oid);
9117              $this->setImageSubBuffer($file, 'n', $this->n);
9118              $out = '<</Type /XObject';
9119              $out .= ' /Subtype /Image';
9120              $out .= ' /Width '.$info['w'];
9121              $out .= ' /Height '.$info['h'];
9122              if (array_key_exists('masked', $info)) {
9123                  $out .= ' /SMask '.($this->n - 1).' 0 R';
9124              }
9125              // set color space
9126              $icc = false;
9127              if (isset($info['icc']) AND ($info['icc'] !== false)) {
9128                  // ICC Colour Space
9129                  $icc = true;
9130                  $out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
9131              } elseif ($info['cs'] == 'Indexed') {
9132                  // Indexed Colour Space
9133                  $out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
9134              } else {
9135                  // Device Colour Space
9136                  $out .= ' /ColorSpace /'.$info['cs'];
9137              }
9138              if ($info['cs'] == 'DeviceCMYK') {
9139                  $out .= ' /Decode [1 0 1 0 1 0 1 0]';
9140              }
9141              $out .= ' /BitsPerComponent '.$info['bpc'];
9142              if (isset($altoid) AND ($altoid > 0)) {
9143                  // reference to alternate images dictionary
9144                  $out .= ' /Alternates '.$altoid.' 0 R';
9145              }
9146              if (isset($info['exurl']) AND !empty($info['exurl'])) {
9147                  // external stream
9148                  $out .= ' /Length 0';
9149                  $out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
9150                  if (isset($info['f'])) {
9151                      $out .= ' /FFilter /'.$info['f'];
9152                  }
9153                  $out .= ' >>';
9154                  $out .= ' stream'."\n".'endstream';
9155              } else {
9156                  if (isset($info['f'])) {
9157                      $out .= ' /Filter /'.$info['f'];
9158                  }
9159                  if (isset($info['parms'])) {
9160                      $out .= ' '.$info['parms'];
9161                  }
9162                  if (isset($info['trns']) AND is_array($info['trns'])) {
9163                      $trns = '';
9164                      $count_info = count($info['trns']);
9165                      if ($info['cs'] == 'Indexed') {
9166                          $maxval =(pow(2, $info['bpc']) - 1);
9167                          for ($i = 0; $i < $count_info; ++$i) {
9168                              if (($info['trns'][$i] != 0) AND ($info['trns'][$i] != $maxval)) {
9169                                  // this is not a binary type mask @TODO: create a SMask
9170                                  $trns = '';
9171                                  break;
9172                              } elseif (empty($trns) AND ($info['trns'][$i] == 0)) {
9173                                  // store the first fully transparent value
9174                                  $trns .= $i.' '.$i.' ';
9175                              }
9176                          }
9177                      } else {
9178                          // grayscale or RGB
9179                          for ($i = 0; $i < $count_info; ++$i) {
9180                              if ($info['trns'][$i] == 0) {
9181                                  $trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
9182                              }
9183                          }
9184                      }
9185                      // Colour Key Masking
9186                      if (!empty($trns)) {
9187                          $out .= ' /Mask ['.$trns.']';
9188                      }
9189                  }
9190                  $stream = $this->_getrawstream($info['data']);
9191                  $out .= ' /Length '.strlen($stream).' >>';
9192                  $out .= ' stream'."\n".$stream."\n".'endstream';
9193              }
9194              $out .= "\n".'endobj';
9195              $this->_out($out);
9196              if ($icc) {
9197                  // ICC colour profile
9198                  $this->_newobj();
9199                  $icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
9200                  $icc = $this->_getrawstream($icc);
9201                  $this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9202              } elseif ($info['cs'] == 'Indexed') {
9203                  // colour palette
9204                  $this->_newobj();
9205                  $pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
9206                  $pal = $this->_getrawstream($pal);
9207                  $this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
9208              }
9209          }
9210      }
9211  
9212      /**
9213       * Output Form XObjects Templates.
9214       * @author Nicola Asuni
9215       * @since 5.8.017 (2010-08-24)
9216       * @protected
9217       * @see startTemplate(), endTemplate(), printTemplate()
9218       */
9219  	protected function _putxobjects() {
9220          foreach ($this->xobjects as $key => $data) {
9221              if (isset($data['outdata'])) {
9222                  $stream = str_replace($this->epsmarker, '', trim($data['outdata']));
9223                  $out = $this->_getobj($data['n'])."\n";
9224                  $out .= '<<';
9225                  $out .= ' /Type /XObject';
9226                  $out .= ' /Subtype /Form';
9227                  $out .= ' /FormType 1';
9228                  if ($this->compress) {
9229                      $stream = gzcompress($stream);
9230                      $out .= ' /Filter /FlateDecode';
9231                  }
9232                  $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));
9233                  $out .= ' /Matrix [1 0 0 1 0 0]';
9234                  $out .= ' /Resources <<';
9235                  $out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9236                  if (!$this->pdfa_mode) {
9237                      // transparency
9238                      if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
9239                          $out .= ' /ExtGState <<';
9240                          foreach ($data['extgstates'] as $k => $extgstate) {
9241                              if (isset($this->extgstates[$k]['name'])) {
9242                                  $out .= ' /'.$this->extgstates[$k]['name'];
9243                              } else {
9244                                  $out .= ' /GS'.$k;
9245                              }
9246                              $out .= ' '.$this->extgstates[$k]['n'].' 0 R';
9247                          }
9248                          $out .= ' >>';
9249                      }
9250                      if (isset($data['gradients']) AND !empty($data['gradients'])) {
9251                          $gp = '';
9252                          $gs = '';
9253                          foreach ($data['gradients'] as $id => $grad) {
9254                              // gradient patterns
9255                              $gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
9256                              // gradient shadings
9257                              $gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
9258                          }
9259                          $out .= ' /Pattern <<'.$gp.' >>';
9260                          $out .= ' /Shading <<'.$gs.' >>';
9261                      }
9262                  }
9263                  // spot colors
9264                  if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
9265                      $out .= ' /ColorSpace <<';
9266                      foreach ($data['spot_colors'] as $name => $color) {
9267                          $out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
9268                      }
9269                      $out .= ' >>';
9270                  }
9271                  // fonts
9272                  if (!empty($data['fonts'])) {
9273                      $out .= ' /Font <<';
9274                      foreach ($data['fonts'] as $fontkey => $fontid) {
9275                          $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9276                      }
9277                      $out .= ' >>';
9278                  }
9279                  // images or nested xobjects
9280                  if (!empty($data['images']) OR !empty($data['xobjects'])) {
9281                      $out .= ' /XObject <<';
9282                      foreach ($data['images'] as $imgid) {
9283                          $out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
9284                      }
9285                      foreach ($data['xobjects'] as $sub_id => $sub_objid) {
9286                          $out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
9287                      }
9288                      $out .= ' >>';
9289                  }
9290                  $out .= ' >>'; //end resources
9291                  if (isset($data['group']) AND ($data['group'] !== false)) {
9292                      // set transparency group
9293                      $out .= ' /Group << /Type /Group /S /Transparency';
9294                      if (is_array($data['group'])) {
9295                          if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
9296                              $out .= ' /CS /'.$data['group']['CS'];
9297                          }
9298                          if (isset($data['group']['I'])) {
9299                              $out .= ' /I /'.($data['group']['I']===true?'true':'false');
9300                          }
9301                          if (isset($data['group']['K'])) {
9302                              $out .= ' /K /'.($data['group']['K']===true?'true':'false');
9303                          }
9304                      }
9305                      $out .= ' >>';
9306                  }
9307                  $stream = $this->_getrawstream($stream, $data['n']);
9308                  $out .= ' /Length '.strlen($stream);
9309                  $out .= ' >>';
9310                  $out .= ' stream'."\n".$stream."\n".'endstream';
9311                  $out .= "\n".'endobj';
9312                  $this->_out($out);
9313              }
9314          }
9315      }
9316  
9317      /**
9318       * Output Spot Colors Resources.
9319       * @protected
9320       * @since 4.0.024 (2008-09-12)
9321       */
9322  	protected function _putspotcolors() {
9323          foreach ($this->spot_colors as $name => $color) {
9324              $this->_newobj();
9325              $this->spot_colors[$name]['n'] = $this->n;
9326              $out = '[/Separation /'.str_replace(' ', '#20', $name);
9327              $out .= ' /DeviceCMYK <<';
9328              $out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
9329              $out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
9330              $out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
9331              $out .= "\n".'endobj';
9332              $this->_out($out);
9333          }
9334      }
9335  
9336      /**
9337       * Return XObjects Dictionary.
9338       * @return string XObjects dictionary
9339       * @protected
9340       * @since 5.8.014 (2010-08-23)
9341       */
9342  	protected function _getxobjectdict() {
9343          $out = '';
9344          foreach ($this->xobjects as $id => $objid) {
9345              $out .= ' /'.$id.' '.$objid['n'].' 0 R';
9346          }
9347          return $out;
9348      }
9349  
9350      /**
9351       * Output Resources Dictionary.
9352       * @protected
9353       */
9354  	protected function _putresourcedict() {
9355          $out = $this->_getobj(2)."\n";
9356          $out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9357          $out .= ' /Font <<';
9358          foreach ($this->fontkeys as $fontkey) {
9359              $font = $this->getFontBuffer($fontkey);
9360              $out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
9361          }
9362          $out .= ' >>';
9363          $out .= ' /XObject <<';
9364          $out .= $this->_getxobjectdict();
9365          $out .= ' >>';
9366          // layers
9367          if (!empty($this->pdflayers)) {
9368              $out .= ' /Properties <<';
9369              foreach ($this->pdflayers as $layer) {
9370                  $out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
9371              }
9372              $out .= ' >>';
9373          }
9374          if (!$this->pdfa_mode) {
9375              // transparency
9376              if (isset($this->extgstates) AND !empty($this->extgstates)) {
9377                  $out .= ' /ExtGState <<';
9378                  foreach ($this->extgstates as $k => $extgstate) {
9379                      if (isset($extgstate['name'])) {
9380                          $out .= ' /'.$extgstate['name'];
9381                      } else {
9382                          $out .= ' /GS'.$k;
9383                      }
9384                      $out .= ' '.$extgstate['n'].' 0 R';
9385                  }
9386                  $out .= ' >>';
9387              }
9388              if (isset($this->gradients) AND !empty($this->gradients)) {
9389                  $gp = '';
9390                  $gs = '';
9391                  foreach ($this->gradients as $id => $grad) {
9392                      // gradient patterns
9393                      $gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
9394                      // gradient shadings
9395                      $gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
9396                  }
9397                  $out .= ' /Pattern <<'.$gp.' >>';
9398                  $out .= ' /Shading <<'.$gs.' >>';
9399              }
9400          }
9401          // spot colors
9402          if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
9403              $out .= ' /ColorSpace <<';
9404              foreach ($this->spot_colors as $color) {
9405                  $out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
9406              }
9407              $out .= ' >>';
9408          }
9409          $out .= ' >>';
9410          $out .= "\n".'endobj';
9411          $this->_out($out);
9412      }
9413  
9414      /**
9415       * Output Resources.
9416       * @protected
9417       */
9418  	protected function _putresources() {
9419          $this->_putextgstates();
9420          $this->_putocg();
9421          $this->_putfonts();
9422          $this->_putimages();
9423          $this->_putspotcolors();
9424          $this->_putshaders();
9425          $this->_putxobjects();
9426          $this->_putresourcedict();
9427          $this->_putdests();
9428          $this->_putEmbeddedFiles();
9429          $this->_putannotsobjs();
9430          $this->_putjavascript();
9431          $this->_putbookmarks();
9432          $this->_putencryption();
9433      }
9434  
9435      /**
9436       * Adds some Metadata information (Document Information Dictionary)
9437       * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
9438       * @return int object id
9439       * @protected
9440       */
9441  	protected function _putinfo() {
9442          $oid = $this->_newobj();
9443          $out = '<<';
9444          // store current isunicode value
9445          $prev_isunicode = $this->isunicode;
9446          if ($this->docinfounicode) {
9447              $this->isunicode = true;
9448          }
9449          if (!TCPDF_STATIC::empty_string($this->title)) {
9450              // The document's title.
9451              $out .= ' /Title '.$this->_textstring($this->title, $oid);
9452          }
9453          if (!TCPDF_STATIC::empty_string($this->author)) {
9454              // The name of the person who created the document.
9455              $out .= ' /Author '.$this->_textstring($this->author, $oid);
9456          }
9457          if (!TCPDF_STATIC::empty_string($this->subject)) {
9458              // The subject of the document.
9459              $out .= ' /Subject '.$this->_textstring($this->subject, $oid);
9460          }
9461          if (!TCPDF_STATIC::empty_string($this->keywords)) {
9462              // Keywords associated with the document.
9463              $out .= ' /Keywords '.$this->_textstring($this->keywords, $oid);
9464          }
9465          if (!TCPDF_STATIC::empty_string($this->creator)) {
9466              // 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.
9467              $out .= ' /Creator '.$this->_textstring($this->creator, $oid);
9468          }
9469          // restore previous isunicode value
9470          $this->isunicode = $prev_isunicode;
9471          // default producer
9472          $out .= ' /Producer '.$this->_textstring(TCPDF_STATIC::getTCPDFProducer(), $oid);
9473          // The date and time the document was created, in human-readable form
9474          $out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
9475          // The date and time the document was most recently modified, in human-readable form
9476          $out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
9477          // A name object indicating whether the document has been modified to include trapping information
9478          $out .= ' /Trapped /False';
9479          $out .= ' >>';
9480          $out .= "\n".'endobj';
9481          $this->_out($out);
9482          return $oid;
9483      }
9484  
9485      /**
9486       * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
9487       * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9488       * @param $xmp (string) Custom XMP data.
9489       * @since 5.9.128 (2011-10-06)
9490       * @public
9491       */
9492  	public function setExtraXMP($xmp) {
9493          $this->custom_xmp = $xmp;
9494      }
9495  
9496      /**
9497       * Put XMP data object and return ID.
9498       * @return (int) The object ID.
9499       * @since 5.9.121 (2011-09-28)
9500       * @protected
9501       */
9502  	protected function _putXMP() {
9503          $oid = $this->_newobj();
9504          // store current isunicode value
9505          $prev_isunicode = $this->isunicode;
9506          $this->isunicode = true;
9507          $prev_encrypted = $this->encrypted;
9508          $this->encrypted = false;
9509          // set XMP data
9510          $xmp = '<?xpacket begin="'.TCPDF_FONTS::unichr(0xfeff, $this->isunicode).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
9511          $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";
9512          $xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
9513          $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
9514          $xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
9515          $xmp .= "\t\t\t".'<dc:title>'."\n";
9516          $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9517          $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->title).'</rdf:li>'."\n";
9518          $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9519          $xmp .= "\t\t\t".'</dc:title>'."\n";
9520          $xmp .= "\t\t\t".'<dc:creator>'."\n";
9521          $xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
9522          $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->author).'</rdf:li>'."\n";
9523          $xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
9524          $xmp .= "\t\t\t".'</dc:creator>'."\n";
9525          $xmp .= "\t\t\t".'<dc:description>'."\n";
9526          $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9527          $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->subject).'</rdf:li>'."\n";
9528          $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9529          $xmp .= "\t\t\t".'</dc:description>'."\n";
9530          $xmp .= "\t\t\t".'<dc:subject>'."\n";
9531          $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9532          $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->keywords).'</rdf:li>'."\n";
9533          $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9534          $xmp .= "\t\t\t".'</dc:subject>'."\n";
9535          $xmp .= "\t\t".'</rdf:Description>'."\n";
9536          // convert doc creation date format
9537          $dcdate = TCPDF_STATIC::getFormattedDate($this->doc_creation_timestamp);
9538          $doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
9539          $doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
9540          $doccreationdate .= substr($dcdate, 14, 3).':'.substr($dcdate, 18, 2);
9541          $doccreationdate = TCPDF_STATIC::_escapeXML($doccreationdate);
9542          // convert doc modification date format
9543          $dmdate = TCPDF_STATIC::getFormattedDate($this->doc_modification_timestamp);
9544          $docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
9545          $docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
9546          $docmoddate .= substr($dmdate, 14, 3).':'.substr($dmdate, 18, 2);
9547          $docmoddate = TCPDF_STATIC::_escapeXML($docmoddate);
9548          $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
9549          $xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
9550          $xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
9551          $xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
9552          $xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
9553          $xmp .= "\t\t".'</rdf:Description>'."\n";
9554          $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
9555          $xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC::_escapeXML($this->keywords).'</pdf:Keywords>'."\n";
9556          $xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC::_escapeXML(TCPDF_STATIC::getTCPDFProducer()).'</pdf:Producer>'."\n";
9557          $xmp .= "\t\t".'</rdf:Description>'."\n";
9558          $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
9559          $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);
9560          $xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
9561          $xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
9562          $xmp .= "\t\t".'</rdf:Description>'."\n";
9563          if ($this->pdfa_mode) {
9564              $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
9565              $xmp .= "\t\t\t".'<pdfaid:part>1</pdfaid:part>'."\n";
9566              $xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
9567              $xmp .= "\t\t".'</rdf:Description>'."\n";
9568          }
9569          // XMP extension schemas
9570          $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";
9571          $xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
9572          $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9573          $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9574          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
9575          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
9576          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
9577          $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9578          $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9579          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
9580          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
9581          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
9582          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9583          $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9584          $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9585          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9586          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
9587          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9588          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9589          $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9590          $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9591          $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9592          $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9593          $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9594          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
9595          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
9596          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
9597          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9598          $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9599          $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9600          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9601          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
9602          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
9603          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
9604          $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9605          $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9606          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9607          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
9608          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
9609          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9610          $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9611          $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9612          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9613          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
9614          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
9615          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9616          $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9617          $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9618          $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9619          $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9620          $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9621          $xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
9622          $xmp .= "\t\t".'</rdf:Description>'."\n";
9623          $xmp .= "\t".'</rdf:RDF>'."\n";
9624          $xmp .= $this->custom_xmp;
9625          $xmp .= '</x:xmpmeta>'."\n";
9626          $xmp .= '<?xpacket end="w"?>';
9627          $out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
9628          // restore previous isunicode value
9629          $this->isunicode = $prev_isunicode;
9630          $this->encrypted = $prev_encrypted;
9631          $this->_out($out);
9632          return $oid;
9633      }
9634  
9635      /**
9636       * Output Catalog.
9637       * @return int object id
9638       * @protected
9639       */
9640  	protected function _putcatalog() {
9641          // put XMP
9642          $xmpobj = $this->_putXMP();
9643          // if required, add standard sRGB_IEC61966-2.1 blackscaled ICC colour profile
9644          if ($this->pdfa_mode OR $this->force_srgb) {
9645              $iccobj = $this->_newobj();
9646              $icc = file_get_contents(dirname(__FILE__).'/include/sRGB.icc');
9647              $filter = '';
9648              if ($this->compress) {
9649                  $filter = ' /Filter /FlateDecode';
9650                  $icc = gzcompress($icc);
9651              }
9652              $icc = $this->_getrawstream($icc);
9653              $this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9654          }
9655          // start catalog
9656          $oid = $this->_newobj();
9657          $out = '<< /Type /Catalog';
9658          $out .= ' /Version /'.$this->PDFVersion;
9659          //$out .= ' /Extensions <<>>';
9660          $out .= ' /Pages 1 0 R';
9661          //$out .= ' /PageLabels ' //...;
9662          $out .= ' /Names <<';
9663          if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
9664              $out .= ' /JavaScript '.$this->n_js;
9665          }
9666          if (!empty($this->efnames)) {
9667              $out .= ' /EmbeddedFiles <</Names [';
9668              foreach ($this->efnames AS $fn => $fref) {
9669                  $out .= ' '.$this->_datastring($fn).' '.$fref;
9670              }
9671              $out .= ' ]>>';
9672          }
9673          $out .= ' >>';
9674          if (!empty($this->dests)) {
9675              $out .= ' /Dests '.($this->n_dests).' 0 R';
9676          }
9677          $out .= $this->_putviewerpreferences();
9678          if (isset($this->LayoutMode) AND (!TCPDF_STATIC::empty_string($this->LayoutMode))) {
9679              $out .= ' /PageLayout /'.$this->LayoutMode;
9680          }
9681          if (isset($this->PageMode) AND (!TCPDF_STATIC::empty_string($this->PageMode))) {
9682              $out .= ' /PageMode /'.$this->PageMode;
9683          }
9684          if (count($this->outlines) > 0) {
9685              $out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
9686              $out .= ' /PageMode /UseOutlines';
9687          }
9688          //$out .= ' /Threads []';
9689          if ($this->ZoomMode == 'fullpage') {
9690              $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
9691          } elseif ($this->ZoomMode == 'fullwidth') {
9692              $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
9693          } elseif ($this->ZoomMode == 'real') {
9694              $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
9695          } elseif (!is_string($this->ZoomMode)) {
9696              $out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
9697          }
9698          //$out .= ' /AA <<>>';
9699          //$out .= ' /URI <<>>';
9700          $out .= ' /Metadata '.$xmpobj.' 0 R';
9701          //$out .= ' /StructTreeRoot <<>>';
9702          //$out .= ' /MarkInfo <<>>';
9703          if (isset($this->l['a_meta_language'])) {
9704              $out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
9705          }
9706          //$out .= ' /SpiderInfo <<>>';
9707          // set OutputIntent to sRGB IEC61966-2.1 if required
9708          if ($this->pdfa_mode OR $this->force_srgb) {
9709              $out .= ' /OutputIntents [<<';
9710              $out .= ' /Type /OutputIntent';
9711              $out .= ' /S /GTS_PDFA1';
9712              $out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9713              $out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9714              $out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
9715              $out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9716              $out .= ' /DestOutputProfile '.$iccobj.' 0 R';
9717              $out .= ' >>]';
9718          }
9719          //$out .= ' /PieceInfo <<>>';
9720          if (!empty($this->pdflayers)) {
9721              $lyrobjs = '';
9722              $lyrobjs_off = '';
9723              $lyrobjs_lock = '';
9724              foreach ($this->pdflayers as $layer) {
9725                  $layer_obj_ref = ' '.$layer['objid'].' 0 R';
9726                  $lyrobjs .= $layer_obj_ref;
9727                  if ($layer['view'] === false) {
9728                      $lyrobjs_off .= $layer_obj_ref;
9729                  }
9730                  if ($layer['lock']) {
9731                      $lyrobjs_lock .= $layer_obj_ref;
9732                  }
9733              }
9734              $out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
9735              $out .= ' /D <<';
9736              $out .= ' /Name '.$this->_textstring('Layers', $oid);
9737              $out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
9738              $out .= ' /BaseState /ON';
9739              $out .= ' /OFF ['.$lyrobjs_off.']';
9740              $out .= ' /Locked ['.$lyrobjs_lock.']';
9741              $out .= ' /Intent /View';
9742              $out .= ' /AS [';
9743              $out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
9744              $out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
9745              $out .= ' ]';
9746              $out .= ' /Order ['.$lyrobjs.']';
9747              $out .= ' /ListMode /AllPages';
9748              //$out .= ' /RBGroups ['..']';
9749              //$out .= ' /Locked ['..']';
9750              $out .= ' >>';
9751              $out .= ' >>';
9752          }
9753          // AcroForm
9754          if (!empty($this->form_obj_id)
9755              OR ($this->sign AND isset($this->signature_data['cert_type']))
9756              OR !empty($this->empty_signature_appearance)) {
9757              $out .= ' /AcroForm <<';
9758              $objrefs = '';
9759              if ($this->sign AND isset($this->signature_data['cert_type'])) {
9760                  // set reference for signature object
9761                  $objrefs .= $this->sig_obj_id.' 0 R';
9762              }
9763              if (!empty($this->empty_signature_appearance)) {
9764                  foreach ($this->empty_signature_appearance as $esa) {
9765                      // set reference for empty signature objects
9766                      $objrefs .= ' '.$esa['objid'].' 0 R';
9767                  }
9768              }
9769              if (!empty($this->form_obj_id)) {
9770                  foreach($this->form_obj_id as $objid) {
9771                      $objrefs .= ' '.$objid.' 0 R';
9772                  }
9773              }
9774              $out .= ' /Fields ['.$objrefs.']';
9775              // It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
9776              if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
9777                  $out .= ' /NeedAppearances false';
9778              }
9779              if ($this->sign AND isset($this->signature_data['cert_type'])) {
9780                  if ($this->signature_data['cert_type'] > 0) {
9781                      $out .= ' /SigFlags 3';
9782                  } else {
9783                      $out .= ' /SigFlags 1';
9784                  }
9785              }
9786              //$out .= ' /CO ';
9787              if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
9788                  $out .= ' /DR <<';
9789                  $out .= ' /Font <<';
9790                  foreach ($this->annotation_fonts as $fontkey => $fontid) {
9791                      $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9792                  }
9793                  $out .= ' >> >>';
9794              }
9795              $font = $this->getFontBuffer('helvetica');
9796              $out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
9797              $out .= ' /Q '.(($this->rtl)?'2':'0');
9798              //$out .= ' /XFA ';
9799              $out .= ' >>';
9800              // signatures
9801              if ($this->sign AND isset($this->signature_data['cert_type']) 
9802                  AND (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A'))) {
9803                  if ($this->signature_data['cert_type'] > 0) {
9804                      $out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
9805                  } else {
9806                      $out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
9807                  }
9808              }
9809          }
9810          //$out .= ' /Legal <<>>';
9811          //$out .= ' /Requirements []';
9812          //$out .= ' /Collection <<>>';
9813          //$out .= ' /NeedsRendering true';
9814          $out .= ' >>';
9815          $out .= "\n".'endobj';
9816          $this->_out($out);
9817          return $oid;
9818      }
9819  
9820      /**
9821       * Output viewer preferences.
9822       * @return string for viewer preferences
9823       * @author Nicola asuni
9824       * @since 3.1.000 (2008-06-09)
9825       * @protected
9826       */
9827  	protected function _putviewerpreferences() {
9828          $vp = $this->viewer_preferences;
9829          $out = ' /ViewerPreferences <<';
9830          if ($this->rtl) {
9831              $out .= ' /Direction /R2L';
9832          } else {
9833              $out .= ' /Direction /L2R';
9834          }
9835          if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
9836              $out .= ' /HideToolbar true';
9837          }
9838          if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
9839              $out .= ' /HideMenubar true';
9840          }
9841          if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
9842              $out .= ' /HideWindowUI true';
9843          }
9844          if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
9845              $out .= ' /FitWindow true';
9846          }
9847          if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
9848              $out .= ' /CenterWindow true';
9849          }
9850          if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
9851              $out .= ' /DisplayDocTitle true';
9852          }
9853          if (isset($vp['NonFullScreenPageMode'])) {
9854              $out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
9855          }
9856          if (isset($vp['ViewArea'])) {
9857              $out .= ' /ViewArea /'.$vp['ViewArea'];
9858          }
9859          if (isset($vp['ViewClip'])) {
9860              $out .= ' /ViewClip /'.$vp['ViewClip'];
9861          }
9862          if (isset($vp['PrintArea'])) {
9863              $out .= ' /PrintArea /'.$vp['PrintArea'];
9864          }
9865          if (isset($vp['PrintClip'])) {
9866              $out .= ' /PrintClip /'.$vp['PrintClip'];
9867          }
9868          if (isset($vp['PrintScaling'])) {
9869              $out .= ' /PrintScaling /'.$vp['PrintScaling'];
9870          }
9871          if (isset($vp['Duplex']) AND (!TCPDF_STATIC::empty_string($vp['Duplex']))) {
9872              $out .= ' /Duplex /'.$vp['Duplex'];
9873          }
9874          if (isset($vp['PickTrayByPDFSize'])) {
9875              if ($vp['PickTrayByPDFSize']) {
9876                  $out .= ' /PickTrayByPDFSize true';
9877              } else {
9878                  $out .= ' /PickTrayByPDFSize false';
9879              }
9880          }
9881          if (isset($vp['PrintPageRange'])) {
9882              $PrintPageRangeNum = '';
9883              foreach ($vp['PrintPageRange'] as $k => $v) {
9884                  $PrintPageRangeNum .= ' '.($v - 1).'';
9885              }
9886              $out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
9887          }
9888          if (isset($vp['NumCopies'])) {
9889              $out .= ' /NumCopies '.intval($vp['NumCopies']);
9890          }
9891          $out .= ' >>';
9892          return $out;
9893      }
9894  
9895      /**
9896       * Output PDF File Header (7.5.2).
9897       * @protected
9898       */
9899  	protected function _putheader() {
9900          $this->_out('%PDF-'.$this->PDFVersion);
9901          $this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
9902      }
9903  
9904      /**
9905       * Output end of document (EOF).
9906       * @protected
9907       */
9908  	protected function _enddoc() {
9909          if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
9910              // save subset chars of the previous font
9911              $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
9912          }
9913          $this->state = 1;
9914          $this->_putheader();
9915          $this->_putpages();
9916          $this->_putresources();
9917          // empty signature fields
9918          if (!empty($this->empty_signature_appearance)) {
9919              foreach ($this->empty_signature_appearance as $key => $esa) {
9920                  // widget annotation for empty signature
9921                  $out = $this->_getobj($esa['objid'])."\n";
9922                  $out .= '<< /Type /Annot';
9923                  $out .= ' /Subtype /Widget';
9924                  $out .= ' /Rect ['.$esa['rect'].']';
9925                  $out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
9926                  $out .= ' /F 4';
9927                  $out .= ' /FT /Sig';
9928                  $signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
9929                  $out .= ' /T '.$this->_textstring($signame, $esa['objid']);
9930                  $out .= ' /Ff 0';
9931                  $out .= ' >>';
9932                  $out .= "\n".'endobj';
9933                  $this->_out($out);
9934              }
9935          }
9936          // Signature
9937          if ($this->sign AND isset($this->signature_data['cert_type'])) {
9938              // widget annotation for signature
9939              $out = $this->_getobj($this->sig_obj_id)."\n";
9940              $out .= '<< /Type /Annot';
9941              $out .= ' /Subtype /Widget';
9942              $out .= ' /Rect ['.$this->signature_appearance['rect'].']';
9943              $out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
9944              $out .= ' /F 4';
9945              $out .= ' /FT /Sig';
9946              $out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
9947              $out .= ' /Ff 0';
9948              $out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
9949              $out .= ' >>';
9950              $out .= "\n".'endobj';
9951              $this->_out($out);
9952              // signature
9953              $this->_putsignature();
9954          }
9955          // Info
9956          $objid_info = $this->_putinfo();
9957          // Catalog
9958          $objid_catalog = $this->_putcatalog();
9959          // Cross-ref
9960          $o = $this->bufferlen;
9961          // XREF section
9962          $this->_out('xref');
9963          $this->_out('0 '.($this->n + 1));
9964          $this->_out('0000000000 65535 f ');
9965          $freegen = ($this->n + 2);
9966          for ($i=1; $i <= $this->n; ++$i) {
9967              if (!isset($this->offsets[$i]) AND ($i > 1)) {
9968                  $this->_out(sprintf('0000000000 %05d f ', $freegen));
9969                  ++$freegen;
9970              } else {
9971                  $this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
9972              }
9973          }
9974          // TRAILER
9975          $out = 'trailer'."\n";
9976          $out .= '<<';
9977          $out .= ' /Size '.($this->n + 1);
9978          $out .= ' /Root '.$objid_catalog.' 0 R';
9979          $out .= ' /Info '.$objid_info.' 0 R';
9980          if ($this->encrypted) {
9981              $out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
9982          }
9983          $out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
9984          $out .= ' >>';
9985          $this->_out($out);
9986          $this->_out('startxref');
9987          $this->_out($o);
9988          $this->_out('%%EOF');
9989          $this->state = 3; // end-of-doc
9990      }
9991  
9992      /**
9993       * Initialize a new page.
9994       * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
9995       * @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().
9996       * @protected
9997       * @see getPageSizeFromFormat(), setPageFormat()
9998       */
9999  	protected function _beginpage($orientation='', $format='') {
10000          ++$this->page;
10001          $this->pageobjects[$this->page] = array();
10002          $this->setPageBuffer($this->page, '');
10003          // initialize array for graphics tranformation positions inside a page buffer
10004          $this->transfmrk[$this->page] = array();
10005          $this->state = 2;
10006          if (TCPDF_STATIC::empty_string($orientation)) {
10007              if (isset($this->CurOrientation)) {
10008                  $orientation = $this->CurOrientation;
10009              } elseif ($this->fwPt > $this->fhPt) {
10010                  // landscape
10011                  $orientation = 'L';
10012              } else {
10013                  // portrait
10014                  $orientation = 'P';
10015              }
10016          }
10017          if (TCPDF_STATIC::empty_string($format)) {
10018              $this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10019              $this->setPageOrientation($orientation);
10020          } else {
10021              $this->setPageFormat($format, $orientation);
10022          }
10023          if ($this->rtl) {
10024              $this->x = $this->w - $this->rMargin;
10025          } else {
10026              $this->x = $this->lMargin;
10027          }
10028          $this->y = $this->tMargin;
10029          if (isset($this->newpagegroup[$this->page])) {
10030              // start a new group
10031              $this->currpagegroup = $this->newpagegroup[$this->page];
10032              $this->pagegroups[$this->currpagegroup] = 1;
10033          } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
10034              ++$this->pagegroups[$this->currpagegroup];
10035          }
10036      }
10037  
10038      /**
10039       * Mark end of page.
10040       * @protected
10041       */
10042  	protected function _endpage() {
10043          $this->setVisibility('all');
10044          $this->state = 1;
10045      }
10046  
10047      /**
10048       * Begin a new object and return the object number.
10049       * @return int object number
10050       * @protected
10051       */
10052  	protected function _newobj() {
10053          $this->_out($this->_getobj());
10054          return $this->n;
10055      }
10056  
10057      /**
10058       * Return the starting object string for the selected object ID.
10059       * @param $objid (int) Object ID (leave empty to get a new ID).
10060       * @return string the starting object string
10061       * @protected
10062       * @since 5.8.009 (2010-08-20)
10063       */
10064  	protected function _getobj($objid='') {
10065          if ($objid === '') {
10066              ++$this->n;
10067              $objid = $this->n;
10068          }
10069          $this->offsets[$objid] = $this->bufferlen;
10070          $this->pageobjects[$this->page][] = $objid;
10071          return $objid.' 0 obj';
10072      }
10073  
10074      /**
10075       * Underline text.
10076       * @param $x (int) X coordinate
10077       * @param $y (int) Y coordinate
10078       * @param $txt (string) text to underline
10079       * @protected
10080       */
10081  	protected function _dounderline($x, $y, $txt) {
10082          $w = $this->GetStringWidth($txt);
10083          return $this->_dounderlinew($x, $y, $w);
10084      }
10085  
10086      /**
10087       * Underline for rectangular text area.
10088       * @param $x (int) X coordinate
10089       * @param $y (int) Y coordinate
10090       * @param $w (int) width to underline
10091       * @protected
10092       * @since 4.8.008 (2009-09-29)
10093       */
10094  	protected function _dounderlinew($x, $y, $w) {
10095          $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10096          return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
10097      }
10098  
10099      /**
10100       * Line through text.
10101       * @param $x (int) X coordinate
10102       * @param $y (int) Y coordinate
10103       * @param $txt (string) text to linethrough
10104       * @protected
10105       */
10106  	protected function _dolinethrough($x, $y, $txt) {
10107          $w = $this->GetStringWidth($txt);
10108          return $this->_dolinethroughw($x, $y, $w);
10109      }
10110  
10111      /**
10112       * Line through for rectangular text area.
10113       * @param $x (int) X coordinate
10114       * @param $y (int) Y coordinate
10115       * @param $w (int) line length (width)
10116       * @protected
10117       * @since 4.9.008 (2009-09-29)
10118       */
10119  	protected function _dolinethroughw($x, $y, $w) {
10120          $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10121          return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
10122      }
10123  
10124      /**
10125       * Overline text.
10126       * @param $x (int) X coordinate
10127       * @param $y (int) Y coordinate
10128       * @param $txt (string) text to overline
10129       * @protected
10130       * @since 4.9.015 (2010-04-19)
10131       */
10132  	protected function _dooverline($x, $y, $txt) {
10133          $w = $this->GetStringWidth($txt);
10134          return $this->_dooverlinew($x, $y, $w);
10135      }
10136  
10137      /**
10138       * Overline for rectangular text area.
10139       * @param $x (int) X coordinate
10140       * @param $y (int) Y coordinate
10141       * @param $w (int) width to overline
10142       * @protected
10143       * @since 4.9.015 (2010-04-19)
10144       */
10145  	protected function _dooverlinew($x, $y, $w) {
10146          $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10147          return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
10148  
10149      }
10150  
10151      /**
10152       * Format a data string for meta information
10153       * @param $s (string) data string to escape.
10154       * @param $n (int) object ID
10155       * @return string escaped string.
10156       * @protected
10157       */
10158  	protected function _datastring($s, $n=0) {
10159          if ($n == 0) {
10160              $n = $this->n;
10161          }
10162          $s = $this->_encrypt_data($n, $s);
10163          return '('. TCPDF_STATIC::_escape($s).')';
10164      }
10165  
10166      /**
10167       * Set the document creation timestamp
10168       * @param $time (mixed) Document creation timestamp in seconds or date-time string.
10169       * @public
10170       * @since 5.9.152 (2012-03-23)
10171       */
10172  	public function setDocCreationTimestamp($time) {
10173          if (is_string($time)) {
10174              $time = TCPDF_STATIC::getTimestamp($time);
10175          }
10176          $this->doc_creation_timestamp = intval($time);
10177      }
10178  
10179      /**
10180       * Set the document modification timestamp
10181       * @param $time (mixed) Document modification timestamp in seconds or date-time string.
10182       * @public
10183       * @since 5.9.152 (2012-03-23)
10184       */
10185  	public function setDocModificationTimestamp($time) {
10186          if (is_string($time)) {
10187              $time = TCPDF_STATIC::getTimestamp($time);
10188          }
10189          $this->doc_modification_timestamp = intval($time);
10190      }
10191  
10192      /**
10193       * Returns document creation timestamp in seconds.
10194       * @return (int) Creation timestamp in seconds.
10195       * @public
10196       * @since 5.9.152 (2012-03-23)
10197       */
10198  	public function getDocCreationTimestamp() {
10199          return $this->doc_creation_timestamp;
10200      }
10201  
10202      /**
10203       * Returns document modification timestamp in seconds.
10204       * @return (int) Modfication timestamp in seconds.
10205       * @public
10206       * @since 5.9.152 (2012-03-23)
10207       */
10208  	public function getDocModificationTimestamp() {
10209          return $this->doc_modification_timestamp;
10210      }
10211  
10212      /**
10213       * Returns a formatted date for meta information
10214       * @param $n (int) Object ID.
10215       * @param $timestamp (int) Timestamp to convert.
10216       * @return string escaped date string.
10217       * @protected
10218       * @since 4.6.028 (2009-08-25)
10219       */
10220  	protected function _datestring($n=0, $timestamp=0) {
10221          if ((empty($timestamp)) OR ($timestamp < 0)) {
10222              $timestamp = $this->doc_creation_timestamp;
10223          }
10224          return $this->_datastring('D:'.TCPDF_STATIC::getFormattedDate($timestamp), $n);
10225      }
10226  
10227      /**
10228       * Format a text string for meta information
10229       * @param $s (string) string to escape.
10230       * @param $n (int) object ID
10231       * @return string escaped string.
10232       * @protected
10233       */
10234  	protected function _textstring($s, $n=0) {
10235          if ($this->isunicode) {
10236              //Convert string to UTF-16BE
10237              $s = TCPDF_FONTS::UTF8ToUTF16BE($s, true, $this->isunicode, $this->CurrentFont);
10238          }
10239          return $this->_datastring($s, $n);
10240      }
10241  
10242      /**
10243       * get raw output stream.
10244       * @param $s (string) string to output.
10245       * @param $n (int) object reference for encryption mode
10246       * @protected
10247       * @author Nicola Asuni
10248       * @since 5.5.000 (2010-06-22)
10249       */
10250  	protected function _getrawstream($s, $n=0) {
10251          if ($n <= 0) {
10252              // default to current object
10253              $n = $this->n;
10254          }
10255          return $this->_encrypt_data($n, $s);
10256      }
10257  
10258      /**
10259       * Output a string to the document.
10260       * @param $s (string) string to output.
10261       * @protected
10262       */
10263  	protected function _out($s) {
10264          if ($this->state == 2) {
10265              if ($this->inxobj) {
10266                  // we are inside an XObject template
10267                  $this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
10268              } elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
10269                  // puts data before page footer
10270                  $pagebuff = $this->getPageBuffer($this->page);
10271                  $page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
10272                  $footer = substr($pagebuff, -$this->footerlen[$this->page]);
10273                  $this->setPageBuffer($this->page, $page.$s."\n".$footer);
10274                  // update footer position
10275                  $this->footerpos[$this->page] += strlen($s."\n");
10276              } else {
10277                  // set page data
10278                  $this->setPageBuffer($this->page, $s."\n", true);
10279              }
10280          } elseif ($this->state > 0) {
10281              // set general data
10282              $this->setBuffer($s."\n");
10283          }
10284      }
10285  
10286      /**
10287       * Set header font.
10288       * @param $font (array) Array describing the basic font parameters: (family, style, size).
10289       * @public
10290       * @since 1.1
10291       */
10292  	public function setHeaderFont($font) {
10293          $this->header_font = $font;
10294      }
10295  
10296      /**
10297       * Get header font.
10298       * @return array() Array describing the basic font parameters: (family, style, size).
10299       * @public
10300       * @since 4.0.012 (2008-07-24)
10301       */
10302  	public function getHeaderFont() {
10303          return $this->header_font;
10304      }
10305  
10306      /**
10307       * Set footer font.
10308       * @param $font (array) Array describing the basic font parameters: (family, style, size).
10309       * @public
10310       * @since 1.1
10311       */
10312  	public function setFooterFont($font) {
10313          $this->footer_font = $font;
10314      }
10315  
10316      /**
10317       * Get Footer font.
10318       * @return array() Array describing the basic font parameters: (family, style, size).
10319       * @public
10320       * @since 4.0.012 (2008-07-24)
10321       */
10322  	public function getFooterFont() {
10323          return $this->footer_font;
10324      }
10325  
10326      /**
10327       * Set language array.
10328       * @param $language (array)
10329       * @public
10330       * @since 1.1
10331       */
10332  	public function setLanguageArray($language) {
10333          $this->l = $language;
10334          if (isset($this->l['a_meta_dir'])) {
10335              $this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
10336          } else {
10337              $this->rtl = false;
10338          }
10339      }
10340  
10341      /**
10342       * Returns the PDF data.
10343       * @public
10344       */
10345  	public function getPDFData() {
10346          if ($this->state < 3) {
10347              $this->Close();
10348          }
10349          return $this->buffer;
10350      }
10351  
10352      /**
10353       * Output anchor link.
10354       * @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;)
10355       * @param $name (string) link name
10356       * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
10357       * @param $firstline (boolean) if true prints only the first line and return the remaining string.
10358       * @param $color (array) array of RGB text color
10359       * @param $style (string) font style (U, D, B, I)
10360       * @param $firstblock (boolean) if true the string is the starting of a line.
10361       * @return the number of cells used or the remaining text if $firstline = true;
10362       * @public
10363       */
10364  	public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
10365          if (isset($url[1]) AND ($url[0] == '#') AND is_numeric($url[1])) {
10366              // convert url to internal link
10367              $lnkdata = explode(',', $url);
10368              if (isset($lnkdata[0]) ) {
10369                  $page = substr($lnkdata[0], 1);
10370                  if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
10371                      $lnky = floatval($lnkdata[1]);
10372                  } else {
10373                      $lnky = 0;
10374                  }
10375                  $url = $this->AddLink();
10376                  $this->SetLink($url, $lnky, $page);
10377              }
10378          }
10379          // store current settings
10380          $prevcolor = $this->fgcolor;
10381          $prevstyle = $this->FontStyle;
10382          if (empty($color)) {
10383              $this->SetTextColorArray($this->htmlLinkColorArray);
10384          } else {
10385              $this->SetTextColorArray($color);
10386          }
10387          if ($style == -1) {
10388              $this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
10389          } else {
10390              $this->SetFont('', $this->FontStyle.$style);
10391          }
10392          $ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
10393          // restore settings
10394          $this->SetFont('', $prevstyle);
10395          $this->SetTextColorArray($prevcolor);
10396          return $ret;
10397      }
10398  
10399      /**
10400       * Converts pixels to User's Units.
10401       * @param $px (int) pixels
10402       * @return float value in user's unit
10403       * @public
10404       * @see setImageScale(), getImageScale()
10405       */
10406  	public function pixelsToUnits($px) {
10407          return ($px / ($this->imgscale * $this->k));
10408      }
10409  
10410      /**
10411       * Reverse function for htmlentities.
10412       * Convert entities in UTF-8.
10413       * @param $text_to_convert (string) Text to convert.
10414       * @return string converted text string
10415       * @public
10416       */
10417  	public function unhtmlentities($text_to_convert) {
10418          return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
10419      }
10420  
10421      // ENCRYPTION METHODS ----------------------------------
10422  
10423      /**
10424       * Compute encryption key depending on object number where the encrypted data is stored.
10425       * This is used for all strings and streams without crypt filter specifier.
10426       * @param $n (int) object number
10427       * @return int object key
10428       * @protected
10429       * @author Nicola Asuni
10430       * @since 2.0.000 (2008-01-02)
10431       */
10432  	protected function _objectkey($n) {
10433          $objkey = $this->encryptdata['key'].pack('VXxx', $n);
10434          if ($this->encryptdata['mode'] == 2) { // AES-128
10435              // AES padding
10436              $objkey .= "\x73\x41\x6C\x54"; // sAlT
10437          }
10438          $objkey = substr(TCPDF_STATIC::_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
10439          $objkey = substr($objkey, 0, 16);
10440          return $objkey;
10441      }
10442  
10443      /**
10444       * Encrypt the input string.
10445       * @param $n (int) object number
10446       * @param $s (string) data string to encrypt
10447       * @return encrypted string
10448       * @protected
10449       * @author Nicola Asuni
10450       * @since 5.0.005 (2010-05-11)
10451       */
10452  	protected function _encrypt_data($n, $s) {
10453          if (!$this->encrypted) {
10454              return $s;
10455          }
10456          switch ($this->encryptdata['mode']) {
10457              case 0:   // RC4-40
10458              case 1: { // RC4-128
10459                  $s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
10460                  break;
10461              }
10462              case 2: { // AES-128
10463                  $s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
10464                  break;
10465              }
10466              case 3: { // AES-256
10467                  $s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
10468                  break;
10469              }
10470          }
10471          return $s;
10472      }
10473  
10474      /**
10475       * Put encryption on PDF document.
10476       * @protected
10477       * @author Nicola Asuni
10478       * @since 2.0.000 (2008-01-02)
10479       */
10480  	protected function _putencryption() {
10481          if (!$this->encrypted) {
10482              return;
10483          }
10484          $this->encryptdata['objid'] = $this->_newobj();
10485          $out = '<<';
10486          if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
10487              $this->encryptdata['Filter'] = 'Standard';
10488          }
10489          $out .= ' /Filter /'.$this->encryptdata['Filter'];
10490          if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
10491              $out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
10492          }
10493          if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
10494              $this->encryptdata['V'] = 1;
10495          }
10496          // V is a code specifying the algorithm to be used in encrypting and decrypting the document
10497          $out .= ' /V '.$this->encryptdata['V'];
10498          if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
10499              // The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
10500              $out .= ' /Length '.$this->encryptdata['Length'];
10501          } else {
10502              $out .= ' /Length 40';
10503          }
10504          if ($this->encryptdata['V'] >= 4) {
10505              if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
10506                  $this->encryptdata['StmF'] = 'Identity';
10507              }
10508              if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
10509                  // The name of the crypt filter that shall be used when decrypting all strings in the document.
10510                  $this->encryptdata['StrF'] = 'Identity';
10511              }
10512              // A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
10513              if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
10514                  $out .= ' /CF <<';
10515                  $out .= ' /'.$this->encryptdata['StmF'].' <<';
10516                  $out .= ' /Type /CryptFilter';
10517                  if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
10518                      // The method used
10519                      $out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
10520                      if ($this->encryptdata['pubkey']) {
10521                          $out .= ' /Recipients [';
10522                          foreach ($this->encryptdata['Recipients'] as $rec) {
10523                              $out .= ' <'.$rec.'>';
10524                          }
10525                          $out .= ' ]';
10526                          if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
10527                              $out .= ' /EncryptMetadata false';
10528                          } else {
10529                              $out .= ' /EncryptMetadata true';
10530                          }
10531                      }
10532                  } else {
10533                      $out .= ' /CFM /None';
10534                  }
10535                  if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
10536                      // The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
10537                      $out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
10538                  } else {
10539                      $out .= ' /AuthEvent /DocOpen';
10540                  }
10541                  if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
10542                      // The bit length of the encryption key.
10543                      $out .= ' /Length '.$this->encryptdata['CF']['Length'];
10544                  }
10545                  $out .= ' >> >>';
10546              }
10547              // The name of the crypt filter that shall be used by default when decrypting streams.
10548              $out .= ' /StmF /'.$this->encryptdata['StmF'];
10549              // The name of the crypt filter that shall be used when decrypting all strings in the document.
10550              $out .= ' /StrF /'.$this->encryptdata['StrF'];
10551              if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
10552                  // The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
10553                  $out .= ' /EFF /'.$this->encryptdata[''];
10554              }
10555          }
10556          // Additional encryption dictionary entries for the standard security handler
10557          if ($this->encryptdata['pubkey']) {
10558              if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
10559                  $out .= ' /Recipients [';
10560                  foreach ($this->encryptdata['Recipients'] as $rec) {
10561                      $out .= ' <'.$rec.'>';
10562                  }
10563                  $out .= ' ]';
10564              }
10565          } else {
10566              $out .= ' /R';
10567              if ($this->encryptdata['V'] == 5) { // AES-256
10568                  $out .= ' 5';
10569                  $out .= ' /OE ('.TCPDF_STATIC::_escape($this->encryptdata['OE']).')';
10570                  $out .= ' /UE ('.TCPDF_STATIC::_escape($this->encryptdata['UE']).')';
10571                  $out .= ' /Perms ('.TCPDF_STATIC::_escape($this->encryptdata['perms']).')';
10572              } elseif ($this->encryptdata['V'] == 4) { // AES-128
10573                  $out .= ' 4';
10574              } elseif ($this->encryptdata['V'] < 2) { // RC-40
10575                  $out .= ' 2';
10576              } else { // RC-128
10577                  $out .= ' 3';
10578              }
10579              $out .= ' /O ('.TCPDF_STATIC::_escape($this->encryptdata['O']).')';
10580              $out .= ' /U ('.TCPDF_STATIC::_escape($this->encryptdata['U']).')';
10581              $out .= ' /P '.$this->encryptdata['P'];
10582              if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
10583                  $out .= ' /EncryptMetadata false';
10584              } else {
10585                  $out .= ' /EncryptMetadata true';
10586              }
10587          }
10588          $out .= ' >>';
10589          $out .= "\n".'endobj';
10590          $this->_out($out);
10591      }
10592  
10593      /**
10594       * Compute U value (used for encryption)
10595       * @return string U value
10596       * @protected
10597       * @since 2.0.000 (2008-01-02)
10598       * @author Nicola Asuni
10599       */
10600  	protected function _Uvalue() {
10601          if ($this->encryptdata['mode'] == 0) { // RC4-40
10602              return TCPDF_STATIC::_RC4($this->encryptdata['key'], TCPDF_STATIC::$enc_padding, $this->last_enc_key, $this->last_enc_key_c);
10603          } elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
10604              $tmp = TCPDF_STATIC::_md5_16(TCPDF_STATIC::$enc_padding.$this->encryptdata['fileid']);
10605              $enc = TCPDF_STATIC::_RC4($this->encryptdata['key'], $tmp, $this->last_enc_key, $this->last_enc_key_c);
10606              $len = strlen($tmp);
10607              for ($i = 1; $i <= 19; ++$i) {
10608                  $ek = '';
10609                  for ($j = 0; $j < $len; ++$j) {
10610                      $ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
10611                  }
10612                  $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10613              }
10614              $enc .= str_repeat("\x00", 16);
10615              return substr($enc, 0, 32);
10616          } elseif ($this->encryptdata['mode'] == 3) { // AES-256
10617              $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10618              // User Validation Salt
10619              $this->encryptdata['UVS'] = substr($seed, 0, 8);
10620              // User Key Salt
10621              $this->encryptdata['UKS'] = substr($seed, 8, 16);
10622              return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
10623          }
10624      }
10625  
10626      /**
10627       * Compute UE value (used for encryption)
10628       * @return string UE value
10629       * @protected
10630       * @since 5.9.006 (2010-10-19)
10631       * @author Nicola Asuni
10632       */
10633  	protected function _UEvalue() {
10634          $hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
10635          return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10636      }
10637  
10638      /**
10639       * Compute O value (used for encryption)
10640       * @return string O value
10641       * @protected
10642       * @since 2.0.000 (2008-01-02)
10643       * @author Nicola Asuni
10644       */
10645  	protected function _Ovalue() {
10646          if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
10647              $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['owner_password']);
10648              if ($this->encryptdata['mode'] > 0) {
10649                  for ($i = 0; $i < 50; ++$i) {
10650                      $tmp = TCPDF_STATIC::_md5_16($tmp);
10651                  }
10652              }
10653              $owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
10654              $enc = TCPDF_STATIC::_RC4($owner_key, $this->encryptdata['user_password'], $this->last_enc_key, $this->last_enc_key_c);
10655              if ($this->encryptdata['mode'] > 0) {
10656                  $len = strlen($owner_key);
10657                  for ($i = 1; $i <= 19; ++$i) {
10658                      $ek = '';
10659                      for ($j = 0; $j < $len; ++$j) {
10660                          $ek .= chr(ord($owner_key[$j]) ^ $i);
10661                      }
10662                      $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10663                  }
10664              }
10665              return $enc;
10666          } elseif ($this->encryptdata['mode'] == 3) { // AES-256
10667              $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10668              // Owner Validation Salt
10669              $this->encryptdata['OVS'] = substr($seed, 0, 8);
10670              // Owner Key Salt
10671              $this->encryptdata['OKS'] = substr($seed, 8, 16);
10672              return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
10673          }
10674      }
10675  
10676      /**
10677       * Compute OE value (used for encryption)
10678       * @return string OE value
10679       * @protected
10680       * @since 5.9.006 (2010-10-19)
10681       * @author Nicola Asuni
10682       */
10683  	protected function _OEvalue() {
10684          $hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
10685          return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
10686      }
10687  
10688      /**
10689       * Convert password for AES-256 encryption mode
10690       * @param $password (string) password
10691       * @return string password
10692       * @protected
10693       * @since 5.9.006 (2010-10-19)
10694       * @author Nicola Asuni
10695       */
10696  	protected function _fixAES256Password($password) {
10697          $psw = ''; // password to be returned
10698          $psw_array = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($password, $this->isunicode, $this->CurrentFont), $password, $this->rtl, $this->isunicode, $this->CurrentFont);
10699          foreach ($psw_array as $c) {
10700              $psw .= TCPDF_FONTS::unichr($c, $this->isunicode);
10701          }
10702          return substr($psw, 0, 127);
10703      }
10704  
10705      /**
10706       * Compute encryption key
10707       * @protected
10708       * @since 2.0.000 (2008-01-02)
10709       * @author Nicola Asuni
10710       */
10711  	protected function _generateencryptionkey() {
10712          $keybytelen = ($this->encryptdata['Length'] / 8);
10713          if (!$this->encryptdata['pubkey']) { // standard mode
10714              if ($this->encryptdata['mode'] == 3) { // AES-256
10715                  // generate 256 bit random key
10716                  $this->encryptdata['key'] = substr(hash('sha256', TCPDF_STATIC::getRandomSeed(), true), 0, $keybytelen);
10717                  // truncate passwords
10718                  $this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
10719                  $this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
10720                  // Compute U value
10721                  $this->encryptdata['U'] = $this->_Uvalue();
10722                  // Compute UE value
10723                  $this->encryptdata['UE'] = $this->_UEvalue();
10724                  // Compute O value
10725                  $this->encryptdata['O'] = $this->_Ovalue();
10726                  // Compute OE value
10727                  $this->encryptdata['OE'] = $this->_OEvalue();
10728                  // Compute P value
10729                  $this->encryptdata['P'] = $this->encryptdata['protection'];
10730                  // Computing the encryption dictionary's Perms (permissions) value
10731                  $perms = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
10732                  $perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
10733                  if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
10734                      $perms .= 'F';
10735                  } else {
10736                      $perms .= 'T';
10737                  }
10738                  $perms .= 'adb'; // bytes 9-11
10739                  $perms .= 'nick'; // bytes 12-15
10740                  $this->encryptdata['perms'] = TCPDF_STATIC::_AESnopad($this->encryptdata['key'], $perms);
10741              } else { // RC4-40, RC4-128, AES-128
10742                  // Pad passwords
10743                  $this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10744                  $this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10745                  // Compute O value
10746                  $this->encryptdata['O'] = $this->_Ovalue();
10747                  // get default permissions (reverse byte order)
10748                  $permissions = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']);
10749                  // Compute encryption key
10750                  $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
10751                  if ($this->encryptdata['mode'] > 0) {
10752                      for ($i = 0; $i < 50; ++$i) {
10753                          $tmp = TCPDF_STATIC::_md5_16(substr($tmp, 0, $keybytelen));
10754                      }
10755                  }
10756                  $this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
10757                  // Compute U value
10758                  $this->encryptdata['U'] = $this->_Uvalue();
10759                  // Compute P value
10760                  $this->encryptdata['P'] = $this->encryptdata['protection'];
10761              }
10762          } else { // Public-Key mode
10763              // random 20-byte seed
10764              $seed = sha1(TCPDF_STATIC::getRandomSeed(), true);
10765              $recipient_bytes = '';
10766              foreach ($this->encryptdata['pubkeys'] as $pubkey) {
10767                  // for each public certificate
10768                  if (isset($pubkey['p'])) {
10769                      $pkprotection = TCPDF_STATIC::getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
10770                  } else {
10771                      $pkprotection = $this->encryptdata['protection'];
10772                  }
10773                  // get default permissions (reverse byte order)
10774                  $pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
10775                  // envelope data
10776                  $envelope = $seed.$pkpermissions;
10777                  // write the envelope data to a temporary file
10778                  $tempkeyfile = TCPDF_STATIC::getObjFilename('key', $this->file_id);
10779                  $f = TCPDF_STATIC::fopenLocal($tempkeyfile, 'wb');
10780                  if (!$f) {
10781                      $this->Error('Unable to create temporary key file: '.$tempkeyfile);
10782                  }
10783                  $envelope_length = strlen($envelope);
10784                  fwrite($f, $envelope, $envelope_length);
10785                  fclose($f);
10786                  $tempencfile = TCPDF_STATIC::getObjFilename('enc', $this->file_id);
10787                  if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
10788                      $this->Error('Unable to encrypt the file: '.$tempkeyfile);
10789                  }
10790                  // read encryption signature
10791                  $signature = file_get_contents($tempencfile, false, null, $envelope_length);
10792                  // extract signature
10793                  $signature = substr($signature, strpos($signature, 'Content-Disposition'));
10794                  $tmparr = explode("\n\n", $signature);
10795                  $signature = trim($tmparr[1]);
10796                  unset($tmparr);
10797                  // decode signature
10798                  $signature = base64_decode($signature);
10799                  // convert signature to hex
10800                  $hexsignature = current(unpack('H*', $signature));
10801                  // store signature on recipients array
10802                  $this->encryptdata['Recipients'][] = $hexsignature;
10803                  // The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
10804                  $recipient_bytes .= $signature;
10805              }
10806              // calculate encryption key
10807              if ($this->encryptdata['mode'] == 3) { // AES-256
10808                  $this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
10809              } else { // RC4-40, RC4-128, AES-128
10810                  $this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
10811              }
10812          }
10813      }
10814  
10815      /**
10816       * Set document protection
10817       * Remark: the protection against modification is for people who have the full Acrobat product.
10818       * 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.
10819       * 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.
10820       * @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>
10821       * @param $user_pass (String) user password. Empty by default.
10822       * @param $owner_pass (String) owner password. If not specified, a random value is used.
10823       * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
10824       * @param $pubkeys (String) array of recipients containing public-key certificates ('c') and permissions ('p'). For example: array(array('c' => 'file://../examples/data/cert/tcpdf.crt', 'p' => array('print')))
10825       * @public
10826       * @since 2.0.000 (2008-01-02)
10827       * @author Nicola Asuni
10828       */
10829  	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) {
10830          if ($this->pdfa_mode) {
10831              // encryption is not allowed in PDF/A mode
10832              return;
10833          }
10834          $this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
10835          if (($pubkeys !== null) AND (is_array($pubkeys))) {
10836              // public-key mode
10837              $this->encryptdata['pubkeys'] = $pubkeys;
10838              if ($mode == 0) {
10839                  // public-Key Security requires at least 128 bit
10840                  $mode = 1;
10841              }
10842              if (!function_exists('openssl_pkcs7_encrypt')) {
10843                  $this->Error('Public-Key Security requires openssl library.');
10844              }
10845              // Set Public-Key filter (available are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
10846              $this->encryptdata['pubkey'] = true;
10847              $this->encryptdata['Filter'] = 'Adobe.PubSec';
10848              $this->encryptdata['StmF'] = 'DefaultCryptFilter';
10849              $this->encryptdata['StrF'] = 'DefaultCryptFilter';
10850          } else {
10851              // standard mode (password mode)
10852              $this->encryptdata['pubkey'] = false;
10853              $this->encryptdata['Filter'] = 'Standard';
10854              $this->encryptdata['StmF'] = 'StdCF';
10855              $this->encryptdata['StrF'] = 'StdCF';
10856          }
10857          if ($mode > 1) { // AES
10858              if (!extension_loaded('openssl') && !extension_loaded('mcrypt')) {
10859                  $this->Error('AES encryption requires openssl or mcrypt extension (http://www.php.net/manual/en/mcrypt.requirements.php).');
10860              }
10861              if (extension_loaded('openssl') && !in_array('aes-256-cbc', openssl_get_cipher_methods())) {
10862                  $this->Error('AES encryption requires openssl/aes-256-cbc cypher.');
10863              }
10864              if (extension_loaded('mcrypt') && mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
10865                  $this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
10866              }
10867              if (($mode == 3) AND !function_exists('hash')) {
10868                  // the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
10869                  $this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
10870              }
10871          }
10872          if ($owner_pass === null) {
10873              $owner_pass = md5(TCPDF_STATIC::getRandomSeed());
10874          }
10875          $this->encryptdata['user_password'] = $user_pass;
10876          $this->encryptdata['owner_password'] = $owner_pass;
10877          $this->encryptdata['mode'] = $mode;
10878          switch ($mode) {
10879              case 0: { // RC4 40 bit
10880                  $this->encryptdata['V'] = 1;
10881                  $this->encryptdata['Length'] = 40;
10882                  $this->encryptdata['CF']['CFM'] = 'V2';
10883                  break;
10884              }
10885              case 1: { // RC4 128 bit
10886                  $this->encryptdata['V'] = 2;
10887                  $this->encryptdata['Length'] = 128;
10888                  $this->encryptdata['CF']['CFM'] = 'V2';
10889                  if ($this->encryptdata['pubkey']) {
10890                      $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
10891                      $this->encryptdata['Recipients'] = array();
10892                  }
10893                  break;
10894              }
10895              case 2: { // AES 128 bit
10896                  $this->encryptdata['V'] = 4;
10897                  $this->encryptdata['Length'] = 128;
10898                  $this->encryptdata['CF']['CFM'] = 'AESV2';
10899                  $this->encryptdata['CF']['Length'] = 128;
10900                  if ($this->encryptdata['pubkey']) {
10901                      $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
10902                      $this->encryptdata['Recipients'] = array();
10903                  }
10904                  break;
10905              }
10906              case 3: { // AES 256 bit
10907                  $this->encryptdata['V'] = 5;
10908                  $this->encryptdata['Length'] = 256;
10909                  $this->encryptdata['CF']['CFM'] = 'AESV3';
10910                  $this->encryptdata['CF']['Length'] = 256;
10911                  if ($this->encryptdata['pubkey']) {
10912                      $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
10913                      $this->encryptdata['Recipients'] = array();
10914                  }
10915                  break;
10916              }
10917          }
10918          $this->encrypted = true;
10919          $this->encryptdata['fileid'] = TCPDF_STATIC::convertHexStringToString($this->file_id);
10920          $this->_generateencryptionkey();
10921      }
10922  
10923      // END OF ENCRYPTION FUNCTIONS -------------------------
10924  
10925      // START TRANSFORMATIONS SECTION -----------------------
10926  
10927      /**
10928       * Starts a 2D tranformation saving current graphic state.
10929       * This function must be called before scaling, mirroring, translation, rotation and skewing.
10930       * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
10931       * @public
10932       * @since 2.1.000 (2008-01-07)
10933       * @see StartTransform(), StopTransform()
10934       */
10935  	public function StartTransform() {
10936          if ($this->state != 2) {
10937              return;
10938          }
10939          $this->_outSaveGraphicsState();
10940          if ($this->inxobj) {
10941              // we are inside an XObject template
10942              $this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
10943          } else {
10944              $this->transfmrk[$this->page][] = $this->pagelen[$this->page];
10945          }
10946          ++$this->transfmatrix_key;
10947          $this->transfmatrix[$this->transfmatrix_key] = array();
10948      }
10949  
10950      /**
10951       * Stops a 2D tranformation restoring previous graphic state.
10952       * This function must be called after scaling, mirroring, translation, rotation and skewing.
10953       * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
10954       * @public
10955       * @since 2.1.000 (2008-01-07)
10956       * @see StartTransform(), StopTransform()
10957       */
10958  	public function StopTransform() {
10959          if ($this->state != 2) {
10960              return;
10961          }
10962          $this->_outRestoreGraphicsState();
10963          if (isset($this->transfmatrix[$this->transfmatrix_key])) {
10964              array_pop($this->transfmatrix[$this->transfmatrix_key]);
10965              --$this->transfmatrix_key;
10966          }
10967          if ($this->inxobj) {
10968              // we are inside an XObject template
10969              array_pop($this->xobjects[$this->xobjid]['transfmrk']);
10970          } else {
10971              array_pop($this->transfmrk[$this->page]);
10972          }
10973      }
10974      /**
10975       * Horizontal Scaling.
10976       * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
10977       * @param $x (int) abscissa of the scaling center. Default is current x position
10978       * @param $y (int) ordinate of the scaling center. Default is current y position
10979       * @public
10980       * @since 2.1.000 (2008-01-07)
10981       * @see StartTransform(), StopTransform()
10982       */
10983  	public function ScaleX($s_x, $x='', $y='') {
10984          $this->Scale($s_x, 100, $x, $y);
10985      }
10986  
10987      /**
10988       * Vertical Scaling.
10989       * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
10990       * @param $x (int) abscissa of the scaling center. Default is current x position
10991       * @param $y (int) ordinate of the scaling center. Default is current y position
10992       * @public
10993       * @since 2.1.000 (2008-01-07)
10994       * @see StartTransform(), StopTransform()
10995       */
10996  	public function ScaleY($s_y, $x='', $y='') {
10997          $this->Scale(100, $s_y, $x, $y);
10998      }
10999  
11000      /**
11001       * Vertical and horizontal proportional Scaling.
11002       * @param $s (float) scaling factor for width and height as percent. 0 is not allowed.
11003       * @param $x (int) abscissa of the scaling center. Default is current x position
11004       * @param $y (int) ordinate of the scaling center. Default is current y position
11005       * @public
11006       * @since 2.1.000 (2008-01-07)
11007       * @see StartTransform(), StopTransform()
11008       */
11009  	public function ScaleXY($s, $x='', $y='') {
11010          $this->Scale($s, $s, $x, $y);
11011      }
11012  
11013      /**
11014       * Vertical and horizontal non-proportional Scaling.
11015       * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
11016       * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
11017       * @param $x (int) abscissa of the scaling center. Default is current x position
11018       * @param $y (int) ordinate of the scaling center. Default is current y position
11019       * @public
11020       * @since 2.1.000 (2008-01-07)
11021       * @see StartTransform(), StopTransform()
11022       */
11023  	public function Scale($s_x, $s_y, $x='', $y='') {
11024          if ($x === '') {
11025              $x = $this->x;
11026          }
11027          if ($y === '') {
11028              $y = $this->y;
11029          }
11030          if (($s_x == 0) OR ($s_y == 0)) {
11031              $this->Error('Please do not use values equal to zero for scaling');
11032          }
11033          $y = ($this->h - $y) * $this->k;
11034          $x *= $this->k;
11035          //calculate elements of transformation matrix
11036          $s_x /= 100;
11037          $s_y /= 100;
11038          $tm = array();
11039          $tm[0] = $s_x;
11040          $tm[1] = 0;
11041          $tm[2] = 0;
11042          $tm[3] = $s_y;
11043          $tm[4] = $x * (1 - $s_x);
11044          $tm[5] = $y * (1 - $s_y);
11045          //scale the coordinate system
11046          $this->Transform($tm);
11047      }
11048  
11049      /**
11050       * Horizontal Mirroring.
11051       * @param $x (int) abscissa of the point. Default is current x position
11052       * @public
11053       * @since 2.1.000 (2008-01-07)
11054       * @see StartTransform(), StopTransform()
11055       */
11056  	public function MirrorH($x='') {
11057          $this->Scale(-100, 100, $x);
11058      }
11059  
11060      /**
11061       * Verical Mirroring.
11062       * @param $y (int) ordinate of the point. Default is current y position
11063       * @public
11064       * @since 2.1.000 (2008-01-07)
11065       * @see StartTransform(), StopTransform()
11066       */
11067  	public function MirrorV($y='') {
11068          $this->Scale(100, -100, '', $y);
11069      }
11070  
11071      /**
11072       * Point reflection mirroring.
11073       * @param $x (int) abscissa of the point. Default is current x position
11074       * @param $y (int) ordinate of the point. Default is current y position
11075       * @public
11076       * @since 2.1.000 (2008-01-07)
11077       * @see StartTransform(), StopTransform()
11078       */
11079  	public function MirrorP($x='',$y='') {
11080          $this->Scale(-100, -100, $x, $y);
11081      }
11082  
11083      /**
11084       * Reflection against a straight line through point (x, y) with the gradient angle (angle).
11085       * @param $angle (float) gradient angle of the straight line. Default is 0 (horizontal line).
11086       * @param $x (int) abscissa of the point. Default is current x position
11087       * @param $y (int) ordinate of the point. Default is current y position
11088       * @public
11089       * @since 2.1.000 (2008-01-07)
11090       * @see StartTransform(), StopTransform()
11091       */
11092  	public function MirrorL($angle=0, $x='',$y='') {
11093          $this->Scale(-100, 100, $x, $y);
11094          $this->Rotate(-2*($angle-90), $x, $y);
11095      }
11096  
11097      /**
11098       * Translate graphic object horizontally.
11099       * @param $t_x (int) movement to the right (or left for RTL)
11100       * @public
11101       * @since 2.1.000 (2008-01-07)
11102       * @see StartTransform(), StopTransform()
11103       */
11104  	public function TranslateX($t_x) {
11105          $this->Translate($t_x, 0);
11106      }
11107  
11108      /**
11109       * Translate graphic object vertically.
11110       * @param $t_y (int) movement to the bottom
11111       * @public
11112       * @since 2.1.000 (2008-01-07)
11113       * @see StartTransform(), StopTransform()
11114       */
11115  	public function TranslateY($t_y) {
11116          $this->Translate(0, $t_y);
11117      }
11118  
11119      /**
11120       * Translate graphic object horizontally and vertically.
11121       * @param $t_x (int) movement to the right
11122       * @param $t_y (int) movement to the bottom
11123       * @public
11124       * @since 2.1.000 (2008-01-07)
11125       * @see StartTransform(), StopTransform()
11126       */
11127  	public function Translate($t_x, $t_y) {
11128          //calculate elements of transformation matrix
11129          $tm = array();
11130          $tm[0] = 1;
11131          $tm[1] = 0;
11132          $tm[2] = 0;
11133          $tm[3] = 1;
11134          $tm[4] = $t_x * $this->k;
11135          $tm[5] = -$t_y * $this->k;
11136          //translate the coordinate system
11137          $this->Transform($tm);
11138      }
11139  
11140      /**
11141       * Rotate object.
11142       * @param $angle (float) angle in degrees for counter-clockwise rotation
11143       * @param $x (int) abscissa of the rotation center. Default is current x position
11144       * @param $y (int) ordinate of the rotation center. Default is current y position
11145       * @public
11146       * @since 2.1.000 (2008-01-07)
11147       * @see StartTransform(), StopTransform()
11148       */
11149  	public function Rotate($angle, $x='', $y='') {
11150          if ($x === '') {
11151              $x = $this->x;
11152          }
11153          if ($y === '') {
11154              $y = $this->y;
11155          }
11156          $y = ($this->h - $y) * $this->k;
11157          $x *= $this->k;
11158          //calculate elements of transformation matrix
11159          $tm = array();
11160          $tm[0] = cos(deg2rad($angle));
11161          $tm[1] = sin(deg2rad($angle));
11162          $tm[2] = -$tm[1];
11163          $tm[3] = $tm[0];
11164          $tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
11165          $tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
11166          //rotate the coordinate system around ($x,$y)
11167          $this->Transform($tm);
11168      }
11169  
11170      /**
11171       * Skew horizontally.
11172       * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11173       * @param $x (int) abscissa of the skewing center. default is current x position
11174       * @param $y (int) ordinate of the skewing center. default is current y position
11175       * @public
11176       * @since 2.1.000 (2008-01-07)
11177       * @see StartTransform(), StopTransform()
11178       */
11179  	public function SkewX($angle_x, $x='', $y='') {
11180          $this->Skew($angle_x, 0, $x, $y);
11181      }
11182  
11183      /**
11184       * Skew vertically.
11185       * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11186       * @param $x (int) abscissa of the skewing center. default is current x position
11187       * @param $y (int) ordinate of the skewing center. default is current y position
11188       * @public
11189       * @since 2.1.000 (2008-01-07)
11190       * @see StartTransform(), StopTransform()
11191       */
11192  	public function SkewY($angle_y, $x='', $y='') {
11193          $this->Skew(0, $angle_y, $x, $y);
11194      }
11195  
11196      /**
11197       * Skew.
11198       * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11199       * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11200       * @param $x (int) abscissa of the skewing center. default is current x position
11201       * @param $y (int) ordinate of the skewing center. default is current y position
11202       * @public
11203       * @since 2.1.000 (2008-01-07)
11204       * @see StartTransform(), StopTransform()
11205       */
11206  	public function Skew($angle_x, $angle_y, $x='', $y='') {
11207          if ($x === '') {
11208              $x = $this->x;
11209          }
11210          if ($y === '') {
11211              $y = $this->y;
11212          }
11213          if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
11214              $this->Error('Please use values between -90 and +90 degrees for Skewing.');
11215          }
11216          $x *= $this->k;
11217          $y = ($this->h - $y) * $this->k;
11218          //calculate elements of transformation matrix
11219          $tm = array();
11220          $tm[0] = 1;
11221          $tm[1] = tan(deg2rad($angle_y));
11222          $tm[2] = tan(deg2rad($angle_x));
11223          $tm[3] = 1;
11224          $tm[4] = -$tm[2] * $y;
11225          $tm[5] = -$tm[1] * $x;
11226          //skew the coordinate system
11227          $this->Transform($tm);
11228      }
11229  
11230      /**
11231       * Apply graphic transformations.
11232       * @param $tm (array) transformation matrix
11233       * @protected
11234       * @since 2.1.000 (2008-01-07)
11235       * @see StartTransform(), StopTransform()
11236       */
11237  	protected function Transform($tm) {
11238          if ($this->state != 2) {
11239              return;
11240          }
11241          $this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
11242          // add tranformation matrix
11243          $this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
11244          // update transformation mark
11245          if ($this->inxobj) {
11246              // we are inside an XObject template
11247              if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
11248                  $key = key($this->xobjects[$this->xobjid]['transfmrk']);
11249                  $this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
11250              }
11251          } elseif (end($this->transfmrk[$this->page]) !== false) {
11252              $key = key($this->transfmrk[$this->page]);
11253              $this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
11254          }
11255      }
11256  
11257      // END TRANSFORMATIONS SECTION -------------------------
11258  
11259      // START GRAPHIC FUNCTIONS SECTION ---------------------
11260      // The following section is based on the code provided by David Hernandez Sanz
11261  
11262      /**
11263       * 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.
11264       * @param $width (float) The width.
11265       * @public
11266       * @since 1.0
11267       * @see Line(), Rect(), Cell(), MultiCell()
11268       */
11269  	public function SetLineWidth($width) {
11270          //Set line width
11271          $this->LineWidth = $width;
11272          $this->linestyleWidth = sprintf('%F w', ($width * $this->k));
11273          if ($this->state == 2) {
11274              $this->_out($this->linestyleWidth);
11275          }
11276      }
11277  
11278      /**
11279       * Returns the current the line width.
11280       * @return int Line width
11281       * @public
11282       * @since 2.1.000 (2008-01-07)
11283       * @see Line(), SetLineWidth()
11284       */
11285  	public function GetLineWidth() {
11286          return $this->LineWidth;
11287      }
11288  
11289      /**
11290       * Set line style.
11291       * @param $style (array) Line style. Array with keys among the following:
11292       * <ul>
11293       *     <li>width (float): Width of the line in user units.</li>
11294       *     <li>cap (string): Type of cap to put on the line. Possible values are:
11295       * butt, round, square. The difference between "square" and "butt" is that
11296       * "square" projects a flat end past the end of the line.</li>
11297       *     <li>join (string): Type of join. Possible values are: miter, round,
11298       * bevel.</li>
11299       *     <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
11300       * series of length values, which are the lengths of the on and off dashes.
11301       * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
11302       * 1 off, 2 on, 1 off, ...</li>
11303       *     <li>phase (integer): Modifier on the dash pattern which is used to shift
11304       * the point at which the pattern starts.</li>
11305       *     <li>color (array): Draw color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName).</li>
11306       * </ul>
11307       * @param $ret (boolean) if true do not send the command.
11308       * @return string the PDF command
11309       * @public
11310       * @since 2.1.000 (2008-01-08)
11311       */
11312  	public function SetLineStyle($style, $ret=false) {
11313          $s = ''; // string to be returned
11314          if (!is_array($style)) {
11315              return;
11316          }
11317          if (isset($style['width'])) {
11318              $this->LineWidth = $style['width'];
11319              $this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
11320              $s .= $this->linestyleWidth.' ';
11321          }
11322          if (isset($style['cap'])) {
11323              $ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
11324              if (isset($ca[$style['cap']])) {
11325                  $this->linestyleCap = $ca[$style['cap']].' J';
11326                  $s .= $this->linestyleCap.' ';
11327              }
11328          }
11329          if (isset($style['join'])) {
11330              $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
11331              if (isset($ja[$style['join']])) {
11332                  $this->linestyleJoin = $ja[$style['join']].' j';
11333                  $s .= $this->linestyleJoin.' ';
11334              }
11335          }
11336          if (isset($style['dash'])) {
11337              $dash_string = '';
11338              if ($style['dash']) {
11339                  if (preg_match('/^.+,/', $style['dash']) > 0) {
11340                      $tab = explode(',', $style['dash']);
11341                  } else {
11342                      $tab = array($style['dash']);
11343                  }
11344                  $dash_string = '';
11345                  foreach ($tab as $i => $v) {
11346                      if ($i) {
11347                          $dash_string .= ' ';
11348                      }
11349                      $dash_string .= sprintf('%F', $v);
11350                  }
11351              }
11352              if (!isset($style['phase']) OR !$style['dash']) {
11353                  $style['phase'] = 0;
11354              }
11355              $this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
11356              $s .= $this->linestyleDash.' ';
11357          }
11358          if (isset($style['color'])) {
11359              $s .= $this->SetDrawColorArray($style['color'], true).' ';
11360          }
11361          if (!$ret AND ($this->state == 2)) {
11362              $this->_out($s);
11363          }
11364          return $s;
11365      }
11366  
11367      /**
11368       * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
11369       * @param $x (float) Abscissa of point.
11370       * @param $y (float) Ordinate of point.
11371       * @protected
11372       * @since 2.1.000 (2008-01-08)
11373       */
11374  	protected function _outPoint($x, $y) {
11375          if ($this->state == 2) {
11376              $this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
11377          }
11378      }
11379  
11380      /**
11381       * Append a straight line segment from the current point to the point (x, y).
11382       * The new current point shall be (x, y).
11383       * @param $x (float) Abscissa of end point.
11384       * @param $y (float) Ordinate of end point.
11385       * @protected
11386       * @since 2.1.000 (2008-01-08)
11387       */
11388  	protected function _outLine($x, $y) {
11389          if ($this->state == 2) {
11390              $this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
11391          }
11392      }
11393  
11394      /**
11395       * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
11396       * @param $x (float) Abscissa of upper-left corner.
11397       * @param $y (float) Ordinate of upper-left corner.
11398       * @param $w (float) Width.
11399       * @param $h (float) Height.
11400       * @param $op (string) options
11401       * @protected
11402       * @since 2.1.000 (2008-01-08)
11403       */
11404  	protected function _outRect($x, $y, $w, $h, $op) {
11405          if ($this->state == 2) {
11406              $this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k), (($this->h - $y) * $this->k), ($w * $this->k), (-$h * $this->k), $op));
11407          }
11408      }
11409  
11410      /**
11411       * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x2, y2) as the Bezier control points.
11412       * The new current point shall be (x3, y3).
11413       * @param $x1 (float) Abscissa of control point 1.
11414       * @param $y1 (float) Ordinate of control point 1.
11415       * @param $x2 (float) Abscissa of control point 2.
11416       * @param $y2 (float) Ordinate of control point 2.
11417       * @param $x3 (float) Abscissa of end point.
11418       * @param $y3 (float) Ordinate of end point.
11419       * @protected
11420       * @since 2.1.000 (2008-01-08)
11421       */
11422  	protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
11423          if ($this->state == 2) {
11424              $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)));
11425          }
11426      }
11427  
11428      /**
11429       * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using the current point and (x2, y2) as the Bezier control points.
11430       * The new current point shall be (x3, y3).
11431       * @param $x2 (float) Abscissa of control point 2.
11432       * @param $y2 (float) Ordinate of control point 2.
11433       * @param $x3 (float) Abscissa of end point.
11434       * @param $y3 (float) Ordinate of end point.
11435       * @protected
11436       * @since 4.9.019 (2010-04-26)
11437       */
11438  	protected function _outCurveV($x2, $y2, $x3, $y3) {
11439          if ($this->state == 2) {
11440              $this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11441          }
11442      }
11443  
11444      /**
11445       * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x3, y3) as the Bezier control points.
11446       * The new current point shall be (x3, y3).
11447       * @param $x1 (float) Abscissa of control point 1.
11448       * @param $y1 (float) Ordinate of control point 1.
11449       * @param $x3 (float) Abscissa of end point.
11450       * @param $y3 (float) Ordinate of end point.
11451       * @protected
11452       * @since 2.1.000 (2008-01-08)
11453       */
11454  	protected function _outCurveY($x1, $y1, $x3, $y3) {
11455          if ($this->state == 2) {
11456              $this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11457          }
11458      }
11459  
11460      /**
11461       * Draws a line between two points.
11462       * @param $x1 (float) Abscissa of first point.
11463       * @param $y1 (float) Ordinate of first point.
11464       * @param $x2 (float) Abscissa of second point.
11465       * @param $y2 (float) Ordinate of second point.
11466       * @param $style (array) Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
11467       * @public
11468       * @since 1.0
11469       * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
11470       */
11471  	public function Line($x1, $y1, $x2, $y2, $style=array()) {
11472          if ($this->state != 2) {
11473              return;
11474          }
11475          if (is_array($style)) {
11476              $this->SetLineStyle($style);
11477          }
11478          $this->_outPoint($x1, $y1);
11479          $this->_outLine($x2, $y2);
11480          $this->_out('S');
11481      }
11482  
11483      /**
11484       * Draws a rectangle.
11485       * @param $x (float) Abscissa of upper-left corner.
11486       * @param $y (float) Ordinate of upper-left corner.
11487       * @param $w (float) Width.
11488       * @param $h (float) Height.
11489       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11490       * @param $border_style (array) Border style of rectangle. Array with keys among the following:
11491       * <ul>
11492       *     <li>all: Line style of all borders. Array like for SetLineStyle().</li>
11493       *     <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
11494       * </ul>
11495       * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
11496       * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11497       * @public
11498       * @since 1.0
11499       * @see SetLineStyle()
11500       */
11501  	public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
11502          if ($this->state != 2) {
11503              return;
11504          }
11505          if (empty($style)) {
11506              $style = 'S';
11507          }
11508          if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
11509              // set background color
11510              $this->SetFillColorArray($fill_color);
11511          }
11512          if (!empty($border_style)) {
11513              if (isset($border_style['all']) AND !empty($border_style['all'])) {
11514                  //set global style for border
11515                  $this->SetLineStyle($border_style['all']);
11516                  $border_style = array();
11517              } else {
11518                  // remove stroke operator from style
11519                  $opnostroke = array('S' => '', 'D' => '', 's' => '', 'd' => '', 'B' => 'F', 'FD' => 'F', 'DF' => 'F', 'B*' => 'F*', 'F*D' => 'F*', 'DF*' => 'F*', 'b' => 'f', 'fd' => 'f', 'df' => 'f', 'b*' => 'f*', 'f*d' => 'f*', 'df*' => 'f*' );
11520                  if (isset($opnostroke[$style])) {
11521                      $style = $opnostroke[$style];
11522                  }
11523              }
11524          }
11525          if (!empty($style)) {
11526              $op = TCPDF_STATIC::getPathPaintOperator($style);
11527              $this->_outRect($x, $y, $w, $h, $op);
11528          }
11529          if (!empty($border_style)) {
11530              $border_style2 = array();
11531              foreach ($border_style as $line => $value) {
11532                  $length = strlen($line);
11533                  for ($i = 0; $i < $length; ++$i) {
11534                      $border_style2[$line[$i]] = $value;
11535                  }
11536              }
11537              $border_style = $border_style2;
11538              if (isset($border_style['L']) AND $border_style['L']) {
11539                  $this->Line($x, $y, $x, $y + $h, $border_style['L']);
11540              }
11541              if (isset($border_style['T']) AND $border_style['T']) {
11542                  $this->Line($x, $y, $x + $w, $y, $border_style['T']);
11543              }
11544              if (isset($border_style['R']) AND $border_style['R']) {
11545                  $this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
11546              }
11547              if (isset($border_style['B']) AND $border_style['B']) {
11548                  $this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
11549              }
11550          }
11551      }
11552  
11553      /**
11554       * Draws a Bezier curve.
11555       * The Bezier curve is a tangent to the line between the control points at
11556       * either end of the curve.
11557       * @param $x0 (float) Abscissa of start point.
11558       * @param $y0 (float) Ordinate of start point.
11559       * @param $x1 (float) Abscissa of control point 1.
11560       * @param $y1 (float) Ordinate of control point 1.
11561       * @param $x2 (float) Abscissa of control point 2.
11562       * @param $y2 (float) Ordinate of control point 2.
11563       * @param $x3 (float) Abscissa of end point.
11564       * @param $y3 (float) Ordinate of end point.
11565       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11566       * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11567       * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11568       * @public
11569       * @see SetLineStyle()
11570       * @since 2.1.000 (2008-01-08)
11571       */
11572  	public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
11573          if ($this->state != 2) {
11574              return;
11575          }
11576          if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11577              $this->SetFillColorArray($fill_color);
11578          }
11579          $op = TCPDF_STATIC::getPathPaintOperator($style);
11580          if ($line_style) {
11581              $this->SetLineStyle($line_style);
11582          }
11583          $this->_outPoint($x0, $y0);
11584          $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11585          $this->_out($op);
11586      }
11587  
11588      /**
11589       * Draws a poly-Bezier curve.
11590       * Each Bezier curve segment is a tangent to the line between the control points at
11591       * either end of the curve.
11592       * @param $x0 (float) Abscissa of start point.
11593       * @param $y0 (float) Ordinate of start point.
11594       * @param $segments (float) An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
11595       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11596       * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11597       * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11598       * @public
11599       * @see SetLineStyle()
11600       * @since 3.0008 (2008-05-12)
11601       */
11602  	public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
11603          if ($this->state != 2) {
11604              return;
11605          }
11606          if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11607              $this->SetFillColorArray($fill_color);
11608          }
11609          $op = TCPDF_STATIC::getPathPaintOperator($style);
11610          if ($op == 'f') {
11611              $line_style = array();
11612          }
11613          if ($line_style) {
11614              $this->SetLineStyle($line_style);
11615          }
11616          $this->_outPoint($x0, $y0);
11617          foreach ($segments as $segment) {
11618              list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
11619              $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11620          }
11621          $this->_out($op);
11622      }
11623  
11624      /**
11625       * Draws an ellipse.
11626       * An ellipse is formed from n Bezier curves.
11627       * @param $x0 (float) Abscissa of center point.
11628       * @param $y0 (float) Ordinate of center point.
11629       * @param $rx (float) Horizontal radius.
11630       * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11631       * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
11632       * @param $astart: (float) Angle start of draw line. Default value: 0.
11633       * @param $afinish: (float) Angle finish of draw line. Default value: 360.
11634       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11635       * @param $line_style (array) Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
11636       * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11637       * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11638       * @author Nicola Asuni
11639       * @public
11640       * @since 2.1.000 (2008-01-08)
11641       */
11642  	public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11643          if ($this->state != 2) {
11644              return;
11645          }
11646          if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
11647              $ry = $rx;
11648          }
11649          if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11650              $this->SetFillColorArray($fill_color);
11651          }
11652          $op = TCPDF_STATIC::getPathPaintOperator($style);
11653          if ($op == 'f') {
11654              $line_style = array();
11655          }
11656          if ($line_style) {
11657              $this->SetLineStyle($line_style);
11658          }
11659          $this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
11660          $this->_out($op);
11661      }
11662  
11663      /**
11664       * Append an elliptical arc to the current path.
11665       * An ellipse is formed from n Bezier curves.
11666       * @param $xc (float) Abscissa of center point.
11667       * @param $yc (float) Ordinate of center point.
11668       * @param $rx (float) Horizontal radius.
11669       * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11670       * @param $xang: (float) Angle between the X-axis and the major axis of the ellipse. Default value: 0.
11671       * @param $angs: (float) Angle start of draw line. Default value: 0.
11672       * @param $angf: (float) Angle finish of draw line. Default value: 360.
11673       * @param $pie (boolean) if true do not mark the border point (used to draw pie sectors).
11674       * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11675       * @param $startpoint (boolean) if true output a starting point.
11676       * @param $ccw (boolean) if true draws in counter-clockwise.
11677       * @param $svg (boolean) if true the angles are in svg mode (already calculated).
11678       * @return array bounding box coordinates (x min, y min, x max, y max)
11679       * @author Nicola Asuni
11680       * @protected
11681       * @since 4.9.019 (2010-04-26)
11682       */
11683  	protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
11684          if (($rx <= 0) OR ($ry < 0)) {
11685              return;
11686          }
11687          $k = $this->k;
11688          if ($nc < 2) {
11689              $nc = 2;
11690          }
11691          $xmin = 2147483647;
11692          $ymin = 2147483647;
11693          $xmax = 0;
11694          $ymax = 0;
11695          if ($pie) {
11696              // center of the arc
11697              $this->_outPoint($xc, $yc);
11698          }
11699          $xang = deg2rad((float) $xang);
11700          $angs = deg2rad((float) $angs);
11701          $angf = deg2rad((float) $angf);
11702          if ($svg) {
11703              $as = $angs;
11704              $af = $angf;
11705          } else {
11706              $as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
11707              $af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
11708          }
11709          if ($as < 0) {
11710              $as += (2 * M_PI);
11711          }
11712          if ($af < 0) {
11713              $af += (2 * M_PI);
11714          }
11715          if ($ccw AND ($as > $af)) {
11716              // reverse rotation
11717              $as -= (2 * M_PI);
11718          } elseif (!$ccw AND ($as < $af)) {
11719              // reverse rotation
11720              $af -= (2 * M_PI);
11721          }
11722          $total_angle = ($af - $as);
11723          if ($nc < 2) {
11724              $nc = 2;
11725          }
11726          // total arcs to draw
11727          $nc *= (2 * abs($total_angle) / M_PI);
11728          $nc = round($nc) + 1;
11729          // angle of each arc
11730          $arcang = ($total_angle / $nc);
11731          // center point in PDF coordinates
11732          $x0 = $xc;
11733          $y0 = ($this->h - $yc);
11734          // starting angle
11735          $ang = $as;
11736          $alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
11737          $cos_xang = cos($xang);
11738          $sin_xang = sin($xang);
11739          $cos_ang = cos($ang);
11740          $sin_ang = sin($ang);
11741          // first arc point
11742          $px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11743          $py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11744          // first Bezier control point
11745          $qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11746          $qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11747          if ($pie) {
11748              // line from center to arc starting point
11749              $this->_outLine($px1, $this->h - $py1);
11750          } elseif ($startpoint) {
11751              // arc starting point
11752              $this->_outPoint($px1, $this->h - $py1);
11753          }
11754          // draw arcs
11755          for ($i = 1; $i <= $nc; ++$i) {
11756              // starting angle
11757              $ang = $as + ($i * $arcang);
11758              if ($i == $nc) {
11759                  $ang = $af;
11760              }
11761              $cos_ang = cos($ang);
11762              $sin_ang = sin($ang);
11763              // second arc point
11764              $px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11765              $py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11766              // second Bezier control point
11767              $qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11768              $qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11769              // draw arc
11770              $cx1 = ($px1 + $qx1);
11771              $cy1 = ($this->h - ($py1 + $qy1));
11772              $cx2 = ($px2 - $qx2);
11773              $cy2 = ($this->h - ($py2 - $qy2));
11774              $cx3 = $px2;
11775              $cy3 = ($this->h - $py2);
11776              $this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
11777              // get bounding box coordinates
11778              $xmin = min($xmin, $cx1, $cx2, $cx3);
11779              $ymin = min($ymin, $cy1, $cy2, $cy3);
11780              $xmax = max($xmax, $cx1, $cx2, $cx3);
11781              $ymax = max($ymax, $cy1, $cy2, $cy3);
11782              // move to next point
11783              $px1 = $px2;
11784              $py1 = $py2;
11785              $qx1 = $qx2;
11786              $qy1 = $qy2;
11787          }
11788          if ($pie) {
11789              $this->_outLine($xc, $yc);
11790              // get bounding box coordinates
11791              $xmin = min($xmin, $xc);
11792              $ymin = min($ymin, $yc);
11793              $xmax = max($xmax, $xc);
11794              $ymax = max($ymax, $yc);
11795          }
11796          return array($xmin, $ymin, $xmax, $ymax);
11797      }
11798  
11799      /**
11800       * Draws a circle.
11801       * A circle is formed from n Bezier curves.
11802       * @param $x0 (float) Abscissa of center point.
11803       * @param $y0 (float) Ordinate of center point.
11804       * @param $r (float) Radius.
11805       * @param $angstr: (float) Angle start of draw line. Default value: 0.
11806       * @param $angend: (float) Angle finish of draw line. Default value: 360.
11807       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11808       * @param $line_style (array) Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
11809       * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11810       * @param $nc (integer) Number of curves used to draw a 90 degrees portion of circle.
11811       * @public
11812       * @since 2.1.000 (2008-01-08)
11813       */
11814  	public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11815          $this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
11816      }
11817  
11818      /**
11819       * Draws a polygonal line
11820       * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11821       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11822       * @param $line_style (array) Line style of polygon. Array with keys among the following:
11823       * <ul>
11824       *     <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11825       *     <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11826       * </ul>
11827       * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11828       * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11829       * @since 4.8.003 (2009-09-15)
11830       * @public
11831       */
11832  	public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
11833          $this->Polygon($p, $style, $line_style, $fill_color, false);
11834      }
11835  
11836      /**
11837       * Draws a polygon.
11838       * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11839       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11840       * @param $line_style (array) Line style of polygon. Array with keys among the following:
11841       * <ul>
11842       *     <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11843       *     <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11844       * </ul>
11845       * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11846       * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11847       * @param $closed (boolean) if true the polygon is closes, otherwise will remain open
11848       * @public
11849       * @since 2.1.000 (2008-01-08)
11850       */
11851  	public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
11852          if ($this->state != 2) {
11853              return;
11854          }
11855          $nc = count($p); // number of coordinates
11856          $np = $nc / 2; // number of points
11857          if ($closed) {
11858              // close polygon by adding the first 2 points at the end (one line)
11859              for ($i = 0; $i < 4; ++$i) {
11860                  $p[$nc + $i] = $p[$i];
11861              }
11862              // copy style for the last added line
11863              if (isset($line_style[0])) {
11864                  $line_style[$np] = $line_style[0];
11865              }
11866              $nc += 4;
11867          }
11868          if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11869              $this->SetFillColorArray($fill_color);
11870          }
11871          $op = TCPDF_STATIC::getPathPaintOperator($style);
11872          if ($op == 'f') {
11873              $line_style = array();
11874          }
11875          $draw = true;
11876          if ($line_style) {
11877              if (isset($line_style['all'])) {
11878                  $this->SetLineStyle($line_style['all']);
11879              } else {
11880                  $draw = false;
11881                  if ($op == 'B') {
11882                      // draw fill
11883                      $op = 'f';
11884                      $this->_outPoint($p[0], $p[1]);
11885                      for ($i = 2; $i < $nc; $i = $i + 2) {
11886                          $this->_outLine($p[$i], $p[$i + 1]);
11887                      }
11888                      $this->_out($op);
11889                  }
11890                  // draw outline
11891                  $this->_outPoint($p[0], $p[1]);
11892                  for ($i = 2; $i < $nc; $i = $i + 2) {
11893                      $line_num = ($i / 2) - 1;
11894                      if (isset($line_style[$line_num])) {
11895                          if ($line_style[$line_num] != 0) {
11896                              if (is_array($line_style[$line_num])) {
11897                                  $this->_out('S');
11898                                  $this->SetLineStyle($line_style[$line_num]);
11899                                  $this->_outPoint($p[$i - 2], $p[$i - 1]);
11900                                  $this->_outLine($p[$i], $p[$i + 1]);
11901                                  $this->_out('S');
11902                                  $this->_outPoint($p[$i], $p[$i + 1]);
11903                              } else {
11904                                  $this->_outLine($p[$i], $p[$i + 1]);
11905                              }
11906                          }
11907                      } else {
11908                          $this->_outLine($p[$i], $p[$i + 1]);
11909                      }
11910                  }
11911                  $this->_out($op);
11912              }
11913          }
11914          if ($draw) {
11915              $this->_outPoint($p[0], $p[1]);
11916              for ($i = 2; $i < $nc; $i = $i + 2) {
11917                  $this->_outLine($p[$i], $p[$i + 1]);
11918              }
11919              $this->_out($op);
11920          }
11921      }
11922  
11923      /**
11924       * Draws a regular polygon.
11925       * @param $x0 (float) Abscissa of center point.
11926       * @param $y0 (float) Ordinate of center point.
11927       * @param $r: (float) Radius of inscribed circle.
11928       * @param $ns (integer) Number of sides.
11929       * @param $angle (float) Angle oriented (anti-clockwise). Default value: 0.
11930       * @param $draw_circle (boolean) Draw inscribed circle or not. Default value: false.
11931       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11932       * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
11933       * <ul>
11934       *     <li>all: Line style of all sides. Array like for SetLineStyle().</li>
11935       *     <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
11936       * </ul>
11937       * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
11938       * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11939       * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
11940       * <ul>
11941       *     <li>D or empty string: Draw (default).</li>
11942       *     <li>F: Fill.</li>
11943       *     <li>DF or FD: Draw and fill.</li>
11944       *     <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
11945       *     <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
11946       * </ul>
11947       * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
11948       * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
11949       * @public
11950       * @since 2.1.000 (2008-01-08)
11951       */
11952  	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()) {
11953          if (3 > $ns) {
11954              $ns = 3;
11955          }
11956          if ($draw_circle) {
11957              $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
11958          }
11959          $p = array();
11960          for ($i = 0; $i < $ns; ++$i) {
11961              $a = $angle + ($i * 360 / $ns);
11962              $a_rad = deg2rad((float) $a);
11963              $p[] = $x0 + ($r * sin($a_rad));
11964              $p[] = $y0 + ($r * cos($a_rad));
11965          }
11966          $this->Polygon($p, $style, $line_style, $fill_color);
11967      }
11968  
11969      /**
11970       * Draws a star polygon
11971       * @param $x0 (float) Abscissa of center point.
11972       * @param $y0 (float) Ordinate of center point.
11973       * @param $r (float) Radius of inscribed circle.
11974       * @param $nv (integer) Number of vertices.
11975       * @param $ng (integer) Number of gap (if ($ng % $nv = 1) then is a regular polygon).
11976       * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
11977       * @param $draw_circle: (boolean) Draw inscribed circle or not. Default value is false.
11978       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11979       * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
11980       * <ul>
11981       *     <li>all: Line style of all sides. Array like for
11982       * SetLineStyle().</li>
11983       *     <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
11984       * </ul>
11985       * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
11986       * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11987       * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
11988       * <ul>
11989       *     <li>D or empty string: Draw (default).</li>
11990       *     <li>F: Fill.</li>
11991       *     <li>DF or FD: Draw and fill.</li>
11992       *     <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
11993       *     <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
11994       * </ul>
11995       * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
11996       * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
11997       * @public
11998       * @since 2.1.000 (2008-01-08)
11999       */
12000  	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()) {
12001          if ($nv < 2) {
12002              $nv = 2;
12003          }
12004          if ($draw_circle) {
12005              $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12006          }
12007          $p2 = array();
12008          $visited = array();
12009          for ($i = 0; $i < $nv; ++$i) {
12010              $a = $angle + ($i * 360 / $nv);
12011              $a_rad = deg2rad((float) $a);
12012              $p2[] = $x0 + ($r * sin($a_rad));
12013              $p2[] = $y0 + ($r * cos($a_rad));
12014              $visited[] = false;
12015          }
12016          $p = array();
12017          $i = 0;
12018          do {
12019              $p[] = $p2[$i * 2];
12020              $p[] = $p2[($i * 2) + 1];
12021              $visited[$i] = true;
12022              $i += $ng;
12023              $i %= $nv;
12024          } while (!$visited[$i]);
12025          $this->Polygon($p, $style, $line_style, $fill_color);
12026      }
12027  
12028      /**
12029       * Draws a rounded rectangle.
12030       * @param $x (float) Abscissa of upper-left corner.
12031       * @param $y (float) Ordinate of upper-left corner.
12032       * @param $w (float) Width.
12033       * @param $h (float) Height.
12034       * @param $r (float) the radius of the circle used to round off the corners of the rectangle.
12035       * @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").
12036       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12037       * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12038       * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
12039       * @public
12040       * @since 2.1.000 (2008-01-08)
12041       */
12042  	public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12043          $this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
12044      }
12045  
12046      /**
12047       * Draws a rounded rectangle.
12048       * @param $x (float) Abscissa of upper-left corner.
12049       * @param $y (float) Ordinate of upper-left corner.
12050       * @param $w (float) Width.
12051       * @param $h (float) Height.
12052       * @param $rx (float) the x-axis radius of the ellipse used to round off the corners of the rectangle.
12053       * @param $ry (float) the y-axis radius of the ellipse used to round off the corners of the rectangle.
12054       * @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").
12055       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12056       * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12057       * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
12058       * @public
12059       * @since 4.9.019 (2010-04-22)
12060       */
12061  	public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12062          if ($this->state != 2) {
12063              return;
12064          }
12065          if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
12066              // Not rounded
12067              $this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
12068              return;
12069          }
12070          // Rounded
12071          if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12072              $this->SetFillColorArray($fill_color);
12073          }
12074          $op = TCPDF_STATIC::getPathPaintOperator($style);
12075          if ($op == 'f') {
12076              $border_style = array();
12077          }
12078          if ($border_style) {
12079              $this->SetLineStyle($border_style);
12080          }
12081          $MyArc = 4 / 3 * (sqrt(2) - 1);
12082          $this->_outPoint($x + $rx, $y);
12083          $xc = $x + $w - $rx;
12084          $yc = $y + $ry;
12085          $this->_outLine($xc, $y);
12086          if ($round_corner[0]) {
12087              $this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
12088          } else {
12089              $this->_outLine($x + $w, $y);
12090          }
12091          $xc = $x + $w - $rx;
12092          $yc = $y + $h - $ry;
12093          $this->_outLine($x + $w, $yc);
12094          if ($round_corner[1]) {
12095              $this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
12096          } else {
12097              $this->_outLine($x + $w, $y + $h);
12098          }
12099          $xc = $x + $rx;
12100          $yc = $y + $h - $ry;
12101          $this->_outLine($xc, $y + $h);
12102          if ($round_corner[2]) {
12103              $this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
12104          } else {
12105              $this->_outLine($x, $y + $h);
12106          }
12107          $xc = $x + $rx;
12108          $yc = $y + $ry;
12109          $this->_outLine($x, $yc);
12110          if ($round_corner[3]) {
12111              $this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
12112          } else {
12113              $this->_outLine($x, $y);
12114              $this->_outLine($x + $rx, $y);
12115          }
12116          $this->_out($op);
12117      }
12118  
12119      /**
12120       * Draws a grahic arrow.
12121       * @param $x0 (float) Abscissa of first point.
12122       * @param $y0 (float) Ordinate of first point.
12123       * @param $x1 (float) Abscissa of second point.
12124       * @param $y1 (float) Ordinate of second point.
12125       * @param $head_style (int) (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
12126       * @param $arm_size (float) length of arrowhead arms
12127       * @param $arm_angle (int) angle between an arm and the shaft
12128       * @author Piotr Galecki, Nicola Asuni, Andy Meier
12129       * @since 4.6.018 (2009-07-10)
12130       */
12131  	public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
12132          // getting arrow direction angle
12133          // 0 deg angle is when both arms go along X axis. angle grows clockwise.
12134          $dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
12135          if ($dir_angle < 0) {
12136              $dir_angle += (2 * M_PI);
12137          }
12138          $arm_angle = deg2rad($arm_angle);
12139          $sx1 = $x1;
12140          $sy1 = $y1;
12141          if ($head_style > 0) {
12142              // calculate the stopping point for the arrow shaft
12143              $sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
12144              $sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
12145          }
12146          // main arrow line / shaft
12147          $this->Line($x0, $y0, $sx1, $sy1);
12148          // left arrowhead arm tip
12149          $x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
12150          $y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
12151          // right arrowhead arm tip
12152          $x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
12153          $y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
12154          $mode = 'D';
12155          $style = array();
12156          switch ($head_style) {
12157              case 0: {
12158                  // draw only arrowhead arms
12159                  $mode = 'D';
12160                  $style = array(1, 1, 0);
12161                  break;
12162              }
12163              case 1: {
12164                  // draw closed arrowhead, but no fill
12165                  $mode = 'D';
12166                  break;
12167              }
12168              case 2: {
12169                  // closed and filled arrowhead
12170                  $mode = 'DF';
12171                  break;
12172              }
12173              case 3: {
12174                  // filled arrowhead
12175                  $mode = 'F';
12176                  break;
12177              }
12178          }
12179          $this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
12180      }
12181  
12182      // END GRAPHIC FUNCTIONS SECTION -----------------------
12183  
12184      /**
12185       * Add a Named Destination.
12186       * NOTE: destination names are unique, so only last entry will be saved.
12187       * @param $name (string) Destination name.
12188       * @param $y (float) Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
12189       * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12190       * @param $x (float) X position in user units of the destiantion on the selected page (default = -1 = current position;).
12191       * @return (string) Stripped named destination identifier or false in case of error.
12192       * @public
12193       * @author Christian Deligant, Nicola Asuni
12194       * @since 5.9.097 (2011-06-23)
12195       */
12196  	public function setDestination($name, $y=-1, $page='', $x=-1) {
12197          // remove unsupported characters
12198          $name = TCPDF_STATIC::encodeNameObject($name);
12199          if (TCPDF_STATIC::empty_string($name)) {
12200              return false;
12201          }
12202          if ($y == -1) {
12203              $y = $this->GetY();
12204          } elseif ($y < 0) {
12205              $y = 0;
12206          } elseif ($y > $this->h) {
12207              $y = $this->h;
12208          }
12209          if ($x == -1) {
12210              $x = $this->GetX();
12211          } elseif ($x < 0) {
12212              $x = 0;
12213          } elseif ($x > $this->w) {
12214              $x = $this->w;
12215          }
12216          $fixed = false;
12217          if (!empty($page) AND ($page[0] == '*')) {
12218              $page = intval(substr($page, 1));
12219              // this page number will not be changed when moving/add/deleting pages
12220              $fixed = true;
12221          }
12222          if (empty($page)) {
12223              $page = $this->PageNo();
12224              if (empty($page)) {
12225                  return;
12226              }
12227          }
12228          $this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
12229          return $name;
12230      }
12231  
12232      /**
12233       * Return the Named Destination array.
12234       * @return (array) Named Destination array.
12235       * @public
12236       * @author Nicola Asuni
12237       * @since 5.9.097 (2011-06-23)
12238       */
12239  	public function getDestination() {
12240          return $this->dests;
12241      }
12242  
12243      /**
12244       * Insert Named Destinations.
12245       * @protected
12246       * @author Johannes G\FCntert, Nicola Asuni
12247       * @since 5.9.098 (2011-06-23)
12248       */
12249  	protected function _putdests() {
12250          if (empty($this->dests)) {
12251              return;
12252          }
12253          $this->n_dests = $this->_newobj();
12254          $out = ' <<';
12255          foreach($this->dests as $name => $o) {
12256              $out .= ' /'.$name.' '.sprintf('[%u 0 R /XYZ %F %F null]', $this->page_obj_id[($o['p'])], ($o['x'] * $this->k), ($this->pagedim[$o['p']]['h'] - ($o['y'] * $this->k)));
12257          }
12258          $out .= ' >>';
12259          $out .= "\n".'endobj';
12260          $this->_out($out);
12261      }
12262  
12263      /**
12264       * Adds a bookmark - alias for Bookmark().
12265       * @param $txt (string) Bookmark description.
12266       * @param $level (int) Bookmark level (minimum value is 0).
12267       * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12268       * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12269       * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12270       * @param $color (array) RGB color array (values from 0 to 255).
12271       * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12272       * @param $link (mixed) URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
12273       * @public
12274       */
12275  	public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12276          $this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
12277      }
12278  
12279      /**
12280       * Adds a bookmark.
12281       * @param $txt (string) Bookmark description.
12282       * @param $level (int) Bookmark level (minimum value is 0).
12283       * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12284       * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12285       * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12286       * @param $color (array) RGB color array (values from 0 to 255).
12287       * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12288       * @param $link (mixed) URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
12289       * @public
12290       * @since 2.1.002 (2008-02-12)
12291       */
12292  	public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12293          if ($level < 0) {
12294              $level = 0;
12295          }
12296          if (isset($this->outlines[0])) {
12297              $lastoutline = end($this->outlines);
12298              $maxlevel = $lastoutline['l'] + 1;
12299          } else {
12300              $maxlevel = 0;
12301          }
12302          if ($level > $maxlevel) {
12303              $level = $maxlevel;
12304          }
12305          if ($y == -1) {
12306              $y = $this->GetY();
12307          } elseif ($y < 0) {
12308              $y = 0;
12309          } elseif ($y > $this->h) {
12310              $y = $this->h;
12311          }
12312          if ($x == -1) {
12313              $x = $this->GetX();
12314          } elseif ($x < 0) {
12315              $x = 0;
12316          } elseif ($x > $this->w) {
12317              $x = $this->w;
12318          }
12319          $fixed = false;
12320          if (!empty($page) AND ($page[0] == '*')) {
12321              $page = intval(substr($page, 1));
12322              // this page number will not be changed when moving/add/deleting pages
12323              $fixed = true;
12324          }
12325          if (empty($page)) {
12326              $page = $this->PageNo();
12327              if (empty($page)) {
12328                  return;
12329              }
12330          }
12331          $this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
12332      }
12333  
12334      /**
12335       * Sort bookmarks for page and key.
12336       * @protected
12337       * @since 5.9.119 (2011-09-19)
12338       */
12339  	protected function sortBookmarks() {
12340          // get sorting columns
12341          $outline_p = array();
12342          $outline_y = array();
12343          foreach ($this->outlines as $key => $row) {
12344              $outline_p[$key] = $row['p'];
12345              $outline_k[$key] = $key;
12346          }
12347          // sort outlines by page and original position
12348          array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
12349      }
12350  
12351      /**
12352       * Create a bookmark PDF string.
12353       * @protected
12354       * @author Olivier Plathey, Nicola Asuni
12355       * @since 2.1.002 (2008-02-12)
12356       */
12357  	protected function _putbookmarks() {
12358          $nb = count($this->outlines);
12359          if ($nb == 0) {
12360              return;
12361          }
12362          // sort bookmarks
12363          $this->sortBookmarks();
12364          $lru = array();
12365          $level = 0;
12366          foreach ($this->outlines as $i => $o) {
12367              if ($o['l'] > 0) {
12368                  $parent = $lru[($o['l'] - 1)];
12369                  //Set parent and last pointers
12370                  $this->outlines[$i]['parent'] = $parent;
12371                  $this->outlines[$parent]['last'] = $i;
12372                  if ($o['l'] > $level) {
12373                      //Level increasing: set first pointer
12374                      $this->outlines[$parent]['first'] = $i;
12375                  }
12376              } else {
12377                  $this->outlines[$i]['parent'] = $nb;
12378              }
12379              if (($o['l'] <= $level) AND ($i > 0)) {
12380                  //Set prev and next pointers
12381                  $prev = $lru[$o['l']];
12382                  $this->outlines[$prev]['next'] = $i;
12383                  $this->outlines[$i]['prev'] = $prev;
12384              }
12385              $lru[$o['l']] = $i;
12386              $level = $o['l'];
12387          }
12388          //Outline items
12389          $n = $this->n + 1;
12390          $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';
12391          foreach ($this->outlines as $i => $o) {
12392              $oid = $this->_newobj();
12393              // covert HTML title to string
12394              $title = preg_replace($nltags, "\n", $o['t']);
12395              $title = preg_replace("/[\r]+/si", '', $title);
12396              $title = preg_replace("/[\n]+/si", "\n", $title);
12397              $title = strip_tags($title);
12398              $title = $this->stringTrim($title);
12399              $out = '<</Title '.$this->_textstring($title, $oid);
12400              $out .= ' /Parent '.($n + $o['parent']).' 0 R';
12401              if (isset($o['prev'])) {
12402                  $out .= ' /Prev '.($n + $o['prev']).' 0 R';
12403              }
12404              if (isset($o['next'])) {
12405                  $out .= ' /Next '.($n + $o['next']).' 0 R';
12406              }
12407              if (isset($o['first'])) {
12408                  $out .= ' /First '.($n + $o['first']).' 0 R';
12409              }
12410              if (isset($o['last'])) {
12411                  $out .= ' /Last '.($n + $o['last']).' 0 R';
12412              }
12413              if (isset($o['u']) AND !empty($o['u'])) {
12414                  // link
12415                  if (is_string($o['u'])) {
12416                      if ($o['u'][0] == '#') {
12417                          // internal destination
12418                          $out .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($o['u'], 1));
12419                      } elseif ($o['u'][0] == '%') {
12420                          // embedded PDF file
12421                          $filename = basename(substr($o['u'], 1));
12422                          $out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
12423                      } elseif ($o['u'][0] == '*') {
12424                          // embedded generic file
12425                          $filename = basename(substr($o['u'], 1));
12426                          $jsa = 'var D=event.target.doc;var MyData=D.dataObjects;for (var i in MyData) if (MyData[i].path=="'.$filename.'") D.exportDataObject( { cName : MyData[i].name, nLaunch : 2});';
12427                          $out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
12428                      } else {
12429                          // external URI link
12430                          $out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
12431                      }
12432                  } elseif (isset($this->links[$o['u']])) {
12433                      // internal link ID
12434                      $l = $this->links[$o['u']];
12435                      if (isset($this->page_obj_id[($l['p'])])) {
12436                          $out .= sprintf(' /Dest [%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($l['p'])], ($this->pagedim[$l['p']]['h'] - ($l['y'] * $this->k)));
12437                      }
12438                  }
12439              } elseif (isset($this->page_obj_id[($o['p'])])) {
12440                  // link to a page
12441                  $out .= ' '.sprintf('/Dest [%u 0 R /XYZ %F %F null]', $this->page_obj_id[($o['p'])], ($o['x'] * $this->k), ($this->pagedim[$o['p']]['h'] - ($o['y'] * $this->k)));
12442              }
12443              // set font style
12444              $style = 0;
12445              if (!empty($o['s'])) {
12446                  // bold
12447                  if (strpos($o['s'], 'B') !== false) {
12448                      $style |= 2;
12449                  }
12450                  // oblique
12451                  if (strpos($o['s'], 'I') !== false) {
12452                      $style |= 1;
12453                  }
12454              }
12455              $out .= sprintf(' /F %d', $style);
12456              // set bookmark color
12457              if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
12458                  $color = array_values($o['c']);
12459                  $out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
12460              } else {
12461                  // black
12462                  $out .= ' /C [0.0 0.0 0.0]';
12463              }
12464              $out .= ' /Count 0'; // normally closed item
12465              $out .= ' >>';
12466              $out .= "\n".'endobj';
12467              $this->_out($out);
12468          }
12469          //Outline root
12470          $this->OutlineRoot = $this->_newobj();
12471          $this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
12472      }
12473  
12474      // --- JAVASCRIPT ------------------------------------------------------
12475  
12476      /**
12477       * Adds a javascript
12478       * @param $script (string) Javascript code
12479       * @public
12480       * @author Johannes G\FCntert, Nicola Asuni
12481       * @since 2.1.002 (2008-02-12)
12482       */
12483  	public function IncludeJS($script) {
12484          $this->javascript .= $script;
12485      }
12486  
12487      /**
12488       * Adds a javascript object and return object ID
12489       * @param $script (string) Javascript code
12490       * @param $onload (boolean) if true executes this object when opening the document
12491       * @return int internal object ID
12492       * @public
12493       * @author Nicola Asuni
12494       * @since 4.8.000 (2009-09-07)
12495       */
12496  	public function addJavascriptObject($script, $onload=false) {
12497          if ($this->pdfa_mode) {
12498              // javascript is not allowed in PDF/A mode
12499              return false;
12500          }
12501          ++$this->n;
12502          $this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
12503          return $this->n;
12504      }
12505  
12506      /**
12507       * Create a javascript PDF string.
12508       * @protected
12509       * @author Johannes G\FCntert, Nicola Asuni
12510       * @since 2.1.002 (2008-02-12)
12511       */
12512  	protected function _putjavascript() {
12513          if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
12514              return;
12515          }
12516          if (strpos($this->javascript, 'this.addField') > 0) {
12517              if (!$this->ur['enabled']) {
12518                  //$this->setUserRights();
12519              }
12520              // the following two lines are used to avoid form fields duplication after saving
12521              // The addField method only works when releasing user rights (UR3)
12522              $jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
12523              $jsb = "getField('tcpdfdocsaved').value='saved';";
12524              $this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
12525          }
12526          // name tree for javascript
12527          $this->n_js = '<< /Names [';
12528          if (!empty($this->javascript)) {
12529              $this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
12530          }
12531          if (!empty($this->js_objects)) {
12532              foreach ($this->js_objects as $key => $val) {
12533                  if ($val['onload']) {
12534                      $this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
12535                  }
12536              }
12537          }
12538          $this->n_js .= ' ] >>';
12539          // default Javascript object
12540          if (!empty($this->javascript)) {
12541              $obj_id = $this->_newobj();
12542              $out = '<< /S /JavaScript';
12543              $out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
12544              $out .= ' >>';
12545              $out .= "\n".'endobj';
12546              $this->_out($out);
12547          }
12548          // additional Javascript objects
12549          if (!empty($this->js_objects)) {
12550              foreach ($this->js_objects as $key => $val) {
12551                  $out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
12552                  $this->_out($out);
12553              }
12554          }
12555      }
12556  
12557      /**
12558       * Adds a javascript form field.
12559       * @param $type (string) field type
12560       * @param $name (string) field name
12561       * @param $x (int) horizontal position
12562       * @param $y (int) vertical position
12563       * @param $w (int) width
12564       * @param $h (int) height
12565       * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12566       * @protected
12567       * @author Denis Van Nuffelen, Nicola Asuni
12568       * @since 2.1.002 (2008-02-12)
12569       */
12570  	protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
12571          if ($this->rtl) {
12572              $x = $x - $w;
12573          }
12574          // the followind avoid fields duplication after saving the document
12575          $this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
12576          $k = $this->k;
12577          $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";
12578          $this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
12579          while (list($key, $val) = each($prop)) {
12580              if (strcmp(substr($key, -5), 'Color') == 0) {
12581                  $val = TCPDF_COLORS::_JScolor($val);
12582              } else {
12583                  $val = "'".$val."'";
12584              }
12585              $this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
12586          }
12587          if ($this->rtl) {
12588              $this->x -= $w;
12589          } else {
12590              $this->x += $w;
12591          }
12592          $this->javascript .= '}';
12593      }
12594  
12595      // --- FORM FIELDS -----------------------------------------------------
12596  
12597  
12598  
12599      /**
12600       * Set default properties for form fields.
12601       * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12602       * @public
12603       * @author Nicola Asuni
12604       * @since 4.8.000 (2009-09-06)
12605       */
12606  	public function setFormDefaultProp($prop=array()) {
12607          $this->default_form_prop = $prop;
12608      }
12609  
12610      /**
12611       * Return the default properties for form fields.
12612       * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12613       * @public
12614       * @author Nicola Asuni
12615       * @since 4.8.000 (2009-09-06)
12616       */
12617  	public function getFormDefaultProp() {
12618          return $this->default_form_prop;
12619      }
12620  
12621      /**
12622       * Creates a text field
12623       * @param $name (string) field name
12624       * @param $w (float) Width of the rectangle
12625       * @param $h (float) Height of the rectangle
12626       * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12627       * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12628       * @param $x (float) Abscissa of the upper-left corner of the rectangle
12629       * @param $y (float) Ordinate of the upper-left corner of the rectangle
12630       * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12631       * @public
12632       * @author Nicola Asuni
12633       * @since 4.8.000 (2009-09-07)
12634       */
12635  	public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12636          if ($x === '') {
12637              $x = $this->x;
12638          }
12639          if ($y === '') {
12640              $y = $this->y;
12641          }
12642          // check page for no-write regions and adapt page margins if necessary
12643          list($x, $y) = $this->checkPageRegions($h, $x, $y);
12644          if ($js) {
12645              $this->_addfield('text', $name, $x, $y, $w, $h, $prop);
12646              return;
12647          }
12648          // get default style
12649          $prop = array_merge($this->getFormDefaultProp(), $prop);
12650          // get annotation data
12651          $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12652          // set default appearance stream
12653          $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12654          $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12655          $popt['da'] = $fontstyle;
12656          // build appearance stream
12657          $popt['ap'] = array();
12658          $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12659          $text = '';
12660          if (isset($prop['value']) AND !empty($prop['value'])) {
12661              $text = $prop['value'];
12662          } elseif (isset($opt['v']) AND !empty($opt['v'])) {
12663              $text = $opt['v'];
12664          }
12665          $tmpid = $this->startTemplate($w, $h, false);
12666          $align = '';
12667          if (isset($popt['q'])) {
12668              switch ($popt['q']) {
12669                  case 0: {
12670                      $align = 'L';
12671                      break;
12672                  }
12673                  case 1: {
12674                      $align = 'C';
12675                      break;
12676                  }
12677                  case 2: {
12678                      $align = 'R';
12679                      break;
12680                  }
12681                  default: {
12682                      $align = '';
12683                      break;
12684                  }
12685              }
12686          }
12687          $this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12688          $this->endTemplate();
12689          --$this->n;
12690          $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12691          unset($this->xobjects[$tmpid]);
12692          $popt['ap']['n'] .= 'Q EMC';
12693          // merge options
12694          $opt = array_merge($popt, $opt);
12695          // remove some conflicting options
12696          unset($opt['bs']);
12697          // set remaining annotation data
12698          $opt['Subtype'] = 'Widget';
12699          $opt['ft'] = 'Tx';
12700          $opt['t'] = $name;
12701          // Additional annotation's parameters (check _putannotsobj() method):
12702          //$opt['f']
12703          //$opt['as']
12704          //$opt['bs']
12705          //$opt['be']
12706          //$opt['c']
12707          //$opt['border']
12708          //$opt['h']
12709          //$opt['mk'];
12710          //$opt['mk']['r']
12711          //$opt['mk']['bc'];
12712          //$opt['mk']['bg'];
12713          unset($opt['mk']['ca']);
12714          unset($opt['mk']['rc']);
12715          unset($opt['mk']['ac']);
12716          unset($opt['mk']['i']);
12717          unset($opt['mk']['ri']);
12718          unset($opt['mk']['ix']);
12719          unset($opt['mk']['if']);
12720          //$opt['mk']['if']['sw'];
12721          //$opt['mk']['if']['s'];
12722          //$opt['mk']['if']['a'];
12723          //$opt['mk']['if']['fb'];
12724          unset($opt['mk']['tp']);
12725          //$opt['tu']
12726          //$opt['tm']
12727          //$opt['ff']
12728          //$opt['v']
12729          //$opt['dv']
12730          //$opt['a']
12731          //$opt['aa']
12732          //$opt['q']
12733          $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12734          if ($this->rtl) {
12735              $this->x -= $w;
12736          } else {
12737              $this->x += $w;
12738          }
12739      }
12740  
12741      /**
12742       * Creates a RadioButton field.
12743       * @param $name (string) Field name.
12744       * @param $w (int) Width of the radio button.
12745       * @param $prop (array) Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12746       * @param $opt (array) Annotation parameters. Possible values are described on official PDF32000_2008 reference.
12747       * @param $onvalue (string) Value to be returned if selected.
12748       * @param $checked (boolean) Define the initial state.
12749       * @param $x (float) Abscissa of the upper-left corner of the rectangle
12750       * @param $y (float) Ordinate of the upper-left corner of the rectangle
12751       * @param $js (boolean) If true put the field using JavaScript (requires Acrobat Writer to be rendered).
12752       * @public
12753       * @author Nicola Asuni
12754       * @since 4.8.000 (2009-09-07)
12755       */
12756  	public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
12757          if ($x === '') {
12758              $x = $this->x;
12759          }
12760          if ($y === '') {
12761              $y = $this->y;
12762          }
12763          // check page for no-write regions and adapt page margins if necessary
12764          list($x, $y) = $this->checkPageRegions($w, $x, $y);
12765          if ($js) {
12766              $this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
12767              return;
12768          }
12769          if (TCPDF_STATIC::empty_string($onvalue)) {
12770              $onvalue = 'On';
12771          }
12772          if ($checked) {
12773              $defval = $onvalue;
12774          } else {
12775              $defval = 'Off';
12776          }
12777          // set font
12778          $font = 'zapfdingbats';
12779          if ($this->pdfa_mode) {
12780              // all fonts must be embedded
12781              $font = 'pdfa'.$font;
12782          }
12783          $this->AddFont($font);
12784          $tmpfont = $this->getFontBuffer($font);
12785          // set data for parent group
12786          if (!isset($this->radiobutton_groups[$this->page])) {
12787              $this->radiobutton_groups[$this->page] = array();
12788          }
12789          if (!isset($this->radiobutton_groups[$this->page][$name])) {
12790              $this->radiobutton_groups[$this->page][$name] = array();
12791              ++$this->n;
12792              $this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
12793              $this->radio_groups[] = $this->n;
12794          }
12795          $kid = ($this->n + 1);
12796          // save object ID to be added on Kids entry on parent object
12797          $this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
12798          // get default style
12799          $prop = array_merge($this->getFormDefaultProp(), $prop);
12800          $prop['NoToggleToOff'] = 'true';
12801          $prop['Radio'] = 'true';
12802          $prop['borderStyle'] = 'inset';
12803          // get annotation data
12804          $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12805          // set additional default options
12806          $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
12807          $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
12808          $popt['da'] = $fontstyle;
12809          // build appearance stream
12810          $popt['ap'] = array();
12811          $popt['ap']['n'] = array();
12812          $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
12813          $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
12814          $popt['ap']['n'][$onvalue] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(108).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
12815          $popt['ap']['n']['Off'] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(109).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
12816          if (!isset($popt['mk'])) {
12817              $popt['mk'] = array();
12818          }
12819          $popt['mk']['ca'] = '(l)';
12820          // merge options
12821          $opt = array_merge($popt, $opt);
12822          // set remaining annotation data
12823          $opt['Subtype'] = 'Widget';
12824          $opt['ft'] = 'Btn';
12825          if ($checked) {
12826              $opt['v'] = array('/'.$onvalue);
12827              $opt['as'] = $onvalue;
12828          } else {
12829              $opt['as'] = 'Off';
12830          }
12831          // store readonly flag
12832          if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
12833              $this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
12834          }
12835          $this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
12836          $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
12837          if ($this->rtl) {
12838              $this->x -= $w;
12839          } else {
12840              $this->x += $w;
12841          }
12842      }
12843  
12844      /**
12845       * Creates a List-box field
12846       * @param $name (string) field name
12847       * @param $w (int) width
12848       * @param $h (int) height
12849       * @param $values (array) array containing the list of values.
12850       * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12851       * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12852       * @param $x (float) Abscissa of the upper-left corner of the rectangle
12853       * @param $y (float) Ordinate of the upper-left corner of the rectangle
12854       * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12855       * @public
12856       * @author Nicola Asuni
12857       * @since 4.8.000 (2009-09-07)
12858       */
12859  	public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12860          if ($x === '') {
12861              $x = $this->x;
12862          }
12863          if ($y === '') {
12864              $y = $this->y;
12865          }
12866          // check page for no-write regions and adapt page margins if necessary
12867          list($x, $y) = $this->checkPageRegions($h, $x, $y);
12868          if ($js) {
12869              $this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
12870              $s = '';
12871              foreach ($values as $value) {
12872                  if (is_array($value)) {
12873                      $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
12874                  } else {
12875                      $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
12876                  }
12877              }
12878              $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
12879              return;
12880          }
12881          // get default style
12882          $prop = array_merge($this->getFormDefaultProp(), $prop);
12883          // get annotation data
12884          $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12885          // set additional default values
12886          $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12887          $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12888          $popt['da'] = $fontstyle;
12889          // build appearance stream
12890          $popt['ap'] = array();
12891          $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12892          $text = '';
12893          foreach($values as $item) {
12894              if (is_array($item)) {
12895                  $text .= $item[1]."\n";
12896              } else {
12897                  $text .= $item."\n";
12898              }
12899          }
12900          $tmpid = $this->startTemplate($w, $h, false);
12901          $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12902          $this->endTemplate();
12903          --$this->n;
12904          $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12905          unset($this->xobjects[$tmpid]);
12906          $popt['ap']['n'] .= 'Q EMC';
12907          // merge options
12908          $opt = array_merge($popt, $opt);
12909          // set remaining annotation data
12910          $opt['Subtype'] = 'Widget';
12911          $opt['ft'] = 'Ch';
12912          $opt['t'] = $name;
12913          $opt['opt'] = $values;
12914          unset($opt['mk']['ca']);
12915          unset($opt['mk']['rc']);
12916          unset($opt['mk']['ac']);
12917          unset($opt['mk']['i']);
12918          unset($opt['mk']['ri']);
12919          unset($opt['mk']['ix']);
12920          unset($opt['mk']['if']);
12921          unset($opt['mk']['tp']);
12922          $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12923          if ($this->rtl) {
12924              $this->x -= $w;
12925          } else {
12926              $this->x += $w;
12927          }
12928      }
12929  
12930      /**
12931       * Creates a Combo-box field
12932       * @param $name (string) field name
12933       * @param $w (int) width
12934       * @param $h (int) height
12935       * @param $values (array) array containing the list of values.
12936       * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12937       * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12938       * @param $x (float) Abscissa of the upper-left corner of the rectangle
12939       * @param $y (float) Ordinate of the upper-left corner of the rectangle
12940       * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12941       * @public
12942       * @author Nicola Asuni
12943       * @since 4.8.000 (2009-09-07)
12944       */
12945  	public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12946          if ($x === '') {
12947              $x = $this->x;
12948          }
12949          if ($y === '') {
12950              $y = $this->y;
12951          }
12952          // check page for no-write regions and adapt page margins if necessary
12953          list($x, $y) = $this->checkPageRegions($h, $x, $y);
12954          if ($js) {
12955              $this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
12956              $s = '';
12957              foreach ($values as $value) {
12958                  if (is_array($value)) {
12959                      $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
12960                  } else {
12961                      $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
12962                  }
12963              }
12964              $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
12965              return;
12966          }
12967          // get default style
12968          $prop = array_merge($this->getFormDefaultProp(), $prop);
12969          $prop['Combo'] = true;
12970          // get annotation data
12971          $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12972          // set additional default options
12973          $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12974          $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12975          $popt['da'] = $fontstyle;
12976          // build appearance stream
12977          $popt['ap'] = array();
12978          $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12979          $text = '';
12980          foreach($values as $item) {
12981              if (is_array($item)) {
12982                  $text .= $item[1]."\n";
12983              } else {
12984                  $text .= $item."\n";
12985              }
12986          }
12987          $tmpid = $this->startTemplate($w, $h, false);
12988          $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12989          $this->endTemplate();
12990          --$this->n;
12991          $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12992          unset($this->xobjects[$tmpid]);
12993          $popt['ap']['n'] .= 'Q EMC';
12994          // merge options
12995          $opt = array_merge($popt, $opt);
12996          // set remaining annotation data
12997          $opt['Subtype'] = 'Widget';
12998          $opt['ft'] = 'Ch';
12999          $opt['t'] = $name;
13000          $opt['opt'] = $values;
13001          unset($opt['mk']['ca']);
13002          unset($opt['mk']['rc']);
13003          unset($opt['mk']['ac']);
13004          unset($opt['mk']['i']);
13005          unset($opt['mk']['ri']);
13006          unset($opt['mk']['ix']);
13007          unset($opt['mk']['if']);
13008          unset($opt['mk']['tp']);
13009          $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13010          if ($this->rtl) {
13011              $this->x -= $w;
13012          } else {
13013              $this->x += $w;
13014          }
13015      }
13016  
13017      /**
13018       * Creates a CheckBox field
13019       * @param $name (string) field name
13020       * @param $w (int) width
13021       * @param $checked (boolean) define the initial state.
13022       * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13023       * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13024       * @param $onvalue (string) value to be returned if selected.
13025       * @param $x (float) Abscissa of the upper-left corner of the rectangle
13026       * @param $y (float) Ordinate of the upper-left corner of the rectangle
13027       * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13028       * @public
13029       * @author Nicola Asuni
13030       * @since 4.8.000 (2009-09-07)
13031       */
13032  	public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
13033          if ($x === '') {
13034              $x = $this->x;
13035          }
13036          if ($y === '') {
13037              $y = $this->y;
13038          }
13039          // check page for no-write regions and adapt page margins if necessary
13040          list($x, $y) = $this->checkPageRegions($w, $x, $y);
13041          if ($js) {
13042              $this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
13043              return;
13044          }
13045          if (!isset($prop['value'])) {
13046              $prop['value'] = array('Yes');
13047          }
13048          // get default style
13049          $prop = array_merge($this->getFormDefaultProp(), $prop);
13050          $prop['borderStyle'] = 'inset';
13051          // get annotation data
13052          $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13053          // set additional default options
13054          $font = 'zapfdingbats';
13055          if ($this->pdfa_mode) {
13056              // all fonts must be embedded
13057              $font = 'pdfa'.$font;
13058          }
13059          $this->AddFont($font);
13060          $tmpfont = $this->getFontBuffer($font);
13061          $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
13062          $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
13063          $popt['da'] = $fontstyle;
13064          // build appearance stream
13065          $popt['ap'] = array();
13066          $popt['ap']['n'] = array();
13067          $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
13068          $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
13069          $popt['ap']['n']['Yes'] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(110).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
13070          $popt['ap']['n']['Off'] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(111).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
13071          // merge options
13072          $opt = array_merge($popt, $opt);
13073          // set remaining annotation data
13074          $opt['Subtype'] = 'Widget';
13075          $opt['ft'] = 'Btn';
13076          $opt['t'] = $name;
13077          if (TCPDF_STATIC::empty_string($onvalue)) {
13078              $onvalue = 'Yes';
13079          }
13080          $opt['opt'] = array($onvalue);
13081          if ($checked) {
13082              $opt['v'] = array('/Yes');
13083              $opt['as'] = 'Yes';
13084          } else {
13085              $opt['v'] = array('/Off');
13086              $opt['as'] = 'Off';
13087          }
13088          $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13089          if ($this->rtl) {
13090              $this->x -= $w;
13091          } else {
13092              $this->x += $w;
13093          }
13094      }
13095  
13096      /**
13097       * Creates a button field
13098       * @param $name (string) field name
13099       * @param $w (int) width
13100       * @param $h (int) height
13101       * @param $caption (string) caption.
13102       * @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.
13103       * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13104       * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13105       * @param $x (float) Abscissa of the upper-left corner of the rectangle
13106       * @param $y (float) Ordinate of the upper-left corner of the rectangle
13107       * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13108       * @public
13109       * @author Nicola Asuni
13110       * @since 4.8.000 (2009-09-07)
13111       */
13112  	public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
13113          if ($x === '') {
13114              $x = $this->x;
13115          }
13116          if ($y === '') {
13117              $y = $this->y;
13118          }
13119          // check page for no-write regions and adapt page margins if necessary
13120          list($x, $y) = $this->checkPageRegions($h, $x, $y);
13121          if ($js) {
13122              $this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
13123              $this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
13124              $this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
13125              $this->javascript .= 'f'.$name.".highlight='push';\n";
13126              $this->javascript .= 'f'.$name.".print=false;\n";
13127              return;
13128          }
13129          // get default style
13130          $prop = array_merge($this->getFormDefaultProp(), $prop);
13131          $prop['Pushbutton'] = 'true';
13132          $prop['highlight'] = 'push';
13133          $prop['display'] = 'display.noPrint';
13134          // get annotation data
13135          $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13136          $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13137          $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13138          $popt['da'] = $fontstyle;
13139          // build appearance stream
13140          $popt['ap'] = array();
13141          $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13142          $tmpid = $this->startTemplate($w, $h, false);
13143          $bw = (2 / $this->k); // border width
13144          $border = array(
13145              'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13146              'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
13147              'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13148              'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
13149          $this->SetFillColor(204);
13150          $this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
13151          $this->endTemplate();
13152          --$this->n;
13153          $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13154          unset($this->xobjects[$tmpid]);
13155          $popt['ap']['n'] .= 'Q EMC';
13156          // set additional default options
13157          if (!isset($popt['mk'])) {
13158              $popt['mk'] = array();
13159          }
13160          $ann_obj_id = ($this->n + 1);
13161          if (!empty($action) AND !is_array($action)) {
13162              $ann_obj_id = ($this->n + 2);
13163          }
13164          $popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
13165          $popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
13166          $popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
13167          // merge options
13168          $opt = array_merge($popt, $opt);
13169          // set remaining annotation data
13170          $opt['Subtype'] = 'Widget';
13171          $opt['ft'] = 'Btn';
13172          $opt['t'] = $caption;
13173          $opt['v'] = $name;
13174          if (!empty($action)) {
13175              if (is_array($action)) {
13176                  // form action options as on section 12.7.5 of PDF32000_2008.
13177                  $opt['aa'] = '/D <<';
13178                  $bmode = array('SubmitForm', 'ResetForm', 'ImportData');
13179                  foreach ($action AS $key => $val) {
13180                      if (($key == 'S') AND in_array($val, $bmode)) {
13181                          $opt['aa'] .= ' /S /'.$val;
13182                      } elseif (($key == 'F') AND (!empty($val))) {
13183                          $opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
13184                      } elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
13185                          $opt['aa'] .= ' /Fields [';
13186                          foreach ($val AS $field) {
13187                              $opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
13188                          }
13189                          $opt['aa'] .= ']';
13190                      } elseif (($key == 'Flags')) {
13191                          $ff = 0;
13192                          if (is_array($val)) {
13193                              foreach ($val AS $flag) {
13194                                  switch ($flag) {
13195                                      case 'Include/Exclude': {
13196                                          $ff += 1 << 0;
13197                                          break;
13198                                      }
13199                                      case 'IncludeNoValueFields': {
13200                                          $ff += 1 << 1;
13201                                          break;
13202                                      }
13203                                      case 'ExportFormat': {
13204                                          $ff += 1 << 2;
13205                                          break;
13206                                      }
13207                                      case 'GetMethod': {
13208                                          $ff += 1 << 3;
13209                                          break;
13210                                      }
13211                                      case 'SubmitCoordinates': {
13212                                          $ff += 1 << 4;
13213                                          break;
13214                                      }
13215                                      case 'XFDF': {
13216                                          $ff += 1 << 5;
13217                                          break;
13218                                      }
13219                                      case 'IncludeAppendSaves': {
13220                                          $ff += 1 << 6;
13221                                          break;
13222                                      }
13223                                      case 'IncludeAnnotations': {
13224                                          $ff += 1 << 7;
13225                                          break;
13226                                      }
13227                                      case 'SubmitPDF': {
13228                                          $ff += 1 << 8;
13229                                          break;
13230                                      }
13231                                      case 'CanonicalFormat': {
13232                                          $ff += 1 << 9;
13233                                          break;
13234                                      }
13235                                      case 'ExclNonUserAnnots': {
13236                                          $ff += 1 << 10;
13237                                          break;
13238                                      }
13239                                      case 'ExclFKey': {
13240                                          $ff += 1 << 11;
13241                                          break;
13242                                      }
13243                                      case 'EmbedForm': {
13244                                          $ff += 1 << 13;
13245                                          break;
13246                                      }
13247                                  }
13248                              }
13249                          } else {
13250                              $ff = intval($val);
13251                          }
13252                          $opt['aa'] .= ' /Flags '.$ff;
13253                      }
13254                  }
13255                  $opt['aa'] .= ' >>';
13256              } else {
13257                  // Javascript action or raw action command
13258                  $js_obj_id = $this->addJavascriptObject($action);
13259                  $opt['aa'] = '/D '.$js_obj_id.' 0 R';
13260              }
13261          }
13262          $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13263          if ($this->rtl) {
13264              $this->x -= $w;
13265          } else {
13266              $this->x += $w;
13267          }
13268      }
13269  
13270      // --- END FORMS FIELDS ------------------------------------------------
13271  
13272      /**
13273       * Add certification signature (DocMDP or UR3)
13274       * You can set only one signature type
13275       * @protected
13276       * @author Nicola Asuni
13277       * @since 4.6.008 (2009-05-07)
13278       */
13279  	protected function _putsignature() {
13280          if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
13281              return;
13282          }
13283          $sigobjid = ($this->sig_obj_id + 1);
13284          $out = $this->_getobj($sigobjid)."\n";
13285          $out .= '<< /Type /Sig';
13286          $out .= ' /Filter /Adobe.PPKLite';
13287          $out .= ' /SubFilter /adbe.pkcs7.detached';
13288          $out .= ' '.TCPDF_STATIC::$byterange_string;
13289          $out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
13290          if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
13291              $out .= ' /Reference ['; // array of signature reference dictionaries
13292              $out .= ' << /Type /SigRef';
13293              if ($this->signature_data['cert_type'] > 0) {
13294                  $out .= ' /TransformMethod /DocMDP';
13295                  $out .= ' /TransformParams <<';
13296                  $out .= ' /Type /TransformParams';
13297                  $out .= ' /P '.$this->signature_data['cert_type'];
13298                  $out .= ' /V /1.2';
13299              } else {
13300                  $out .= ' /TransformMethod /UR3';
13301                  $out .= ' /TransformParams <<';
13302                  $out .= ' /Type /TransformParams';
13303                  $out .= ' /V /2.2';
13304                  if (!TCPDF_STATIC::empty_string($this->ur['document'])) {
13305                      $out .= ' /Document['.$this->ur['document'].']';
13306                  }
13307                  if (!TCPDF_STATIC::empty_string($this->ur['form'])) {
13308                      $out .= ' /Form['.$this->ur['form'].']';
13309                  }
13310                  if (!TCPDF_STATIC::empty_string($this->ur['signature'])) {
13311                      $out .= ' /Signature['.$this->ur['signature'].']';
13312                  }
13313                  if (!TCPDF_STATIC::empty_string($this->ur['annots'])) {
13314                      $out .= ' /Annots['.$this->ur['annots'].']';
13315                  }
13316                  if (!TCPDF_STATIC::empty_string($this->ur['ef'])) {
13317                      $out .= ' /EF['.$this->ur['ef'].']';
13318                  }
13319                  if (!TCPDF_STATIC::empty_string($this->ur['formex'])) {
13320                      $out .= ' /FormEX['.$this->ur['formex'].']';
13321                  }
13322              }
13323              $out .= ' >>'; // close TransformParams
13324              // optional digest data (values must be calculated and replaced later)
13325              //$out .= ' /Data ********** 0 R';
13326              //$out .= ' /DigestMethod/MD5';
13327              //$out .= ' /DigestLocation[********** 34]';
13328              //$out .= ' /DigestValue<********************************>';
13329              $out .= ' >>';
13330              $out .= ' ]'; // end of reference
13331          }
13332          if (isset($this->signature_data['info']['Name']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Name'])) {
13333              $out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
13334          }
13335          if (isset($this->signature_data['info']['Location']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Location'])) {
13336              $out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
13337          }
13338          if (isset($this->signature_data['info']['Reason']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Reason'])) {
13339              $out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
13340          }
13341          if (isset($this->signature_data['info']['ContactInfo']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['ContactInfo'])) {
13342              $out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
13343          }
13344          $out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
13345          $out .= ' >>';
13346          $out .= "\n".'endobj';
13347          $this->_out($out);
13348      }
13349  
13350      /**
13351       * Set User's Rights for PDF Reader
13352       * WARNING: This is experimental and currently do not work.
13353       * Check the PDF Reference 8.7.1 Transform Methods,
13354       * Table 8.105 Entries in the UR transform parameters dictionary
13355       * @param $enable (boolean) if true enable user's rights on PDF reader
13356       * @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.
13357       * @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.
13358       * @param $form (string) Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
13359       * @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.
13360       * @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
13361       Names specifying additional embedded-files-related usage rights for the document.
13362       * @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.
13363       * @public
13364       * @author Nicola Asuni
13365       * @since 2.9.000 (2008-03-26)
13366       */
13367  	public function setUserRights(
13368              $enable=true,
13369              $document='/FullSave',
13370              $annots='/Create/Delete/Modify/Copy/Import/Export',
13371              $form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
13372              $signature='/Modify',
13373              $ef='/Create/Delete/Modify/Import',
13374              $formex='') {
13375          $this->ur['enabled'] = $enable;
13376          $this->ur['document'] = $document;
13377          $this->ur['annots'] = $annots;
13378          $this->ur['form'] = $form;
13379          $this->ur['signature'] = $signature;
13380          $this->ur['ef'] = $ef;
13381          $this->ur['formex'] = $formex;
13382          if (!$this->sign) {
13383              $this->setSignature('', '', '', '', 0, array());
13384          }
13385      }
13386  
13387      /**
13388       * Enable document signature (requires the OpenSSL Library).
13389       * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
13390       * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13391       * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13392       * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
13393       * @param $signing_cert (mixed) signing certificate (string or filename prefixed with 'file://')
13394       * @param $private_key (mixed) private key (string or filename prefixed with 'file://')
13395       * @param $private_key_password (string) password
13396       * @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.
13397       * @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.
13398       * @param $info (array) array of option information: Name, Location, Reason, ContactInfo.
13399       * @param $approval (string) Enable approval signature eg. for PDF incremental update
13400       * @public
13401       * @author Nicola Asuni
13402       * @since 4.6.005 (2009-04-24)
13403       */
13404  	public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array(), $approval='') {
13405          // to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13406          // to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13407          // to convert pfx certificate to pem: openssl
13408          //     OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
13409          $this->sign = true;
13410          ++$this->n;
13411          $this->sig_obj_id = $this->n; // signature widget
13412          ++$this->n; // signature object ($this->sig_obj_id + 1)
13413          $this->signature_data = array();
13414          if (strlen($signing_cert) == 0) {
13415              $this->Error('Please provide a certificate file and password!');
13416          }
13417          if (strlen($private_key) == 0) {
13418              $private_key = $signing_cert;
13419          }
13420          $this->signature_data['signcert'] = $signing_cert;
13421          $this->signature_data['privkey'] = $private_key;
13422          $this->signature_data['password'] = $private_key_password;
13423          $this->signature_data['extracerts'] = $extracerts;
13424          $this->signature_data['cert_type'] = $cert_type;
13425          $this->signature_data['info'] = $info;
13426          $this->signature_data['approval'] = $approval;
13427      }
13428  
13429      /**
13430       * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
13431       * @param $x (float) Abscissa of the upper-left corner.
13432       * @param $y (float) Ordinate of the upper-left corner.
13433       * @param $w (float) Width of the signature area.
13434       * @param $h (float) Height of the signature area.
13435       * @param $page (int) option page number (if < 0 the current page is used).
13436       * @param $name (string) Name of the signature.
13437       * @public
13438       * @author Nicola Asuni
13439       * @since 5.3.011 (2010-06-17)
13440       */
13441  	public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13442          $this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13443      }
13444  
13445      /**
13446       * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
13447       * @param $x (float) Abscissa of the upper-left corner.
13448       * @param $y (float) Ordinate of the upper-left corner.
13449       * @param $w (float) Width of the signature area.
13450       * @param $h (float) Height of the signature area.
13451       * @param $page (int) option page number (if < 0 the current page is used).
13452       * @param $name (string) Name of the signature.
13453       * @public
13454       * @author Nicola Asuni
13455       * @since 5.9.101 (2011-07-06)
13456       */
13457  	public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13458          ++$this->n;
13459          $this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13460      }
13461  
13462      /**
13463       * Get the array that defines the signature appearance (page and rectangle coordinates).
13464       * @param $x (float) Abscissa of the upper-left corner.
13465       * @param $y (float) Ordinate of the upper-left corner.
13466       * @param $w (float) Width of the signature area.
13467       * @param $h (float) Height of the signature area.
13468       * @param $page (int) option page number (if < 0 the current page is used).
13469       * @param $name (string) Name of the signature.
13470       * @return (array) Array defining page and rectangle coordinates of signature appearance.
13471       * @protected
13472       * @author Nicola Asuni
13473       * @since 5.9.101 (2011-07-06)
13474       */
13475  	protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13476          $sigapp = array();
13477          if (($page < 1) OR ($page > $this->numpages)) {
13478              $sigapp['page'] = $this->page;
13479          } else {
13480              $sigapp['page'] = intval($page);
13481          }
13482          if (empty($name)) {
13483              $sigapp['name'] = 'Signature';
13484          } else {
13485              $sigapp['name'] = $name;
13486          }
13487          $a = $x * $this->k;
13488          $b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
13489          $c = $w * $this->k;
13490          $d = $h * $this->k;
13491          $sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
13492          return $sigapp;
13493      }
13494  
13495      /**
13496       * Enable document timestamping (requires the OpenSSL Library).
13497       * The trusted timestamping improve document security that means that no one should be able to change the document once it has been recorded.
13498       * Use with digital signature only!
13499       * @param $tsa_host (string) Time Stamping Authority (TSA) server (prefixed with 'https://')
13500       * @param $tsa_username (string) Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional)
13501       * @param $tsa_password (string) Specifies the password for TSA authorization (optional)
13502       * @param $tsa_cert (string) Specifies the location of TSA certificate for authorization (optional for cURL)
13503       * @public
13504       * @author Richard Stockinger
13505       * @since 6.0.090 (2014-06-16)
13506       */
13507  	public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') {
13508          $this->tsa_data = array();
13509          if (!function_exists('curl_init')) {
13510              $this->Error('Please enable cURL PHP extension!');
13511          }
13512          if (strlen($tsa_host) == 0) {
13513              $this->Error('Please specify the host of Time Stamping Authority (TSA)!');
13514          }
13515          $this->tsa_data['tsa_host'] = $tsa_host;
13516          if (is_file($tsa_username)) {
13517              $this->tsa_data['tsa_auth'] = $tsa_username;
13518          } else {
13519              $this->tsa_data['tsa_username'] = $tsa_username;
13520          }
13521          $this->tsa_data['tsa_password'] = $tsa_password;
13522          $this->tsa_data['tsa_cert'] = $tsa_cert;
13523          $this->tsa_timestamp = true;
13524      }
13525  
13526      /**
13527       * NOT YET IMPLEMENTED
13528       * Request TSA for a timestamp
13529       * @param $signature (string) Digital signature as binary string
13530       * @return (string) Timestamped digital signature
13531       * @protected
13532       * @author Richard Stockinger
13533       * @since 6.0.090 (2014-06-16)
13534       */
13535  	protected function applyTSA($signature) {
13536          if (!$this->tsa_timestamp) {
13537              return $signature;
13538          }
13539          //@TODO: implement this feature
13540          return $signature;
13541      }
13542  
13543      /**
13544       * Create a new page group.
13545       * NOTE: call this function before calling AddPage()
13546       * @param $page (int) starting group page (leave empty for next page).
13547       * @public
13548       * @since 3.0.000 (2008-03-27)
13549       */
13550  	public function startPageGroup($page='') {
13551          if (empty($page)) {
13552              $page = $this->page + 1;
13553          }
13554          $this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
13555      }
13556  
13557      /**
13558       * Set the starting page number.
13559       * @param $num (int) Starting page number.
13560       * @since 5.9.093 (2011-06-16)
13561       * @public
13562       */
13563  	public function setStartingPageNumber($num=1) {
13564          $this->starting_page_number = max(0, intval($num));
13565      }
13566  
13567      /**
13568       * Returns the string alias used right align page numbers.
13569       * If the current font is unicode type, the returned string wil contain an additional open curly brace.
13570       * @return string
13571       * @since 5.9.099 (2011-06-27)
13572       * @public
13573       */
13574  	public function getAliasRightShift() {
13575          // calculate aproximatively the ratio between widths of aliases and replacements.
13576          $ref = '{'.TCPDF_STATIC::$alias_right_shift.'}{'.TCPDF_STATIC::$alias_tot_pages.'}{'.TCPDF_STATIC::$alias_num_page.'}';
13577          $rep = str_repeat(' ', $this->GetNumChars($ref));
13578          $wrep = $this->GetStringWidth($rep);
13579          if ($wrep > 0) {
13580              $wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
13581          } else {
13582              $wdiff = 1;
13583          }
13584          $sdiff = sprintf('%F', $wdiff);
13585          $alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
13586          if ($this->isUnicodeFont()) {
13587              $alias = '{'.$alias;
13588          }
13589          return $alias;
13590      }
13591  
13592      /**
13593       * Returns the string alias used for the total number of pages.
13594       * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13595       * This alias will be replaced by the total number of pages in the document.
13596       * @return string
13597       * @since 4.0.018 (2008-08-08)
13598       * @public
13599       */
13600  	public function getAliasNbPages() {
13601          if ($this->isUnicodeFont()) {
13602              return '{'.TCPDF_STATIC::$alias_tot_pages.'}';
13603          }
13604          return TCPDF_STATIC::$alias_tot_pages;
13605      }
13606  
13607      /**
13608       * Returns the string alias used for the page number.
13609       * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13610       * This alias will be replaced by the page number.
13611       * @return string
13612       * @since 4.5.000 (2009-01-02)
13613       * @public
13614       */
13615  	public function getAliasNumPage() {
13616          if ($this->isUnicodeFont()) {
13617              return '{'.TCPDF_STATIC::$alias_num_page.'}';
13618          }
13619          return TCPDF_STATIC::$alias_num_page;
13620      }
13621  
13622      /**
13623       * Return the alias for the total number of pages in the current page group.
13624       * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13625       * This alias will be replaced by the total number of pages in this group.
13626       * @return alias of the current page group
13627       * @public
13628       * @since 3.0.000 (2008-03-27)
13629       */
13630  	public function getPageGroupAlias() {
13631          if ($this->isUnicodeFont()) {
13632              return '{'.TCPDF_STATIC::$alias_group_tot_pages.'}';
13633          }
13634          return TCPDF_STATIC::$alias_group_tot_pages;
13635      }
13636  
13637      /**
13638       * Return the alias for the page number on the current page group.
13639       * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13640       * This alias will be replaced by the page number (relative to the belonging group).
13641       * @return alias of the current page group
13642       * @public
13643       * @since 4.5.000 (2009-01-02)
13644       */
13645  	public function getPageNumGroupAlias() {
13646          if ($this->isUnicodeFont()) {
13647              return '{'.TCPDF_STATIC::$alias_group_num_page.'}';
13648          }
13649          return TCPDF_STATIC::$alias_group_num_page;
13650      }
13651  
13652      /**
13653       * Return the current page in the group.
13654       * @return current page in the group
13655       * @public
13656       * @since 3.0.000 (2008-03-27)
13657       */
13658  	public function getGroupPageNo() {
13659          return $this->pagegroups[$this->currpagegroup];
13660      }
13661  
13662      /**
13663       * Returns the current group page number formatted as a string.
13664       * @public
13665       * @since 4.3.003 (2008-11-18)
13666       * @see PaneNo(), formatPageNumber()
13667       */
13668  	public function getGroupPageNoFormatted() {
13669          return TCPDF_STATIC::formatPageNumber($this->getGroupPageNo());
13670      }
13671  
13672      /**
13673       * Returns the current page number formatted as a string.
13674       * @public
13675       * @since 4.2.005 (2008-11-06)
13676       * @see PaneNo(), formatPageNumber()
13677       */
13678  	public function PageNoFormatted() {
13679          return TCPDF_STATIC::formatPageNumber($this->PageNo());
13680      }
13681  
13682      /**
13683       * Put pdf layers.
13684       * @protected
13685       * @since 3.0.000 (2008-03-27)
13686       */
13687  	protected function _putocg() {
13688          if (empty($this->pdflayers)) {
13689              return;
13690          }
13691          foreach ($this->pdflayers as $key => $layer) {
13692               $this->pdflayers[$key]['objid'] = $this->_newobj();
13693               $out = '<< /Type /OCG';
13694               $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
13695               $out .= ' /Usage <<';
13696               if (isset($layer['print']) AND ($layer['print'] !== NULL)) {
13697                  $out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
13698               }
13699               $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
13700               $out .= ' >> >>';
13701               $out .= "\n".'endobj';
13702               $this->_out($out);
13703          }
13704      }
13705  
13706      /**
13707       * Start a new pdf layer.
13708       * @param $name (string) Layer name (only a-z letters and numbers). Leave empty for automatic name.
13709       * @param $print (boolean|null) Set to TRUE to print this layer, FALSE to not print and NULL to not set this option
13710       * @param $view (boolean) Set to true to view this layer.
13711       * @param $lock (boolean) If true lock the layer
13712       * @public
13713       * @since 5.9.102 (2011-07-13)
13714       */
13715  	public function startLayer($name='', $print=true, $view=true, $lock=true) {
13716          if ($this->state != 2) {
13717              return;
13718          }
13719          $layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
13720          if (empty($name)) {
13721              $name = $layer;
13722          } else {
13723              $name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
13724          }
13725          $this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view, 'lock' => $lock);
13726          $this->openMarkedContent = true;
13727          $this->_out('/OC /'.$layer.' BDC');
13728      }
13729  
13730      /**
13731       * End the current PDF layer.
13732       * @public
13733       * @since 5.9.102 (2011-07-13)
13734       */
13735  	public function endLayer() {
13736          if ($this->state != 2) {
13737              return;
13738          }
13739          if ($this->openMarkedContent) {
13740              // close existing open marked-content layer
13741              $this->_out('EMC');
13742              $this->openMarkedContent = false;
13743          }
13744      }
13745  
13746      /**
13747       * Set the visibility of the successive elements.
13748       * This can be useful, for instance, to put a background
13749       * image or color that will show on screen but won't print.
13750       * @param $v (string) visibility mode. Legal values are: all, print, screen or view.
13751       * @public
13752       * @since 3.0.000 (2008-03-27)
13753       */
13754  	public function setVisibility($v) {
13755          if ($this->state != 2) {
13756              return;
13757          }
13758          $this->endLayer();
13759          switch($v) {
13760              case 'print': {
13761                  $this->startLayer('Print', true, false);
13762                  break;
13763              }
13764              case 'view':
13765              case 'screen': {
13766                  $this->startLayer('View', false, true);
13767                  break;
13768              }
13769              case 'all': {
13770                  $this->_out('');
13771                  break;
13772              }
13773              default: {
13774                  $this->Error('Incorrect visibility: '.$v);
13775                  break;
13776              }
13777          }
13778      }
13779  
13780      /**
13781       * Add transparency parameters to the current extgstate
13782       * @param $parms (array) parameters
13783       * @return the number of extgstates
13784       * @protected
13785       * @since 3.0.000 (2008-03-27)
13786       */
13787  	protected function addExtGState($parms) {
13788          if ($this->pdfa_mode) {
13789              // transparencies are not allowed in PDF/A mode
13790              return;
13791          }
13792          // check if this ExtGState already exist
13793          foreach ($this->extgstates as $i => $ext) {
13794              if ($ext['parms'] == $parms) {
13795                  if ($this->inxobj) {
13796                      // we are inside an XObject template
13797                      $this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
13798                  }
13799                  // return reference to existing ExtGState
13800                  return $i;
13801              }
13802          }
13803          $n = (count($this->extgstates) + 1);
13804          $this->extgstates[$n] = array('parms' => $parms);
13805          if ($this->inxobj) {
13806              // we are inside an XObject template
13807              $this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
13808          }
13809          return $n;
13810      }
13811  
13812      /**
13813       * Add an extgstate
13814       * @param $gs (array) extgstate
13815       * @protected
13816       * @since 3.0.000 (2008-03-27)
13817       */
13818  	protected function setExtGState($gs) {
13819          if ($this->pdfa_mode OR ($this->state != 2)) {
13820              // transparency is not allowed in PDF/A mode
13821              return;
13822          }
13823          $this->_out(sprintf('/GS%d gs', $gs));
13824      }
13825  
13826      /**
13827       * Put extgstates for object transparency
13828       * @protected
13829       * @since 3.0.000 (2008-03-27)
13830       */
13831  	protected function _putextgstates() {
13832          foreach ($this->extgstates as $i => $ext) {
13833              $this->extgstates[$i]['n'] = $this->_newobj();
13834              $out = '<< /Type /ExtGState';
13835              foreach ($ext['parms'] as $k => $v) {
13836                  if (is_float($v)) {
13837                      $v = sprintf('%F', $v);
13838                  } elseif ($v === true) {
13839                      $v = 'true';
13840                  } elseif ($v === false) {
13841                      $v = 'false';
13842                  }
13843                  $out .= ' /'.$k.' '.$v;
13844              }
13845              $out .= ' >>';
13846              $out .= "\n".'endobj';
13847              $this->_out($out);
13848          }
13849      }
13850  
13851      /**
13852       * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
13853       * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13854       * @param $stroking (boolean) If true apply overprint for stroking operations.
13855       * @param $nonstroking (boolean) If true apply overprint for painting operations other than stroking.
13856       * @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).
13857       * @public
13858       * @since 5.9.152 (2012-03-23)
13859       */
13860  	public function setOverprint($stroking=true, $nonstroking='', $mode=0) {
13861          if ($this->state != 2) {
13862              return;
13863          }
13864          $stroking = $stroking ? true : false;
13865          if (TCPDF_STATIC::empty_string($nonstroking)) {
13866              // default value if not set
13867              $nonstroking = $stroking;
13868          } else {
13869              $nonstroking = $nonstroking ? true : false;
13870          }
13871          if (($mode != 0) AND ($mode != 1)) {
13872              $mode = 0;
13873          }
13874          $this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
13875          $gs = $this->addExtGState($this->overprint);
13876          $this->setExtGState($gs);
13877      }
13878  
13879      /**
13880       * Get the overprint mode array (OP, op, OPM).
13881       * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13882       * @return array.
13883       * @public
13884       * @since 5.9.152 (2012-03-23)
13885       */
13886  	public function getOverprint() {
13887          return $this->overprint;
13888      }
13889  
13890      /**
13891       * Set alpha for stroking (CA) and non-stroking (ca) operations.
13892       * @param $stroking (float) Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
13893       * @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
13894       * @param $nonstroking (float) Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
13895       * @param $ais (boolean)
13896       * @public
13897       * @since 3.0.000 (2008-03-27)
13898       */
13899  	public function setAlpha($stroking=1, $bm='Normal', $nonstroking='', $ais=false) {
13900          if ($this->pdfa_mode) {
13901              // transparency is not allowed in PDF/A mode
13902              return;
13903          }
13904          $stroking = floatval($stroking);
13905          if (TCPDF_STATIC::empty_string($nonstroking)) {
13906              // default value if not set
13907              $nonstroking = $stroking;
13908          } else {
13909              $nonstroking = floatval($nonstroking);
13910          }
13911          if ($bm[0] == '/') {
13912              // remove trailing slash
13913              $bm = substr($bm, 1);
13914          }
13915          if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
13916              $bm = 'Normal';
13917          }
13918          $ais = $ais ? true : false;
13919          $this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
13920          $gs = $this->addExtGState($this->alpha);
13921          $this->setExtGState($gs);
13922      }
13923  
13924      /**
13925       * Get the alpha mode array (CA, ca, BM, AIS).
13926       * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13927       * @return array.
13928       * @public
13929       * @since 5.9.152 (2012-03-23)
13930       */
13931  	public function getAlpha() {
13932          return $this->alpha;
13933      }
13934  
13935      /**
13936       * Set the default JPEG compression quality (1-100)
13937       * @param $quality (int) JPEG quality, integer between 1 and 100
13938       * @public
13939       * @since 3.0.000 (2008-03-27)
13940       */
13941  	public function setJPEGQuality($quality) {
13942          if (($quality < 1) OR ($quality > 100)) {
13943              $quality = 75;
13944          }
13945          $this->jpeg_quality = intval($quality);
13946      }
13947  
13948      /**
13949       * Set the default number of columns in a row for HTML tables.
13950       * @param $cols (int) number of columns
13951       * @public
13952       * @since 3.0.014 (2008-06-04)
13953       */
13954  	public function setDefaultTableColumns($cols=4) {
13955          $this->default_table_columns = intval($cols);
13956      }
13957  
13958      /**
13959       * Set the height of the cell (line height) respect the font height.
13960       * @param $h (int) cell proportion respect font height (typical value = 1.25).
13961       * @public
13962       * @since 3.0.014 (2008-06-04)
13963       */
13964  	public function setCellHeightRatio($h) {
13965          $this->cell_height_ratio = $h;
13966      }
13967  
13968      /**
13969       * return the height of cell repect font height.
13970       * @public
13971       * @since 4.0.012 (2008-07-24)
13972       */
13973  	public function getCellHeightRatio() {
13974          return $this->cell_height_ratio;
13975      }
13976  
13977      /**
13978       * Set the PDF version (check PDF reference for valid values).
13979       * @param $version (string) PDF document version.
13980       * @public
13981       * @since 3.1.000 (2008-06-09)
13982       */
13983  	public function setPDFVersion($version='1.7') {
13984          if ($this->pdfa_mode) {
13985              // PDF/A mode
13986              $this->PDFVersion = '1.4';
13987          } else {
13988              $this->PDFVersion = $version;
13989          }
13990      }
13991  
13992      /**
13993       * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
13994       * (see Section 8.1 of PDF reference, "Viewer Preferences").
13995       * <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>
13996       * @param $preferences (array) array of options.
13997       * @author Nicola Asuni
13998       * @public
13999       * @since 3.1.000 (2008-06-09)
14000       */
14001  	public function setViewerPreferences($preferences) {
14002          $this->viewer_preferences = $preferences;
14003      }
14004  
14005      /**
14006       * Paints color transition registration bars
14007       * @param $x (float) abscissa of the top left corner of the rectangle.
14008       * @param $y (float) ordinate of the top left corner of the rectangle.
14009       * @param $w (float) width of the rectangle.
14010       * @param $h (float) height of the rectangle.
14011       * @param $transition (boolean) if true prints tcolor transitions to white.
14012       * @param $vertical (boolean) if true prints bar vertically.
14013       * @param $colors (string) colors to print separated by comma. Valid values are: A,W,R,G,B,C,M,Y,K,RGB,CMYK,ALL,ALLSPOT,<SPOT_COLOR_NAME>. Where: A = grayscale black, W = grayscale white, R = RGB red, G RGB green, B RGB blue, C = CMYK cyan, M = CMYK magenta, Y = CMYK yellow, K = CMYK key/black, RGB = RGB registration color, CMYK = CMYK registration color, ALL = Spot registration color, ALLSPOT = print all defined spot colors, <SPOT_COLOR_NAME> = name of the spot color to print.
14014       * @author Nicola Asuni
14015       * @since 4.9.000 (2010-03-26)
14016       * @public
14017       */
14018  	public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
14019          if (strpos($colors, 'ALLSPOT') !== false) {
14020              // expand spot colors
14021              $spot_colors = '';
14022              foreach ($this->spot_colors as $spot_color_name => $v) {
14023                  $spot_colors .= ','.$spot_color_name;
14024              }
14025              if (!empty($spot_colors)) {
14026                  $spot_colors = substr($spot_colors, 1);
14027                  $colors = str_replace('ALLSPOT', $spot_colors, $colors);
14028              } else {
14029                  $colors = str_replace('ALLSPOT', 'NONE', $colors);
14030              }
14031          }
14032          $bars = explode(',', $colors);
14033          $numbars = count($bars); // number of bars to print
14034          if ($numbars <= 0) {
14035              return;
14036          }
14037          // set bar measures
14038          if ($vertical) {
14039              $coords = array(0, 0, 0, 1);
14040              $wb = $w / $numbars; // bar width
14041              $hb = $h; // bar height
14042              $xd = $wb; // delta x
14043              $yd = 0; // delta y
14044          } else {
14045              $coords = array(1, 0, 0, 0);
14046              $wb = $w; // bar width
14047              $hb = $h / $numbars; // bar height
14048              $xd = 0; // delta x
14049              $yd = $hb; // delta y
14050          }
14051          $xb = $x;
14052          $yb = $y;
14053          foreach ($bars as $col) {
14054              switch ($col) {
14055                  // set transition colors
14056                  case 'A': { // BLACK (GRAYSCALE)
14057                      $col_a = array(255);
14058                      $col_b = array(0);
14059                      break;
14060                  }
14061                  case 'W': { // WHITE (GRAYSCALE)
14062                      $col_a = array(0);
14063                      $col_b = array(255);
14064                      break;
14065                  }
14066                  case 'R': { // RED (RGB)
14067                      $col_a = array(255,255,255);
14068                      $col_b = array(255,0,0);
14069                      break;
14070                  }
14071                  case 'G': { // GREEN (RGB)
14072                      $col_a = array(255,255,255);
14073                      $col_b = array(0,255,0);
14074                      break;
14075                  }
14076                  case 'B': { // BLUE (RGB)
14077                      $col_a = array(255,255,255);
14078                      $col_b = array(0,0,255);
14079                      break;
14080                  }
14081                  case 'C': { // CYAN (CMYK)
14082                      $col_a = array(0,0,0,0);
14083                      $col_b = array(100,0,0,0);
14084                      break;
14085                  }
14086                  case 'M': { // MAGENTA (CMYK)
14087                      $col_a = array(0,0,0,0);
14088                      $col_b = array(0,100,0,0);
14089                      break;
14090                  }
14091                  case 'Y': { // YELLOW (CMYK)
14092                      $col_a = array(0,0,0,0);
14093                      $col_b = array(0,0,100,0);
14094                      break;
14095                  }
14096                  case 'K': { // KEY - BLACK (CMYK)
14097                      $col_a = array(0,0,0,0);
14098                      $col_b = array(0,0,0,100);
14099                      break;
14100                  }
14101                  case 'RGB': { // BLACK REGISTRATION (RGB)
14102                      $col_a = array(255,255,255);
14103                      $col_b = array(0,0,0);
14104                      break;
14105                  }
14106                  case 'CMYK': { // BLACK REGISTRATION (CMYK)
14107                      $col_a = array(0,0,0,0);
14108                      $col_b = array(100,100,100,100);
14109                      break;
14110                  }
14111                  case 'ALL': { // SPOT COLOR REGISTRATION
14112                      $col_a = array(0,0,0,0,'None');
14113                      $col_b = array(100,100,100,100,'All');
14114                      break;
14115                  }
14116                  case 'NONE': { // SKIP THIS COLOR
14117                      $col_a = array(0,0,0,0,'None');
14118                      $col_b = array(0,0,0,0,'None');
14119                      break;
14120                  }
14121                  default: { // SPECIFIC SPOT COLOR NAME
14122                      $col_a = array(0,0,0,0,'None');
14123                      $col_b = TCPDF_COLORS::getSpotColor($col, $this->spot_colors);
14124                      if ($col_b === false) {
14125                          // in case of error defaults to the registration color
14126                          $col_b = array(100,100,100,100,'All');
14127                      }
14128                      break;
14129                  }
14130              }
14131              if ($col != 'NONE') {
14132                  if ($transition) {
14133                      // color gradient
14134                      $this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
14135                  } else {
14136                      $this->SetFillColorArray($col_b);
14137                      // colored rectangle
14138                      $this->Rect($xb, $yb, $wb, $hb, 'F', array());
14139                  }
14140                  $xb += $xd;
14141                  $yb += $yd;
14142              }
14143          }
14144      }
14145  
14146      /**
14147       * Paints crop marks.
14148       * @param $x (float) abscissa of the crop mark center.
14149       * @param $y (float) ordinate of the crop mark center.
14150       * @param $w (float) width of the crop mark.
14151       * @param $h (float) height of the crop mark.
14152       * @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.
14153       * @param $color (array) crop mark color (default spot registration color).
14154       * @author Nicola Asuni
14155       * @since 4.9.000 (2010-03-26)
14156       * @public
14157       */
14158  	public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
14159          $this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
14160          $type = strtoupper($type);
14161          $type = preg_replace('/[^A-Z\-\,]*/', '', $type);
14162          // split type in single components
14163          $type = str_replace('-', ',', $type);
14164          $type = str_replace('TL', 'T,L', $type);
14165          $type = str_replace('TR', 'T,R', $type);
14166          $type = str_replace('BL', 'F,L', $type);
14167          $type = str_replace('BR', 'F,R', $type);
14168          $type = str_replace('A', 'T,L', $type);
14169          $type = str_replace('B', 'T,R', $type);
14170          $type = str_replace('T,RO', 'BO', $type);
14171          $type = str_replace('C', 'F,L', $type);
14172          $type = str_replace('D', 'F,R', $type);
14173          $crops = explode(',', strtoupper($type));
14174          // remove duplicates
14175          $crops = array_unique($crops);
14176          $dw = ($w / 4); // horizontal space to leave before the intersection point
14177          $dh = ($h / 4); // vertical space to leave before the intersection point
14178          foreach ($crops as $crop) {
14179              switch ($crop) {
14180                  case 'T':
14181                  case 'TOP': {
14182                      $x1 = $x;
14183                      $y1 = ($y - $h);
14184                      $x2 = $x;
14185                      $y2 = ($y - $dh);
14186                      break;
14187                  }
14188                  case 'F':
14189                  case 'BOTTOM': {
14190                      $x1 = $x;
14191                      $y1 = ($y + $dh);
14192                      $x2 = $x;
14193                      $y2 = ($y + $h);
14194                      break;
14195                  }
14196                  case 'L':
14197                  case 'LEFT': {
14198                      $x1 = ($x - $w);
14199                      $y1 = $y;
14200                      $x2 = ($x - $dw);
14201                      $y2 = $y;
14202                      break;
14203                  }
14204                  case 'R':
14205                  case 'RIGHT': {
14206                      $x1 = ($x + $dw);
14207                      $y1 = $y;
14208                      $x2 = ($x + $w);
14209                      $y2 = $y;
14210                      break;
14211                  }
14212              }
14213              $this->Line($x1, $y1, $x2, $y2);
14214          }
14215      }
14216  
14217      /**
14218       * Paints a registration mark
14219       * @param $x (float) abscissa of the registration mark center.
14220       * @param $y (float) ordinate of the registration mark center.
14221       * @param $r (float) radius of the crop mark.
14222       * @param $double (boolean) if true print two concentric crop marks.
14223       * @param $cola (array) crop mark color (default spot registration color 'All').
14224       * @param $colb (array) second crop mark color (default spot registration color 'None').
14225       * @author Nicola Asuni
14226       * @since 4.9.000 (2010-03-26)
14227       * @public
14228       */
14229  	public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
14230          $line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
14231          $this->SetFillColorArray($cola);
14232          $this->PieSector($x, $y, $r, 90, 180, 'F');
14233          $this->PieSector($x, $y, $r, 270, 360, 'F');
14234          $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14235          if ($double) {
14236              $ri = $r * 0.5;
14237              $this->SetFillColorArray($colb);
14238              $this->PieSector($x, $y, $ri, 90, 180, 'F');
14239              $this->PieSector($x, $y, $ri, 270, 360, 'F');
14240              $this->SetFillColorArray($cola);
14241              $this->PieSector($x, $y, $ri, 0, 90, 'F');
14242              $this->PieSector($x, $y, $ri, 180, 270, 'F');
14243              $this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
14244          }
14245      }
14246  
14247      /**
14248       * Paints a CMYK registration mark
14249       * @param $x (float) abscissa of the registration mark center.
14250       * @param $y (float) ordinate of the registration mark center.
14251       * @param $r (float) radius of the crop mark.
14252       * @author Nicola Asuni
14253       * @since 6.0.038 (2013-09-30)
14254       * @public
14255       */
14256  	public function registrationMarkCMYK($x, $y, $r) {
14257          // line width
14258          $lw = max((0.5 / $this->k),($r / 8));
14259          // internal radius
14260          $ri = ($r * 0.6);
14261          // external radius
14262          $re = ($r * 1.3);
14263          // Cyan
14264          $this->SetFillColorArray(array(100,0,0,0));
14265          $this->PieSector($x, $y, $ri, 270, 360, 'F');
14266          // Magenta
14267          $this->SetFillColorArray(array(0,100,0,0));
14268          $this->PieSector($x, $y, $ri, 0, 90, 'F');
14269          // Yellow
14270          $this->SetFillColorArray(array(0,0,100,0));
14271          $this->PieSector($x, $y, $ri, 90, 180, 'F');
14272          // Key - black
14273          $this->SetFillColorArray(array(0,0,0,100));
14274          $this->PieSector($x, $y, $ri, 180, 270, 'F');
14275          // registration color
14276          $line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
14277          $this->SetFillColorArray(array(100,100,100,100,'All'));
14278          // external circle
14279          $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14280          // cross lines
14281          $this->Line($x, ($y - $re), $x, ($y - $ri));
14282          $this->Line($x, ($y + $ri), $x, ($y + $re));
14283          $this->Line(($x - $re), $y, ($x - $ri), $y);
14284          $this->Line(($x + $ri), $y, ($x + $re), $y);
14285      }
14286  
14287      /**
14288       * Paints a linear colour gradient.
14289       * @param $x (float) abscissa of the top left corner of the rectangle.
14290       * @param $y (float) ordinate of the top left corner of the rectangle.
14291       * @param $w (float) width of the rectangle.
14292       * @param $h (float) height of the rectangle.
14293       * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14294       * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14295       * @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).
14296       * @author Andreas W\FCrmser, Nicola Asuni
14297       * @since 3.1.000 (2008-06-09)
14298       * @public
14299       */
14300  	public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
14301          $this->Clip($x, $y, $w, $h);
14302          $this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14303      }
14304  
14305      /**
14306       * Paints a radial colour gradient.
14307       * @param $x (float) abscissa of the top left corner of the rectangle.
14308       * @param $y (float) ordinate of the top left corner of the rectangle.
14309       * @param $w (float) width of the rectangle.
14310       * @param $h (float) height of the rectangle.
14311       * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14312       * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14313       * @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.
14314       * @author Andreas W\FCrmser, Nicola Asuni
14315       * @since 3.1.000 (2008-06-09)
14316       * @public
14317       */
14318  	public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
14319          $this->Clip($x, $y, $w, $h);
14320          $this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14321      }
14322  
14323      /**
14324       * Paints a coons patch mesh.
14325       * @param $x (float) abscissa of the top left corner of the rectangle.
14326       * @param $y (float) ordinate of the top left corner of the rectangle.
14327       * @param $w (float) width of the rectangle.
14328       * @param $h (float) height of the rectangle.
14329       * @param $col1 (array) first color (lower left corner) (RGB components).
14330       * @param $col2 (array) second color (lower right corner) (RGB components).
14331       * @param $col3 (array) third color (upper right corner) (RGB components).
14332       * @param $col4 (array) fourth color (upper left corner) (RGB components).
14333       * @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>
14334       * @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
14335       * @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
14336       * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14337       * @author Andreas W\FCrmser, Nicola Asuni
14338       * @since 3.1.000 (2008-06-09)
14339       * @public
14340       */
14341  	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) {
14342          if ($this->pdfa_mode OR ($this->state != 2)) {
14343              return;
14344          }
14345          $this->Clip($x, $y, $w, $h);
14346          $n = count($this->gradients) + 1;
14347          $this->gradients[$n] = array();
14348          $this->gradients[$n]['type'] = 6; //coons patch mesh
14349          $this->gradients[$n]['coords'] = array();
14350          $this->gradients[$n]['antialias'] = $antialias;
14351          $this->gradients[$n]['colors'] = array();
14352          $this->gradients[$n]['transparency'] = false;
14353          //check the coords array if it is the simple array or the multi patch array
14354          if (!isset($coords[0]['f'])) {
14355              //simple array -> convert to multi patch array
14356              if (!isset($col1[1])) {
14357                  $col1[1] = $col1[2] = $col1[0];
14358              }
14359              if (!isset($col2[1])) {
14360                  $col2[1] = $col2[2] = $col2[0];
14361              }
14362              if (!isset($col3[1])) {
14363                  $col3[1] = $col3[2] = $col3[0];
14364              }
14365              if (!isset($col4[1])) {
14366                  $col4[1] = $col4[2] = $col4[0];
14367              }
14368              $patch_array[0]['f'] = 0;
14369              $patch_array[0]['points'] = $coords;
14370              $patch_array[0]['colors'][0]['r'] = $col1[0];
14371              $patch_array[0]['colors'][0]['g'] = $col1[1];
14372              $patch_array[0]['colors'][0]['b'] = $col1[2];
14373              $patch_array[0]['colors'][1]['r'] = $col2[0];
14374              $patch_array[0]['colors'][1]['g'] = $col2[1];
14375              $patch_array[0]['colors'][1]['b'] = $col2[2];
14376              $patch_array[0]['colors'][2]['r'] = $col3[0];
14377              $patch_array[0]['colors'][2]['g'] = $col3[1];
14378              $patch_array[0]['colors'][2]['b'] = $col3[2];
14379              $patch_array[0]['colors'][3]['r'] = $col4[0];
14380              $patch_array[0]['colors'][3]['g'] = $col4[1];
14381              $patch_array[0]['colors'][3]['b'] = $col4[2];
14382          } else {
14383              //multi patch array
14384              $patch_array = $coords;
14385          }
14386          $bpcd = 65535; //16 bits per coordinate
14387          //build the data stream
14388          $this->gradients[$n]['stream'] = '';
14389          $count_patch = count($patch_array);
14390          for ($i=0; $i < $count_patch; ++$i) {
14391              $this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
14392              $count_points = count($patch_array[$i]['points']);
14393              for ($j=0; $j < $count_points; ++$j) {
14394                  //each point as 16 bit
14395                  $patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
14396                  if ($patch_array[$i]['points'][$j] < 0) {
14397                      $patch_array[$i]['points'][$j] = 0;
14398                  }
14399                  if ($patch_array[$i]['points'][$j] > $bpcd) {
14400                      $patch_array[$i]['points'][$j] = $bpcd;
14401                  }
14402                  $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
14403                  $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
14404              }
14405              $count_cols = count($patch_array[$i]['colors']);
14406              for ($j=0; $j < $count_cols; ++$j) {
14407                  //each color component as 8 bit
14408                  $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
14409                  $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
14410                  $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
14411              }
14412          }
14413          //paint the gradient
14414          $this->_out('/Sh'.$n.' sh');
14415          //restore previous Graphic State
14416          $this->_outRestoreGraphicsState();
14417          if ($this->inxobj) {
14418              // we are inside an XObject template
14419              $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14420          }
14421      }
14422  
14423      /**
14424       * Set a rectangular clipping area.
14425       * @param $x (float) abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
14426       * @param $y (float) ordinate of the top left corner of the rectangle.
14427       * @param $w (float) width of the rectangle.
14428       * @param $h (float) height of the rectangle.
14429       * @author Andreas W\FCrmser, Nicola Asuni
14430       * @since 3.1.000 (2008-06-09)
14431       * @protected
14432       */
14433  	protected function Clip($x, $y, $w, $h) {
14434          if ($this->state != 2) {
14435               return;
14436          }
14437          if ($this->rtl) {
14438              $x = $this->w - $x - $w;
14439          }
14440          //save current Graphic State
14441          $s = 'q';
14442          //set clipping area
14443          $s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
14444          //set up transformation matrix for gradient
14445          $s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
14446          $this->_out($s);
14447      }
14448  
14449      /**
14450       * Output gradient.
14451       * @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)
14452       * @param $coords (array) array of coordinates.
14453       * @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).
14454       * @param $background (array) An array of colour components appropriate to the colour space, specifying a single background colour value.
14455       * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14456       * @author Nicola Asuni
14457       * @since 3.1.000 (2008-06-09)
14458       * @public
14459       */
14460  	public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
14461          if ($this->pdfa_mode OR ($this->state != 2)) {
14462              return;
14463          }
14464          $n = count($this->gradients) + 1;
14465          $this->gradients[$n] = array();
14466          $this->gradients[$n]['type'] = $type;
14467          $this->gradients[$n]['coords'] = $coords;
14468          $this->gradients[$n]['antialias'] = $antialias;
14469          $this->gradients[$n]['colors'] = array();
14470          $this->gradients[$n]['transparency'] = false;
14471          // color space
14472          $numcolspace = count($stops[0]['color']);
14473          $bcolor = array_values($background);
14474          switch($numcolspace) {
14475              case 5:   // SPOT
14476              case 4: { // CMYK
14477                  $this->gradients[$n]['colspace'] = 'DeviceCMYK';
14478                  if (!empty($background)) {
14479                      $this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
14480                  }
14481                  break;
14482              }
14483              case 3: { // RGB
14484                  $this->gradients[$n]['colspace'] = 'DeviceRGB';
14485                  if (!empty($background)) {
14486                      $this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
14487                  }
14488                  break;
14489              }
14490              case 1: { // GRAY SCALE
14491                  $this->gradients[$n]['colspace'] = 'DeviceGray';
14492                  if (!empty($background)) {
14493                      $this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
14494                  }
14495                  break;
14496              }
14497          }
14498          $num_stops = count($stops);
14499          $last_stop_id = $num_stops - 1;
14500          foreach ($stops as $key => $stop) {
14501              $this->gradients[$n]['colors'][$key] = array();
14502              // offset represents a location along the gradient vector
14503              if (isset($stop['offset'])) {
14504                  $this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
14505              } else {
14506                  if ($key == 0) {
14507                      $this->gradients[$n]['colors'][$key]['offset'] = 0;
14508                  } elseif ($key == $last_stop_id) {
14509                      $this->gradients[$n]['colors'][$key]['offset'] = 1;
14510                  } else {
14511                      $offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
14512                      $this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
14513                  }
14514              }
14515              if (isset($stop['opacity'])) {
14516                  $this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
14517                  if ((!$this->pdfa_mode) AND ($stop['opacity'] < 1)) {
14518                      $this->gradients[$n]['transparency'] = true;
14519                  }
14520              } else {
14521                  $this->gradients[$n]['colors'][$key]['opacity'] = 1;
14522              }
14523              // exponent for the exponential interpolation function
14524              if (isset($stop['exponent'])) {
14525                  $this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
14526              } else {
14527                  $this->gradients[$n]['colors'][$key]['exponent'] = 1;
14528              }
14529              // set colors
14530              $color = array_values($stop['color']);
14531              switch($numcolspace) {
14532                  case 5:   // SPOT
14533                  case 4: { // CMYK
14534                      $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
14535                      break;
14536                  }
14537                  case 3: { // RGB
14538                      $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
14539                      break;
14540                  }
14541                  case 1: { // GRAY SCALE
14542                      $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
14543                      break;
14544                  }
14545              }
14546          }
14547          if ($this->gradients[$n]['transparency']) {
14548              // paint luminosity gradient
14549              $this->_out('/TGS'.$n.' gs');
14550          }
14551          //paint the gradient
14552          $this->_out('/Sh'.$n.' sh');
14553          //restore previous Graphic State
14554          $this->_outRestoreGraphicsState();
14555          if ($this->inxobj) {
14556              // we are inside an XObject template
14557              $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14558          }
14559      }
14560  
14561      /**
14562       * Output gradient shaders.
14563       * @author Nicola Asuni
14564       * @since 3.1.000 (2008-06-09)
14565       * @protected
14566       */
14567  	function _putshaders() {
14568          if ($this->pdfa_mode) {
14569              return;
14570          }
14571          $idt = count($this->gradients); //index for transparency gradients
14572          foreach ($this->gradients as $id => $grad) {
14573              if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
14574                  $fc = $this->_newobj();
14575                  $out = '<<';
14576                  $out .= ' /FunctionType 3';
14577                  $out .= ' /Domain [0 1]';
14578                  $functions = '';
14579                  $bounds = '';
14580                  $encode = '';
14581                  $i = 1;
14582                  $num_cols = count($grad['colors']);
14583                  $lastcols = $num_cols - 1;
14584                  for ($i = 1; $i < $num_cols; ++$i) {
14585                      $functions .= ($fc + $i).' 0 R ';
14586                      if ($i < $lastcols) {
14587                          $bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
14588                      }
14589                      $encode .= '0 1 ';
14590                  }
14591                  $out .= ' /Functions ['.trim($functions).']';
14592                  $out .= ' /Bounds ['.trim($bounds).']';
14593                  $out .= ' /Encode ['.trim($encode).']';
14594                  $out .= ' >>';
14595                  $out .= "\n".'endobj';
14596                  $this->_out($out);
14597                  for ($i = 1; $i < $num_cols; ++$i) {
14598                      $this->_newobj();
14599                      $out = '<<';
14600                      $out .= ' /FunctionType 2';
14601                      $out .= ' /Domain [0 1]';
14602                      $out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
14603                      $out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
14604                      $out .= ' /N '.$grad['colors'][$i]['exponent'];
14605                      $out .= ' >>';
14606                      $out .= "\n".'endobj';
14607                      $this->_out($out);
14608                  }
14609                  // set transparency functions
14610                  if ($grad['transparency']) {
14611                      $ft = $this->_newobj();
14612                      $out = '<<';
14613                      $out .= ' /FunctionType 3';
14614                      $out .= ' /Domain [0 1]';
14615                      $functions = '';
14616                      $i = 1;
14617                      $num_cols = count($grad['colors']);
14618                      for ($i = 1; $i < $num_cols; ++$i) {
14619                          $functions .= ($ft + $i).' 0 R ';
14620                      }
14621                      $out .= ' /Functions ['.trim($functions).']';
14622                      $out .= ' /Bounds ['.trim($bounds).']';
14623                      $out .= ' /Encode ['.trim($encode).']';
14624                      $out .= ' >>';
14625                      $out .= "\n".'endobj';
14626                      $this->_out($out);
14627                      for ($i = 1; $i < $num_cols; ++$i) {
14628                          $this->_newobj();
14629                          $out = '<<';
14630                          $out .= ' /FunctionType 2';
14631                          $out .= ' /Domain [0 1]';
14632                          $out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
14633                          $out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
14634                          $out .= ' /N '.$grad['colors'][$i]['exponent'];
14635                          $out .= ' >>';
14636                          $out .= "\n".'endobj';
14637                          $this->_out($out);
14638                      }
14639                  }
14640              }
14641              // set shading object
14642              $this->_newobj();
14643              $out = '<< /ShadingType '.$grad['type'];
14644              if (isset($grad['colspace'])) {
14645                  $out .= ' /ColorSpace /'.$grad['colspace'];
14646              } else {
14647                  $out .= ' /ColorSpace /DeviceRGB';
14648              }
14649              if (isset($grad['background']) AND !empty($grad['background'])) {
14650                  $out .= ' /Background ['.$grad['background'].']';
14651              }
14652              if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
14653                  $out .= ' /AntiAlias true';
14654              }
14655              if ($grad['type'] == 2) {
14656                  $out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
14657                  $out .= ' /Domain [0 1]';
14658                  $out .= ' /Function '.$fc.' 0 R';
14659                  $out .= ' /Extend [true true]';
14660                  $out .= ' >>';
14661              } elseif ($grad['type'] == 3) {
14662                  //x0, y0, r0, x1, y1, r1
14663                  //at this this time radius of inner circle is 0
14664                  $out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
14665                  $out .= ' /Domain [0 1]';
14666                  $out .= ' /Function '.$fc.' 0 R';
14667                  $out .= ' /Extend [true true]';
14668                  $out .= ' >>';
14669              } elseif ($grad['type'] == 6) {
14670                  $out .= ' /BitsPerCoordinate 16';
14671                  $out .= ' /BitsPerComponent 8';
14672                  $out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
14673                  $out .= ' /BitsPerFlag 8';
14674                  $stream = $this->_getrawstream($grad['stream']);
14675                  $out .= ' /Length '.strlen($stream);
14676                  $out .= ' >>';
14677                  $out .= ' stream'."\n".$stream."\n".'endstream';
14678              }
14679              $out .= "\n".'endobj';
14680              $this->_out($out);
14681              if ($grad['transparency']) {
14682                  $shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
14683                  $shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
14684              }
14685              $this->gradients[$id]['id'] = $this->n;
14686              // set pattern object
14687              $this->_newobj();
14688              $out = '<< /Type /Pattern /PatternType 2';
14689              $out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
14690              $out .= ' >>';
14691              $out .= "\n".'endobj';
14692              $this->_out($out);
14693              $this->gradients[$id]['pattern'] = $this->n;
14694              // set shading and pattern for transparency mask
14695              if ($grad['transparency']) {
14696                  // luminosity pattern
14697                  $idgs = $id + $idt;
14698                  $this->_newobj();
14699                  $this->_out($shading_transparency);
14700                  $this->gradients[$idgs]['id'] = $this->n;
14701                  $this->_newobj();
14702                  $out = '<< /Type /Pattern /PatternType 2';
14703                  $out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
14704                  $out .= ' >>';
14705                  $out .= "\n".'endobj';
14706                  $this->_out($out);
14707                  $this->gradients[$idgs]['pattern'] = $this->n;
14708                  // luminosity XObject
14709                  $oid = $this->_newobj();
14710                  $this->xobjects['LX'.$oid] = array('n' => $oid);
14711                  $filter = '';
14712                  $stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
14713                  if ($this->compress) {
14714                      $filter = ' /Filter /FlateDecode';
14715                      $stream = gzcompress($stream);
14716                  }
14717                  $stream = $this->_getrawstream($stream);
14718                  $out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
14719                  $out .= ' /Length '.strlen($stream);
14720                  $rect = sprintf('%F %F', $this->wPt, $this->hPt);
14721                  $out .= ' /BBox [0 0 '.$rect.']';
14722                  $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
14723                  $out .= ' /Resources <<';
14724                  $out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
14725                  $out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
14726                  $out .= ' >>';
14727                  $out .= ' >> ';
14728                  $out .= ' stream'."\n".$stream."\n".'endstream';
14729                  $out .= "\n".'endobj';
14730                  $this->_out($out);
14731                  // SMask
14732                  $this->_newobj();
14733                  $out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
14734                  $this->_out($out);
14735                  // ExtGState
14736                  $this->_newobj();
14737                  $out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
14738                  $this->_out($out);
14739                  $this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
14740              }
14741          }
14742      }
14743  
14744      /**
14745       * Draw the sector of a circle.
14746       * It can be used for instance to render pie charts.
14747       * @param $xc (float) abscissa of the center.
14748       * @param $yc (float) ordinate of the center.
14749       * @param $r (float) radius.
14750       * @param $a (float) start angle (in degrees).
14751       * @param $b (float) end angle (in degrees).
14752       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14753       * @param $cw: (float) indicates whether to go clockwise (default: true).
14754       * @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.
14755       * @author Maxime Delorme, Nicola Asuni
14756       * @since 3.1.000 (2008-06-09)
14757       * @public
14758       */
14759  	public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
14760          $this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
14761      }
14762  
14763      /**
14764       * Draw the sector of an ellipse.
14765       * It can be used for instance to render pie charts.
14766       * @param $xc (float) abscissa of the center.
14767       * @param $yc (float) ordinate of the center.
14768       * @param $rx (float) the x-axis radius.
14769       * @param $ry (float) the y-axis radius.
14770       * @param $a (float) start angle (in degrees).
14771       * @param $b (float) end angle (in degrees).
14772       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14773       * @param $cw: (float) indicates whether to go clockwise.
14774       * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
14775       * @param $nc (integer) Number of curves used to draw a 90 degrees portion of arc.
14776       * @author Maxime Delorme, Nicola Asuni
14777       * @since 3.1.000 (2008-06-09)
14778       * @public
14779       */
14780  	public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
14781          if ($this->state != 2) {
14782               return;
14783          }
14784          if ($this->rtl) {
14785              $xc = ($this->w - $xc);
14786          }
14787          $op = TCPDF_STATIC::getPathPaintOperator($style);
14788          if ($op == 'f') {
14789              $line_style = array();
14790          }
14791          if ($cw) {
14792              $d = $b;
14793              $b = (360 - $a + $o);
14794              $a = (360 - $d + $o);
14795          } else {
14796              $b += $o;
14797              $a += $o;
14798          }
14799          $this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
14800          $this->_out($op);
14801      }
14802  
14803      /**
14804       * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
14805       * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
14806       * Only vector drawing is supported, not text or bitmap.
14807       * 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).
14808       * @param $file (string) Name of the file containing the image or a '@' character followed by the EPS/AI data string.
14809       * @param $x (float) Abscissa of the upper-left corner.
14810       * @param $y (float) Ordinate of the upper-left corner.
14811       * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
14812       * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
14813       * @param $link (mixed) URL or identifier returned by AddLink().
14814       * @param $useBoundingBox (boolean) specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
14815       * @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>
14816       * @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>
14817       * @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)))
14818       * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
14819       * @param $fixoutvals (boolean) if true remove values outside the bounding box.
14820       * @author Valentin Schmidt, Nicola Asuni
14821       * @since 3.1.000 (2008-06-09)
14822       * @public
14823       */
14824  	public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
14825          if ($this->state != 2) {
14826               return;
14827          }
14828          if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
14829              // convert EPS to raster image using GD or ImageMagick libraries
14830              return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14831          }
14832          if ($x === '') {
14833              $x = $this->x;
14834          }
14835          if ($y === '') {
14836              $y = $this->y;
14837          }
14838          // check page for no-write regions and adapt page margins if necessary
14839          list($x, $y) = $this->checkPageRegions($h, $x, $y);
14840          $k = $this->k;
14841          if ($file[0] === '@') { // image from string
14842              $data = substr($file, 1);
14843          } else { // EPS/AI file
14844              $data = TCPDF_STATIC::fileGetContents($file);
14845          }
14846          if ($data === FALSE) {
14847              $this->Error('EPS file not found: '.$file);
14848          }
14849          $regs = array();
14850          // EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
14851          preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
14852          if (count($regs) > 1) {
14853              $version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
14854              if (strpos($version_str, 'Adobe Illustrator') !== false) {
14855                  $versexp = explode(' ', $version_str);
14856                  $version = (float)array_pop($versexp);
14857                  if ($version >= 9) {
14858                      $this->Error('This version of Adobe Illustrator file is not supported: '.$file);
14859                  }
14860              }
14861          }
14862          // strip binary bytes in front of PS-header
14863          $start = strpos($data, '%!PS-Adobe');
14864          if ($start > 0) {
14865              $data = substr($data, $start);
14866          }
14867          // find BoundingBox params
14868          preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
14869          if (count($regs) > 1) {
14870              list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
14871          } else {
14872              $this->Error('No BoundingBox found in EPS/AI file: '.$file);
14873          }
14874          $start = strpos($data, '%%EndSetup');
14875          if ($start === false) {
14876              $start = strpos($data, '%%EndProlog');
14877          }
14878          if ($start === false) {
14879              $start = strpos($data, '%%BoundingBox');
14880          }
14881          $data = substr($data, $start);
14882          $end = strpos($data, '%%PageTrailer');
14883          if ($end===false) {
14884              $end = strpos($data, 'showpage');
14885          }
14886          if ($end) {
14887              $data = substr($data, 0, $end);
14888          }
14889          // calculate image width and height on document
14890          if (($w <= 0) AND ($h <= 0)) {
14891              $w = ($x2 - $x1) / $k;
14892              $h = ($y2 - $y1) / $k;
14893          } elseif ($w <= 0) {
14894              $w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
14895          } elseif ($h <= 0) {
14896              $h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
14897          }
14898          // fit the image on available space
14899          list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
14900          if ($this->rasterize_vector_images) {
14901              // convert EPS to raster image using GD or ImageMagick libraries
14902              return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14903          }
14904          // set scaling factors
14905          $scale_x = $w / (($x2 - $x1) / $k);
14906          $scale_y = $h / (($y2 - $y1) / $k);
14907          // set alignment
14908          $this->img_rb_y = $y + $h;
14909          // set alignment
14910          if ($this->rtl) {
14911              if ($palign == 'L') {
14912                  $ximg = $this->lMargin;
14913              } elseif ($palign == 'C') {
14914                  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
14915              } elseif ($palign == 'R') {
14916                  $ximg = $this->w - $this->rMargin - $w;
14917              } else {
14918                  $ximg = $x - $w;
14919              }
14920              $this->img_rb_x = $ximg;
14921          } else {
14922              if ($palign == 'L') {
14923                  $ximg = $this->lMargin;
14924              } elseif ($palign == 'C') {
14925                  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
14926              } elseif ($palign == 'R') {
14927                  $ximg = $this->w - $this->rMargin - $w;
14928              } else {
14929                  $ximg = $x;
14930              }
14931              $this->img_rb_x = $ximg + $w;
14932          }
14933          if ($useBoundingBox) {
14934              $dx = $ximg * $k - $x1;
14935              $dy = $y * $k - $y1;
14936          } else {
14937              $dx = $ximg * $k;
14938              $dy = $y * $k;
14939          }
14940          // save the current graphic state
14941          $this->_out('q'.$this->epsmarker);
14942          // translate
14943          $this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
14944          // scale
14945          if (isset($scale_x)) {
14946              $this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
14947          }
14948          // handle pc/unix/mac line endings
14949          $lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
14950          $u=0;
14951          $cnt = count($lines);
14952          for ($i=0; $i < $cnt; ++$i) {
14953              $line = $lines[$i];
14954              if (($line == '') OR ($line[0] == '%')) {
14955                  continue;
14956              }
14957              $len = strlen($line);
14958              // check for spot color names
14959              $color_name = '';
14960              if (strcasecmp('x', substr(trim($line), -1)) == 0) {
14961                  if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
14962                      // extract spot color name
14963                      $color_name = $matches[0];
14964                      // remove color name from string
14965                      $line = str_replace(' '.$color_name, '', $line);
14966                      // remove pharentesis from color name
14967                      $color_name = substr($color_name, 1, -1);
14968                  }
14969              }
14970              $chunks = explode(' ', $line);
14971              $cmd = trim(array_pop($chunks));
14972              // RGB
14973              if (($cmd == 'Xa') OR ($cmd == 'XA')) {
14974                  $b = array_pop($chunks);
14975                  $g = array_pop($chunks);
14976                  $r = array_pop($chunks);
14977                  $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!
14978                  continue;
14979              }
14980              $skip = false;
14981              if ($fixoutvals) {
14982                  // check for values outside the bounding box
14983                  switch ($cmd) {
14984                      case 'm':
14985                      case 'l':
14986                      case 'L': {
14987                          // skip values outside bounding box
14988                          foreach ($chunks as $key => $val) {
14989                              if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
14990                                  $skip = true;
14991                              } elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
14992                                  $skip = true;
14993                              }
14994                          }
14995                      }
14996                  }
14997              }
14998              switch ($cmd) {
14999                  case 'm':
15000                  case 'l':
15001                  case 'v':
15002                  case 'y':
15003                  case 'c':
15004                  case 'k':
15005                  case 'K':
15006                  case 'g':
15007                  case 'G':
15008                  case 's':
15009                  case 'S':
15010                  case 'J':
15011                  case 'j':
15012                  case 'w':
15013                  case 'M':
15014                  case 'd':
15015                  case 'n': {
15016                      if ($skip) {
15017                          break;
15018                      }
15019                      $this->_out($line);
15020                      break;
15021                  }
15022                  case 'x': {// custom fill color
15023                      if (empty($color_name)) {
15024                          // CMYK color
15025                          list($col_c, $col_m, $col_y, $col_k) = $chunks;
15026                          $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
15027                      } else {
15028                          // Spot Color (CMYK + tint)
15029                          list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15030                          $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15031                          $color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15032                          $this->_out($color_cmd);
15033                      }
15034                      break;
15035                  }
15036                  case 'X': { // custom stroke color
15037                      if (empty($color_name)) {
15038                          // CMYK color
15039                          list($col_c, $col_m, $col_y, $col_k) = $chunks;
15040                          $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
15041                      } else {
15042                          // Spot Color (CMYK + tint)
15043                          list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15044                          $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15045                          $color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15046                          $this->_out($color_cmd);
15047                      }
15048                      break;
15049                  }
15050                  case 'Y':
15051                  case 'N':
15052                  case 'V':
15053                  case 'L':
15054                  case 'C': {
15055                      if ($skip) {
15056                          break;
15057                      }
15058                      $line[($len - 1)] = strtolower($cmd);
15059                      $this->_out($line);
15060                      break;
15061                  }
15062                  case 'b':
15063                  case 'B': {
15064                      $this->_out($cmd . '*');
15065                      break;
15066                  }
15067                  case 'f':
15068                  case 'F': {
15069                      if ($u > 0) {
15070                          $isU = false;
15071                          $max = min(($i + 5), $cnt);
15072                          for ($j = ($i + 1); $j < $max; ++$j) {
15073                              $isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
15074                          }
15075                          if ($isU) {
15076                              $this->_out('f*');
15077                          }
15078                      } else {
15079                          $this->_out('f*');
15080                      }
15081                      break;
15082                  }
15083                  case '*u': {
15084                      ++$u;
15085                      break;
15086                  }
15087                  case '*U': {
15088                      --$u;
15089                      break;
15090                  }
15091              }
15092          }
15093          // restore previous graphic state
15094          $this->_out($this->epsmarker.'Q');
15095          if (!empty($border)) {
15096              $bx = $this->x;
15097              $by = $this->y;
15098              $this->x = $ximg;
15099              if ($this->rtl) {
15100                  $this->x += $w;
15101              }
15102              $this->y = $y;
15103              $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
15104              $this->x = $bx;
15105              $this->y = $by;
15106          }
15107          if ($link) {
15108              $this->Link($ximg, $y, $w, $h, $link, 0);
15109          }
15110          // set pointer to align the next text/objects
15111          switch($align) {
15112              case 'T':{
15113                  $this->y = $y;
15114                  $this->x = $this->img_rb_x;
15115                  break;
15116              }
15117              case 'M':{
15118                  $this->y = $y + round($h/2);
15119                  $this->x = $this->img_rb_x;
15120                  break;
15121              }
15122              case 'B':{
15123                  $this->y = $this->img_rb_y;
15124                  $this->x = $this->img_rb_x;
15125                  break;
15126              }
15127              case 'N':{
15128                  $this->SetY($this->img_rb_y);
15129                  break;
15130              }
15131              default:{
15132                  break;
15133              }
15134          }
15135          $this->endlinex = $this->img_rb_x;
15136      }
15137  
15138      /**
15139       * Set document barcode.
15140       * @param $bc (string) barcode
15141       * @public
15142       */
15143  	public function setBarcode($bc='') {
15144          $this->barcode = $bc;
15145      }
15146  
15147      /**
15148       * Get current barcode.
15149       * @return string
15150       * @public
15151       * @since 4.0.012 (2008-07-24)
15152       */
15153  	public function getBarcode() {
15154          return $this->barcode;
15155      }
15156  
15157      /**
15158       * Print a Linear Barcode.
15159       * @param $code (string) code to print
15160       * @param $type (string) type of barcode (see tcpdf_barcodes_1d.php for supported formats).
15161       * @param $x (int) x position in user units (empty string = current x position)
15162       * @param $y (int) y position in user units (empty string = current y position)
15163       * @param $w (int) width in user units (empty string = remaining page width)
15164       * @param $h (int) height in user units (empty string = remaining page height)
15165       * @param $xres (float) width of the smallest bar in user units (empty string = default value = 0.4mm)
15166       * @param $style (array) array of options:<ul>
15167       * <li>boolean $style['border'] if true prints a border</li>
15168       * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
15169       * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
15170       * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
15171       * <li>array $style['fgcolor'] color array for bars and text</li>
15172       * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
15173       * <li>boolean $style['text'] if true prints text below the barcode</li>
15174       * <li>string $style['label'] override default label</li>
15175       * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
15176       * <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>
15177       * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
15178       * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
15179       * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
15180       * <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>
15181       * <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>
15182       * @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>
15183       * @author Nicola Asuni
15184       * @since 3.1.000 (2008-06-09)
15185       * @public
15186       */
15187  	public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style='', $align='') {
15188          if (TCPDF_STATIC::empty_string(trim($code))) {
15189              return;
15190          }
15191          require_once(dirname(__FILE__).'/tcpdf_barcodes_1d.php');
15192          // save current graphic settings
15193          $gvars = $this->getGraphicVars();
15194          // create new barcode object
15195          $barcodeobj = new TCPDFBarcode($code, $type);
15196          $arrcode = $barcodeobj->getBarcodeArray();
15197          if (($arrcode === false) OR empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
15198              $this->Error('Error in 1D barcode string');
15199          }
15200          if ($arrcode['maxh'] <= 0) {
15201              $arrcode['maxh'] = 1;
15202          }
15203          // set default values
15204          if (!isset($style['position'])) {
15205              $style['position'] = '';
15206          } elseif ($style['position'] == 'S') {
15207              // keep this for backward compatibility
15208              $style['position'] = '';
15209              $style['stretch'] = true;
15210          }
15211          if (!isset($style['fitwidth'])) {
15212              if (!isset($style['stretch'])) {
15213                  $style['fitwidth'] = true;
15214              } else {
15215                  $style['fitwidth'] = false;
15216              }
15217          }
15218          if ($style['fitwidth']) {
15219              // disable stretch
15220              $style['stretch'] = false;
15221          }
15222          if (!isset($style['stretch'])) {
15223              if (($w === '') OR ($w <= 0)) {
15224                  $style['stretch'] = false;
15225              } else {
15226                  $style['stretch'] = true;
15227              }
15228          }
15229          if (!isset($style['fgcolor'])) {
15230              $style['fgcolor'] = array(0,0,0); // default black
15231          }
15232          if (!isset($style['bgcolor'])) {
15233              $style['bgcolor'] = false; // default transparent
15234          }
15235          if (!isset($style['border'])) {
15236              $style['border'] = false;
15237          }
15238          $fontsize = 0;
15239          if (!isset($style['text'])) {
15240              $style['text'] = false;
15241          }
15242          if ($style['text'] AND isset($style['font'])) {
15243              if (isset($style['fontsize'])) {
15244                  $fontsize = $style['fontsize'];
15245              }
15246              $this->SetFont($style['font'], '', $fontsize);
15247          }
15248          if (!isset($style['stretchtext'])) {
15249              $style['stretchtext'] = 4;
15250          }
15251          if ($x === '') {
15252              $x = $this->x;
15253          }
15254          if ($y === '') {
15255              $y = $this->y;
15256          }
15257          // check page for no-write regions and adapt page margins if necessary
15258          list($x, $y) = $this->checkPageRegions($h, $x, $y);
15259          if (($w === '') OR ($w <= 0)) {
15260              if ($this->rtl) {
15261                  $w = $x - $this->lMargin;
15262              } else {
15263                  $w = $this->w - $this->rMargin - $x;
15264              }
15265          }
15266          // padding
15267          if (!isset($style['padding'])) {
15268              $padding = 0;
15269          } elseif ($style['padding'] === 'auto') {
15270              $padding = 10 * ($w / ($arrcode['maxw'] + 20));
15271          } else {
15272              $padding = floatval($style['padding']);
15273          }
15274          // horizontal padding
15275          if (!isset($style['hpadding'])) {
15276              $hpadding = $padding;
15277          } elseif ($style['hpadding'] === 'auto') {
15278              $hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
15279          } else {
15280              $hpadding = floatval($style['hpadding']);
15281          }
15282          // vertical padding
15283          if (!isset($style['vpadding'])) {
15284              $vpadding = $padding;
15285          } elseif ($style['vpadding'] === 'auto') {
15286              $vpadding = ($hpadding / 2);
15287          } else {
15288              $vpadding = floatval($style['vpadding']);
15289          }
15290          // calculate xres (single bar width)
15291          $max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
15292          if ($style['stretch']) {
15293              $xres = $max_xres;
15294          } else {
15295              if (TCPDF_STATIC::empty_string($xres)) {
15296                  $xres = (0.141 * $this->k); // default bar width = 0.4 mm
15297              }
15298              if ($xres > $max_xres) {
15299                  // correct xres to fit on $w
15300                  $xres = $max_xres;
15301              }
15302              if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
15303                  OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
15304                  $hpadding = 10 * $xres;
15305                  if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
15306                      $vpadding = ($hpadding / 2);
15307                  }
15308              }
15309          }
15310          if ($style['fitwidth']) {
15311              $wold = $w;
15312              $w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
15313              if (isset($style['cellfitalign'])) {
15314                  switch ($style['cellfitalign']) {
15315                      case 'L': {
15316                          if ($this->rtl) {
15317                              $x -= ($wold - $w);
15318                          }
15319                          break;
15320                      }
15321                      case 'R': {
15322                          if (!$this->rtl) {
15323                              $x += ($wold - $w);
15324                          }
15325                          break;
15326                      }
15327                      case 'C': {
15328                          if ($this->rtl) {
15329                              $x -= (($wold - $w) / 2);
15330                          } else {
15331                              $x += (($wold - $w) / 2);
15332                          }
15333                          break;
15334                      }
15335                      default : {
15336                          break;
15337                      }
15338                  }
15339              }
15340          }
15341          $text_height = $this->getCellHeight($fontsize / $this->k);
15342          // height
15343          if (($h === '') OR ($h <= 0)) {
15344              // set default height
15345              $h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
15346          }
15347          $barh = $h - $text_height - (2 * $vpadding);
15348          if ($barh <=0) {
15349              // try to reduce font or padding to fit barcode on available height
15350              if ($text_height > $h) {
15351                  $fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
15352                  $text_height = $this->getCellHeight($fontsize / $this->k);
15353                  $this->SetFont($style['font'], '', $fontsize);
15354              }
15355              if ($vpadding > 0) {
15356                  $vpadding = (($h - $text_height) / 4);
15357              }
15358              $barh = $h - $text_height - (2 * $vpadding);
15359          }
15360          // fit the barcode on available space
15361          list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15362          // set alignment
15363          $this->img_rb_y = $y + $h;
15364          // set alignment
15365          if ($this->rtl) {
15366              if ($style['position'] == 'L') {
15367                  $xpos = $this->lMargin;
15368              } elseif ($style['position'] == 'C') {
15369                  $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15370              } elseif ($style['position'] == 'R') {
15371                  $xpos = $this->w - $this->rMargin - $w;
15372              } else {
15373                  $xpos = $x - $w;
15374              }
15375              $this->img_rb_x = $xpos;
15376          } else {
15377              if ($style['position'] == 'L') {
15378                  $xpos = $this->lMargin;
15379              } elseif ($style['position'] == 'C') {
15380                  $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15381              } elseif ($style['position'] == 'R') {
15382                  $xpos = $this->w - $this->rMargin - $w;
15383              } else {
15384                  $xpos = $x;
15385              }
15386              $this->img_rb_x = $xpos + $w;
15387          }
15388          $xpos_rect = $xpos;
15389          if (!isset($style['align'])) {
15390              $style['align'] = 'C';
15391          }
15392          switch ($style['align']) {
15393              case 'L': {
15394                  $xpos = $xpos_rect + $hpadding;
15395                  break;
15396              }
15397              case 'R': {
15398                  $xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
15399                  break;
15400              }
15401              case 'C':
15402              default : {
15403                  $xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
15404                  break;
15405              }
15406          }
15407          $xpos_text = $xpos;
15408          // barcode is always printed in LTR direction
15409          $tempRTL = $this->rtl;
15410          $this->rtl = false;
15411          // print background color
15412          if ($style['bgcolor']) {
15413              $this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15414          } elseif ($style['border']) {
15415              $this->Rect($xpos_rect, $y, $w, $h, 'D');
15416          }
15417          // set foreground color
15418          $this->SetDrawColorArray($style['fgcolor']);
15419          $this->SetTextColorArray($style['fgcolor']);
15420          // print bars
15421          foreach ($arrcode['bcode'] as $k => $v) {
15422              $bw = ($v['w'] * $xres);
15423              if ($v['t']) {
15424                  // draw a vertical bar
15425                  $ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
15426                  $this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
15427              }
15428              $xpos += $bw;
15429          }
15430          // print text
15431          if ($style['text']) {
15432              if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
15433                  $label = $style['label'];
15434              } else {
15435                  $label = $code;
15436              }
15437              $txtwidth = ($arrcode['maxw'] * $xres);
15438              if ($this->GetStringWidth($label) > $txtwidth) {
15439                  $style['stretchtext'] = 2;
15440              }
15441              // print text
15442              $this->x = $xpos_text;
15443              $this->y = $y + $vpadding + $barh;
15444              $cellpadding = $this->cell_padding;
15445              $this->SetCellPadding(0);
15446              $this->Cell($txtwidth, '', $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
15447              $this->cell_padding = $cellpadding;
15448          }
15449          // restore original direction
15450          $this->rtl = $tempRTL;
15451          // restore previous settings
15452          $this->setGraphicVars($gvars);
15453          // set pointer to align the next text/objects
15454          switch($align) {
15455              case 'T':{
15456                  $this->y = $y;
15457                  $this->x = $this->img_rb_x;
15458                  break;
15459              }
15460              case 'M':{
15461                  $this->y = $y + round($h / 2);
15462                  $this->x = $this->img_rb_x;
15463                  break;
15464              }
15465              case 'B':{
15466                  $this->y = $this->img_rb_y;
15467                  $this->x = $this->img_rb_x;
15468                  break;
15469              }
15470              case 'N':{
15471                  $this->SetY($this->img_rb_y);
15472                  break;
15473              }
15474              default:{
15475                  break;
15476              }
15477          }
15478          $this->endlinex = $this->img_rb_x;
15479      }
15480  
15481      /**
15482       * Print 2D Barcode.
15483       * @param $code (string) code to print
15484       * @param $type (string) type of barcode (see tcpdf_barcodes_2d.php for supported formats).
15485       * @param $x (int) x position in user units
15486       * @param $y (int) y position in user units
15487       * @param $w (int) width in user units
15488       * @param $h (int) height in user units
15489       * @param $style (array) array of options:<ul>
15490       * <li>boolean $style['border'] if true prints a border around the barcode</li>
15491       * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
15492       * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
15493       * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
15494       * <li>int $style['module_width'] width of a single module in points</li>
15495       * <li>int $style['module_height'] height of a single module in points</li>
15496       * <li>array $style['fgcolor'] color array for bars and text</li>
15497       * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
15498       * <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>
15499       * <li>$style['module_height'] height of a single module in points</li></ul>
15500       * @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>
15501       * @param $distort (boolean) if true distort the barcode to fit width and height, otherwise preserve aspect ratio
15502       * @author Nicola Asuni
15503       * @since 4.5.037 (2009-04-07)
15504       * @public
15505       */
15506  	public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style='', $align='', $distort=false) {
15507          if (TCPDF_STATIC::empty_string(trim($code))) {
15508              return;
15509          }
15510          require_once(dirname(__FILE__).'/tcpdf_barcodes_2d.php');
15511          // save current graphic settings
15512          $gvars = $this->getGraphicVars();
15513          // create new barcode object
15514          $barcodeobj = new TCPDF2DBarcode($code, $type);
15515          $arrcode = $barcodeobj->getBarcodeArray();
15516          if (($arrcode === false) OR empty($arrcode) OR !isset($arrcode['num_rows']) OR ($arrcode['num_rows'] == 0) OR !isset($arrcode['num_cols']) OR ($arrcode['num_cols'] == 0)) {
15517              $this->Error('Error in 2D barcode string');
15518          }
15519          // set default values
15520          if (!isset($style['position'])) {
15521              $style['position'] = '';
15522          }
15523          if (!isset($style['fgcolor'])) {
15524              $style['fgcolor'] = array(0,0,0); // default black
15525          }
15526          if (!isset($style['bgcolor'])) {
15527              $style['bgcolor'] = false; // default transparent
15528          }
15529          if (!isset($style['border'])) {
15530              $style['border'] = false;
15531          }
15532          // padding
15533          if (!isset($style['padding'])) {
15534              $style['padding'] = 0;
15535          } elseif ($style['padding'] === 'auto') {
15536              $style['padding'] = 4;
15537          }
15538          if (!isset($style['hpadding'])) {
15539              $style['hpadding'] = $style['padding'];
15540          } elseif ($style['hpadding'] === 'auto') {
15541              $style['hpadding'] = 4;
15542          }
15543          if (!isset($style['vpadding'])) {
15544              $style['vpadding'] = $style['padding'];
15545          } elseif ($style['vpadding'] === 'auto') {
15546              $style['vpadding'] = 4;
15547          }
15548          $hpad = (2 * $style['hpadding']);
15549          $vpad = (2 * $style['vpadding']);
15550          // cell (module) dimension
15551          if (!isset($style['module_width'])) {
15552              $style['module_width'] = 1; // width of a single module in points
15553          }
15554          if (!isset($style['module_height'])) {
15555              $style['module_height'] = 1; // height of a single module in points
15556          }
15557          if ($x === '') {
15558              $x = $this->x;
15559          }
15560          if ($y === '') {
15561              $y = $this->y;
15562          }
15563          // check page for no-write regions and adapt page margins if necessary
15564          list($x, $y) = $this->checkPageRegions($h, $x, $y);
15565          // number of barcode columns and rows
15566          $rows = $arrcode['num_rows'];
15567          $cols = $arrcode['num_cols'];
15568          if (($rows <= 0) || ($cols <= 0)){
15569              $this->Error('Error in 2D barcode string');
15570          }
15571          // module width and height
15572          $mw = $style['module_width'];
15573          $mh = $style['module_height'];
15574          if (($mw <= 0) OR ($mh <= 0)) {
15575              $this->Error('Error in 2D barcode string');
15576          }
15577          // get max dimensions
15578          if ($this->rtl) {
15579              $maxw = $x - $this->lMargin;
15580          } else {
15581              $maxw = $this->w - $this->rMargin - $x;
15582          }
15583          $maxh = ($this->h - $this->tMargin - $this->bMargin);
15584          $ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
15585          $ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
15586          if (!$distort) {
15587              if (($maxw * $ratioHW) > $maxh) {
15588                  $maxw = $maxh * $ratioWH;
15589              }
15590              if (($maxh * $ratioWH) > $maxw) {
15591                  $maxh = $maxw * $ratioHW;
15592              }
15593          }
15594          // set maximum dimensions
15595          if ($w > $maxw) {
15596              $w = $maxw;
15597          }
15598          if ($h > $maxh) {
15599              $h = $maxh;
15600          }
15601          // set dimensions
15602          if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
15603              $w = ($cols + $hpad) * ($mw / $this->k);
15604              $h = ($rows + $vpad) * ($mh / $this->k);
15605          } elseif (($w === '') OR ($w <= 0)) {
15606              $w = $h * $ratioWH;
15607          } elseif (($h === '') OR ($h <= 0)) {
15608              $h = $w * $ratioHW;
15609          }
15610          // barcode size (excluding padding)
15611          $bw = ($w * $cols) / ($cols + $hpad);
15612          $bh = ($h * $rows) / ($rows + $vpad);
15613          // dimension of single barcode cell unit
15614          $cw = $bw / $cols;
15615          $ch = $bh / $rows;
15616          if (!$distort) {
15617              if (($cw / $ch) > ($mw / $mh)) {
15618                  // correct horizontal distortion
15619                  $cw = $ch * $mw / $mh;
15620                  $bw = $cw * $cols;
15621                  $style['hpadding'] = ($w - $bw) / (2 * $cw);
15622              } else {
15623                  // correct vertical distortion
15624                  $ch = $cw * $mh / $mw;
15625                  $bh = $ch * $rows;
15626                  $style['vpadding'] = ($h - $bh) / (2 * $ch);
15627              }
15628          }
15629          // fit the barcode on available space
15630          list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15631          // set alignment
15632          $this->img_rb_y = $y + $h;
15633          // set alignment
15634          if ($this->rtl) {
15635              if ($style['position'] == 'L') {
15636                  $xpos = $this->lMargin;
15637              } elseif ($style['position'] == 'C') {
15638                  $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15639              } elseif ($style['position'] == 'R') {
15640                  $xpos = $this->w - $this->rMargin - $w;
15641              } else {
15642                  $xpos = $x - $w;
15643              }
15644              $this->img_rb_x = $xpos;
15645          } else {
15646              if ($style['position'] == 'L') {
15647                  $xpos = $this->lMargin;
15648              } elseif ($style['position'] == 'C') {
15649                  $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15650              } elseif ($style['position'] == 'R') {
15651                  $xpos = $this->w - $this->rMargin - $w;
15652              } else {
15653                  $xpos = $x;
15654              }
15655              $this->img_rb_x = $xpos + $w;
15656          }
15657          $xstart = $xpos + ($style['hpadding'] * $cw);
15658          $ystart = $y + ($style['vpadding'] * $ch);
15659          // barcode is always printed in LTR direction
15660          $tempRTL = $this->rtl;
15661          $this->rtl = false;
15662          // print background color
15663          if ($style['bgcolor']) {
15664              $this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15665          } elseif ($style['border']) {
15666              $this->Rect($xpos, $y, $w, $h, 'D');
15667          }
15668          // set foreground color
15669          $this->SetDrawColorArray($style['fgcolor']);
15670          // print barcode cells
15671          // for each row
15672          for ($r = 0; $r < $rows; ++$r) {
15673              $xr = $xstart;
15674              // for each column
15675              for ($c = 0; $c < $cols; ++$c) {
15676                  if ($arrcode['bcode'][$r][$c] == 1) {
15677                      // draw a single barcode cell
15678                      $this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
15679                  }
15680                  $xr += $cw;
15681              }
15682              $ystart += $ch;
15683          }
15684          // restore original direction
15685          $this->rtl = $tempRTL;
15686          // restore previous settings
15687          $this->setGraphicVars($gvars);
15688          // set pointer to align the next text/objects
15689          switch($align) {
15690              case 'T':{
15691                  $this->y = $y;
15692                  $this->x = $this->img_rb_x;
15693                  break;
15694              }
15695              case 'M':{
15696                  $this->y = $y + round($h/2);
15697                  $this->x = $this->img_rb_x;
15698                  break;
15699              }
15700              case 'B':{
15701                  $this->y = $this->img_rb_y;
15702                  $this->x = $this->img_rb_x;
15703                  break;
15704              }
15705              case 'N':{
15706                  $this->SetY($this->img_rb_y);
15707                  break;
15708              }
15709              default:{
15710                  break;
15711              }
15712          }
15713          $this->endlinex = $this->img_rb_x;
15714      }
15715  
15716      /**
15717       * Returns an array containing current margins:
15718       * <ul>
15719              <li>$ret['left'] = left margin</li>
15720              <li>$ret['right'] = right margin</li>
15721              <li>$ret['top'] = top margin</li>
15722              <li>$ret['bottom'] = bottom margin</li>
15723              <li>$ret['header'] = header margin</li>
15724              <li>$ret['footer'] = footer margin</li>
15725              <li>$ret['cell'] = cell padding array</li>
15726              <li>$ret['padding_left'] = cell left padding</li>
15727              <li>$ret['padding_top'] = cell top padding</li>
15728              <li>$ret['padding_right'] = cell right padding</li>
15729              <li>$ret['padding_bottom'] = cell bottom padding</li>
15730       * </ul>
15731       * @return array containing all margins measures
15732       * @public
15733       * @since 3.2.000 (2008-06-23)
15734       */
15735  	public function getMargins() {
15736          $ret = array(
15737              'left' => $this->lMargin,
15738              'right' => $this->rMargin,
15739              'top' => $this->tMargin,
15740              'bottom' => $this->bMargin,
15741              'header' => $this->header_margin,
15742              'footer' => $this->footer_margin,
15743              'cell' => $this->cell_padding,
15744              'padding_left' => $this->cell_padding['L'],
15745              'padding_top' => $this->cell_padding['T'],
15746              'padding_right' => $this->cell_padding['R'],
15747              'padding_bottom' => $this->cell_padding['B']
15748          );
15749          return $ret;
15750      }
15751  
15752      /**
15753       * Returns an array containing original margins:
15754       * <ul>
15755              <li>$ret['left'] = left margin</li>
15756              <li>$ret['right'] = right margin</li>
15757       * </ul>
15758       * @return array containing all margins measures
15759       * @public
15760       * @since 4.0.012 (2008-07-24)
15761       */
15762  	public function getOriginalMargins() {
15763          $ret = array(
15764              'left' => $this->original_lMargin,
15765              'right' => $this->original_rMargin
15766          );
15767          return $ret;
15768      }
15769  
15770      /**
15771       * Returns the current font size.
15772       * @return current font size
15773       * @public
15774       * @since 3.2.000 (2008-06-23)
15775       */
15776  	public function getFontSize() {
15777          return $this->FontSize;
15778      }
15779  
15780      /**
15781       * Returns the current font size in points unit.
15782       * @return current font size in points unit
15783       * @public
15784       * @since 3.2.000 (2008-06-23)
15785       */
15786  	public function getFontSizePt() {
15787          return $this->FontSizePt;
15788      }
15789  
15790      /**
15791       * Returns the current font family name.
15792       * @return string current font family name
15793       * @public
15794       * @since 4.3.008 (2008-12-05)
15795       */
15796  	public function getFontFamily() {
15797          return $this->FontFamily;
15798      }
15799  
15800      /**
15801       * Returns the current font style.
15802       * @return string current font style
15803       * @public
15804       * @since 4.3.008 (2008-12-05)
15805       */
15806  	public function getFontStyle() {
15807          return $this->FontStyle;
15808      }
15809  
15810      /**
15811       * Cleanup HTML code (requires HTML Tidy library).
15812       * @param $html (string) htmlcode to fix
15813       * @param $default_css (string) CSS commands to add
15814       * @param $tagvs (array) parameters for setHtmlVSpace method
15815       * @param $tidy_options (array) options for tidy_parse_string function
15816       * @return string XHTML code cleaned up
15817       * @author Nicola Asuni
15818       * @public
15819       * @since 5.9.017 (2010-11-16)
15820       * @see setHtmlVSpace()
15821       */
15822  	public function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='') {
15823          return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces);
15824      }
15825  
15826      /**
15827       * Returns the border width from CSS property
15828       * @param $width (string) border width
15829       * @return int with in user units
15830       * @protected
15831       * @since 5.7.000 (2010-08-02)
15832       */
15833  	protected function getCSSBorderWidth($width) {
15834          if ($width == 'thin') {
15835              $width = (2 / $this->k);
15836          } elseif ($width == 'medium') {
15837              $width = (4 / $this->k);
15838          } elseif ($width == 'thick') {
15839              $width = (6 / $this->k);
15840          } else {
15841              $width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
15842          }
15843          return $width;
15844      }
15845  
15846      /**
15847       * Returns the border dash style from CSS property
15848       * @param $style (string) border style to convert
15849       * @return int sash style (return -1 in case of none or hidden border)
15850       * @protected
15851       * @since 5.7.000 (2010-08-02)
15852       */
15853  	protected function getCSSBorderDashStyle($style) {
15854          switch (strtolower($style)) {
15855              case 'none':
15856              case 'hidden': {
15857                  $dash = -1;
15858                  break;
15859              }
15860              case 'dotted': {
15861                  $dash = 1;
15862                  break;
15863              }
15864              case 'dashed': {
15865                  $dash = 3;
15866                  break;
15867              }
15868              case 'double':
15869              case 'groove':
15870              case 'ridge':
15871              case 'inset':
15872              case 'outset':
15873              case 'solid':
15874              default: {
15875                  $dash = 0;
15876                  break;
15877              }
15878          }
15879          return $dash;
15880      }
15881  
15882      /**
15883       * Returns the border style array from CSS border properties
15884       * @param $cssborder (string) border properties
15885       * @return array containing border properties
15886       * @protected
15887       * @since 5.7.000 (2010-08-02)
15888       */
15889  	protected function getCSSBorderStyle($cssborder) {
15890          $bprop = preg_split('/[\s]+/', trim($cssborder));
15891          $border = array(); // value to be returned
15892          switch (count($bprop)) {
15893              case 3: {
15894                  $width = $bprop[0];
15895                  $style = $bprop[1];
15896                  $color = $bprop[2];
15897                  break;
15898              }
15899              case 2: {
15900                  $width = 'medium';
15901                  $style = $bprop[0];
15902                  $color = $bprop[1];
15903                  break;
15904              }
15905              case 1: {
15906                  $width = 'medium';
15907                  $style = $bprop[0];
15908                  $color = 'black';
15909                  break;
15910              }
15911              default: {
15912                  $width = 'medium';
15913                  $style = 'solid';
15914                  $color = 'black';
15915                  break;
15916              }
15917          }
15918          if ($style == 'none') {
15919              return array();
15920          }
15921          $border['cap'] = 'square';
15922          $border['join'] = 'miter';
15923          $border['dash'] = $this->getCSSBorderDashStyle($style);
15924          if ($border['dash'] < 0) {
15925              return array();
15926          }
15927          $border['width'] = $this->getCSSBorderWidth($width);
15928          $border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
15929          return $border;
15930      }
15931  
15932      /**
15933       * Get the internal Cell padding from CSS attribute.
15934       * @param $csspadding (string) padding properties
15935       * @param $width (float) width of the containing element
15936       * @return array of cell paddings
15937       * @public
15938       * @since 5.9.000 (2010-10-04)
15939       */
15940  	public function getCSSPadding($csspadding, $width=0) {
15941          $padding = preg_split('/[\s]+/', trim($csspadding));
15942          $cell_padding = array(); // value to be returned
15943          switch (count($padding)) {
15944              case 4: {
15945                  $cell_padding['T'] = $padding[0];
15946                  $cell_padding['R'] = $padding[1];
15947                  $cell_padding['B'] = $padding[2];
15948                  $cell_padding['L'] = $padding[3];
15949                  break;
15950              }
15951              case 3: {
15952                  $cell_padding['T'] = $padding[0];
15953                  $cell_padding['R'] = $padding[1];
15954                  $cell_padding['B'] = $padding[2];
15955                  $cell_padding['L'] = $padding[1];
15956                  break;
15957              }
15958              case 2: {
15959                  $cell_padding['T'] = $padding[0];
15960                  $cell_padding['R'] = $padding[1];
15961                  $cell_padding['B'] = $padding[0];
15962                  $cell_padding['L'] = $padding[1];
15963                  break;
15964              }
15965              case 1: {
15966                  $cell_padding['T'] = $padding[0];
15967                  $cell_padding['R'] = $padding[0];
15968                  $cell_padding['B'] = $padding[0];
15969                  $cell_padding['L'] = $padding[0];
15970                  break;
15971              }
15972              default: {
15973                  return $this->cell_padding;
15974              }
15975          }
15976          if ($width == 0) {
15977              $width = $this->w - $this->lMargin - $this->rMargin;
15978          }
15979          $cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
15980          $cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
15981          $cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
15982          $cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
15983          return $cell_padding;
15984      }
15985  
15986      /**
15987       * Get the internal Cell margin from CSS attribute.
15988       * @param $cssmargin (string) margin properties
15989       * @param $width (float) width of the containing element
15990       * @return array of cell margins
15991       * @public
15992       * @since 5.9.000 (2010-10-04)
15993       */
15994  	public function getCSSMargin($cssmargin, $width=0) {
15995          $margin = preg_split('/[\s]+/', trim($cssmargin));
15996          $cell_margin = array(); // value to be returned
15997          switch (count($margin)) {
15998              case 4: {
15999                  $cell_margin['T'] = $margin[0];
16000                  $cell_margin['R'] = $margin[1];
16001                  $cell_margin['B'] = $margin[2];
16002                  $cell_margin['L'] = $margin[3];
16003                  break;
16004              }
16005              case 3: {
16006                  $cell_margin['T'] = $margin[0];
16007                  $cell_margin['R'] = $margin[1];
16008                  $cell_margin['B'] = $margin[2];
16009                  $cell_margin['L'] = $margin[1];
16010                  break;
16011              }
16012              case 2: {
16013                  $cell_margin['T'] = $margin[0];
16014                  $cell_margin['R'] = $margin[1];
16015                  $cell_margin['B'] = $margin[0];
16016                  $cell_margin['L'] = $margin[1];
16017                  break;
16018              }
16019              case 1: {
16020                  $cell_margin['T'] = $margin[0];
16021                  $cell_margin['R'] = $margin[0];
16022                  $cell_margin['B'] = $margin[0];
16023                  $cell_margin['L'] = $margin[0];
16024                  break;
16025              }
16026              default: {
16027                  return $this->cell_margin;
16028              }
16029          }
16030          if ($width == 0) {
16031              $width = $this->w - $this->lMargin - $this->rMargin;
16032          }
16033          $cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
16034          $cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
16035          $cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
16036          $cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
16037          return $cell_margin;
16038      }
16039  
16040      /**
16041       * Get the border-spacing from CSS attribute.
16042       * @param $cssbspace (string) border-spacing CSS properties
16043       * @param $width (float) width of the containing element
16044       * @return array of border spacings
16045       * @public
16046       * @since 5.9.010 (2010-10-27)
16047       */
16048  	public function getCSSBorderMargin($cssbspace, $width=0) {
16049          $space = preg_split('/[\s]+/', trim($cssbspace));
16050          $border_spacing = array(); // value to be returned
16051          switch (count($space)) {
16052              case 2: {
16053                  $border_spacing['H'] = $space[0];
16054                  $border_spacing['V'] = $space[1];
16055                  break;
16056              }
16057              case 1: {
16058                  $border_spacing['H'] = $space[0];
16059                  $border_spacing['V'] = $space[0];
16060                  break;
16061              }
16062              default: {
16063                  return array('H' => 0, 'V' => 0);
16064              }
16065          }
16066          if ($width == 0) {
16067              $width = $this->w - $this->lMargin - $this->rMargin;
16068          }
16069          $border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
16070          $border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
16071          return $border_spacing;
16072      }
16073  
16074      /**
16075       * Returns the letter-spacing value from CSS value
16076       * @param $spacing (string) letter-spacing value
16077       * @param $parent (float) font spacing (tracking) value of the parent element
16078       * @return float quantity to increases or decreases the space between characters in a text.
16079       * @protected
16080       * @since 5.9.000 (2010-10-02)
16081       */
16082  	protected function getCSSFontSpacing($spacing, $parent=0) {
16083          $val = 0; // value to be returned
16084          $spacing = trim($spacing);
16085          switch ($spacing) {
16086              case 'normal': {
16087                  $val = 0;
16088                  break;
16089              }
16090              case 'inherit': {
16091                  if ($parent == 'normal') {
16092                      $val = 0;
16093                  } else {
16094                      $val = $parent;
16095                  }
16096                  break;
16097              }
16098              default: {
16099                  $val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
16100              }
16101          }
16102          return $val;
16103      }
16104  
16105      /**
16106       * Returns the percentage of font stretching from CSS value
16107       * @param $stretch (string) stretch mode
16108       * @param $parent (float) stretch value of the parent element
16109       * @return float font stretching percentage
16110       * @protected
16111       * @since 5.9.000 (2010-10-02)
16112       */
16113  	protected function getCSSFontStretching($stretch, $parent=100) {
16114          $val = 100; // value to be returned
16115          $stretch = trim($stretch);
16116          switch ($stretch) {
16117              case 'ultra-condensed': {
16118                  $val = 40;
16119                  break;
16120              }
16121              case 'extra-condensed': {
16122                  $val = 55;
16123                  break;
16124              }
16125              case 'condensed': {
16126                  $val = 70;
16127                  break;
16128              }
16129              case 'semi-condensed': {
16130                  $val = 85;
16131                  break;
16132              }
16133              case 'normal': {
16134                  $val = 100;
16135                  break;
16136              }
16137              case 'semi-expanded': {
16138                  $val = 115;
16139                  break;
16140              }
16141              case 'expanded': {
16142                  $val = 130;
16143                  break;
16144              }
16145              case 'extra-expanded': {
16146                  $val = 145;
16147                  break;
16148              }
16149              case 'ultra-expanded': {
16150                  $val = 160;
16151                  break;
16152              }
16153              case 'wider': {
16154                  $val = ($parent + 10);
16155                  break;
16156              }
16157              case 'narrower': {
16158                  $val = ($parent - 10);
16159                  break;
16160              }
16161              case 'inherit': {
16162                  if ($parent == 'normal') {
16163                      $val = 100;
16164                  } else {
16165                      $val = $parent;
16166                  }
16167                  break;
16168              }
16169              default: {
16170                  $val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
16171              }
16172          }
16173          return $val;
16174      }
16175  
16176      /**
16177       * Convert HTML string containing font size value to points
16178       * @param $val (string) String containing font size value and unit.
16179       * @param $refsize (float) Reference font size in points.
16180       * @param $parent_size (float) Parent font size in points.
16181       * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
16182       * @return float value in points
16183       * @public
16184       */
16185  	public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
16186          $refsize = TCPDF_FONTS::getFontRefSize($refsize);
16187          $parent_size = TCPDF_FONTS::getFontRefSize($parent_size, $refsize);
16188          switch ($val) {
16189              case 'xx-small': {
16190                  $size = ($refsize - 4);
16191                  break;
16192              }
16193              case 'x-small': {
16194                  $size = ($refsize - 3);
16195                  break;
16196              }
16197              case 'small': {
16198                  $size = ($refsize - 2);
16199                  break;
16200              }
16201              case 'medium': {
16202                  $size = $refsize;
16203                  break;
16204              }
16205              case 'large': {
16206                  $size = ($refsize + 2);
16207                  break;
16208              }
16209              case 'x-large': {
16210                  $size = ($refsize + 4);
16211                  break;
16212              }
16213              case 'xx-large': {
16214                  $size = ($refsize + 6);
16215                  break;
16216              }
16217              case 'smaller': {
16218                  $size = ($parent_size - 3);
16219                  break;
16220              }
16221              case 'larger': {
16222                  $size = ($parent_size + 3);
16223                  break;
16224              }
16225              default: {
16226                  $size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
16227              }
16228          }
16229          return $size;
16230      }
16231  
16232      /**
16233       * Returns the HTML DOM array.
16234       * @param $html (string) html code
16235       * @return array
16236       * @protected
16237       * @since 3.2.000 (2008-06-20)
16238       */
16239  	protected function getHtmlDomArray($html) {
16240          // array of CSS styles ( selector => properties).
16241          $css = array();
16242          // get CSS array defined at previous call
16243          $matches = array();
16244          if (preg_match_all('/<cssarray>([^\<]*)<\/cssarray>/isU', $html, $matches) > 0) {
16245              if (isset($matches[1][0])) {
16246                  $css = array_merge($css, json_decode($this->unhtmlentities($matches[1][0]), true));
16247              }
16248              $html = preg_replace('/<cssarray>(.*?)<\/cssarray>/isU', '', $html);
16249          }
16250          // extract external CSS files
16251          $matches = array();
16252          if (preg_match_all('/<link([^\>]*)>/isU', $html, $matches) > 0) {
16253              foreach ($matches[1] as $key => $link) {
16254                  $type = array();
16255                  if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
16256                      $type = array();
16257                      preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
16258                      // get 'all' and 'print' media, other media types are discarded
16259                      // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16260                      if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16261                          $type = array();
16262                          if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
16263                              // read CSS data file
16264                              $cssdata = TCPDF_STATIC::fileGetContents(trim($type[1]));
16265                              if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
16266                                  $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16267                              }
16268                          }
16269                      }
16270                  }
16271              }
16272          }
16273          // extract style tags
16274          $matches = array();
16275          if (preg_match_all('/<style([^\>]*)>([^\<]*)<\/style>/isU', $html, $matches) > 0) {
16276              foreach ($matches[1] as $key => $media) {
16277                  $type = array();
16278                  preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
16279                  // get 'all' and 'print' media, other media types are discarded
16280                  // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16281                  if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16282                      $cssdata = $matches[2][$key];
16283                      $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16284                  }
16285              }
16286          }
16287          // create a special tag to contain the CSS array (used for table content)
16288          $csstagarray = '<cssarray>'.htmlentities(json_encode($css)).'</cssarray>';
16289          // remove head and style blocks
16290          $html = preg_replace('/<head([^\>]*)>(.*?)<\/head>/siU', '', $html);
16291          $html = preg_replace('/<style([^\>]*)>([^\<]*)<\/style>/isU', '', $html);
16292          // define block tags
16293          $blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
16294          // define self-closing tags
16295          $selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
16296          // remove all unsupported tags (the line below lists all supported tags)
16297          $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>');
16298          //replace some blank characters
16299          $html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
16300          $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);
16301          $html = preg_replace('@(\r\n|\r)@', "\n", $html);
16302          $repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
16303          $html = strtr($html, $repTable);
16304          $offset = 0;
16305          while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
16306              $html_a = substr($html, 0, $offset);
16307              $html_b = substr($html, $offset, ($pos - $offset + 6));
16308              while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
16309                  // preserve newlines on <pre> tag
16310                  $html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
16311              }
16312              while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
16313                  // preserve spaces on <pre> tag
16314                  $html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
16315              }
16316              $html = $html_a.$html_b.substr($html, $pos + 6);
16317              $offset = strlen($html_a.$html_b);
16318          }
16319          $offset = 0;
16320          while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
16321              $html_a = substr($html, 0, $offset);
16322              $html_b = substr($html, $offset, ($pos - $offset + 11));
16323              while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
16324                  // preserve newlines on <textarea> tag
16325                  $html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
16326                  $html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
16327              }
16328              $html = $html_a.$html_b.substr($html, $pos + 11);
16329              $offset = strlen($html_a.$html_b);
16330          }
16331          $html = preg_replace('/([\s]*)<option/si', '<option', $html);
16332          $html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
16333          $offset = 0;
16334          while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
16335              $html_a = substr($html, 0, $offset);
16336              $html_b = substr($html, $offset, ($pos - $offset + 9));
16337              while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
16338                  $html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
16339                  $html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
16340              }
16341              $html = $html_a.$html_b.substr($html, $pos + 9);
16342              $offset = strlen($html_a.$html_b);
16343          }
16344          if (preg_match("'</select'si", $html)) {
16345              $html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
16346              $html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
16347          }
16348          $html = str_replace("\n", ' ', $html);
16349          // restore textarea newlines
16350          $html = str_replace('<TBR>', "\n", $html);
16351          // remove extra spaces from code
16352          $html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
16353          $html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
16354          $html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
16355          $html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
16356          $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);
16357          $html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
16358          $html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
16359          $html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
16360          $html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
16361          $html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
16362          $html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
16363          $html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
16364          $html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
16365          $html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
16366          $html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
16367          $html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
16368          $html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
16369          $html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
16370          $html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
16371          // trim string
16372          $html = $this->stringTrim($html);
16373          // fix br tag after li
16374          $html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
16375          // fix first image tag alignment
16376          $html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
16377          // pattern for generic tag
16378          $tagpattern = '/(<[^>]+>)/';
16379          // explodes the string
16380          $a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
16381          // count elements
16382          $maxel = count($a);
16383          $elkey = 0;
16384          $key = 0;
16385          // create an array of elements
16386          $dom = array();
16387          $dom[$key] = array();
16388          // set inheritable properties fot the first void element
16389          // 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
16390          $dom[$key]['tag'] = false;
16391          $dom[$key]['block'] = false;
16392          $dom[$key]['value'] = '';
16393          $dom[$key]['parent'] = 0;
16394          $dom[$key]['hide'] = false;
16395          $dom[$key]['fontname'] = $this->FontFamily;
16396          $dom[$key]['fontstyle'] = $this->FontStyle;
16397          $dom[$key]['fontsize'] = $this->FontSizePt;
16398          $dom[$key]['font-stretch'] = $this->font_stretching;
16399          $dom[$key]['letter-spacing'] = $this->font_spacing;
16400          $dom[$key]['stroke'] = $this->textstrokewidth;
16401          $dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
16402          $dom[$key]['clip'] = ($this->textrendermode > 3);
16403          $dom[$key]['line-height'] = $this->cell_height_ratio;
16404          $dom[$key]['bgcolor'] = false;
16405          $dom[$key]['fgcolor'] = $this->fgcolor; // color
16406          $dom[$key]['strokecolor'] = $this->strokecolor;
16407          $dom[$key]['align'] = '';
16408          $dom[$key]['listtype'] = '';
16409          $dom[$key]['text-indent'] = 0;
16410          $dom[$key]['text-transform'] = '';
16411          $dom[$key]['border'] = array();
16412          $dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
16413          $thead = false; // true when we are inside the THEAD tag
16414          ++$key;
16415          $level = array();
16416          array_push($level, 0); // root
16417          while ($elkey < $maxel) {
16418              $dom[$key] = array();
16419              $element = $a[$elkey];
16420              $dom[$key]['elkey'] = $elkey;
16421              if (preg_match($tagpattern, $element)) {
16422                  // html tag
16423                  $element = substr($element, 1, -1);
16424                  // get tag name
16425                  preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
16426                  $tagname = strtolower($tag[1]);
16427                  // check if we are inside a table header
16428                  if ($tagname == 'thead') {
16429                      if ($element[0] == '/') {
16430                          $thead = false;
16431                      } else {
16432                          $thead = true;
16433                      }
16434                      ++$elkey;
16435                      continue;
16436                  }
16437                  $dom[$key]['tag'] = true;
16438                  $dom[$key]['value'] = $tagname;
16439                  if (in_array($dom[$key]['value'], $blocktags)) {
16440                      $dom[$key]['block'] = true;
16441                  } else {
16442                      $dom[$key]['block'] = false;
16443                  }
16444                  if ($element[0] == '/') {
16445                      // *** closing html tag
16446                      $dom[$key]['opening'] = false;
16447                      $dom[$key]['parent'] = end($level);
16448                      array_pop($level);
16449                      $dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
16450                      $dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
16451                      $dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
16452                      $dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
16453                      $dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
16454                      $dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
16455                      $dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
16456                      $dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
16457                      $dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
16458                      $dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
16459                      $dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
16460                      $dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
16461                      $dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
16462                      $dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
16463                      $dom[$key]['text-transform'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['text-transform'];
16464                      $dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
16465                      if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
16466                          $dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
16467                      }
16468                      // set the number of columns in table tag
16469                      if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
16470                          $dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
16471                      }
16472                      if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
16473                          $dom[($dom[$key]['parent'])]['content'] = $csstagarray;
16474                          for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
16475                              $dom[($dom[$key]['parent'])]['content'] .= stripslashes($a[$dom[$i]['elkey']]);
16476                          }
16477                          $key = $i;
16478                          // mark nested tables
16479                          $dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
16480                          // remove thead sections from nested tables
16481                          $dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
16482                          $dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
16483                      }
16484                      // store header rows on a new table
16485                      if (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['thead'] === true)) {
16486                          if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
16487                              $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
16488                          }
16489                          for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
16490                              $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
16491                          }
16492                          if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
16493                              $dom[($dom[$key]['parent'])]['attribute'] = array();
16494                          }
16495                          // header elements must be always contained in a single page
16496                          $dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
16497                      }
16498                      if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
16499                          // remove the nobr attributes from the table header
16500                          $dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
16501                          $dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
16502                      }
16503                  } else {
16504                      // *** opening or self-closing html tag
16505                      $dom[$key]['opening'] = true;
16506                      $dom[$key]['parent'] = end($level);
16507                      if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
16508                          // self-closing tag
16509                          $dom[$key]['self'] = true;
16510                      } else {
16511                          // opening tag
16512                          array_push($level, $key);
16513                          $dom[$key]['self'] = false;
16514                      }
16515                      // copy some values from parent
16516                      $parentkey = 0;
16517                      if ($key > 0) {
16518                          $parentkey = $dom[$key]['parent'];
16519                          $dom[$key]['hide'] = $dom[$parentkey]['hide'];
16520                          $dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
16521                          $dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
16522                          $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
16523                          $dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
16524                          $dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
16525                          $dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
16526                          $dom[$key]['fill'] = $dom[$parentkey]['fill'];
16527                          $dom[$key]['clip'] = $dom[$parentkey]['clip'];
16528                          $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16529                          $dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
16530                          $dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
16531                          $dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
16532                          $dom[$key]['align'] = $dom[$parentkey]['align'];
16533                          $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16534                          $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16535                          $dom[$key]['text-transform'] = $dom[$parentkey]['text-transform'];
16536                          $dom[$key]['border'] = array();
16537                          $dom[$key]['dir'] = $dom[$parentkey]['dir'];
16538                      }
16539                      // get attributes
16540                      preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
16541                      $dom[$key]['attribute'] = array(); // reset attribute array
16542                      while (list($id, $name) = each($attr_array[1])) {
16543                          $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
16544                      }
16545                      if (!empty($css)) {
16546                          // merge CSS style to current style
16547                          list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC::getCSSdataArray($dom, $key, $css);
16548                          $dom[$key]['attribute']['style'] = TCPDF_STATIC::getTagStyleFromCSSarray($dom[$key]['cssdata']);
16549                      }
16550                      // split style attributes
16551                      if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
16552                          // get style attributes
16553                          preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
16554                          $dom[$key]['style'] = array(); // reset style attribute array
16555                          while (list($id, $name) = each($style_array[1])) {
16556                              // in case of duplicate attribute the last replace the previous
16557                              $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
16558                          }
16559                          // --- get some style attributes ---
16560                          // text direction
16561                          if (isset($dom[$key]['style']['direction'])) {
16562                              $dom[$key]['dir'] = $dom[$key]['style']['direction'];
16563                          }
16564                          // display
16565                          if (isset($dom[$key]['style']['display'])) {
16566                              $dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
16567                          }
16568                          // font family
16569                          if (isset($dom[$key]['style']['font-family'])) {
16570                              $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
16571                          }
16572                          // list-style-type
16573                          if (isset($dom[$key]['style']['list-style-type'])) {
16574                              $dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
16575                              if ($dom[$key]['listtype'] == 'inherit') {
16576                                  $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16577                              }
16578                          }
16579                          // text-indent
16580                          if (isset($dom[$key]['style']['text-indent'])) {
16581                              $dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
16582                              if ($dom[$key]['text-indent'] == 'inherit') {
16583                                  $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16584                              }
16585                          }
16586                          // text-transform
16587                          if (isset($dom[$key]['style']['text-transform'])) {
16588                              $dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
16589                          }
16590                          // font size
16591                          if (isset($dom[$key]['style']['font-size'])) {
16592                              $fsize = trim($dom[$key]['style']['font-size']);
16593                              $dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
16594                          }
16595                          // font-stretch
16596                          if (isset($dom[$key]['style']['font-stretch'])) {
16597                              $dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
16598                          }
16599                          // letter-spacing
16600                          if (isset($dom[$key]['style']['letter-spacing'])) {
16601                              $dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
16602                          }
16603                          // line-height (internally is the cell height ratio)
16604                          if (isset($dom[$key]['style']['line-height'])) {
16605                              $lineheight = trim($dom[$key]['style']['line-height']);
16606                              switch ($lineheight) {
16607                                  // A normal line height. This is default
16608                                  case 'normal': {
16609                                      $dom[$key]['line-height'] = $dom[0]['line-height'];
16610                                      break;
16611                                  }
16612                                  case 'inherit': {
16613                                      $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16614                                  }
16615                                  default: {
16616                                      if (is_numeric($lineheight)) {
16617                                          // convert to percentage of font height
16618                                          $lineheight = ($lineheight * 100).'%';
16619                                      }
16620                                      $dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
16621                                      if (substr($lineheight, -1) !== '%') {
16622                                          if ($dom[$key]['fontsize'] <= 0) {
16623                                              $dom[$key]['line-height'] = 1;
16624                                          } else {
16625                                              $dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding['T'] - $this->cell_padding['B']) / $dom[$key]['fontsize']);
16626                                          }
16627                                      }
16628                                  }
16629                              }
16630                          }
16631                          // font style
16632                          if (isset($dom[$key]['style']['font-weight'])) {
16633                              if (strtolower($dom[$key]['style']['font-weight'][0]) == 'n') {
16634                                  if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
16635                                      $dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
16636                                  }
16637                              } elseif (strtolower($dom[$key]['style']['font-weight'][0]) == 'b') {
16638                                  $dom[$key]['fontstyle'] .= 'B';
16639                              }
16640                          }
16641                          if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style'][0]) == 'i')) {
16642                              $dom[$key]['fontstyle'] .= 'I';
16643                          }
16644                          // font color
16645                          if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['color']))) {
16646                              $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors);
16647                          } elseif ($dom[$key]['value'] == 'a') {
16648                              $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16649                          }
16650                          // background color
16651                          if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['background-color']))) {
16652                              $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors);
16653                          }
16654                          // text-decoration
16655                          if (isset($dom[$key]['style']['text-decoration'])) {
16656                              $decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
16657                              foreach ($decors as $dec) {
16658                                  $dec = trim($dec);
16659                                  if (!TCPDF_STATIC::empty_string($dec)) {
16660                                      if ($dec[0] == 'u') {
16661                                          // underline
16662                                          $dom[$key]['fontstyle'] .= 'U';
16663                                      } elseif ($dec[0] == 'l') {
16664                                          // line-through
16665                                          $dom[$key]['fontstyle'] .= 'D';
16666                                      } elseif ($dec[0] == 'o') {
16667                                          // overline
16668                                          $dom[$key]['fontstyle'] .= 'O';
16669                                      }
16670                                  }
16671                              }
16672                          } elseif ($dom[$key]['value'] == 'a') {
16673                              $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16674                          }
16675                          // check for width attribute
16676                          if (isset($dom[$key]['style']['width'])) {
16677                              $dom[$key]['width'] = $dom[$key]['style']['width'];
16678                          }
16679                          // check for height attribute
16680                          if (isset($dom[$key]['style']['height'])) {
16681                              $dom[$key]['height'] = $dom[$key]['style']['height'];
16682                          }
16683                          // check for text alignment
16684                          if (isset($dom[$key]['style']['text-align'])) {
16685                              $dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align'][0]);
16686                          }
16687                          // check for CSS border properties
16688                          if (isset($dom[$key]['style']['border'])) {
16689                              $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
16690                              if (!empty($borderstyle)) {
16691                                  $dom[$key]['border']['LTRB'] = $borderstyle;
16692                              }
16693                          }
16694                          if (isset($dom[$key]['style']['border-color'])) {
16695                              $brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
16696                              if (isset($brd_colors[3])) {
16697                                  $dom[$key]['border']['L']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[3], $this->spot_colors);
16698                              }
16699                              if (isset($brd_colors[1])) {
16700                                  $dom[$key]['border']['R']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[1], $this->spot_colors);
16701                              }
16702                              if (isset($brd_colors[0])) {
16703                                  $dom[$key]['border']['T']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[0], $this->spot_colors);
16704                              }
16705                              if (isset($brd_colors[2])) {
16706                                  $dom[$key]['border']['B']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[2], $this->spot_colors);
16707                              }
16708                          }
16709                          if (isset($dom[$key]['style']['border-width'])) {
16710                              $brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
16711                              if (isset($brd_widths[3])) {
16712                                  $dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
16713                              }
16714                              if (isset($brd_widths[1])) {
16715                                  $dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
16716                              }
16717                              if (isset($brd_widths[0])) {
16718                                  $dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
16719                              }
16720                              if (isset($brd_widths[2])) {
16721                                  $dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
16722                              }
16723                          }
16724                          if (isset($dom[$key]['style']['border-style'])) {
16725                              $brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
16726                              if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
16727                                  $dom[$key]['border']['L']['cap'] = 'square';
16728                                  $dom[$key]['border']['L']['join'] = 'miter';
16729                                  $dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
16730                                  if ($dom[$key]['border']['L']['dash'] < 0) {
16731                                      $dom[$key]['border']['L'] = array();
16732                                  }
16733                              }
16734                              if (isset($brd_styles[1])) {
16735                                  $dom[$key]['border']['R']['cap'] = 'square';
16736                                  $dom[$key]['border']['R']['join'] = 'miter';
16737                                  $dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
16738                                  if ($dom[$key]['border']['R']['dash'] < 0) {
16739                                      $dom[$key]['border']['R'] = array();
16740                                  }
16741                              }
16742                              if (isset($brd_styles[0])) {
16743                                  $dom[$key]['border']['T']['cap'] = 'square';
16744                                  $dom[$key]['border']['T']['join'] = 'miter';
16745                                  $dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
16746                                  if ($dom[$key]['border']['T']['dash'] < 0) {
16747                                      $dom[$key]['border']['T'] = array();
16748                                  }
16749                              }
16750                              if (isset($brd_styles[2])) {
16751                                  $dom[$key]['border']['B']['cap'] = 'square';
16752                                  $dom[$key]['border']['B']['join'] = 'miter';
16753                                  $dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
16754                                  if ($dom[$key]['border']['B']['dash'] < 0) {
16755                                      $dom[$key]['border']['B'] = array();
16756                                  }
16757                              }
16758                          }
16759                          $cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
16760                          foreach ($cellside as $bsk => $bsv) {
16761                              if (isset($dom[$key]['style']['border-'.$bsv])) {
16762                                  $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
16763                                  if (!empty($borderstyle)) {
16764                                      $dom[$key]['border'][$bsk] = $borderstyle;
16765                                  }
16766                              }
16767                              if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
16768                                  $dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors);
16769                              }
16770                              if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
16771                                  $dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
16772                              }
16773                              if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
16774                                  $dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
16775                                  if ($dom[$key]['border'][$bsk]['dash'] < 0) {
16776                                      $dom[$key]['border'][$bsk] = array();
16777                                  }
16778                              }
16779                          }
16780                          // check for CSS padding properties
16781                          if (isset($dom[$key]['style']['padding'])) {
16782                              $dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
16783                          } else {
16784                              $dom[$key]['padding'] = $this->cell_padding;
16785                          }
16786                          foreach ($cellside as $psk => $psv) {
16787                              if (isset($dom[$key]['style']['padding-'.$psv])) {
16788                                  $dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
16789                              }
16790                          }
16791                          // check for CSS margin properties
16792                          if (isset($dom[$key]['style']['margin'])) {
16793                              $dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
16794                          } else {
16795                              $dom[$key]['margin'] = $this->cell_margin;
16796                          }
16797                          foreach ($cellside as $psk => $psv) {
16798                              if (isset($dom[$key]['style']['margin-'.$psv])) {
16799                                  $dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
16800                              }
16801                          }
16802                          // check for CSS border-spacing properties
16803                          if (isset($dom[$key]['style']['border-spacing'])) {
16804                              $dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
16805                          }
16806                          // page-break-inside
16807                          if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
16808                              $dom[$key]['attribute']['nobr'] = 'true';
16809                          }
16810                          // page-break-before
16811                          if (isset($dom[$key]['style']['page-break-before'])) {
16812                              if ($dom[$key]['style']['page-break-before'] == 'always') {
16813                                  $dom[$key]['attribute']['pagebreak'] = 'true';
16814                              } elseif ($dom[$key]['style']['page-break-before'] == 'left') {
16815                                  $dom[$key]['attribute']['pagebreak'] = 'left';
16816                              } elseif ($dom[$key]['style']['page-break-before'] == 'right') {
16817                                  $dom[$key]['attribute']['pagebreak'] = 'right';
16818                              }
16819                          }
16820                          // page-break-after
16821                          if (isset($dom[$key]['style']['page-break-after'])) {
16822                              if ($dom[$key]['style']['page-break-after'] == 'always') {
16823                                  $dom[$key]['attribute']['pagebreakafter'] = 'true';
16824                              } elseif ($dom[$key]['style']['page-break-after'] == 'left') {
16825                                  $dom[$key]['attribute']['pagebreakafter'] = 'left';
16826                              } elseif ($dom[$key]['style']['page-break-after'] == 'right') {
16827                                  $dom[$key]['attribute']['pagebreakafter'] = 'right';
16828                              }
16829                          }
16830                      }
16831                      if (isset($dom[$key]['attribute']['display'])) {
16832                          $dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
16833                      }
16834                      if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
16835                          $borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
16836                          if (!empty($borderstyle)) {
16837                              $dom[$key]['border']['LTRB'] = $borderstyle;
16838                          }
16839                      }
16840                      // check for font tag
16841                      if ($dom[$key]['value'] == 'font') {
16842                          // font family
16843                          if (isset($dom[$key]['attribute']['face'])) {
16844                              $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
16845                          }
16846                          // font size
16847                          if (isset($dom[$key]['attribute']['size'])) {
16848                              if ($key > 0) {
16849                                  if ($dom[$key]['attribute']['size'][0] == '+') {
16850                                      $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
16851                                  } elseif ($dom[$key]['attribute']['size'][0] == '-') {
16852                                      $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
16853                                  } else {
16854                                      $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16855                                  }
16856                              } else {
16857                                  $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16858                              }
16859                          }
16860                      }
16861                      // force natural alignment for lists
16862                      if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
16863                          AND (!isset($dom[$key]['align']) OR TCPDF_STATIC::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
16864                          if ($this->rtl) {
16865                              $dom[$key]['align'] = 'R';
16866                          } else {
16867                              $dom[$key]['align'] = 'L';
16868                          }
16869                      }
16870                      if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
16871                          if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
16872                              $dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
16873                          }
16874                      }
16875                      if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
16876                          $dom[$key]['fontstyle'] .= 'B';
16877                      }
16878                      if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
16879                          $dom[$key]['fontstyle'] .= 'I';
16880                      }
16881                      if ($dom[$key]['value'] == 'u') {
16882                          $dom[$key]['fontstyle'] .= 'U';
16883                      }
16884                      if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
16885                          $dom[$key]['fontstyle'] .= 'D';
16886                      }
16887                      if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
16888                          $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16889                      }
16890                      if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
16891                          $dom[$key]['fontname'] = $this->default_monospaced_font;
16892                      }
16893                      if (!empty($dom[$key]['value']) AND ($dom[$key]['value'][0] == 'h') AND (intval($dom[$key]['value']{1}) > 0) AND (intval($dom[$key]['value']{1}) < 7)) {
16894                          // headings h1, h2, h3, h4, h5, h6
16895                          if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
16896                              $headsize = (4 - intval($dom[$key]['value']{1})) * 2;
16897                              $dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
16898                          }
16899                          if (!isset($dom[$key]['style']['font-weight'])) {
16900                              $dom[$key]['fontstyle'] .= 'B';
16901                          }
16902                      }
16903                      if (($dom[$key]['value'] == 'table')) {
16904                          $dom[$key]['rows'] = 0; // number of rows
16905                          $dom[$key]['trids'] = array(); // IDs of TR elements
16906                          $dom[$key]['thead'] = ''; // table header rows
16907                      }
16908                      if (($dom[$key]['value'] == 'tr')) {
16909                          $dom[$key]['cols'] = 0;
16910                          if ($thead) {
16911                              $dom[$key]['thead'] = true;
16912                              // rows on thead block are printed as a separate table
16913                          } else {
16914                              $dom[$key]['thead'] = false;
16915                              // store the number of rows on table element
16916                              ++$dom[($dom[$key]['parent'])]['rows'];
16917                              // store the TR elements IDs on table element
16918                              array_push($dom[($dom[$key]['parent'])]['trids'], $key);
16919                          }
16920                      }
16921                      if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
16922                          if (isset($dom[$key]['attribute']['colspan'])) {
16923                              $colspan = intval($dom[$key]['attribute']['colspan']);
16924                          } else {
16925                              $colspan = 1;
16926                          }
16927                          $dom[$key]['attribute']['colspan'] = $colspan;
16928                          $dom[($dom[$key]['parent'])]['cols'] += $colspan;
16929                      }
16930                      // text direction
16931                      if (isset($dom[$key]['attribute']['dir'])) {
16932                          $dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
16933                      }
16934                      // set foreground color attribute
16935                      if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['color']))) {
16936                          $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors);
16937                      } elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
16938                          $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16939                      }
16940                      // set background color attribute
16941                      if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['bgcolor']))) {
16942                          $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors);
16943                      }
16944                      // set stroke color attribute
16945                      if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['strokecolor']))) {
16946                          $dom[$key]['strokecolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors);
16947                      }
16948                      // check for width attribute
16949                      if (isset($dom[$key]['attribute']['width'])) {
16950                          $dom[$key]['width'] = $dom[$key]['attribute']['width'];
16951                      }
16952                      // check for height attribute
16953                      if (isset($dom[$key]['attribute']['height'])) {
16954                          $dom[$key]['height'] = $dom[$key]['attribute']['height'];
16955                      }
16956                      // check for text alignment
16957                      if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
16958                          $dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align'][0]);
16959                      }
16960                      // check for text rendering mode (the following attributes do not exist in HTML)
16961                      if (isset($dom[$key]['attribute']['stroke'])) {
16962                          // font stroke width
16963                          $dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
16964                      }
16965                      if (isset($dom[$key]['attribute']['fill'])) {
16966                          // font fill
16967                          if ($dom[$key]['attribute']['fill'] == 'true') {
16968                              $dom[$key]['fill'] = true;
16969                          } else {
16970                              $dom[$key]['fill'] = false;
16971                          }
16972                      }
16973                      if (isset($dom[$key]['attribute']['clip'])) {
16974                          // clipping mode
16975                          if ($dom[$key]['attribute']['clip'] == 'true') {
16976                              $dom[$key]['clip'] = true;
16977                          } else {
16978                              $dom[$key]['clip'] = false;
16979                          }
16980                      }
16981                  } // end opening tag
16982              } else {
16983                  // text
16984                  $dom[$key]['tag'] = false;
16985                  $dom[$key]['block'] = false;
16986                  $dom[$key]['parent'] = end($level);
16987                  $dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
16988                  if (!empty($dom[$dom[$key]['parent']]['text-transform'])) {
16989                      // text-transform for unicode requires mb_convert_case (Multibyte String Functions)
16990                      if (function_exists('mb_convert_case')) {
16991                          $ttm = array('capitalize' => MB_CASE_TITLE, 'uppercase' => MB_CASE_UPPER, 'lowercase' => MB_CASE_LOWER);
16992                          if (isset($ttm[$dom[$dom[$key]['parent']]['text-transform']])) {
16993                              $element = mb_convert_case($element, $ttm[$dom[$dom[$key]['parent']]['text-transform']], $this->encoding);
16994                          }
16995                      } elseif (!$this->isunicode) {
16996                          switch ($dom[$dom[$key]['parent']]['text-transform']) {
16997                              case 'capitalize': {
16998                                  $element = ucwords(strtolower($element));
16999                                  break;
17000                              }
17001                              case 'uppercase': {
17002                                  $element = strtoupper($element);
17003                                  break;
17004                              }
17005                              case 'lowercase': {
17006                                  $element = strtolower($element);
17007                                  break;
17008                              }
17009                          }
17010                      }
17011                  }
17012                  $dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
17013              }
17014              ++$elkey;
17015              ++$key;
17016          }
17017          return $dom;
17018      }
17019  
17020      /**
17021       * Returns the string used to find spaces
17022       * @return string
17023       * @protected
17024       * @author Nicola Asuni
17025       * @since 4.8.024 (2010-01-15)
17026       */
17027  	protected function getSpaceString() {
17028          $spacestr = chr(32);
17029          if ($this->isUnicodeFont()) {
17030              $spacestr = chr(0).chr(32);
17031          }
17032          return $spacestr;
17033      }
17034  
17035      /**
17036       * Return an hash code used to ensure that the serialized data has been generated by this TCPDF instance.
17037       * @param $data (string) serialized data
17038       * @return string
17039       * @public static
17040       */
17041  	protected function getHashForTCPDFtagParams($data) {
17042          return md5(strlen($data).$this->file_id.$data);
17043      }
17044  
17045      /**
17046       * Serialize an array of parameters to be used with TCPDF tag in HTML code.
17047       * @param $data (array) parameters array
17048       * @return string containing serialized data
17049       * @public static
17050       */
17051  	public function serializeTCPDFtagParameters($data) {
17052          $encoded = urlencode(json_encode($data));
17053          return $this->getHashForTCPDFtagParams($encoded).$encoded;
17054      }
17055  
17056      /**
17057       * Unserialize parameters to be used with TCPDF tag in HTML code.
17058       * @param $data (string) serialized data
17059       * @return array containing unserialized data
17060       * @protected static
17061       */
17062  	protected function unserializeTCPDFtagParameters($data) {
17063          $hash = substr($data, 0, 32);
17064          $encoded = substr($data, 32);
17065          if ($hash != $this->getHashForTCPDFtagParams($encoded)) {
17066              $this->Error('Invalid parameters');
17067          }
17068          return json_decode(urldecode($encoded), true);
17069      }
17070  
17071      /**
17072       * Prints a cell (rectangular area) with optional borders, background color and html text string.
17073       * 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 />
17074       * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
17075       * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17076       * 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
17077       * NOTE: all the HTML attributes must be enclosed in double-quote.
17078       * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
17079       * @param $h (float) Cell minimum height. The cell extends automatically if needed.
17080       * @param $x (float) upper-left corner X coordinate
17081       * @param $y (float) upper-left corner Y coordinate
17082       * @param $html (string) html text to print. Default value: empty string.
17083       * @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)))
17084       * @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>
17085  Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
17086       * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
17087       * @param $reseth (boolean) if true reset the last cell height (default true).
17088       * @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>
17089       * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
17090       * @see Multicell(), writeHTML()
17091       * @public
17092       */
17093  	public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
17094          return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
17095      }
17096  
17097      /**
17098       * Allows to preserve some HTML formatting (limited support).<br />
17099       * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17100       * 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
17101       * NOTE: all the HTML attributes must be enclosed in double-quote.
17102       * @param $html (string) text to display
17103       * @param $ln (boolean) if true add a new line after text (default = true)
17104       * @param $fill (boolean) Indicates if the background must be painted (true) or transparent (false).
17105       * @param $reseth (boolean) if true reset the last cell height (default false).
17106       * @param $cell (boolean) if true add the current left (or right for RTL) padding to each Write (default false).
17107       * @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>
17108       * @public
17109       */
17110  	public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
17111          $gvars = $this->getGraphicVars();
17112          // store current values
17113          $prev_cell_margin = $this->cell_margin;
17114          $prev_cell_padding = $this->cell_padding;
17115          $prevPage = $this->page;
17116          $prevlMargin = $this->lMargin;
17117          $prevrMargin = $this->rMargin;
17118          $curfontname = $this->FontFamily;
17119          $curfontstyle = $this->FontStyle;
17120          $curfontsize = $this->FontSizePt;
17121          $curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
17122          $curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
17123          $curfontstretcing = $this->font_stretching;
17124          $curfonttracking = $this->font_spacing;
17125          $this->newline = true;
17126          $newline = true;
17127          $startlinepage = $this->page;
17128          $minstartliney = $this->y;
17129          $maxbottomliney = 0;
17130          $startlinex = $this->x;
17131          $startliney = $this->y;
17132          $yshift = 0;
17133          $loop = 0;
17134          $curpos = 0;
17135          $this_method_vars = array();
17136          $undo = false;
17137          $fontaligned = false;
17138          $reverse_dir = false; // true when the text direction is reversed
17139          $this->premode = false;
17140          if ($this->inxobj) {
17141              // we are inside an XObject template
17142              $pask = count($this->xobjects[$this->xobjid]['annotations']);
17143          } elseif (isset($this->PageAnnots[$this->page])) {
17144              $pask = count($this->PageAnnots[$this->page]);
17145          } else {
17146              $pask = 0;
17147          }
17148          if ($this->inxobj) {
17149              // we are inside an XObject template
17150              $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17151          } elseif (!$this->InFooter) {
17152              if (isset($this->footerlen[$this->page])) {
17153                  $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17154              } else {
17155                  $this->footerpos[$this->page] = $this->pagelen[$this->page];
17156              }
17157              $startlinepos = $this->footerpos[$this->page];
17158          } else {
17159              // we are inside the footer
17160              $startlinepos = $this->pagelen[$this->page];
17161          }
17162          $lalign = $align;
17163          $plalign = $align;
17164          if ($this->rtl) {
17165              $w = $this->x - $this->lMargin;
17166          } else {
17167              $w = $this->w - $this->rMargin - $this->x;
17168          }
17169          $w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
17170          if ($cell) {
17171              if ($this->rtl) {
17172                  $this->x -= $this->cell_padding['R'];
17173                  $this->lMargin += $this->cell_padding['R'];
17174              } else {
17175                  $this->x += $this->cell_padding['L'];
17176                  $this->rMargin += $this->cell_padding['L'];
17177              }
17178          }
17179          if ($this->customlistindent >= 0) {
17180              $this->listindent = $this->customlistindent;
17181          } else {
17182              $this->listindent = $this->GetStringWidth('000000');
17183          }
17184          $this->listindentlevel = 0;
17185          // save previous states
17186          $prev_cell_height_ratio = $this->cell_height_ratio;
17187          $prev_listnum = $this->listnum;
17188          $prev_listordered = $this->listordered;
17189          $prev_listcount = $this->listcount;
17190          $prev_lispacer = $this->lispacer;
17191          $this->listnum = 0;
17192          $this->listordered = array();
17193          $this->listcount = array();
17194          $this->lispacer = '';
17195          if ((TCPDF_STATIC::empty_string($this->lasth)) OR ($reseth)) {
17196              // reset row height
17197              $this->resetLastH();
17198          }
17199          $dom = $this->getHtmlDomArray($html);
17200          $maxel = count($dom);
17201          $key = 0;
17202          while ($key < $maxel) {
17203              if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND $dom[$key]['hide']) {
17204                  // store the node key
17205                  $hidden_node_key = $key;
17206                  if ($dom[$key]['self']) {
17207                      // skip just this self-closing tag
17208                      ++$key;
17209                  } else {
17210                      // skip this and all children tags
17211                      while (($key < $maxel) AND (!$dom[$key]['tag'] OR $dom[$key]['opening'] OR ($dom[$key]['parent'] != $hidden_node_key))) {
17212                          // skip hidden objects
17213                          ++$key;
17214                      }
17215                      ++$key;
17216                  }
17217              }
17218              if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
17219                  // check for pagebreak
17220                  if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
17221                      // add a page (or trig AcceptPageBreak() for multicolumn mode)
17222                      $this->checkPageBreak($this->PageBreakTrigger + 1);
17223                      $this->htmlvspace = ($this->PageBreakTrigger + 1);
17224                  }
17225                  if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
17226                      OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
17227                      // add a page (or trig AcceptPageBreak() for multicolumn mode)
17228                      $this->checkPageBreak($this->PageBreakTrigger + 1);
17229                      $this->htmlvspace = ($this->PageBreakTrigger + 1);
17230                  }
17231              }
17232              if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
17233                  if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
17234                      $dom[$key]['attribute']['nobr'] = false;
17235                  } else {
17236                      // store current object
17237                      $this->startTransaction();
17238                      // save this method vars
17239                      $this_method_vars['html'] = $html;
17240                      $this_method_vars['ln'] = $ln;
17241                      $this_method_vars['fill'] = $fill;
17242                      $this_method_vars['reseth'] = $reseth;
17243                      $this_method_vars['cell'] = $cell;
17244                      $this_method_vars['align'] = $align;
17245                      $this_method_vars['gvars'] = $gvars;
17246                      $this_method_vars['prevPage'] = $prevPage;
17247                      $this_method_vars['prev_cell_margin'] = $prev_cell_margin;
17248                      $this_method_vars['prev_cell_padding'] = $prev_cell_padding;
17249                      $this_method_vars['prevlMargin'] = $prevlMargin;
17250                      $this_method_vars['prevrMargin'] = $prevrMargin;
17251                      $this_method_vars['curfontname'] = $curfontname;
17252                      $this_method_vars['curfontstyle'] = $curfontstyle;
17253                      $this_method_vars['curfontsize'] = $curfontsize;
17254                      $this_method_vars['curfontascent'] = $curfontascent;
17255                      $this_method_vars['curfontdescent'] = $curfontdescent;
17256                      $this_method_vars['curfontstretcing'] = $curfontstretcing;
17257                      $this_method_vars['curfonttracking'] = $curfonttracking;
17258                      $this_method_vars['minstartliney'] = $minstartliney;
17259                      $this_method_vars['maxbottomliney'] = $maxbottomliney;
17260                      $this_method_vars['yshift'] = $yshift;
17261                      $this_method_vars['startlinepage'] = $startlinepage;
17262                      $this_method_vars['startlinepos'] = $startlinepos;
17263                      $this_method_vars['startlinex'] = $startlinex;
17264                      $this_method_vars['startliney'] = $startliney;
17265                      $this_method_vars['newline'] = $newline;
17266                      $this_method_vars['loop'] = $loop;
17267                      $this_method_vars['curpos'] = $curpos;
17268                      $this_method_vars['pask'] = $pask;
17269                      $this_method_vars['lalign'] = $lalign;
17270                      $this_method_vars['plalign'] = $plalign;
17271                      $this_method_vars['w'] = $w;
17272                      $this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
17273                      $this_method_vars['prev_listnum'] = $prev_listnum;
17274                      $this_method_vars['prev_listordered'] = $prev_listordered;
17275                      $this_method_vars['prev_listcount'] = $prev_listcount;
17276                      $this_method_vars['prev_lispacer'] = $prev_lispacer;
17277                      $this_method_vars['fontaligned'] = $fontaligned;
17278                      $this_method_vars['key'] = $key;
17279                      $this_method_vars['dom'] = $dom;
17280                  }
17281              }
17282              // print THEAD block
17283              if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
17284                  if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC::empty_string($dom[$dom[$key]['parent']]['thead'])) {
17285                      $this->inthead = true;
17286                      // print table header (thead)
17287                      $this->writeHTML($this->thead, false, false, false, false, '');
17288                      // check if we are on a new page or on a new column
17289                      if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
17290                          // we are on a new page or on a new column and the total object height is less than the available vertical space.
17291                          // restore previous object
17292                          $this->rollbackTransaction(true);
17293                          // restore previous values
17294                          foreach ($this_method_vars as $vkey => $vval) {
17295                              $$vkey = $vval;
17296                          }
17297                          // disable table header
17298                          $tmp_thead = $this->thead;
17299                          $this->thead = '';
17300                          // add a page (or trig AcceptPageBreak() for multicolumn mode)
17301                          $pre_y = $this->y;
17302                          if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
17303                              // fix for multicolumn mode
17304                              $startliney = $this->y;
17305                          }
17306                          $this->start_transaction_page = $this->page;
17307                          $this->start_transaction_y = $this->y;
17308                          // restore table header
17309                          $this->thead = $tmp_thead;
17310                          // fix table border properties
17311                          if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
17312                              $tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
17313                          } elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
17314                              $tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
17315                          } else {
17316                              $tmp_cellspacing = 0;
17317                          }
17318                          $dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
17319                          $dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
17320                          $dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
17321                          $xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
17322                          $dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
17323                          $dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
17324                          // print table header (thead)
17325                          $this->writeHTML($this->thead, false, false, false, false, '');
17326                      }
17327                  }
17328                  // move $key index forward to skip THEAD block
17329                  while ( ($key < $maxel) AND (!(
17330                      ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
17331                      OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
17332                      ++$key;
17333                  }
17334              }
17335              if ($dom[$key]['tag'] OR ($key == 0)) {
17336                  if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
17337                      $dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
17338                  }
17339                  // vertically align image in line
17340                  if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
17341                      // get image height
17342                      $imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], ($dom[$key]['fontsize'] / $this->k), 'px');
17343                      $autolinebreak = false;
17344                      if (!empty($dom[$key]['width'])) {
17345                          $imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], ($dom[$key]['fontsize'] / $this->k), 'px', false);
17346                          if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
17347                              AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
17348                              OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
17349                              // add automatic line break
17350                              $autolinebreak = true;
17351                              $this->Ln('', $cell);
17352                              if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
17353                                  // go back to evaluate this line break
17354                                  --$key;
17355                              }
17356                          }
17357                      }
17358                      if (!$autolinebreak) {
17359                          if ($this->inPageBody()) {
17360                              $pre_y = $this->y;
17361                              // check for page break
17362                              if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
17363                                  // fix for multicolumn mode
17364                                  $startliney = $this->y;
17365                              }
17366                          }
17367                          if ($this->page > $startlinepage) {
17368                              // fix line splitted over two pages
17369                              if (isset($this->footerlen[$startlinepage])) {
17370                                  $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17371                              }
17372                              // line to be moved one page forward
17373                              $pagebuff = $this->getPageBuffer($startlinepage);
17374                              $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17375                              $tstart = substr($pagebuff, 0, $startlinepos);
17376                              $tend = substr($this->getPageBuffer($startlinepage), $curpos);
17377                              // remove line from previous page
17378                              $this->setPageBuffer($startlinepage, $tstart.''.$tend);
17379                              $pagebuff = $this->getPageBuffer($this->page);
17380                              $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17381                              $tend = substr($pagebuff, $this->cntmrk[$this->page]);
17382                              // add line start to current page
17383                              $yshift = ($minstartliney - $this->y);
17384                              if ($fontaligned) {
17385                                  $yshift += ($curfontsize / $this->k);
17386                              }
17387                              $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17388                              $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17389                              // shift the annotations and links
17390                              if (isset($this->PageAnnots[$this->page])) {
17391                                  $next_pask = count($this->PageAnnots[$this->page]);
17392                              } else {
17393                                  $next_pask = 0;
17394                              }
17395                              if (isset($this->PageAnnots[$startlinepage])) {
17396                                  foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17397                                      if ($pak >= $pask) {
17398                                          $this->PageAnnots[$this->page][] = $pac;
17399                                          unset($this->PageAnnots[$startlinepage][$pak]);
17400                                          $npak = count($this->PageAnnots[$this->page]) - 1;
17401                                          $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17402                                      }
17403                                  }
17404                              }
17405                              $pask = $next_pask;
17406                              $startlinepos = $this->cntmrk[$this->page];
17407                              $startlinepage = $this->page;
17408                              $startliney = $this->y;
17409                              $this->newline = false;
17410                          }
17411                          $this->y += ($this->getCellHeight($curfontsize / $this->k) - ($curfontdescent * $this->cell_height_ratio) - $imgh);
17412                          $minstartliney = min($this->y, $minstartliney);
17413                          $maxbottomliney = ($startliney + $this->getCellHeight($curfontsize / $this->k));
17414                      }
17415                  } elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
17416                      // account for different font size
17417                      $pfontname = $curfontname;
17418                      $pfontstyle = $curfontstyle;
17419                      $pfontsize = $curfontsize;
17420                      $fontname = (isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname);
17421                      $fontstyle = (isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle);
17422                      $fontsize = (isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize);
17423                      $fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
17424                      $fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
17425                      if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
17426                          OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17427                          OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
17428                          if (($key < ($maxel - 1)) AND (
17429                                  ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
17430                                  OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17431                                  OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize)
17432                                  AND ($fontsize >= 0) AND ($curfontsize >= 0)
17433                                  AND (($fontsize != $curfontsize) OR ($fontstyle != $curfontstyle) OR ($fontname != $curfontname)))
17434                              )) {
17435                              if ($this->page > $startlinepage) {
17436                                  // fix lines splitted over two pages
17437                                  if (isset($this->footerlen[$startlinepage])) {
17438                                      $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17439                                  }
17440                                  // line to be moved one page forward
17441                                  $pagebuff = $this->getPageBuffer($startlinepage);
17442                                  $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17443                                  $tstart = substr($pagebuff, 0, $startlinepos);
17444                                  $tend = substr($this->getPageBuffer($startlinepage), $curpos);
17445                                  // remove line start from previous page
17446                                  $this->setPageBuffer($startlinepage, $tstart.''.$tend);
17447                                  $pagebuff = $this->getPageBuffer($this->page);
17448                                  $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17449                                  $tend = substr($pagebuff, $this->cntmrk[$this->page]);
17450                                  // add line start to current page
17451                                  $yshift = ($minstartliney - $this->y);
17452                                  $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17453                                  $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17454                                  // shift the annotations and links
17455                                  if (isset($this->PageAnnots[$this->page])) {
17456                                      $next_pask = count($this->PageAnnots[$this->page]);
17457                                  } else {
17458                                      $next_pask = 0;
17459                                  }
17460                                  if (isset($this->PageAnnots[$startlinepage])) {
17461                                      foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17462                                          if ($pak >= $pask) {
17463                                              $this->PageAnnots[$this->page][] = $pac;
17464                                              unset($this->PageAnnots[$startlinepage][$pak]);
17465                                              $npak = count($this->PageAnnots[$this->page]) - 1;
17466                                              $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17467                                          }
17468                                      }
17469                                  }
17470                                  $pask = $next_pask;
17471                                  $startlinepos = $this->cntmrk[$this->page];
17472                                  $startlinepage = $this->page;
17473                                  $startliney = $this->y;
17474                              }
17475                              if (!isset($dom[$key]['line-height'])) {
17476                                  $dom[$key]['line-height'] = $this->cell_height_ratio;
17477                              }
17478                              if (!$dom[$key]['block']) {
17479                                  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']))) {
17480                                      $this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
17481                                  }
17482                                  if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
17483                                      $current_line_align_data = array($key, $minstartliney, $maxbottomliney);
17484                                      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)))) {
17485                                          $minstartliney = min($this->y, $line_align_data[1]);
17486                                          $maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $line_align_data[2]);
17487                                      } else {
17488                                          $minstartliney = min($this->y, $minstartliney);
17489                                          $maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $maxbottomliney);
17490                                      }
17491                                      $line_align_data = $current_line_align_data;
17492                                  }
17493                              }
17494                              $this->cell_height_ratio = $dom[$key]['line-height'];
17495                              $fontaligned = true;
17496                          }
17497                          $this->SetFont($fontname, $fontstyle, $fontsize);
17498                          // reset row height
17499                          $this->resetLastH();
17500                          $curfontname = $fontname;
17501                          $curfontstyle = $fontstyle;
17502                          $curfontsize = $fontsize;
17503                          $curfontascent = $fontascent;
17504                          $curfontdescent = $fontdescent;
17505                      }
17506                  }
17507                  // set text rendering mode
17508                  $textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
17509                  $textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
17510                  $textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
17511                  $this->setTextRenderingMode($textstroke, $textfill, $textclip);
17512                  if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
17513                      $this->setFontStretching($dom[$key]['font-stretch']);
17514                  }
17515                  if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
17516                      $this->setFontSpacing($dom[$key]['letter-spacing']);
17517                  }
17518                  if (($plalign == 'J') AND $dom[$key]['block']) {
17519                      $plalign = '';
17520                  }
17521                  // get current position on page buffer
17522                  $curpos = $this->pagelen[$startlinepage];
17523                  if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
17524                      $this->SetFillColorArray($dom[$key]['bgcolor']);
17525                      $wfill = true;
17526                  } else {
17527                      $wfill = $fill | false;
17528                  }
17529                  if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
17530                      $this->SetTextColorArray($dom[$key]['fgcolor']);
17531                  }
17532                  if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
17533                      $this->SetDrawColorArray($dom[$key]['strokecolor']);
17534                  }
17535                  if (isset($dom[$key]['align'])) {
17536                      $lalign = $dom[$key]['align'];
17537                  }
17538                  if (TCPDF_STATIC::empty_string($lalign)) {
17539                      $lalign = $align;
17540                  }
17541              }
17542              // align lines
17543              if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
17544                  $newline = true;
17545                  $fontaligned = false;
17546                  // we are at the beginning of a new line
17547                  if (isset($startlinex)) {
17548                      $yshift = ($minstartliney - $startliney);
17549                      if (($yshift > 0) OR ($this->page > $startlinepage)) {
17550                          $yshift = 0;
17551                      }
17552                      $t_x = 0;
17553                      // the last line must be shifted to be aligned as requested
17554                      $linew = abs($this->endlinex - $startlinex);
17555                      if ($this->inxobj) {
17556                          // we are inside an XObject template
17557                          $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
17558                          if (isset($opentagpos)) {
17559                              $midpos = $opentagpos;
17560                          } else {
17561                              $midpos = 0;
17562                          }
17563                          if ($midpos > 0) {
17564                              $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
17565                              $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
17566                          } else {
17567                              $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
17568                              $pend = '';
17569                          }
17570                      } else {
17571                          $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
17572                          if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17573                              $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17574                              $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
17575                          } elseif (isset($opentagpos)) {
17576                              $midpos = $opentagpos;
17577                          } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17578                              $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17579                              $midpos = $this->footerpos[$startlinepage];
17580                          } else {
17581                              $midpos = 0;
17582                          }
17583                          if ($midpos > 0) {
17584                              $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
17585                              $pend = substr($this->getPageBuffer($startlinepage), $midpos);
17586                          } else {
17587                              $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
17588                              $pend = '';
17589                          }
17590                      }
17591                      if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
17592                          // calculate shifting amount
17593                          $tw = $w;
17594                          if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
17595                              $tw += $this->cell_padding['R'];
17596                          }
17597                          if ($this->lMargin != $prevlMargin) {
17598                              $tw += ($prevlMargin - $this->lMargin);
17599                          }
17600                          if ($this->rMargin != $prevrMargin) {
17601                              $tw += ($prevrMargin - $this->rMargin);
17602                          }
17603                          $one_space_width = $this->GetStringWidth(chr(32));
17604                          $no = 0; // number of spaces on a line contained on a single block
17605                          if ($this->isRTLTextDir()) { // RTL
17606                              // remove left space if exist
17607                              $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
17608                              if ($pos1 > 0) {
17609                                  $pos1 = intval($pos1);
17610                                  if ($this->isUnicodeFont()) {
17611                                      $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
17612                                      $spacelen = 2;
17613                                  } else {
17614                                      $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
17615                                      $spacelen = 1;
17616                                  }
17617                                  if ($pos1 == $pos2) {
17618                                      $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
17619                                      if (substr($pmid, $pos1, 4) == '[()]') {
17620                                          $linew -= $one_space_width;
17621                                      } elseif ($pos1 == strpos($pmid, '[(')) {
17622                                          $no = 1;
17623                                      }
17624                                  }
17625                              }
17626                          } else { // LTR
17627                              // remove right space if exist
17628                              $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
17629                              if ($pos1 > 0) {
17630                                  $pos1 = intval($pos1);
17631                                  if ($this->isUnicodeFont()) {
17632                                      $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
17633                                      $spacelen = 2;
17634                                  } else {
17635                                      $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
17636                                      $spacelen = 1;
17637                                  }
17638                                  if ($pos1 == $pos2) {
17639                                      $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
17640                                      $linew -= $one_space_width;
17641                                  }
17642                              }
17643                          }
17644                          $mdiff = ($tw - $linew);
17645                          if ($plalign == 'C') {
17646                              if ($this->rtl) {
17647                                  $t_x = -($mdiff / 2);
17648                              } else {
17649                                  $t_x = ($mdiff / 2);
17650                              }
17651                          } elseif ($plalign == 'R') {
17652                              // right alignment on LTR document
17653                              $t_x = $mdiff;
17654                          } elseif ($plalign == 'L') {
17655                              // left alignment on RTL document
17656                              $t_x = -$mdiff;
17657                          } elseif (($plalign == 'J') AND ($plalign == $lalign)) {
17658                              // Justification
17659                              if ($this->isRTLTextDir()) {
17660                                  // align text on the left
17661                                  $t_x = -$mdiff;
17662                              }
17663                              $ns = 0; // number of spaces
17664                              $pmidtemp = $pmid;
17665                              // escape special characters
17666                              $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
17667                              $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
17668                              // search spaces
17669                              if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
17670                                  $spacestr = $this->getSpaceString();
17671                                  $maxkk = count($lnstring[1]) - 1;
17672                                  for ($kk=0; $kk <= $maxkk; ++$kk) {
17673                                      // restore special characters
17674                                      $lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
17675                                      $lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
17676                                      // store number of spaces on the strings
17677                                      $lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
17678                                      // count total spaces on line
17679                                      $ns += $lnstring[2][$kk];
17680                                      $lnstring[3][$kk] = $ns;
17681                                  }
17682                                  if ($ns == 0) {
17683                                      $ns = 1;
17684                                  }
17685                                  // calculate additional space to add to each existing space
17686                                  $spacewidth = ($mdiff / ($ns - $no)) * $this->k;
17687                                  if ($this->FontSize <= 0) {
17688                                      $this->FontSize = 1;
17689                                  }
17690                                  $spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
17691                                  if ($this->font_spacing != 0) {
17692                                      // fixed spacing mode
17693                                      $osw = -1000 * $this->font_spacing / $this->FontSize;
17694                                      $spacewidthu += $osw;
17695                                  }
17696                                  $nsmax = $ns;
17697                                  $ns = 0;
17698                                  reset($lnstring);
17699                                  $offset = 0;
17700                                  $strcount = 0;
17701                                  $prev_epsposbeg = 0;
17702                                  $textpos = 0;
17703                                  if ($this->isRTLTextDir()) {
17704                                      $textpos = $this->wPt;
17705                                  }
17706                                  while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
17707                                      // check if we are inside a string section '[( ... )]'
17708                                      $stroffset = strpos($pmid, '[(', $offset);
17709                                      if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
17710                                          // set offset to the end of string section
17711                                          $offset = strpos($pmid, ')]', $stroffset);
17712                                          while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
17713                                              $offset = strpos($pmid, ')]', ($offset + 1));
17714                                          }
17715                                          if ($offset === false) {
17716                                              $this->Error('HTML Justification: malformed PDF code.');
17717                                          }
17718                                          continue;
17719                                      }
17720                                      if ($this->isRTLTextDir()) {
17721                                          $spacew = ($spacewidth * ($nsmax - $ns));
17722                                      } else {
17723                                          $spacew = ($spacewidth * $ns);
17724                                      }
17725                                      $offset = $strpiece[2][1] + strlen($strpiece[2][0]);
17726                                      $epsposend = strpos($pmid, $this->epsmarker.'Q', $offset);                
17727                                      if ($epsposend !== null) {
17728                                          $epsposend += strlen($this->epsmarker.'Q');
17729                                          $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
17730                                          if ($epsposbeg === null) {
17731                                              $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
17732                                              $prev_epsposbeg = $epsposbeg;
17733                                          }
17734                                          if (($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend)) {
17735                                              // shift EPS images
17736                                              $trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
17737                                              $pmid_b = substr($pmid, 0, $epsposbeg);
17738                                              $pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
17739                                              $pmid_e = substr($pmid, $epsposend);
17740                                              $pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
17741                                              $offset = $epsposend;
17742                                              continue;
17743                                          }
17744                                      }
17745                                      $currentxpos = 0;
17746                                      // shift blocks of code
17747                                      switch ($strpiece[2][0]) {
17748                                          case 'Td':
17749                                          case 'cm':
17750                                          case 'm':
17751                                          case 'l': {
17752                                              // get current X position
17753                                              preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
17754                                              if (!isset($xmatches[1])) {
17755                                                  break;
17756                                              }
17757                                              $currentxpos = $xmatches[1];
17758                                              $textpos = $currentxpos;
17759                                              if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
17760                                                  $ns = $lnstring[3][$strcount];
17761                                                  if ($this->isRTLTextDir()) {
17762                                                      $spacew = ($spacewidth * ($nsmax - $ns));
17763                                                  }
17764                                                  ++$strcount;
17765                                              }
17766                                              // justify block
17767                                              if (preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $pmatch) == 1) {
17768                                                  $newpmid = sprintf('%F',(floatval($pmatch[1]) + $spacew)).' '.$pmatch[2].' x*#!#*x'.$pmatch[3].$pmatch[4];
17769                                                  $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17770                                                  unset($pmatch, $newpmid);
17771                                              }
17772                                              break;
17773                                          }
17774                                          case 're': {
17775                                              // justify block
17776                                              if (!TCPDF_STATIC::empty_string($this->lispacer)) {
17777                                                  $this->lispacer = '';
17778                                                  continue;
17779                                              }
17780                                              preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
17781                                              if (!isset($xmatches[1])) {
17782                                                  break;
17783                                              }
17784                                              $currentxpos = $xmatches[1];
17785                                              $x_diff = 0;
17786                                              $w_diff = 0;
17787                                              if ($this->isRTLTextDir()) { // RTL
17788                                                  if ($currentxpos < $textpos) {
17789                                                      $x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
17790                                                      $w_diff = ($spacewidth * $lnstring[2][$strcount]);
17791                                                  } else {
17792                                                      if ($strcount > 0) {
17793                                                          $x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
17794                                                          $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17795                                                      }
17796                                                  }
17797                                              } else { // LTR
17798                                                  if ($currentxpos > $textpos) {
17799                                                      if ($strcount > 0) {
17800                                                          $x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
17801                                                      }
17802                                                      $w_diff = ($spacewidth * $lnstring[2][$strcount]);
17803                                                  } else {
17804                                                      if ($strcount > 1) {
17805                                                          $x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
17806                                                      }
17807                                                      if ($strcount > 0) {
17808                                                          $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17809                                                      }
17810                                                  }
17811                                              }
17812                                              if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $pmatch) == 1) {
17813                                                  $newx = sprintf('%F',(floatval($pmatch[1]) + $x_diff));
17814                                                  $neww = sprintf('%F',(floatval($pmatch[3]) + $w_diff));
17815                                                  $newpmid = $newx.' '.$pmatch[2].' '.$neww.' '.$pmatch[4].' x*#!#*x'.$pmatch[5].$pmatch[6];
17816                                                  $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17817                                                  unset($pmatch, $newpmid, $newx, $neww);
17818                                              }
17819                                              break;
17820                                          }
17821                                          case 'c': {
17822                                              // get current X position
17823                                              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);
17824                                              if (!isset($xmatches[1])) {
17825                                                  break;
17826                                              }
17827                                              $currentxpos = $xmatches[1];
17828                                              // justify block
17829                                              if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$xmatches[4].')[\s]('.$xmatches[5].')[\s]('.$strpiece[1][0].')[\s](c)([\s]*)/x', $pmid, $pmatch) == 1) {
17830                                                  $newx1 = sprintf('%F',(floatval($pmatch[1]) + $spacew));
17831                                                  $newx2 = sprintf('%F',(floatval($pmatch[3]) + $spacew));
17832                                                  $newx3 = sprintf('%F',(floatval($pmatch[5]) + $spacew));
17833                                                  $newpmid = $newx1.' '.$pmatch[2].' '.$newx2.' '.$pmatch[4].' '.$newx3.' '.$pmatch[6].' x*#!#*x'.$pmatch[7].$pmatch[8];
17834                                                  $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17835                                                  unset($pmatch, $newpmid, $newx1, $newx2, $newx3);
17836                                              }
17837                                              break;
17838                                          }
17839                                      }
17840                                      // shift the annotations and links
17841                                      $cxpos = ($currentxpos / $this->k);
17842                                      $lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
17843                                      if ($this->inxobj) {
17844                                          // we are inside an XObject template
17845                                          foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
17846                                              if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17847                                                  if ($cxpos > $lmpos) {
17848                                                      $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
17849                                                      $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17850                                                  } else {
17851                                                      $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17852                                                  }
17853                                                  break;
17854                                              }
17855                                          }
17856                                      } elseif (isset($this->PageAnnots[$this->page])) {
17857                                          foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
17858                                              if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17859                                                  if ($cxpos > $lmpos) {
17860                                                      $this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
17861                                                      $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17862                                                  } else {
17863                                                      $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17864                                                  }
17865                                                  break;
17866                                              }
17867                                          }
17868                                      }
17869                                  } // end of while
17870                                  // remove markers
17871                                  $pmid = str_replace('x*#!#*x', '', $pmid);
17872                                  if ($this->isUnicodeFont()) {
17873                                      // multibyte characters
17874                                      $spacew = $spacewidthu;
17875                                      if ($this->font_stretching != 100) {
17876                                          // word spacing is affected by stretching
17877                                          $spacew /= ($this->font_stretching / 100);
17878                                      }
17879                                      // escape special characters
17880                                      $pos = 0;
17881                                      $pmid = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmid);
17882                                      $pmid = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmid);
17883                                      if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmid, $pamatch) > 0) {
17884                                          foreach($pamatch[0] as $pk => $pmatch) {
17885                                              $replace = $pamatch[1][$pk];
17886                                              $replace = str_replace('#!#OP#!#', '(', $replace);
17887                                              $replace = str_replace('#!#CP#!#', ')', $replace);
17888                                              $newpmid = '[('.str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacew).' (', $replace).')]';
17889                                              $pos = strpos($pmid, $pmatch, $pos);
17890                                              if ($pos !== FALSE) {
17891                                                  $pmid = substr_replace($pmid, $newpmid, $pos, strlen($pmatch));
17892                                              }
17893                                              ++$pos;
17894                                          }
17895                                          unset($pamatch);
17896                                      }
17897                                      if ($this->inxobj) {
17898                                          // we are inside an XObject template
17899                                          $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
17900                                      } else {
17901                                          $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
17902                                      }
17903                                      $endlinepos = strlen($pstart."\n".$pmid."\n");
17904                                  } else {
17905                                      // non-unicode (single-byte characters)
17906                                      if ($this->font_stretching != 100) {
17907                                          // word spacing (Tw) is affected by stretching
17908                                          $spacewidth /= ($this->font_stretching / 100);
17909                                      }
17910                                      $rs = sprintf('%F Tw', $spacewidth);
17911                                      $pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
17912                                      if ($this->inxobj) {
17913                                          // we are inside an XObject template
17914                                          $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
17915                                      } else {
17916                                          $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
17917                                      }
17918                                      $endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
17919                                  }
17920                              }
17921                          } // end of J
17922                      } // end if $startlinex
17923                      if (($t_x != 0) OR ($yshift < 0)) {
17924                          // shift the line
17925                          $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
17926                          $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
17927                          $endlinepos = strlen($pstart);
17928                          if ($this->inxobj) {
17929                              // we are inside an XObject template
17930                              $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
17931                              foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
17932                                  if ($pak >= $pask) {
17933                                      $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
17934                                      $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
17935                                  }
17936                              }
17937                          } else {
17938                              $this->setPageBuffer($startlinepage, $pstart.$pend);
17939                              // shift the annotations and links
17940                              if (isset($this->PageAnnots[$this->page])) {
17941                                  foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
17942                                      if ($pak >= $pask) {
17943                                          $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
17944                                          $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
17945                                      }
17946                                  }
17947                              }
17948                          }
17949                          $this->y -= $yshift;
17950                      }
17951                  }
17952                  $pbrk = $this->checkPageBreak($this->lasth);
17953                  $this->newline = false;
17954                  $startlinex = $this->x;
17955                  $startliney = $this->y;
17956                  if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
17957                      $startliney -= ((0.3 * $this->FontSizePt) / $this->k);
17958                  } elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
17959                      $startliney -= (($this->FontSizePt / 0.7) / $this->k);
17960                  } else {
17961                      $minstartliney = $startliney;
17962                      $maxbottomliney = ($this->y + $this->getCellHeight($fontsize / $this->k));
17963                  }
17964                  $startlinepage = $this->page;
17965                  if (isset($endlinepos) AND (!$pbrk)) {
17966                      $startlinepos = $endlinepos;
17967                  } else {
17968                      if ($this->inxobj) {
17969                          // we are inside an XObject template
17970                          $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17971                      } elseif (!$this->InFooter) {
17972                          if (isset($this->footerlen[$this->page])) {
17973                              $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17974                          } else {
17975                              $this->footerpos[$this->page] = $this->pagelen[$this->page];
17976                          }
17977                          $startlinepos = $this->footerpos[$this->page];
17978                      } else {
17979                          $startlinepos = $this->pagelen[$this->page];
17980                      }
17981                  }
17982                  unset($endlinepos);
17983                  $plalign = $lalign;
17984                  if (isset($this->PageAnnots[$this->page])) {
17985                      $pask = count($this->PageAnnots[$this->page]);
17986                  } else {
17987                      $pask = 0;
17988                  }
17989                  if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
17990                      AND (isset($this->emptypagemrk[$this->page]))
17991                      AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
17992                      $this->SetFont($fontname, $fontstyle, $fontsize);
17993                      if ($wfill) {
17994                          $this->SetFillColorArray($this->bgcolor);
17995                      }
17996                  }
17997              } // end newline
17998              if (isset($opentagpos)) {
17999                  unset($opentagpos);
18000              }
18001              if ($dom[$key]['tag']) {
18002                  if ($dom[$key]['opening']) {
18003                      // get text indentation (if any)
18004                      if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
18005                          $this->textindent = $dom[$key]['text-indent'];
18006                          $this->newline = true;
18007                      }
18008                      // table
18009                      if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
18010                          // available page width
18011                          if ($this->rtl) {
18012                              $wtmp = $this->x - $this->lMargin;
18013                          } else {
18014                              $wtmp = $this->w - $this->rMargin - $this->x;
18015                          }
18016                          // get cell spacing
18017                          if (isset($dom[$key]['attribute']['cellspacing'])) {
18018                              $clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
18019                              $cellspacing = array('H' => $clsp, 'V' => $clsp);
18020                          } elseif (isset($dom[$key]['border-spacing'])) {
18021                              $cellspacing = $dom[$key]['border-spacing'];
18022                          } else {
18023                              $cellspacing = array('H' => 0, 'V' => 0);
18024                          }
18025                          // table width
18026                          if (isset($dom[$key]['width'])) {
18027                              $table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
18028                          } else {
18029                              $table_width = $wtmp;
18030                          }
18031                          $table_width -= (2 * $cellspacing['H']);
18032                          if (!$this->inthead) {
18033                              $this->y += $cellspacing['V'];
18034                          }
18035                          if ($this->rtl) {
18036                              $cellspacingx = -$cellspacing['H'];
18037                          } else {
18038                              $cellspacingx = $cellspacing['H'];
18039                          }
18040                          // total table width without cellspaces
18041                          $table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
18042                          // minimum column width
18043                          $table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
18044                          // array of custom column widths
18045                          $table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
18046                      }
18047                      // table row
18048                      if ($dom[$key]['value'] == 'tr') {
18049                          // reset column counter
18050                          $colid = 0;
18051                      }
18052                      // table cell
18053                      if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18054                          $trid = $dom[$key]['parent'];
18055                          $table_el = $dom[$trid]['parent'];
18056                          if (!isset($dom[$table_el]['cols'])) {
18057                              $dom[$table_el]['cols'] = $dom[$trid]['cols'];
18058                          }
18059                          // store border info
18060                          $tdborder = 0;
18061                          if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
18062                              $tdborder = $dom[$key]['border'];
18063                          }
18064                          $colspan = intval($dom[$key]['attribute']['colspan']);
18065                          if ($colspan <= 0) {
18066                              $colspan = 1;
18067                          }
18068                          $old_cell_padding = $this->cell_padding;
18069                          if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
18070                              $crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
18071                              $current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
18072                          } elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
18073                              $current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
18074                          } else {
18075                              $current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
18076                          }
18077                          $this->cell_padding = $current_cell_padding;
18078                          if (isset($dom[$key]['height'])) {
18079                              // minimum cell height
18080                              $cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
18081                          } else {
18082                              $cellh = 0;
18083                          }
18084                          if (isset($dom[$key]['content'])) {
18085                              $cell_content = $dom[$key]['content'];
18086                          } else {
18087                              $cell_content = '&nbsp;';
18088                          }
18089                          $tagtype = $dom[$key]['value'];
18090                          $parentid = $key;
18091                          while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
18092                              // move $key index forward
18093                              ++$key;
18094                          }
18095                          if (!isset($dom[$trid]['startpage'])) {
18096                              $dom[$trid]['startpage'] = $this->page;
18097                          } else {
18098                              $this->setPage($dom[$trid]['startpage']);
18099                          }
18100                          if (!isset($dom[$trid]['startcolumn'])) {
18101                              $dom[$trid]['startcolumn'] = $this->current_column;
18102                          } elseif ($this->current_column != $dom[$trid]['startcolumn']) {
18103                              $tmpx = $this->x;
18104                              $this->selectColumn($dom[$trid]['startcolumn']);
18105                              $this->x = $tmpx;
18106                          }
18107                          if (!isset($dom[$trid]['starty'])) {
18108                              $dom[$trid]['starty'] = $this->y;
18109                          } else {
18110                              $this->y = $dom[$trid]['starty'];
18111                          }
18112                          if (!isset($dom[$trid]['startx'])) {
18113                              $dom[$trid]['startx'] = $this->x;
18114                              $this->x += $cellspacingx;
18115                          } else {
18116                              $this->x += ($cellspacingx / 2);
18117                          }
18118                          if (isset($dom[$parentid]['attribute']['rowspan'])) {
18119                              $rowspan = intval($dom[$parentid]['attribute']['rowspan']);
18120                          } else {
18121                              $rowspan = 1;
18122                          }
18123                          // skip row-spanned cells started on the previous rows
18124                          if (isset($dom[$table_el]['rowspans'])) {
18125                              $rsk = 0;
18126                              $rskmax = count($dom[$table_el]['rowspans']);
18127                              while ($rsk < $rskmax) {
18128                                  $trwsp = $dom[$table_el]['rowspans'][$rsk];
18129                                  $rsstartx = $trwsp['startx'];
18130                                  $rsendx = $trwsp['endx'];
18131                                  // account for margin changes
18132                                  if ($trwsp['startpage'] < $this->page) {
18133                                      if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
18134                                          $dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
18135                                          $rsstartx -= $dl;
18136                                          $rsendx -= $dl;
18137                                      } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
18138                                          $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
18139                                          $rsstartx += $dl;
18140                                          $rsendx += $dl;
18141                                      }
18142                                  }
18143                                  if (($trwsp['rowspan'] > 0)
18144                                      AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
18145                                      AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
18146                                      AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
18147                                      // set the starting X position of the current cell
18148                                      $this->x = $rsendx + $cellspacingx;
18149                                      // increment column indicator
18150                                      $colid += $trwsp['colspan'];
18151                                      if (($trwsp['rowspan'] == 1)
18152                                          AND (isset($dom[$trid]['endy']))
18153                                          AND (isset($dom[$trid]['endpage']))
18154                                          AND (isset($dom[$trid]['endcolumn']))
18155                                          AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
18156                                          AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18157                                          // set ending Y position for row
18158                                          $dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18159                                          $dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
18160                                      }
18161                                      $rsk = 0;
18162                                  } else {
18163                                      ++$rsk;
18164                                  }
18165                              }
18166                          }
18167                          if (isset($dom[$parentid]['width'])) {
18168                              // user specified width
18169                              $cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
18170                              $tmpcw = ($cellw / $colspan);
18171                              for ($i = 0; $i < $colspan; ++$i) {
18172                                  $table_colwidths[($colid + $i)] = $tmpcw;
18173                              }
18174                          } else {
18175                              // inherit column width
18176                              $cellw = 0;
18177                              for ($i = 0; $i < $colspan; ++$i) {
18178                                  $cellw += (isset($table_colwidths[($colid + $i)]) ? $table_colwidths[($colid + $i)] : 0);
18179                              }
18180                          }
18181                          $cellw += (($colspan - 1) * $cellspacing['H']);
18182                          // increment column indicator
18183                          $colid += $colspan;
18184                          // add rowspan information to table element
18185                          if ($rowspan > 1) {
18186                              $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));
18187                          }
18188                          $cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
18189                          if ($rowspan > 1) {
18190                              $dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
18191                          }
18192                          // push background colors
18193                          if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
18194                              $dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
18195                          }
18196                          // store border info
18197                          if (isset($tdborder) AND !empty($tdborder)) {
18198                              $dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
18199                          }
18200                          $prevLastH = $this->lasth;
18201                          // store some info for multicolumn mode
18202                          if ($this->rtl) {
18203                              $this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
18204                          } else {
18205                              $this->colxshift['x'] = $this->x - $this->lMargin;
18206                          }
18207                          $this->colxshift['s'] = $cellspacing;
18208                          $this->colxshift['p'] = $current_cell_padding;
18209                          // ****** write the cell content ******
18210                          $this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
18211                          // restore some values
18212                          $this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
18213                          $this->lasth = $prevLastH;
18214                          $this->cell_padding = $old_cell_padding;
18215                          $dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
18216                          // update the end of row position
18217                          if ($rowspan <= 1) {
18218                              if (isset($dom[$trid]['endy'])) {
18219                                  if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
18220                                      $dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
18221                                  } elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
18222                                      $dom[$trid]['endy'] = $this->y;
18223                                  }
18224                              } else {
18225                                  $dom[$trid]['endy'] = $this->y;
18226                              }
18227                              if (isset($dom[$trid]['endpage'])) {
18228                                  $dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
18229                              } else {
18230                                  $dom[$trid]['endpage'] = $this->page;
18231                              }
18232                              if (isset($dom[$trid]['endcolumn'])) {
18233                                  $dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
18234                              } else {
18235                                  $dom[$trid]['endcolumn'] = $this->current_column;
18236                              }
18237                          } else {
18238                              // account for row-spanned cells
18239                              $dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
18240                              $dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
18241                              $dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
18242                              $dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
18243                          }
18244                          if (isset($dom[$table_el]['rowspans'])) {
18245                              // update endy and endpage on rowspanned cells
18246                              foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
18247                                  if ($trwsp['rowspan'] > 0) {
18248                                      if (isset($dom[$trid]['endpage'])) {
18249                                          if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18250                                              $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18251                                          } elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
18252                                              $dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
18253                                              $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
18254                                              $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
18255                                          } else {
18256                                              $dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
18257                                          }
18258                                      }
18259                                  }
18260                              }
18261                          }
18262                          $this->x += ($cellspacingx / 2);
18263                      } else {
18264                          // opening tag (or self-closing tag)
18265                          if (!isset($opentagpos)) {
18266                              if ($this->inxobj) {
18267                                  // we are inside an XObject template
18268                                  $opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
18269                              } elseif (!$this->InFooter) {
18270                                  if (isset($this->footerlen[$this->page])) {
18271                                      $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18272                                  } else {
18273                                      $this->footerpos[$this->page] = $this->pagelen[$this->page];
18274                                  }
18275                                  $opentagpos = $this->footerpos[$this->page];
18276                              }
18277                          }
18278                          $dom = $this->openHTMLTagHandler($dom, $key, $cell);
18279                      }
18280                  } else { // closing tag
18281                      $prev_numpages = $this->numpages;
18282                      $old_bordermrk = $this->bordermrk[$this->page];
18283                      $dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
18284                      if ($this->bordermrk[$this->page] > $old_bordermrk) {
18285                          $startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
18286                      }
18287                      if ($prev_numpages > $this->numpages) {
18288                          $startlinepage = $this->page;
18289                      }
18290                  }
18291              } elseif (strlen($dom[$key]['value']) > 0) {
18292                  // print list-item
18293                  if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) {
18294                      $this->SetFont($pfontname, $pfontstyle, $pfontsize);
18295                      $this->resetLastH();
18296                      $minstartliney = $this->y;
18297                      $maxbottomliney = ($startliney + $this->getCellHeight($this->FontSize));
18298                      if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
18299                          $this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
18300                      }
18301                      $this->SetFont($curfontname, $curfontstyle, $curfontsize);
18302                      $this->resetLastH();
18303                      if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
18304                          $pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
18305                          $pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
18306                          $this->y += ($this->getCellHeight(($pfontsize - $curfontsize) / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
18307                          $minstartliney = min($this->y, $minstartliney);
18308                          $maxbottomliney = max(($this->y + $this->getCellHeight($pfontsize / $this->k)), $maxbottomliney);
18309                      }
18310                  }
18311                  // text
18312                  $this->htmlvspace = 0;
18313                  if ((!$this->premode) AND $this->isRTLTextDir()) {
18314                      // reverse spaces order
18315                      $lsp = ''; // left spaces
18316                      $rsp = ''; // right spaces
18317                      if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18318                          $lsp = $matches[1];
18319                      }
18320                      if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18321                          $rsp = $matches[1];
18322                      }
18323                      $dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
18324                  }
18325                  if ($newline) {
18326                      if (!$this->premode) {
18327                          $prelen = strlen($dom[$key]['value']);
18328                          if ($this->isRTLTextDir()) {
18329                              // right trim except non-breaking space
18330                              $dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
18331                          } else {
18332                              // left trim except non-breaking space
18333                              $dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
18334                          }
18335                          $postlen = strlen($dom[$key]['value']);
18336                          if (($postlen == 0) AND ($prelen > 0)) {
18337                              $dom[$key]['trimmed_space'] = true;
18338                          }
18339                      }
18340                      $newline = false;
18341                      $firstblock = true;
18342                  } else {
18343                      $firstblock = false;
18344                      // replace empty multiple spaces string with a single space
18345                      $dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
18346                  }
18347                  $strrest = '';
18348                  if ($this->rtl) {
18349                      $this->x -= $this->textindent;
18350                  } else {
18351                      $this->x += $this->textindent;
18352                  }
18353                  if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
18354                      $strlinelen = $this->GetStringWidth($dom[$key]['value']);
18355                      if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
18356                          // HTML <a> Link
18357                          $hrefcolor = '';
18358                          if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
18359                              $hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
18360                          }
18361                          $hrefstyle = -1;
18362                          if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
18363                              $hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
18364                          }
18365                          $strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
18366                      } else {
18367                          $wadj = 0; // space to leave for block continuity
18368                          if ($this->rtl) {
18369                              $cwa = ($this->x - $this->lMargin);
18370                          } else {
18371                              $cwa = ($this->w - $this->rMargin - $this->x);
18372                          }
18373                          if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
18374                              // check the next text blocks for continuity
18375                              $nkey = ($key + 1);
18376                              $write_block = true;
18377                              $same_textdir = true;
18378                              $tmp_fontname = $this->FontFamily;
18379                              $tmp_fontstyle = $this->FontStyle;
18380                              $tmp_fontsize = $this->FontSizePt;
18381                              while ($write_block AND isset($dom[$nkey])) {
18382                                  if ($dom[$nkey]['tag']) {
18383                                      if ($dom[$nkey]['block']) {
18384                                          // end of block
18385                                          $write_block = false;
18386                                      }
18387                                      $tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
18388                                      $tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
18389                                      $tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
18390                                      $same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
18391                                  } else {
18392                                      $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'+/', $this->re_space['m'], $dom[$nkey]['value']);
18393                                      if (isset($nextstr[0]) AND $same_textdir) {
18394                                          $wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
18395                                          if (isset($nextstr[1])) {
18396                                              $write_block = false;
18397                                          }
18398                                      }
18399                                  }
18400                                  ++$nkey;
18401                              }
18402                          }
18403                          if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
18404                              $wadj = 0;
18405                              $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $dom[$key]['value']);
18406                              $numblks = count($nextstr);
18407                              if ($numblks > 1) {
18408                                  // try to split on blank spaces
18409                                  $wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
18410                              } else {
18411                                  // set the entire block on new line
18412                                  $wadj = $this->GetStringWidth($nextstr[0]);
18413                              }
18414                          }
18415                          // check for reversed text direction
18416                          if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
18417                              // LTR text on RTL direction or RTL text on LTR direction
18418                              $reverse_dir = true;
18419                              $this->rtl = !$this->rtl;
18420                              $revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
18421                              if ($this->rtl) {
18422                                  $this->x += $revshift;
18423                              } else {
18424                                  $this->x -= $revshift;
18425                              }
18426                              $xws = $this->x;
18427                          }
18428                          // ****** write only until the end of the line and get the rest ******
18429                          $strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
18430                          // restore default direction
18431                          if ($reverse_dir AND ($wadj == 0)) {
18432                              $this->x = $xws;
18433                              $this->rtl = !$this->rtl;
18434                              $reverse_dir = false;
18435                          }
18436                      }
18437                  }
18438                  $this->textindent = 0;
18439                  if (strlen($strrest) > 0) {
18440                      // store the remaining string on the previous $key position
18441                      $this->newline = true;
18442                      if ($strrest == $dom[$key]['value']) {
18443                          // used to avoid infinite loop
18444                          ++$loop;
18445                      } else {
18446                          $loop = 0;
18447                      }
18448                      $dom[$key]['value'] = $strrest;
18449                      if ($cell) {
18450                          if ($this->rtl) {
18451                              $this->x -= $this->cell_padding['R'];
18452                          } else {
18453                              $this->x += $this->cell_padding['L'];
18454                          }
18455                      }
18456                      if ($loop < 3) {
18457                          --$key;
18458                      }
18459                  } else {
18460                      $loop = 0;
18461                      // add the positive font spacing of the last character (if any)
18462                       if ($this->font_spacing > 0) {
18463                           if ($this->rtl) {
18464                              $this->x -= $this->font_spacing;
18465                          } else {
18466                              $this->x += $this->font_spacing;
18467                          }
18468                      }
18469                  }
18470              }
18471              ++$key;
18472              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')) {
18473                  // check if we are on a new page or on a new column
18474                  if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
18475                      // we are on a new page or on a new column and the total object height is less than the available vertical space.
18476                      // restore previous object
18477                      $this->rollbackTransaction(true);
18478                      // restore previous values
18479                      foreach ($this_method_vars as $vkey => $vval) {
18480                          $$vkey = $vval;
18481                      }
18482                      if (!empty($dom[$key]['thead'])) {
18483                          $this->inthead = true;
18484                      }
18485                      // add a page (or trig AcceptPageBreak() for multicolumn mode)
18486                      $pre_y = $this->y;
18487                      if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
18488                          $startliney = $this->y;
18489                      }
18490                      $undo = true; // avoid infinite loop
18491                  } else {
18492                      $undo = false;
18493                  }
18494              }
18495          } // end for each $key
18496          // align the last line
18497          if (isset($startlinex)) {
18498              $yshift = ($minstartliney - $startliney);
18499              if (($yshift > 0) OR ($this->page > $startlinepage)) {
18500                  $yshift = 0;
18501              }
18502              $t_x = 0;
18503              // the last line must be shifted to be aligned as requested
18504              $linew = abs($this->endlinex - $startlinex);
18505              if ($this->inxobj) {
18506                  // we are inside an XObject template
18507                  $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
18508                  if (isset($opentagpos)) {
18509                      $midpos = $opentagpos;
18510                  } else {
18511                      $midpos = 0;
18512                  }
18513                  if ($midpos > 0) {
18514                      $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
18515                      $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
18516                  } else {
18517                      $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
18518                      $pend = '';
18519                  }
18520              } else {
18521                  $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
18522                  if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18523                      $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18524                      $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
18525                  } elseif (isset($opentagpos)) {
18526                      $midpos = $opentagpos;
18527                  } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18528                      $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18529                      $midpos = $this->footerpos[$startlinepage];
18530                  } else {
18531                      $midpos = 0;
18532                  }
18533                  if ($midpos > 0) {
18534                      $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
18535                      $pend = substr($this->getPageBuffer($startlinepage), $midpos);
18536                  } else {
18537                      $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
18538                      $pend = '';
18539                  }
18540              }
18541              if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
18542                  // calculate shifting amount
18543                  $tw = $w;
18544                  if ($this->lMargin != $prevlMargin) {
18545                      $tw += ($prevlMargin - $this->lMargin);
18546                  }
18547                  if ($this->rMargin != $prevrMargin) {
18548                      $tw += ($prevrMargin - $this->rMargin);
18549                  }
18550                  $one_space_width = $this->GetStringWidth(chr(32));
18551                  $no = 0; // number of spaces on a line contained on a single block
18552                  if ($this->isRTLTextDir()) { // RTL
18553                      // remove left space if exist
18554                      $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
18555                      if ($pos1 > 0) {
18556                          $pos1 = intval($pos1);
18557                          if ($this->isUnicodeFont()) {
18558                              $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
18559                              $spacelen = 2;
18560                          } else {
18561                              $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
18562                              $spacelen = 1;
18563                          }
18564                          if ($pos1 == $pos2) {
18565                              $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
18566                              if (substr($pmid, $pos1, 4) == '[()]') {
18567                                  $linew -= $one_space_width;
18568                              } elseif ($pos1 == strpos($pmid, '[(')) {
18569                                  $no = 1;
18570                              }
18571                          }
18572                      }
18573                  } else { // LTR
18574                      // remove right space if exist
18575                      $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
18576                      if ($pos1 > 0) {
18577                          $pos1 = intval($pos1);
18578                          if ($this->isUnicodeFont()) {
18579                              $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
18580                              $spacelen = 2;
18581                          } else {
18582                              $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
18583                              $spacelen = 1;
18584                          }
18585                          if ($pos1 == $pos2) {
18586                              $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
18587                              $linew -= $one_space_width;
18588                          }
18589                      }
18590                  }
18591                  $mdiff = ($tw - $linew);
18592                  if ($plalign == 'C') {
18593                      if ($this->rtl) {
18594                          $t_x = -($mdiff / 2);
18595                      } else {
18596                          $t_x = ($mdiff / 2);
18597                      }
18598                  } elseif ($plalign == 'R') {
18599                      // right alignment on LTR document
18600                      $t_x = $mdiff;
18601                  } elseif ($plalign == 'L') {
18602                      // left alignment on RTL document
18603                      $t_x = -$mdiff;
18604                  }
18605              } // end if startlinex
18606              if (($t_x != 0) OR ($yshift < 0)) {
18607                  // shift the line
18608                  $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18609                  $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18610                  $endlinepos = strlen($pstart);
18611                  if ($this->inxobj) {
18612                      // we are inside an XObject template
18613                      $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18614                      foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18615                          if ($pak >= $pask) {
18616                              $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18617                              $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18618                          }
18619                      }
18620                  } else {
18621                      $this->setPageBuffer($startlinepage, $pstart.$pend);
18622                      // shift the annotations and links
18623                      if (isset($this->PageAnnots[$this->page])) {
18624                          foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18625                              if ($pak >= $pask) {
18626                                  $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18627                                  $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18628                              }
18629                          }
18630                      }
18631                  }
18632                  $this->y -= $yshift;
18633                  $yshift = 0;
18634              }
18635          }
18636          // restore previous values
18637          $this->setGraphicVars($gvars);
18638          if ($this->num_columns > 1) {
18639              $this->selectColumn();
18640          } elseif ($this->page > $prevPage) {
18641              $this->lMargin = $this->pagedim[$this->page]['olm'];
18642              $this->rMargin = $this->pagedim[$this->page]['orm'];
18643          }
18644          // restore previous list state
18645          $this->cell_height_ratio = $prev_cell_height_ratio;
18646          $this->listnum = $prev_listnum;
18647          $this->listordered = $prev_listordered;
18648          $this->listcount = $prev_listcount;
18649          $this->lispacer = $prev_lispacer;
18650          if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
18651              $this->Ln($this->lasth);
18652              if (($this->y < $maxbottomliney) AND ($startlinepage == $this->page)) {
18653                  $this->y = $maxbottomliney;
18654              }
18655          }
18656          unset($dom);
18657      }
18658  
18659      /**
18660       * Process opening tags.
18661       * @param $dom (array) html dom array
18662       * @param $key (int) current element id
18663       * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
18664       * @return $dom array
18665       * @protected
18666       */
18667  	protected function openHTMLTagHandler($dom, $key, $cell) {
18668          $tag = $dom[$key];
18669          $parent = $dom[($dom[$key]['parent'])];
18670          $firsttag = ($key == 1);
18671          // check for text direction attribute
18672          if (isset($tag['dir'])) {
18673              $this->setTempRTL($tag['dir']);
18674          } else {
18675              $this->tmprtl = false;
18676          }
18677          if ($tag['block']) {
18678              $hbz = 0; // distance from y to line bottom
18679              $hb = 0; // vertical space between block tags
18680              // calculate vertical space for block tags
18681              if (isset($this->tagvspaces[$tag['value']][0]['h']) AND ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
18682                  $cur_h = $this->tagvspaces[$tag['value']][0]['h'];
18683              } elseif (isset($tag['fontsize'])) {
18684                  $cur_h = $this->getCellHeight($tag['fontsize'] / $this->k);
18685              } else {
18686                  $cur_h = $this->getCellHeight($this->FontSize);
18687              }
18688              if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
18689                  $on = $this->tagvspaces[$tag['value']][0]['n'];
18690              } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18691                  $on = 0.6;
18692              } else {
18693                  $on = 1;
18694              }
18695              if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br', 'hr')))) {
18696                  $hb = 0;
18697              } else {
18698                  $hb = ($on * $cur_h);
18699              }
18700              if (($this->htmlvspace <= 0) AND ($on > 0)) {
18701                  if (isset($parent['fontsize'])) {
18702                      $hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
18703                  } else {
18704                      $hbz = $this->getCellHeight($this->FontSize);
18705                  }
18706              }
18707              if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
18708                  // fix vertical space after table
18709                  $hbz = 0;
18710              }
18711              // closing vertical space
18712              $hbc = 0;
18713              if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
18714                  $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
18715              } elseif (isset($parent['fontsize'])) {
18716                  $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
18717              } else {
18718                  $pre_h = $this->getCellHeight($this->FontSize);
18719              }
18720              if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
18721                  $cn = $this->tagvspaces[$tag['value']][1]['n'];
18722              } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18723                  $cn = 0.6;
18724              } else {
18725                  $cn = 1;
18726              }
18727              if (isset($this->tagvspaces[$tag['value']][1])) {
18728                  $hbc = ($cn * $pre_h);
18729              }
18730          }
18731          // Opening tag
18732          switch($tag['value']) {
18733              case 'table': {
18734                  $cp = 0;
18735                  $cs = 0;
18736                  $dom[$key]['rowspans'] = array();
18737                  if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
18738                      $this->htmlvspace = 0;
18739                      // set table header
18740                      if (!TCPDF_STATIC::empty_string($dom[$key]['thead'])) {
18741                          // set table header
18742                          $this->thead = $dom[$key]['thead'];
18743                          if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
18744                              $this->theadMargins = array();
18745                              $this->theadMargins['cell_padding'] = $this->cell_padding;
18746                              $this->theadMargins['lmargin'] = $this->lMargin;
18747                              $this->theadMargins['rmargin'] = $this->rMargin;
18748                              $this->theadMargins['page'] = $this->page;
18749                              $this->theadMargins['cell'] = $cell;
18750                              $this->theadMargins['gvars'] = $this->getGraphicVars();
18751                          }
18752                      }
18753                  }
18754                  // store current margins and page
18755                  $dom[$key]['old_cell_padding'] = $this->cell_padding;
18756                  if (isset($tag['attribute']['cellpadding'])) {
18757                      $pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
18758                      $this->SetCellPadding($pad);
18759                  } elseif (isset($tag['padding'])) {
18760                      $this->cell_padding = $tag['padding'];
18761                  }
18762                  if (isset($tag['attribute']['cellspacing'])) {
18763                      $cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
18764                  } elseif (isset($tag['border-spacing'])) {
18765                      $cs = $tag['border-spacing']['V'];
18766                  }
18767                  $prev_y = $this->y;
18768                  if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
18769                      $this->inthead = true;
18770                      // add a page (or trig AcceptPageBreak() for multicolumn mode)
18771                      $this->checkPageBreak($this->PageBreakTrigger + 1);
18772                  }
18773                  break;
18774              }
18775              case 'tr': {
18776                  // array of columns positions
18777                  $dom[$key]['cellpos'] = array();
18778                  break;
18779              }
18780              case 'hr': {
18781                  if ((isset($tag['height'])) AND ($tag['height'] != '')) {
18782                      $hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
18783                  } else {
18784                      $hrHeight = $this->GetLineWidth();
18785                  }
18786                  $this->addHTMLVertSpace($hbz, max($hb, ($hrHeight / 2)), $cell, $firsttag);
18787                  $x = $this->GetX();
18788                  $y = $this->GetY();
18789                  $wtmp = $this->w - $this->lMargin - $this->rMargin;
18790                  if ($cell) {
18791                      $wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
18792                  }
18793                  if ((isset($tag['width'])) AND ($tag['width'] != '')) {
18794                      $hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
18795                  } else {
18796                      $hrWidth = $wtmp;
18797                  }
18798                  $prevlinewidth = $this->GetLineWidth();
18799                  $this->SetLineWidth($hrHeight);
18800                  $this->Line($x, $y, $x + $hrWidth, $y);
18801                  $this->SetLineWidth($prevlinewidth);
18802                  $this->addHTMLVertSpace(max($hbc, ($hrHeight / 2)), 0, $cell, !isset($dom[($key + 1)]));
18803                  break;
18804              }
18805              case 'a': {
18806                  if (array_key_exists('href', $tag['attribute'])) {
18807                      $this->HREF['url'] = $tag['attribute']['href'];
18808                  }
18809                  break;
18810              }
18811              case 'img': {
18812                  if (!empty($tag['attribute']['src'])) {
18813                      if ($tag['attribute']['src'][0] === '@') {
18814                          // data stream
18815                          $tag['attribute']['src'] = '@'.base64_decode(substr($tag['attribute']['src'], 1));
18816                          $type = '';
18817                      } else {
18818                          // get image type
18819                          $type = TCPDF_IMAGES::getImageFileType($tag['attribute']['src']);
18820                      }
18821                      if (!isset($tag['width'])) {
18822                          $tag['width'] = 0;
18823                      }
18824                      if (!isset($tag['height'])) {
18825                          $tag['height'] = 0;
18826                      }
18827                      //if (!isset($tag['attribute']['align'])) {
18828                          // the only alignment supported is "bottom"
18829                          // further development is required for other modes.
18830                          $tag['attribute']['align'] = 'bottom';
18831                      //}
18832                      switch($tag['attribute']['align']) {
18833                          case 'top': {
18834                              $align = 'T';
18835                              break;
18836                          }
18837                          case 'middle': {
18838                              $align = 'M';
18839                              break;
18840                          }
18841                          case 'bottom': {
18842                              $align = 'B';
18843                              break;
18844                          }
18845                          default: {
18846                              $align = 'B';
18847                              break;
18848                          }
18849                      }
18850                      $prevy = $this->y;
18851                      $xpos = $this->x;
18852                      $imglink = '';
18853                      if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
18854                          $imglink = $this->HREF['url'];
18855                          if ($imglink[0] == '#') {
18856                              // convert url to internal link
18857                              $lnkdata = explode(',', $imglink);
18858                              if (isset($lnkdata[0])) {
18859                                  $page = intval(substr($lnkdata[0], 1));
18860                                  if (empty($page) OR ($page <= 0)) {
18861                                      $page = $this->page;
18862                                  }
18863                                  if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
18864                                      $lnky = floatval($lnkdata[1]);
18865                                  } else {
18866                                      $lnky = 0;
18867                                  }
18868                                  $imglink = $this->AddLink();
18869                                  $this->SetLink($imglink, $lnky, $page);
18870                              }
18871                          }
18872                      }
18873                      $border = 0;
18874                      if (isset($tag['border']) AND !empty($tag['border'])) {
18875                          // currently only support 1 (frame) or a combination of 'LTRB'
18876                          $border = $tag['border'];
18877                      }
18878                      $iw = '';
18879                      if (isset($tag['width'])) {
18880                          $iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k), 'px', false);
18881                      }
18882                      $ih = '';
18883                      if (isset($tag['height'])) {
18884                          $ih = $this->getHTMLUnitToUnits($tag['height'], ($tag['fontsize'] / $this->k), 'px', false);
18885                      }
18886                      if (($type == 'eps') OR ($type == 'ai')) {
18887                          $this->ImageEps($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
18888                      } elseif ($type == 'svg') {
18889                          $this->ImageSVG($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
18890                      } else {
18891                          $this->Image($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
18892                      }
18893                      switch($align) {
18894                          case 'T': {
18895                              $this->y = $prevy;
18896                              break;
18897                          }
18898                          case 'M': {
18899                              $this->y = (($this->img_rb_y + $prevy - ($this->getCellHeight($tag['fontsize'] / $this->k))) / 2);
18900                              break;
18901                          }
18902                          case 'B': {
18903                              $this->y = $this->img_rb_y - ($this->getCellHeight($tag['fontsize'] / $this->k) - ($this->getFontDescent($tag['fontname'], $tag['fontstyle'], $tag['fontsize']) * $this->cell_height_ratio));
18904                              break;
18905                          }
18906                      }
18907                  }
18908                  break;
18909              }
18910              case 'dl': {
18911                  ++$this->listnum;
18912                  if ($this->listnum == 1) {
18913                      $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18914                  } else {
18915                      $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
18916                  }
18917                  break;
18918              }
18919              case 'dt': {
18920                  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18921                  break;
18922              }
18923              case 'dd': {
18924                  if ($this->rtl) {
18925                      $this->rMargin += $this->listindent;
18926                  } else {
18927                      $this->lMargin += $this->listindent;
18928                  }
18929                  ++$this->listindentlevel;
18930                  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18931                  break;
18932              }
18933              case 'ul':
18934              case 'ol': {
18935                  ++$this->listnum;
18936                  if ($tag['value'] == 'ol') {
18937                      $this->listordered[$this->listnum] = true;
18938                  } else {
18939                      $this->listordered[$this->listnum] = false;
18940                  }
18941                  if (isset($tag['attribute']['start'])) {
18942                      $this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
18943                  } else {
18944                      $this->listcount[$this->listnum] = 0;
18945                  }
18946                  if ($this->rtl) {
18947                      $this->rMargin += $this->listindent;
18948                      $this->x -= $this->listindent;
18949                  } else {
18950                      $this->lMargin += $this->listindent;
18951                      $this->x += $this->listindent;
18952                  }
18953                  ++$this->listindentlevel;
18954                  if ($this->listnum == 1) {
18955                      if ($key > 1) {
18956                          $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18957                      }
18958                  } else {
18959                      $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
18960                  }
18961                  break;
18962              }
18963              case 'li': {
18964                  if ($key > 2) {
18965                      $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18966                  }
18967                  if ($this->listordered[$this->listnum]) {
18968                      // ordered item
18969                      if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
18970                          $this->lispacer = $parent['attribute']['type'];
18971                      } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
18972                          $this->lispacer = $parent['listtype'];
18973                      } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
18974                          $this->lispacer = $this->lisymbol;
18975                      } else {
18976                          $this->lispacer = '#';
18977                      }
18978                      ++$this->listcount[$this->listnum];
18979                      if (isset($tag['attribute']['value'])) {
18980                          $this->listcount[$this->listnum] = intval($tag['attribute']['value']);
18981                      }
18982                  } else {
18983                      // unordered item
18984                      if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
18985                          $this->lispacer = $parent['attribute']['type'];
18986                      } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
18987                          $this->lispacer = $parent['listtype'];
18988                      } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
18989                          $this->lispacer = $this->lisymbol;
18990                      } else {
18991                          $this->lispacer = '!';
18992                      }
18993                  }
18994                  break;
18995              }
18996              case 'blockquote': {
18997                  if ($this->rtl) {
18998                      $this->rMargin += $this->listindent;
18999                  } else {
19000                      $this->lMargin += $this->listindent;
19001                  }
19002                  ++$this->listindentlevel;
19003                  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19004                  break;
19005              }
19006              case 'br': {
19007                  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19008                  break;
19009              }
19010              case 'div': {
19011                  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19012                  break;
19013              }
19014              case 'p': {
19015                  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19016                  break;
19017              }
19018              case 'pre': {
19019                  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19020                  $this->premode = true;
19021                  break;
19022              }
19023              case 'sup': {
19024                  $this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
19025                  break;
19026              }
19027              case 'sub': {
19028                  $this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
19029                  break;
19030              }
19031              case 'h1':
19032              case 'h2':
19033              case 'h3':
19034              case 'h4':
19035              case 'h5':
19036              case 'h6': {
19037                  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19038                  break;
19039              }
19040              // Form fields (since 4.8.000 - 2009-09-07)
19041              case 'form': {
19042                  if (isset($tag['attribute']['action'])) {
19043                      $this->form_action = $tag['attribute']['action'];
19044                  } else {
19045                      $this->Error('Please explicitly set action attribute path!');
19046                  }
19047                  if (isset($tag['attribute']['enctype'])) {
19048                      $this->form_enctype = $tag['attribute']['enctype'];
19049                  } else {
19050                      $this->form_enctype = 'application/x-www-form-urlencoded';
19051                  }
19052                  if (isset($tag['attribute']['method'])) {
19053                      $this->form_mode = $tag['attribute']['method'];
19054                  } else {
19055                      $this->form_mode = 'post';
19056                  }
19057                  break;
19058              }
19059              case 'input': {
19060                  if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19061                      $name = $tag['attribute']['name'];
19062                  } else {
19063                      break;
19064                  }
19065                  $prop = array();
19066                  $opt = array();
19067                  if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19068                      $prop['readonly'] = true;
19069                  }
19070                  if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19071                      $value = $tag['attribute']['value'];
19072                  }
19073                  if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC::empty_string($tag['attribute']['maxlength'])) {
19074                      $opt['maxlen'] = intval($tag['attribute']['maxlength']);
19075                  }
19076                  $h = $this->getCellHeight($this->FontSize);
19077                  if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19078                      $w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
19079                  } else {
19080                      $w = $h;
19081                  }
19082                  if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
19083                      $checked = true;
19084                  } else {
19085                      $checked = false;
19086                  }
19087                  if (isset($tag['align'])) {
19088                      switch ($tag['align']) {
19089                          case 'C': {
19090                              $opt['q'] = 1;
19091                              break;
19092                          }
19093                          case 'R': {
19094                              $opt['q'] = 2;
19095                              break;
19096                          }
19097                          case 'L':
19098                          default: {
19099                              break;
19100                          }
19101                      }
19102                  }
19103                  switch ($tag['attribute']['type']) {
19104                      case 'text': {
19105                          if (isset($value)) {
19106                              $opt['v'] = $value;
19107                          }
19108                          $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19109                          break;
19110                      }
19111                      case 'password': {
19112                          if (isset($value)) {
19113                              $opt['v'] = $value;
19114                          }
19115                          $prop['password'] = 'true';
19116                          $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19117                          break;
19118                      }
19119                      case 'checkbox': {
19120                          if (!isset($value)) {
19121                              break;
19122                          }
19123                          $this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
19124                          break;
19125                      }
19126                      case 'radio': {
19127                          if (!isset($value)) {
19128                              break;
19129                          }
19130                          $this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
19131                          break;
19132                      }
19133                      case 'submit': {
19134                          if (!isset($value)) {
19135                              $value = 'submit';
19136                          }
19137                          $w = $this->GetStringWidth($value) * 1.5;
19138                          $h *= 1.6;
19139                          $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19140                          $action = array();
19141                          $action['S'] = 'SubmitForm';
19142                          $action['F'] = $this->form_action;
19143                          if ($this->form_enctype != 'FDF') {
19144                              $action['Flags'] = array('ExportFormat');
19145                          }
19146                          if ($this->form_mode == 'get') {
19147                              $action['Flags'] = array('GetMethod');
19148                          }
19149                          $this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
19150                          break;
19151                      }
19152                      case 'reset': {
19153                          if (!isset($value)) {
19154                              $value = 'reset';
19155                          }
19156                          $w = $this->GetStringWidth($value) * 1.5;
19157                          $h *= 1.6;
19158                          $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19159                          $this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
19160                          break;
19161                      }
19162                      case 'file': {
19163                          $prop['fileSelect'] = 'true';
19164                          $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19165                          if (!isset($value)) {
19166                              $value = '*';
19167                          }
19168                          $w = $this->GetStringWidth($value) * 2;
19169                          $h *= 1.2;
19170                          $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19171                          $jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
19172                          $this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19173                          break;
19174                      }
19175                      case 'hidden': {
19176                          if (isset($value)) {
19177                              $opt['v'] = $value;
19178                          }
19179                          $opt['f'] = array('invisible', 'hidden');
19180                          $this->TextField($name, 0, 0, $prop, $opt, '', '', false);
19181                          break;
19182                      }
19183                      case 'image': {
19184                          // THIS TYPE MUST BE FIXED
19185                          if (isset($tag['attribute']['src']) AND !TCPDF_STATIC::empty_string($tag['attribute']['src'])) {
19186                              $img = $tag['attribute']['src'];
19187                          } else {
19188                              break;
19189                          }
19190                          $value = 'img';
19191                          //$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
19192                          if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19193                              $jsaction = $tag['attribute']['onclick'];
19194                          } else {
19195                              $jsaction = '';
19196                          }
19197                          $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19198                          break;
19199                      }
19200                      case 'button': {
19201                          if (!isset($value)) {
19202                              $value = ' ';
19203                          }
19204                          $w = $this->GetStringWidth($value) * 1.5;
19205                          $h *= 1.6;
19206                          $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19207                          if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19208                              $jsaction = $tag['attribute']['onclick'];
19209                          } else {
19210                              $jsaction = '';
19211                          }
19212                          $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19213                          break;
19214                      }
19215                  }
19216                  break;
19217              }
19218              case 'textarea': {
19219                  $prop = array();
19220                  $opt = array();
19221                  if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19222                      $prop['readonly'] = true;
19223                  }
19224                  if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19225                      $name = $tag['attribute']['name'];
19226                  } else {
19227                      break;
19228                  }
19229                  if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19230                      $opt['v'] = $tag['attribute']['value'];
19231                  }
19232                  if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC::empty_string($tag['attribute']['cols'])) {
19233                      $w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
19234                  } else {
19235                      $w = 40;
19236                  }
19237                  if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
19238                      $h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize);
19239                  } else {
19240                      $h = 10;
19241                  }
19242                  $prop['multiline'] = 'true';
19243                  $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19244                  break;
19245              }
19246              case 'select': {
19247                  $h = $this->getCellHeight($this->FontSize);
19248                  if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19249                      $h *= ($tag['attribute']['size'] + 1);
19250                  }
19251                  $prop = array();
19252                  $opt = array();
19253                  if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19254                      $name = $tag['attribute']['name'];
19255                  } else {
19256                      break;
19257                  }
19258                  $w = 0;
19259                  if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
19260                      $options = explode('#!NwL!#', $tag['attribute']['opt']);
19261                      $values = array();
19262                      foreach ($options as $val) {
19263                          if (strpos($val, '#!TaB!#') !== false) {
19264                              $opts = explode('#!TaB!#', $val);
19265                              $values[] = $opts;
19266                              $w = max($w, $this->GetStringWidth($opts[1]));
19267                          } else {
19268                              $values[] = $val;
19269                              $w = max($w, $this->GetStringWidth($val));
19270                          }
19271                      }
19272                  } else {
19273                      break;
19274                  }
19275                  $w *= 2;
19276                  if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
19277                      $prop['multipleSelection'] = 'true';
19278                      $this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19279                  } else {
19280                      $this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19281                  }
19282                  break;
19283              }
19284              case 'tcpdf': {
19285                  if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
19286                      // Special tag used to call TCPDF methods
19287                      if (isset($tag['attribute']['method'])) {
19288                          $tcpdf_method = $tag['attribute']['method'];
19289                          if (method_exists($this, $tcpdf_method)) {
19290                              if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
19291                                  $params = $this->unserializeTCPDFtagParameters($tag['attribute']['params']);
19292                                  call_user_func_array(array($this, $tcpdf_method), $params);
19293                              } else {
19294                                  $this->$tcpdf_method();
19295                              }
19296                              $this->newline = true;
19297                          }
19298                      }
19299                  }
19300                  break;
19301              }
19302              default: {
19303                  break;
19304              }
19305          }
19306          // define tags that support borders and background colors
19307          $bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
19308          if (in_array($tag['value'], $bordertags)) {
19309              // set border
19310              $dom[$key]['borderposition'] = $this->getBorderStartPosition();
19311          }
19312          if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
19313              $pba = $dom[$key]['attribute']['pagebreakafter'];
19314              // check for pagebreak
19315              if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19316                  // add a page (or trig AcceptPageBreak() for multicolumn mode)
19317                  $this->checkPageBreak($this->PageBreakTrigger + 1);
19318              }
19319              if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19320                  OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19321                  // add a page (or trig AcceptPageBreak() for multicolumn mode)
19322                  $this->checkPageBreak($this->PageBreakTrigger + 1);
19323              }
19324          }
19325          return $dom;
19326      }
19327  
19328      /**
19329       * Process closing tags.
19330       * @param $dom (array) html dom array
19331       * @param $key (int) current element id
19332       * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19333       * @param $maxbottomliney (int) maximum y value of current line
19334       * @return $dom array
19335       * @protected
19336       */
19337  	protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
19338          $tag = $dom[$key];
19339          $parent = $dom[($dom[$key]['parent'])];
19340          $lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
19341          $in_table_head = false;
19342          // maximum x position (used to draw borders)
19343          if ($this->rtl) {
19344              $xmax = $this->w;
19345          } else {
19346              $xmax = 0;
19347          }
19348          if ($tag['block']) {
19349              $hbz = 0; // distance from y to line bottom
19350              $hb = 0; // vertical space between block tags
19351              // calculate vertical space for block tags
19352              if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
19353                  $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
19354              } elseif (isset($parent['fontsize'])) {
19355                  $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
19356              } else {
19357                  $pre_h = $this->getCellHeight($this->FontSize);
19358              }
19359              if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
19360                  $cn = $this->tagvspaces[$tag['value']][1]['n'];
19361              } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19362                  $cn = 0.6;
19363              } else {
19364                  $cn = 1;
19365              }
19366              if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
19367                  $hb = 0;
19368              } else {
19369                  $hb = ($cn * $pre_h);
19370              }
19371              if ($maxbottomliney > $this->PageBreakTrigger) {
19372                  $hbz = $this->getCellHeight($this->FontSize);
19373              } elseif ($this->y < $maxbottomliney) {
19374                  $hbz = ($maxbottomliney - $this->y);
19375              }
19376          }
19377          // Closing tag
19378          switch($tag['value']) {
19379              case 'tr': {
19380                  $table_el = $dom[($dom[$key]['parent'])]['parent'];
19381                  if (!isset($parent['endy'])) {
19382                      $dom[($dom[$key]['parent'])]['endy'] = $this->y;
19383                      $parent['endy'] = $this->y;
19384                  }
19385                  if (!isset($parent['endpage'])) {
19386                      $dom[($dom[$key]['parent'])]['endpage'] = $this->page;
19387                      $parent['endpage'] = $this->page;
19388                  }
19389                  if (!isset($parent['endcolumn'])) {
19390                      $dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
19391                      $parent['endcolumn'] = $this->current_column;
19392                  }
19393                  // update row-spanned cells
19394                  if (isset($dom[$table_el]['rowspans'])) {
19395                      foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19396                          $dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
19397                          if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19398                              if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
19399                                  $dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
19400                              } elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
19401                                  $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19402                                  $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19403                                  $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19404                              }
19405                          }
19406                      }
19407                      // report new endy and endpage to the rowspanned cells
19408                      foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19409                          if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19410                              $dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
19411                              $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19412                              $dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
19413                              $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19414                              $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
19415                              $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19416                          }
19417                      }
19418                      // update remaining rowspanned cells
19419                      foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19420                          if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19421                              $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
19422                              $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
19423                              $dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
19424                          }
19425                      }
19426                  }
19427                  $prev_page = $this->page;
19428                  $this->setPage($dom[($dom[$key]['parent'])]['endpage']);
19429                  if ($this->num_columns > 1) {
19430                      if (($prev_page < $this->page)
19431                          AND ((($this->current_column == 0) AND ($dom[($dom[$key]['parent'])]['endcolumn'] == ($this->num_columns - 1)))
19432                              OR ($this->current_column == $dom[($dom[$key]['parent'])]['endcolumn']))) {
19433                          // page jump
19434                          $this->selectColumn(0);
19435                          $dom[($dom[$key]['parent'])]['endcolumn'] = 0;
19436                          $dom[($dom[$key]['parent'])]['endy'] = $this->y;
19437                      } else {
19438                          $this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
19439                          $this->y = $dom[($dom[$key]['parent'])]['endy'];
19440                      }
19441                  } else {
19442                      $this->y = $dom[($dom[$key]['parent'])]['endy'];
19443                  }
19444                  if (isset($dom[$table_el]['attribute']['cellspacing'])) {
19445                      $this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
19446                  } elseif (isset($dom[$table_el]['border-spacing'])) {
19447                      $this->y += $dom[$table_el]['border-spacing']['V'];
19448                  }
19449                  $this->Ln(0, $cell);
19450                  if ($this->current_column == $parent['startcolumn']) {
19451                      $this->x = $parent['startx'];
19452                  }
19453                  // account for booklet mode
19454                  if ($this->page > $parent['startpage']) {
19455                      if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
19456                          $this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
19457                      } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
19458                          $this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
19459                      }
19460                  }
19461                  break;
19462              }
19463              case 'tablehead':
19464                  // closing tag used for the thead part
19465                  $in_table_head = true;
19466                  $this->inthead = false;
19467              case 'table': {
19468                  $table_el = $parent;
19469                  // set default border
19470                  if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
19471                      // set default border
19472                      $border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
19473                  } else {
19474                      $border = 0;
19475                  }
19476                  $default_border = $border;
19477                  // fix bottom line alignment of last line before page break
19478                  foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
19479                      // update row-spanned cells
19480                      if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19481                          foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19482                              if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
19483                                  $dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
19484                              }
19485                              if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
19486                                  $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
19487                              }
19488                          }
19489                      }
19490                      if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
19491                          $pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
19492                          $dom[$prevtrkey]['endy'] = $pgendy;
19493                          // update row-spanned cells
19494                          if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19495                              foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19496                                  if (($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
19497                                      $dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
19498                                      $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
19499                                  }
19500                              }
19501                          }
19502                      }
19503                      $prevtrkey = $trkey;
19504                      $table_el = $dom[($dom[$key]['parent'])];
19505                  }
19506                  // for each row
19507                  if (count($table_el['trids']) > 0) {
19508                      unset($xmax);
19509                  }
19510                  foreach ($table_el['trids'] as $j => $trkey) {
19511                      $parent = $dom[$trkey];
19512                      if (!isset($xmax)) {
19513                          $xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
19514                      }
19515                      // for each cell on the row
19516                      foreach ($parent['cellpos'] as $k => $cellpos) {
19517                          if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
19518                              $cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
19519                              $cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
19520                              $endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
19521                              $startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
19522                              $endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
19523                              $startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
19524                              $endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
19525                          } else {
19526                              $endy = $parent['endy'];
19527                              $startpage = $parent['startpage'];
19528                              $endpage = $parent['endpage'];
19529                              $startcolumn = $parent['startcolumn'];
19530                              $endcolumn = $parent['endcolumn'];
19531                          }
19532                          if ($this->num_columns == 0) {
19533                              $this->num_columns = 1;
19534                          }
19535                          if (isset($cellpos['border'])) {
19536                              $border = $cellpos['border'];
19537                          }
19538                          if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
19539                              $this->SetFillColorArray($cellpos['bgcolor']);
19540                              $fill = true;
19541                          } else {
19542                              $fill = false;
19543                          }
19544                          $x = $cellpos['startx'];
19545                          $y = $parent['starty'];
19546                          $starty = $y;
19547                          $w = abs($cellpos['endx'] - $cellpos['startx']);
19548                          // get border modes
19549                          $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
19550                          $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
19551                          $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19552                          // design borders around HTML cells.
19553                          for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
19554                              $ccode = '';
19555                              $this->setPage($page);
19556                              if ($this->num_columns < 2) {
19557                                  // single-column mode
19558                                  $this->x = $x;
19559                                  $this->y = $this->tMargin;
19560                              }
19561                              // account for margin changes
19562                              if ($page > $startpage) {
19563                                  if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
19564                                      $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
19565                                  } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
19566                                      $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
19567                                  }
19568                              }
19569                              if ($startpage == $endpage) { // single page
19570                                  $deltacol = 0;
19571                                  $deltath = 0;
19572                                  for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
19573                                      $this->selectColumn($column);
19574                                      if ($startcolumn == $endcolumn) { // single column
19575                                          $cborder = $border;
19576                                          $h = $endy - $parent['starty'];
19577                                          $this->y = $y;
19578                                          $this->x = $x;
19579                                      } elseif ($column == $startcolumn) { // first column
19580                                          $cborder = $border_start;
19581                                          $this->y = $starty;
19582                                          $this->x = $x;
19583                                          $h = $this->h - $this->y - $this->bMargin;
19584                                          if ($this->rtl) {
19585                                              $deltacol = $this->x + $this->rMargin - $this->w;
19586                                          } else {
19587                                              $deltacol = $this->x - $this->lMargin;
19588                                          }
19589                                      } elseif ($column == $endcolumn) { // end column
19590                                          $cborder = $border_end;
19591                                          if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19592                                              $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19593                                          }
19594                                          $this->x += $deltacol;
19595                                          $h = $endy - $this->y;
19596                                      } else { // middle column
19597                                          $cborder = $border_middle;
19598                                          if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19599                                              $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19600                                          }
19601                                          $this->x += $deltacol;
19602                                          $h = $this->h - $this->y - $this->bMargin;
19603                                      }
19604                                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19605                                  } // end for each column
19606                              } elseif ($page == $startpage) { // first page
19607                                  $deltacol = 0;
19608                                  $deltath = 0;
19609                                  for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
19610                                      $this->selectColumn($column);
19611                                      if ($column == $startcolumn) { // first column
19612                                          $cborder = $border_start;
19613                                          $this->y = $starty;
19614                                          $this->x = $x;
19615                                          $h = $this->h - $this->y - $this->bMargin;
19616                                          if ($this->rtl) {
19617                                              $deltacol = $this->x + $this->rMargin - $this->w;
19618                                          } else {
19619                                              $deltacol = $this->x - $this->lMargin;
19620                                          }
19621                                      } else { // middle column
19622                                          $cborder = $border_middle;
19623                                          if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19624                                              $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19625                                          }
19626                                          $this->x += $deltacol;
19627                                          $h = $this->h - $this->y - $this->bMargin;
19628                                      }
19629                                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19630                                  } // end for each column
19631                              } elseif ($page == $endpage) { // last page
19632                                  $deltacol = 0;
19633                                  $deltath = 0;
19634                                  for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
19635                                      $this->selectColumn($column);
19636                                      if ($column == $endcolumn) { // end column
19637                                          $cborder = $border_end;
19638                                          if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19639                                              $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19640                                          }
19641                                          $this->x += $deltacol;
19642                                          $h = $endy - $this->y;
19643                                      } else { // middle column
19644                                          $cborder = $border_middle;
19645                                          if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19646                                              $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19647                                          }
19648                                          $this->x += $deltacol;
19649                                          $h = $this->h - $this->y - $this->bMargin;
19650                                      }
19651                                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19652                                  } // end for each column
19653                              } else { // middle page
19654                                  $deltacol = 0;
19655                                  $deltath = 0;
19656                                  for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
19657                                      $this->selectColumn($column);
19658                                      $cborder = $border_middle;
19659                                      if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19660                                          $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19661                                      }
19662                                      $this->x += $deltacol;
19663                                      $h = $this->h - $this->y - $this->bMargin;
19664                                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19665                                  } // end for each column
19666                              }
19667                              if (!empty($cborder) OR !empty($fill)) {
19668                                  $offsetlen = strlen($ccode);
19669                                  // draw border and fill
19670                                  if ($this->inxobj) {
19671                                      // we are inside an XObject template
19672                                      if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
19673                                          $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
19674                                          $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
19675                                          $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
19676                                      } else {
19677                                          $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
19678                                          $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
19679                                      }
19680                                      $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
19681                                      $pstart = substr($pagebuff, 0, $pagemark);
19682                                      $pend = substr($pagebuff, $pagemark);
19683                                      $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
19684                                  } else {
19685                                      // draw border and fill
19686                                      if (end($this->transfmrk[$this->page]) !== false) {
19687                                          $pagemarkkey = key($this->transfmrk[$this->page]);
19688                                          $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
19689                                      } elseif ($this->InFooter) {
19690                                          $pagemark = $this->footerpos[$this->page];
19691                                      } else {
19692                                          $pagemark = $this->intmrk[$this->page];
19693                                      }
19694                                      $pagebuff = $this->getPageBuffer($this->page);
19695                                      $pstart = substr($pagebuff, 0, $pagemark);
19696                                      $pend = substr($pagebuff, $pagemark);
19697                                      $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
19698                                  }
19699                              }
19700                          } // end for each page
19701                          // restore default border
19702                          $border = $default_border;
19703                      } // end for each cell on the row
19704                      if (isset($table_el['attribute']['cellspacing'])) {
19705                          $this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
19706                      } elseif (isset($table_el['border-spacing'])) {
19707                          $this->y += $table_el['border-spacing']['V'];
19708                      }
19709                      $this->Ln(0, $cell);
19710                      $this->x = $parent['startx'];
19711                      if ($endpage > $startpage) {
19712                          if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
19713                              $this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
19714                          } elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
19715                              $this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
19716                          }
19717                      }
19718                  }
19719                  if (!$in_table_head) { // we are not inside a thead section
19720                      $this->cell_padding = $table_el['old_cell_padding'];
19721                      // reset row height
19722                      $this->resetLastH();
19723                      if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
19724                          $plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
19725                          if (($plendiff > 0) AND ($plendiff < 60)) {
19726                              $pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
19727                              if (substr($pagediff, 0, 5) == 'BT /F') {
19728                                  // the difference is only a font setting
19729                                  $plendiff = 0;
19730                              }
19731                          }
19732                          if ($plendiff == 0) {
19733                              // remove last blank page
19734                              $this->deletePage($this->numpages);
19735                          }
19736                      }
19737                      if (isset($this->theadMargins['top'])) {
19738                          // restore top margin
19739                          $this->tMargin = $this->theadMargins['top'];
19740                      }
19741                      if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
19742                          // reset main table header
19743                          $this->thead = '';
19744                          $this->theadMargins = array();
19745                          $this->pagedim[$this->page]['tm'] = $this->tMargin;
19746                      }
19747                  }
19748                  $parent = $table_el;
19749                  break;
19750              }
19751              case 'a': {
19752                  $this->HREF = '';
19753                  break;
19754              }
19755              case 'sup': {
19756                  $this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
19757                  break;
19758              }
19759              case 'sub': {
19760                  $this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
19761                  break;
19762              }
19763              case 'div': {
19764                  $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19765                  break;
19766              }
19767              case 'blockquote': {
19768                  if ($this->rtl) {
19769                      $this->rMargin -= $this->listindent;
19770                  } else {
19771                      $this->lMargin -= $this->listindent;
19772                  }
19773                  --$this->listindentlevel;
19774                  $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19775                  break;
19776              }
19777              case 'p': {
19778                  $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19779                  break;
19780              }
19781              case 'pre': {
19782                  $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19783                  $this->premode = false;
19784                  break;
19785              }
19786              case 'dl': {
19787                  --$this->listnum;
19788                  if ($this->listnum <= 0) {
19789                      $this->listnum = 0;
19790                      $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19791                  } else {
19792                      $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19793                  }
19794                  $this->resetLastH();
19795                  break;
19796              }
19797              case 'dt': {
19798                  $this->lispacer = '';
19799                  $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19800                  break;
19801              }
19802              case 'dd': {
19803                  $this->lispacer = '';
19804                  if ($this->rtl) {
19805                      $this->rMargin -= $this->listindent;
19806                  } else {
19807                      $this->lMargin -= $this->listindent;
19808                  }
19809                  --$this->listindentlevel;
19810                  $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19811                  break;
19812              }
19813              case 'ul':
19814              case 'ol': {
19815                  --$this->listnum;
19816                  $this->lispacer = '';
19817                  if ($this->rtl) {
19818                      $this->rMargin -= $this->listindent;
19819                  } else {
19820                      $this->lMargin -= $this->listindent;
19821                  }
19822                  --$this->listindentlevel;
19823                  if ($this->listnum <= 0) {
19824                      $this->listnum = 0;
19825                      $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19826                  } else {
19827                      $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19828                  }
19829                  $this->resetLastH();
19830                  break;
19831              }
19832              case 'li': {
19833                  $this->lispacer = '';
19834                  $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19835                  break;
19836              }
19837              case 'h1':
19838              case 'h2':
19839              case 'h3':
19840              case 'h4':
19841              case 'h5':
19842              case 'h6': {
19843                  $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19844                  break;
19845              }
19846              // Form fields (since 4.8.000 - 2009-09-07)
19847              case 'form': {
19848                  $this->form_action = '';
19849                  $this->form_enctype = 'application/x-www-form-urlencoded';
19850                  break;
19851              }
19852              default : {
19853                  break;
19854              }
19855          }
19856          // draw border and background (if any)
19857          $this->drawHTMLTagBorder($parent, $xmax);
19858          if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
19859              $pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
19860              // check for pagebreak
19861              if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19862                  // add a page (or trig AcceptPageBreak() for multicolumn mode)
19863                  $this->checkPageBreak($this->PageBreakTrigger + 1);
19864              }
19865              if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19866                  OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19867                  // add a page (or trig AcceptPageBreak() for multicolumn mode)
19868                  $this->checkPageBreak($this->PageBreakTrigger + 1);
19869              }
19870          }
19871          $this->tmprtl = false;
19872          return $dom;
19873      }
19874  
19875      /**
19876       * Add vertical spaces if needed.
19877       * @param $hbz (string) Distance between current y and line bottom.
19878       * @param $hb (string) The height of the break.
19879       * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19880       * @param $firsttag (boolean) set to true when the tag is the first.
19881       * @param $lasttag (boolean) set to true when the tag is the last.
19882       * @protected
19883       */
19884  	protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
19885          if ($firsttag) {
19886              $this->Ln(0, $cell);
19887              $this->htmlvspace = 0;
19888              return;
19889          }
19890          if ($lasttag) {
19891              $this->Ln($hbz, $cell);
19892              $this->htmlvspace = 0;
19893              return;
19894          }
19895          if ($hb < $this->htmlvspace) {
19896              $hd = 0;
19897          } else {
19898              $hd = $hb - $this->htmlvspace;
19899              $this->htmlvspace = $hb;
19900          }
19901          $this->Ln(($hbz + $hd), $cell);
19902      }
19903  
19904      /**
19905       * Return the starting coordinates to draw an html border
19906       * @return array containing top-left border coordinates
19907       * @protected
19908       * @since 5.7.000 (2010-08-03)
19909       */
19910  	protected function getBorderStartPosition() {
19911          if ($this->rtl) {
19912              $xmax = $this->lMargin;
19913          } else {
19914              $xmax = $this->w - $this->rMargin;
19915          }
19916          return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
19917      }
19918  
19919      /**
19920       * Draw an HTML block border and fill
19921       * @param $tag (array) array of tag properties.
19922       * @param $xmax (int) end X coordinate for border.
19923       * @protected
19924       * @since 5.7.000 (2010-08-03)
19925       */
19926  	protected function drawHTMLTagBorder($tag, $xmax) {
19927          if (!isset($tag['borderposition'])) {
19928              // nothing to draw
19929              return;
19930          }
19931          $prev_x = $this->x;
19932          $prev_y = $this->y;
19933          $prev_lasth = $this->lasth;
19934          $border = 0;
19935          $fill = false;
19936          $this->lasth = 0;
19937          if (isset($tag['border']) AND !empty($tag['border'])) {
19938              // get border style
19939              $border = $tag['border'];
19940              if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
19941                  // border for table header
19942                  $border = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19943              }
19944          }
19945          if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
19946              // get background color
19947              $old_bgcolor = $this->bgcolor;
19948              $this->SetFillColorArray($tag['bgcolor']);
19949              $fill = true;
19950          }
19951          if (!$border AND !$fill) {
19952              // nothing to draw
19953              return;
19954          }
19955          if (isset($tag['attribute']['cellspacing'])) {
19956              $clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
19957              $cellspacing = array('H' => $clsp, 'V' => $clsp);
19958          } elseif (isset($tag['border-spacing'])) {
19959              $cellspacing = $tag['border-spacing'];
19960          } else {
19961              $cellspacing = array('H' => 0, 'V' => 0);
19962          }
19963          if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
19964              // draw the border externally respect the sqare edge.
19965              $border['mode'] = 'ext';
19966          }
19967          if ($this->rtl) {
19968              if ($xmax >= $tag['borderposition']['x']) {
19969                  $xmax = $tag['borderposition']['xmax'];
19970              }
19971              $w = ($tag['borderposition']['x'] - $xmax);
19972          } else {
19973              if ($xmax <= $tag['borderposition']['x']) {
19974                  $xmax = $tag['borderposition']['xmax'];
19975              }
19976              $w = ($xmax - $tag['borderposition']['x']);
19977          }
19978          if ($w <= 0) {
19979              return;
19980          }
19981          $w += $cellspacing['H'];
19982          $startpage = $tag['borderposition']['page'];
19983          $startcolumn = $tag['borderposition']['column'];
19984          $x = $tag['borderposition']['x'];
19985          $y = $tag['borderposition']['y'];
19986          $endpage = $this->page;
19987          $starty = $tag['borderposition']['y'] - $cellspacing['V'];
19988          $currentY = $this->y;
19989          $this->x = $x;
19990          // get latest column
19991          $endcolumn = $this->current_column;
19992          if ($this->num_columns == 0) {
19993              $this->num_columns = 1;
19994          }
19995          // get border modes
19996          $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
19997          $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
19998          $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19999          // temporary disable page regions
20000          $temp_page_regions = $this->page_regions;
20001          $this->page_regions = array();
20002          // design borders around HTML cells.
20003          for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
20004              $ccode = '';
20005              $this->setPage($page);
20006              if ($this->num_columns < 2) {
20007                  // single-column mode
20008                  $this->x = $x;
20009                  $this->y = $this->tMargin;
20010              }
20011              // account for margin changes
20012              if ($page > $startpage) {
20013                  if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
20014                      $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
20015                  } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
20016                      $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
20017                  }
20018              }
20019              if ($startpage == $endpage) {
20020                  // single page
20021                  for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
20022                      $this->selectColumn($column);
20023                      if ($startcolumn == $endcolumn) { // single column
20024                          $cborder = $border;
20025                          $h = ($currentY - $y) + $cellspacing['V'];
20026                          $this->y = $starty;
20027                      } elseif ($column == $startcolumn) { // first column
20028                          $cborder = $border_start;
20029                          $this->y = $starty;
20030                          $h = $this->h - $this->y - $this->bMargin;
20031                      } elseif ($column == $endcolumn) { // end column
20032                          $cborder = $border_end;
20033                          $h = $currentY - $this->y;
20034                      } else { // middle column
20035                          $cborder = $border_middle;
20036                          $h = $this->h - $this->y - $this->bMargin;
20037                      }
20038                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20039                  } // end for each column
20040              } elseif ($page == $startpage) { // first page
20041                  for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
20042                      $this->selectColumn($column);
20043                      if ($column == $startcolumn) { // first column
20044                          $cborder = $border_start;
20045                          $this->y = $starty;
20046                          $h = $this->h - $this->y - $this->bMargin;
20047                      } else { // middle column
20048                          $cborder = $border_middle;
20049                          $h = $this->h - $this->y - $this->bMargin;
20050                      }
20051                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20052                  } // end for each column
20053              } elseif ($page == $endpage) { // last page
20054                  for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
20055                      $this->selectColumn($column);
20056                      if ($column == $endcolumn) {
20057                          // end column
20058                          $cborder = $border_end;
20059                          $h = $currentY - $this->y;
20060                      } else {
20061                          // middle column
20062                          $cborder = $border_middle;
20063                          $h = $this->h - $this->y - $this->bMargin;
20064                      }
20065                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20066                  } // end for each column
20067              } else { // middle page
20068                  for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
20069                      $this->selectColumn($column);
20070                      $cborder = $border_middle;
20071                      $h = $this->h - $this->y - $this->bMargin;
20072                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20073                  } // end for each column
20074              }
20075              if ($cborder OR $fill) {
20076                  $offsetlen = strlen($ccode);
20077                  // draw border and fill
20078                  if ($this->inxobj) {
20079                      // we are inside an XObject template
20080                      if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
20081                          $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
20082                          $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
20083                          $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
20084                      } else {
20085                          $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
20086                          $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
20087                      }
20088                      $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
20089                      $pstart = substr($pagebuff, 0, $pagemark);
20090                      $pend = substr($pagebuff, $pagemark);
20091                      $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
20092                  } else {
20093                      if (end($this->transfmrk[$this->page]) !== false) {
20094                          $pagemarkkey = key($this->transfmrk[$this->page]);
20095                          $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
20096                      } elseif ($this->InFooter) {
20097                          $pagemark = $this->footerpos[$this->page];
20098                      } else {
20099                          $pagemark = $this->intmrk[$this->page];
20100                      }
20101                      $pagebuff = $this->getPageBuffer($this->page);
20102                      $pstart = substr($pagebuff, 0, $pagemark);
20103                      $pend = substr($pagebuff, $pagemark);
20104                      $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
20105                      $this->bordermrk[$this->page] += $offsetlen;
20106                      $this->cntmrk[$this->page] += $offsetlen;
20107                  }
20108              }
20109          } // end for each page
20110          // restore page regions
20111          $this->page_regions = $temp_page_regions;
20112          if (isset($old_bgcolor)) {
20113              // restore background color
20114              $this->SetFillColorArray($old_bgcolor);
20115          }
20116          // restore pointer position
20117          $this->x = $prev_x;
20118          $this->y = $prev_y;
20119          $this->lasth = $prev_lasth;
20120      }
20121  
20122      /**
20123       * Set the default bullet to be used as LI bullet symbol
20124       * @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')
20125       * @public
20126       * @since 4.0.028 (2008-09-26)
20127       */
20128  	public function setLIsymbol($symbol='!') {
20129          // check for custom image symbol
20130          if (substr($symbol, 0, 4) == 'img|') {
20131              $this->lisymbol = $symbol;
20132              return;
20133          }
20134          $symbol = strtolower($symbol);
20135          $valid_symbols = array('!', '#', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek');
20136          if (in_array($symbol, $valid_symbols)) {
20137              $this->lisymbol = $symbol;
20138          } else {
20139              $this->lisymbol = '';
20140          }
20141      }
20142  
20143      /**
20144       * Set the booklet mode for double-sided pages.
20145       * @param $booklet (boolean) true set the booklet mode on, false otherwise.
20146       * @param $inner (float) Inner page margin.
20147       * @param $outer (float) Outer page margin.
20148       * @public
20149       * @since 4.2.000 (2008-10-29)
20150       */
20151  	public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
20152          $this->booklet = $booklet;
20153          if ($inner >= 0) {
20154              $this->lMargin = $inner;
20155          }
20156          if ($outer >= 0) {
20157              $this->rMargin = $outer;
20158          }
20159      }
20160  
20161      /**
20162       * Swap the left and right margins.
20163       * @param $reverse (boolean) if true swap left and right margins.
20164       * @protected
20165       * @since 4.2.000 (2008-10-29)
20166       */
20167  	protected function swapMargins($reverse=true) {
20168          if ($reverse) {
20169              // swap left and right margins
20170              $mtemp = $this->original_lMargin;
20171              $this->original_lMargin = $this->original_rMargin;
20172              $this->original_rMargin = $mtemp;
20173              $deltam = $this->original_lMargin - $this->original_rMargin;
20174              $this->lMargin += $deltam;
20175              $this->rMargin -= $deltam;
20176          }
20177      }
20178  
20179      /**
20180       * Set the vertical spaces for HTML tags.
20181       * The array must have the following structure (example):
20182       * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
20183       * The first array level contains the tag names,
20184       * the second level contains 0 for opening tags or 1 for closing tags,
20185       * the third level contains the vertical space unit (h) and the number spaces to add (n).
20186       * If the h parameter is not specified, default values are used.
20187       * @param $tagvs (array) array of tags and relative vertical spaces.
20188       * @public
20189       * @since 4.2.001 (2008-10-30)
20190       */
20191  	public function setHtmlVSpace($tagvs) {
20192          $this->tagvspaces = $tagvs;
20193      }
20194  
20195      /**
20196       * Set custom width for list indentation.
20197       * @param $width (float) width of the indentation. Use negative value to disable it.
20198       * @public
20199       * @since 4.2.007 (2008-11-12)
20200       */
20201  	public function setListIndentWidth($width) {
20202          return $this->customlistindent = floatval($width);
20203      }
20204  
20205      /**
20206       * Set the top/bottom cell sides to be open or closed when the cell cross the page.
20207       * @param $isopen (boolean) if true keeps the top/bottom border open for the cell sides that cross the page.
20208       * @public
20209       * @since 4.2.010 (2008-11-14)
20210       */
20211  	public function setOpenCell($isopen) {
20212          $this->opencell = $isopen;
20213      }
20214  
20215      /**
20216       * Set the color and font style for HTML links.
20217       * @param $color (array) RGB array of colors
20218       * @param $fontstyle (string) additional font styles to add
20219       * @public
20220       * @since 4.4.003 (2008-12-09)
20221       */
20222  	public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
20223          $this->htmlLinkColorArray = $color;
20224          $this->htmlLinkFontStyle = $fontstyle;
20225      }
20226  
20227      /**
20228       * Convert HTML string containing value and unit of measure to user's units or points.
20229       * @param $htmlval (string) String containing values and unit.
20230       * @param $refsize (string) Reference value in points.
20231       * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
20232       * @param $points (boolean) If true returns points, otherwise returns value in user's units.
20233       * @return float value in user's unit or point if $points=true
20234       * @public
20235       * @since 4.4.004 (2008-12-10)
20236       */
20237  	public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
20238          $supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
20239          $retval = 0;
20240          $value = 0;
20241          $unit = 'px';
20242          if ($points) {
20243              $k = 1;
20244          } else {
20245              $k = $this->k;
20246          }
20247          if (in_array($defaultunit, $supportedunits)) {
20248              $unit = $defaultunit;
20249          }
20250          if (is_numeric($htmlval)) {
20251              $value = floatval($htmlval);
20252          } elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
20253              $value = floatval($mnum[1]);
20254              if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
20255                  if (in_array($munit[1], $supportedunits)) {
20256                      $unit = $munit[1];
20257                  }
20258              }
20259          }
20260          switch ($unit) {
20261              // percentage
20262              case '%': {
20263                  $retval = (($value * $refsize) / 100);
20264                  break;
20265              }
20266              // relative-size
20267              case 'em': {
20268                  $retval = ($value * $refsize);
20269                  break;
20270              }
20271              // height of lower case 'x' (about half the font-size)
20272              case 'ex': {
20273                  $retval = ($value * ($refsize / 2));
20274                  break;
20275              }
20276              // absolute-size
20277              case 'in': {
20278                  $retval = (($value * $this->dpi) / $k);
20279                  break;
20280              }
20281              // centimeters
20282              case 'cm': {
20283                  $retval = (($value / 2.54 * $this->dpi) / $k);
20284                  break;
20285              }
20286              // millimeters
20287              case 'mm': {
20288                  $retval = (($value / 25.4 * $this->dpi) / $k);
20289                  break;
20290              }
20291              // one pica is 12 points
20292              case 'pc': {
20293                  $retval = (($value * 12) / $k);
20294                  break;
20295              }
20296              // points
20297              case 'pt': {
20298                  $retval = ($value / $k);
20299                  break;
20300              }
20301              // pixels
20302              case 'px': {
20303                  $retval = $this->pixelsToUnits($value);
20304                  if ($points) {
20305                      $retval *= $this->k;
20306                  }
20307                  break;
20308              }
20309          }
20310          return $retval;
20311      }
20312  
20313      /**
20314       * Output an HTML list bullet or ordered item symbol
20315       * @param $listdepth (int) list nesting level
20316       * @param $listtype (string) type of list
20317       * @param $size (float) current font size
20318       * @protected
20319       * @since 4.4.004 (2008-12-10)
20320       */
20321  	protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
20322          if ($this->state != 2) {
20323              return;
20324          }
20325          $size /= $this->k;
20326          $fill = '';
20327          $bgcolor = $this->bgcolor;
20328          $color = $this->fgcolor;
20329          $strokecolor = $this->strokecolor;
20330          $width = 0;
20331          $textitem = '';
20332          $tmpx = $this->x;
20333          $lspace = $this->GetStringWidth('  ');
20334          if ($listtype == '^') {
20335              // special symbol used for avoid justification of rect bullet
20336              $this->lispacer = '';
20337              return;
20338          } elseif ($listtype == '!') {
20339              // set default list type for unordered list
20340              $deftypes = array('disc', 'circle', 'square');
20341              $listtype = $deftypes[($listdepth - 1) % 3];
20342          } elseif ($listtype == '#') {
20343              // set default list type for ordered list
20344              $listtype = 'decimal';
20345          } elseif (substr($listtype, 0, 4) == 'img|') {
20346              // custom image type ('img|type|width|height|image.ext')
20347              $img = explode('|', $listtype);
20348              $listtype = 'img';
20349          }
20350          switch ($listtype) {
20351              // unordered types
20352              case 'none': {
20353                  break;
20354              }
20355              case 'disc': {
20356                  $r = $size / 6;
20357                  $lspace += (2 * $r);
20358                  if ($this->rtl) {
20359                      $this->x += $lspace;
20360                  } else {
20361                      $this->x -= $lspace;
20362                  }
20363                  $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
20364                  break;
20365              }
20366              case 'circle': {
20367                  $r = $size / 6;
20368                  $lspace += (2 * $r);
20369                  if ($this->rtl) {
20370                      $this->x += $lspace;
20371                  } else {
20372                      $this->x -= $lspace;
20373                  }
20374                  $prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
20375                  $new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
20376                  $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
20377                  $this->_out($prev_line_style); // restore line settings
20378                  break;
20379              }
20380              case 'square': {
20381                  $l = $size / 3;
20382                  $lspace += $l;
20383                  if ($this->rtl) {;
20384                      $this->x += $lspace;
20385                  } else {
20386                      $this->x -= $lspace;
20387                  }
20388                  $this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
20389                  break;
20390              }
20391              case 'img': {
20392                  // 1=>type, 2=>width, 3=>height, 4=>image.ext
20393                  $lspace += $img[2];
20394                  if ($this->rtl) {;
20395                      $this->x += $lspace;
20396                  } else {
20397                      $this->x -= $lspace;
20398                  }
20399                  $imgtype = strtolower($img[1]);
20400                  $prev_y = $this->y;
20401                  switch ($imgtype) {
20402                      case 'svg': {
20403                          $this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
20404                          break;
20405                      }
20406                      case 'ai':
20407                      case 'eps': {
20408                          $this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
20409                          break;
20410                      }
20411                      default: {
20412                          $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);
20413                          break;
20414                      }
20415                  }
20416                  $this->y = $prev_y;
20417                  break;
20418              }
20419              // ordered types
20420              // $this->listcount[$this->listnum];
20421              // $textitem
20422              case '1':
20423              case 'decimal': {
20424                  $textitem = $this->listcount[$this->listnum];
20425                  break;
20426              }
20427              case 'decimal-leading-zero': {
20428                  $textitem = sprintf('%02d', $this->listcount[$this->listnum]);
20429                  break;
20430              }
20431              case 'i':
20432              case 'lower-roman': {
20433                  $textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
20434                  break;
20435              }
20436              case 'I':
20437              case 'upper-roman': {
20438                  $textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
20439                  break;
20440              }
20441              case 'a':
20442              case 'lower-alpha':
20443              case 'lower-latin': {
20444                  $textitem = chr(97 + $this->listcount[$this->listnum] - 1);
20445                  break;
20446              }
20447              case 'A':
20448              case 'upper-alpha':
20449              case 'upper-latin': {
20450                  $textitem = chr(65 + $this->listcount[$this->listnum] - 1);
20451                  break;
20452              }
20453              case 'lower-greek': {
20454                  $textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
20455                  break;
20456              }
20457              /*
20458              // Types to be implemented (special handling)
20459              case 'hebrew': {
20460                  break;
20461              }
20462              case 'armenian': {
20463                  break;
20464              }
20465              case 'georgian': {
20466                  break;
20467              }
20468              case 'cjk-ideographic': {
20469                  break;
20470              }
20471              case 'hiragana': {
20472                  break;
20473              }
20474              case 'katakana': {
20475                  break;
20476              }
20477              case 'hiragana-iroha': {
20478                  break;
20479              }
20480              case 'katakana-iroha': {
20481                  break;
20482              }
20483              */
20484              default: {
20485                  $textitem = $this->listcount[$this->listnum];
20486              }
20487          }
20488          if (!TCPDF_STATIC::empty_string($textitem)) {
20489              // Check whether we need a new page or new column
20490              $prev_y = $this->y;
20491              $h = $this->getCellHeight($this->FontSize);
20492              if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
20493                  $tmpx = $this->x;
20494              }
20495              // print ordered item
20496              if ($this->rtl) {
20497                  $textitem = '.'.$textitem;
20498              } else {
20499                  $textitem = $textitem.'.';
20500              }
20501              $lspace += $this->GetStringWidth($textitem);
20502              if ($this->rtl) {
20503                  $this->x += $lspace;
20504              } else {
20505                  $this->x -= $lspace;
20506              }
20507              $this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
20508          }
20509          $this->x = $tmpx;
20510          $this->lispacer = '^';
20511          // restore colors
20512          $this->SetFillColorArray($bgcolor);
20513          $this->SetDrawColorArray($strokecolor);
20514          $this->SettextColorArray($color);
20515      }
20516  
20517      /**
20518       * Returns current graphic variables as array.
20519       * @return array of graphic variables
20520       * @protected
20521       * @since 4.2.010 (2008-11-14)
20522       */
20523  	protected function getGraphicVars() {
20524          $grapvars = array(
20525              'FontFamily' => $this->FontFamily,
20526              'FontStyle' => $this->FontStyle,
20527              'FontSizePt' => $this->FontSizePt,
20528              'rMargin' => $this->rMargin,
20529              'lMargin' => $this->lMargin,
20530              'cell_padding' => $this->cell_padding,
20531              'cell_margin' => $this->cell_margin,
20532              'LineWidth' => $this->LineWidth,
20533              'linestyleWidth' => $this->linestyleWidth,
20534              'linestyleCap' => $this->linestyleCap,
20535              'linestyleJoin' => $this->linestyleJoin,
20536              'linestyleDash' => $this->linestyleDash,
20537              'textrendermode' => $this->textrendermode,
20538              'textstrokewidth' => $this->textstrokewidth,
20539              'DrawColor' => $this->DrawColor,
20540              'FillColor' => $this->FillColor,
20541              'TextColor' => $this->TextColor,
20542              'ColorFlag' => $this->ColorFlag,
20543              'bgcolor' => $this->bgcolor,
20544              'fgcolor' => $this->fgcolor,
20545              'htmlvspace' => $this->htmlvspace,
20546              'listindent' => $this->listindent,
20547              'listindentlevel' => $this->listindentlevel,
20548              'listnum' => $this->listnum,
20549              'listordered' => $this->listordered,
20550              'listcount' => $this->listcount,
20551              'lispacer' => $this->lispacer,
20552              'cell_height_ratio' => $this->cell_height_ratio,
20553              'font_stretching' => $this->font_stretching,
20554              'font_spacing' => $this->font_spacing,
20555              'alpha' => $this->alpha,
20556              // extended
20557              'lasth' => $this->lasth,
20558              'tMargin' => $this->tMargin,
20559              'bMargin' => $this->bMargin,
20560              'AutoPageBreak' => $this->AutoPageBreak,
20561              'PageBreakTrigger' => $this->PageBreakTrigger,
20562              'x' => $this->x,
20563              'y' => $this->y,
20564              'w' => $this->w,
20565              'h' => $this->h,
20566              'wPt' => $this->wPt,
20567              'hPt' => $this->hPt,
20568              'fwPt' => $this->fwPt,
20569              'fhPt' => $this->fhPt,
20570              'page' => $this->page,
20571              'current_column' => $this->current_column,
20572              'num_columns' => $this->num_columns
20573              );
20574          return $grapvars;
20575      }
20576  
20577      /**
20578       * Set graphic variables.
20579       * @param $gvars (array) array of graphic variablesto restore
20580       * @param $extended (boolean) if true restore extended graphic variables
20581       * @protected
20582       * @since 4.2.010 (2008-11-14)
20583       */
20584  	protected function setGraphicVars($gvars, $extended=false) {
20585          if ($this->state != 2) {
20586               return;
20587          }
20588          $this->FontFamily = $gvars['FontFamily'];
20589          $this->FontStyle = $gvars['FontStyle'];
20590          $this->FontSizePt = $gvars['FontSizePt'];
20591          $this->rMargin = $gvars['rMargin'];
20592          $this->lMargin = $gvars['lMargin'];
20593          $this->cell_padding = $gvars['cell_padding'];
20594          $this->cell_margin = $gvars['cell_margin'];
20595          $this->LineWidth = $gvars['LineWidth'];
20596          $this->linestyleWidth = $gvars['linestyleWidth'];
20597          $this->linestyleCap = $gvars['linestyleCap'];
20598          $this->linestyleJoin = $gvars['linestyleJoin'];
20599          $this->linestyleDash = $gvars['linestyleDash'];
20600          $this->textrendermode = $gvars['textrendermode'];
20601          $this->textstrokewidth = $gvars['textstrokewidth'];
20602          $this->DrawColor = $gvars['DrawColor'];
20603          $this->FillColor = $gvars['FillColor'];
20604          $this->TextColor = $gvars['TextColor'];
20605          $this->ColorFlag = $gvars['ColorFlag'];
20606          $this->bgcolor = $gvars['bgcolor'];
20607          $this->fgcolor = $gvars['fgcolor'];
20608          $this->htmlvspace = $gvars['htmlvspace'];
20609          $this->listindent = $gvars['listindent'];
20610          $this->listindentlevel = $gvars['listindentlevel'];
20611          $this->listnum = $gvars['listnum'];
20612          $this->listordered = $gvars['listordered'];
20613          $this->listcount = $gvars['listcount'];
20614          $this->lispacer = $gvars['lispacer'];
20615          $this->cell_height_ratio = $gvars['cell_height_ratio'];
20616          $this->font_stretching = $gvars['font_stretching'];
20617          $this->font_spacing = $gvars['font_spacing'];
20618          $this->alpha = $gvars['alpha'];
20619          if ($extended) {
20620              // restore extended values
20621              $this->lasth = $gvars['lasth'];
20622              $this->tMargin = $gvars['tMargin'];
20623              $this->bMargin = $gvars['bMargin'];
20624              $this->AutoPageBreak = $gvars['AutoPageBreak'];
20625              $this->PageBreakTrigger = $gvars['PageBreakTrigger'];
20626              $this->x = $gvars['x'];
20627              $this->y = $gvars['y'];
20628              $this->w = $gvars['w'];
20629              $this->h = $gvars['h'];
20630              $this->wPt = $gvars['wPt'];
20631              $this->hPt = $gvars['hPt'];
20632              $this->fwPt = $gvars['fwPt'];
20633              $this->fhPt = $gvars['fhPt'];
20634              $this->page = $gvars['page'];
20635              $this->current_column = $gvars['current_column'];
20636              $this->num_columns = $gvars['num_columns'];
20637          }
20638          $this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
20639          if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
20640              $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
20641          }
20642      }
20643  
20644      /**
20645       * Outputs the "save graphics state" operator 'q'
20646       * @protected
20647       */
20648  	protected function _outSaveGraphicsState() {
20649          $this->_out('q');
20650      }
20651  
20652      /**
20653       * Outputs the "restore graphics state" operator 'Q'
20654       * @protected
20655       */
20656  	protected function _outRestoreGraphicsState() {
20657          $this->_out('Q');
20658      }
20659  
20660      /**
20661       * Set buffer content (always append data).
20662       * @param $data (string) data
20663       * @protected
20664       * @since 4.5.000 (2009-01-02)
20665       */
20666  	protected function setBuffer($data) {
20667          $this->bufferlen += strlen($data);
20668          $this->buffer .= $data;
20669      }
20670  
20671      /**
20672       * Replace the buffer content
20673       * @param $data (string) data
20674       * @protected
20675       * @since 5.5.000 (2010-06-22)
20676       */
20677  	protected function replaceBuffer($data) {
20678          $this->bufferlen = strlen($data);
20679          $this->buffer = $data;
20680      }
20681  
20682      /**
20683       * Get buffer content.
20684       * @return string buffer content
20685       * @protected
20686       * @since 4.5.000 (2009-01-02)
20687       */
20688  	protected function getBuffer() {
20689          return $this->buffer;
20690      }
20691  
20692      /**
20693       * Set page buffer content.
20694       * @param $page (int) page number
20695       * @param $data (string) page data
20696       * @param $append (boolean) if true append data, false replace.
20697       * @protected
20698       * @since 4.5.000 (2008-12-31)
20699       */
20700  	protected function setPageBuffer($page, $data, $append=false) {
20701          if ($append) {
20702              $this->pages[$page] .= $data;
20703          } else {
20704              $this->pages[$page] = $data;
20705          }
20706          if ($append AND isset($this->pagelen[$page])) {
20707              $this->pagelen[$page] += strlen($data);
20708          } else {
20709              $this->pagelen[$page] = strlen($data);
20710          }
20711      }
20712  
20713      /**
20714       * Get page buffer content.
20715       * @param $page (int) page number
20716       * @return string page buffer content or false in case of error
20717       * @protected
20718       * @since 4.5.000 (2008-12-31)
20719       */
20720  	protected function getPageBuffer($page) {
20721          if (isset($this->pages[$page])) {
20722              return $this->pages[$page];
20723          }
20724          return false;
20725      }
20726  
20727      /**
20728       * Set image buffer content.
20729       * @param $image (string) image key
20730       * @param $data (array) image data
20731       * @return int image index number
20732       * @protected
20733       * @since 4.5.000 (2008-12-31)
20734       */
20735  	protected function setImageBuffer($image, $data) {
20736          if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
20737              $this->imagekeys[$this->numimages] = $image;
20738              $data['i'] = $this->numimages;
20739              ++$this->numimages;
20740          }
20741          $this->images[$image] = $data;
20742          return $data['i'];
20743      }
20744  
20745      /**
20746       * Set image buffer content for a specified sub-key.
20747       * @param $image (string) image key
20748       * @param $key (string) image sub-key
20749       * @param $data (array) image data
20750       * @protected
20751       * @since 4.5.000 (2008-12-31)
20752       */
20753  	protected function setImageSubBuffer($image, $key, $data) {
20754          if (!isset($this->images[$image])) {
20755              $this->setImageBuffer($image, array());
20756          }
20757          $this->images[$image][$key] = $data;
20758      }
20759  
20760      /**
20761       * Get image buffer content.
20762       * @param $image (string) image key
20763       * @return string image buffer content or false in case of error
20764       * @protected
20765       * @since 4.5.000 (2008-12-31)
20766       */
20767  	protected function getImageBuffer($image) {
20768          if (isset($this->images[$image])) {
20769              return $this->images[$image];
20770          }
20771          return false;
20772      }
20773  
20774      /**
20775       * Set font buffer content.
20776       * @param $font (string) font key
20777       * @param $data (array) font data
20778       * @protected
20779       * @since 4.5.000 (2009-01-02)
20780       */
20781  	protected function setFontBuffer($font, $data) {
20782          $this->fonts[$font] = $data;
20783          if (!in_array($font, $this->fontkeys)) {
20784              $this->fontkeys[] = $font;
20785              // store object ID for current font
20786              ++$this->n;
20787              $this->font_obj_ids[$font] = $this->n;
20788              $this->setFontSubBuffer($font, 'n', $this->n);
20789          }
20790      }
20791  
20792      /**
20793       * Set font buffer content.
20794       * @param $font (string) font key
20795       * @param $key (string) font sub-key
20796       * @param $data (array) font data
20797       * @protected
20798       * @since 4.5.000 (2009-01-02)
20799       */
20800  	protected function setFontSubBuffer($font, $key, $data) {
20801          if (!isset($this->fonts[$font])) {
20802              $this->setFontBuffer($font, array());
20803          }
20804          $this->fonts[$font][$key] = $data;
20805      }
20806  
20807      /**
20808       * Get font buffer content.
20809       * @param $font (string) font key
20810       * @return string font buffer content or false in case of error
20811       * @protected
20812       * @since 4.5.000 (2009-01-02)
20813       */
20814  	protected function getFontBuffer($font) {
20815          if (isset($this->fonts[$font])) {
20816              return $this->fonts[$font];
20817          }
20818          return false;
20819      }
20820  
20821      /**
20822       * Move a page to a previous position.
20823       * @param $frompage (int) number of the source page
20824       * @param $topage (int) number of the destination page (must be less than $frompage)
20825       * @return true in case of success, false in case of error.
20826       * @public
20827       * @since 4.5.000 (2009-01-02)
20828       */
20829  	public function movePage($frompage, $topage) {
20830          if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
20831              return false;
20832          }
20833          if ($frompage == $this->page) {
20834              // close the page before moving it
20835              $this->endPage();
20836          }
20837          // move all page-related states
20838          $tmppage = $this->getPageBuffer($frompage);
20839          $tmppagedim = $this->pagedim[$frompage];
20840          $tmppagelen = $this->pagelen[$frompage];
20841          $tmpintmrk = $this->intmrk[$frompage];
20842          $tmpbordermrk = $this->bordermrk[$frompage];
20843          $tmpcntmrk = $this->cntmrk[$frompage];
20844          $tmppageobjects = $this->pageobjects[$frompage];
20845          if (isset($this->footerpos[$frompage])) {
20846              $tmpfooterpos = $this->footerpos[$frompage];
20847          }
20848          if (isset($this->footerlen[$frompage])) {
20849              $tmpfooterlen = $this->footerlen[$frompage];
20850          }
20851          if (isset($this->transfmrk[$frompage])) {
20852              $tmptransfmrk = $this->transfmrk[$frompage];
20853          }
20854          if (isset($this->PageAnnots[$frompage])) {
20855              $tmpannots = $this->PageAnnots[$frompage];
20856          }
20857          if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
20858              for ($i = $frompage; $i > $topage; --$i) {
20859                  if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
20860                      --$this->pagegroups[$this->newpagegroup[$i]];
20861                      break;
20862                  }
20863              }
20864              for ($i = $topage; $i > 0; --$i) {
20865                  if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
20866                      ++$this->pagegroups[$this->newpagegroup[$i]];
20867                      break;
20868                  }
20869              }
20870          }
20871          for ($i = $frompage; $i > $topage; --$i) {
20872              $j = $i - 1;
20873              // shift pages down
20874              $this->setPageBuffer($i, $this->getPageBuffer($j));
20875              $this->pagedim[$i] = $this->pagedim[$j];
20876              $this->pagelen[$i] = $this->pagelen[$j];
20877              $this->intmrk[$i] = $this->intmrk[$j];
20878              $this->bordermrk[$i] = $this->bordermrk[$j];
20879              $this->cntmrk[$i] = $this->cntmrk[$j];
20880              $this->pageobjects[$i] = $this->pageobjects[$j];
20881              if (isset($this->footerpos[$j])) {
20882                  $this->footerpos[$i] = $this->footerpos[$j];
20883              } elseif (isset($this->footerpos[$i])) {
20884                  unset($this->footerpos[$i]);
20885              }
20886              if (isset($this->footerlen[$j])) {
20887                  $this->footerlen[$i] = $this->footerlen[$j];
20888              } elseif (isset($this->footerlen[$i])) {
20889                  unset($this->footerlen[$i]);
20890              }
20891              if (isset($this->transfmrk[$j])) {
20892                  $this->transfmrk[$i] = $this->transfmrk[$j];
20893              } elseif (isset($this->transfmrk[$i])) {
20894                  unset($this->transfmrk[$i]);
20895              }
20896              if (isset($this->PageAnnots[$j])) {
20897                  $this->PageAnnots[$i] = $this->PageAnnots[$j];
20898              } elseif (isset($this->PageAnnots[$i])) {
20899                  unset($this->PageAnnots[$i]);
20900              }
20901              if (isset($this->newpagegroup[$j])) {
20902                  $this->newpagegroup[$i] = $this->newpagegroup[$j];
20903                  unset($this->newpagegroup[$j]);
20904              }
20905              if ($this->currpagegroup == $j) {
20906                  $this->currpagegroup = $i;
20907              }
20908          }
20909          $this->setPageBuffer($topage, $tmppage);
20910          $this->pagedim[$topage] = $tmppagedim;
20911          $this->pagelen[$topage] = $tmppagelen;
20912          $this->intmrk[$topage] = $tmpintmrk;
20913          $this->bordermrk[$topage] = $tmpbordermrk;
20914          $this->cntmrk[$topage] = $tmpcntmrk;
20915          $this->pageobjects[$topage] = $tmppageobjects;
20916          if (isset($tmpfooterpos)) {
20917              $this->footerpos[$topage] = $tmpfooterpos;
20918          } elseif (isset($this->footerpos[$topage])) {
20919              unset($this->footerpos[$topage]);
20920          }
20921          if (isset($tmpfooterlen)) {
20922              $this->footerlen[$topage] = $tmpfooterlen;
20923          } elseif (isset($this->footerlen[$topage])) {
20924              unset($this->footerlen[$topage]);
20925          }
20926          if (isset($tmptransfmrk)) {
20927              $this->transfmrk[$topage] = $tmptransfmrk;
20928          } elseif (isset($this->transfmrk[$topage])) {
20929              unset($this->transfmrk[$topage]);
20930          }
20931          if (isset($tmpannots)) {
20932              $this->PageAnnots[$topage] = $tmpannots;
20933          } elseif (isset($this->PageAnnots[$topage])) {
20934              unset($this->PageAnnots[$topage]);
20935          }
20936          // adjust outlines
20937          $tmpoutlines = $this->outlines;
20938          foreach ($tmpoutlines as $key => $outline) {
20939              if (!$outline['f']) {
20940                  if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
20941                      $this->outlines[$key]['p'] = ($outline['p'] + 1);
20942                  } elseif ($outline['p'] == $frompage) {
20943                      $this->outlines[$key]['p'] = $topage;
20944                  }
20945              }
20946          }
20947          // adjust dests
20948          $tmpdests = $this->dests;
20949          foreach ($tmpdests as $key => $dest) {
20950              if (!$dest['f']) {
20951                  if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
20952                      $this->dests[$key]['p'] = ($dest['p'] + 1);
20953                  } elseif ($dest['p'] == $frompage) {
20954                      $this->dests[$key]['p'] = $topage;
20955                  }
20956              }
20957          }
20958          // adjust links
20959          $tmplinks = $this->links;
20960          foreach ($tmplinks as $key => $link) {
20961              if (!$link['f']) {
20962                  if (($link['p'] >= $topage) AND ($link['p'] < $frompage)) {
20963                      $this->links[$key]['p'] = ($link['p'] + 1);
20964                  } elseif ($link['p'] == $frompage) {
20965                      $this->links[$key]['p'] = $topage;
20966                  }
20967              }
20968          }
20969          // adjust javascript
20970          $jfrompage = $frompage;
20971          $jtopage = $topage;
20972          if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
20973              foreach($pamatch[0] as $pk => $pmatch) {
20974                  $pagenum = intval($pamatch[3][$pk]) + 1;
20975                  if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
20976                      $newpage = ($pagenum + 1);
20977                  } elseif ($pagenum == $jfrompage) {
20978                      $newpage = $jtopage;
20979                  } else {
20980                      $newpage = $pagenum;
20981                  }
20982                  --$newpage;
20983                  $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
20984                  $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
20985              }
20986              unset($pamatch);
20987          }
20988          // return to last page
20989          $this->lastPage(true);
20990          return true;
20991      }
20992  
20993      /**
20994       * Remove the specified page.
20995       * @param $page (int) page to remove
20996       * @return true in case of success, false in case of error.
20997       * @public
20998       * @since 4.6.004 (2009-04-23)
20999       */
21000  	public function deletePage($page) {
21001          if (($page < 1) OR ($page > $this->numpages)) {
21002              return false;
21003          }
21004          // delete current page
21005          unset($this->pages[$page]);
21006          unset($this->pagedim[$page]);
21007          unset($this->pagelen[$page]);
21008          unset($this->intmrk[$page]);
21009          unset($this->bordermrk[$page]);
21010          unset($this->cntmrk[$page]);
21011          foreach ($this->pageobjects[$page] as $oid) {
21012              if (isset($this->offsets[$oid])){
21013                  unset($this->offsets[$oid]);
21014              }
21015          }
21016          unset($this->pageobjects[$page]);
21017          if (isset($this->footerpos[$page])) {
21018              unset($this->footerpos[$page]);
21019          }
21020          if (isset($this->footerlen[$page])) {
21021              unset($this->footerlen[$page]);
21022          }
21023          if (isset($this->transfmrk[$page])) {
21024              unset($this->transfmrk[$page]);
21025          }
21026          if (isset($this->PageAnnots[$page])) {
21027              unset($this->PageAnnots[$page]);
21028          }
21029          if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21030              for ($i = $page; $i > 0; --$i) {
21031                  if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
21032                      --$this->pagegroups[$this->newpagegroup[$i]];
21033                      break;
21034                  }
21035              }
21036          }
21037          if (isset($this->pageopen[$page])) {
21038              unset($this->pageopen[$page]);
21039          }
21040          if ($page < $this->numpages) {
21041              // update remaining pages
21042              for ($i = $page; $i < $this->numpages; ++$i) {
21043                  $j = $i + 1;
21044                  // shift pages
21045                  $this->setPageBuffer($i, $this->getPageBuffer($j));
21046                  $this->pagedim[$i] = $this->pagedim[$j];
21047                  $this->pagelen[$i] = $this->pagelen[$j];
21048                  $this->intmrk[$i] = $this->intmrk[$j];
21049                  $this->bordermrk[$i] = $this->bordermrk[$j];
21050                  $this->cntmrk[$i] = $this->cntmrk[$j];
21051                  $this->pageobjects[$i] = $this->pageobjects[$j];
21052                  if (isset($this->footerpos[$j])) {
21053                      $this->footerpos[$i] = $this->footerpos[$j];
21054                  } elseif (isset($this->footerpos[$i])) {
21055                      unset($this->footerpos[$i]);
21056                  }
21057                  if (isset($this->footerlen[$j])) {
21058                      $this->footerlen[$i] = $this->footerlen[$j];
21059                  } elseif (isset($this->footerlen[$i])) {
21060                      unset($this->footerlen[$i]);
21061                  }
21062                  if (isset($this->transfmrk[$j])) {
21063                      $this->transfmrk[$i] = $this->transfmrk[$j];
21064                  } elseif (isset($this->transfmrk[$i])) {
21065                      unset($this->transfmrk[$i]);
21066                  }
21067                  if (isset($this->PageAnnots[$j])) {
21068                      $this->PageAnnots[$i] = $this->PageAnnots[$j];
21069                  } elseif (isset($this->PageAnnots[$i])) {
21070                      unset($this->PageAnnots[$i]);
21071                  }
21072                  if (isset($this->newpagegroup[$j])) {
21073                      $this->newpagegroup[$i] = $this->newpagegroup[$j];
21074                      unset($this->newpagegroup[$j]);
21075                  }
21076                  if ($this->currpagegroup == $j) {
21077                      $this->currpagegroup = $i;
21078                  }
21079                  if (isset($this->pageopen[$j])) {
21080                      $this->pageopen[$i] = $this->pageopen[$j];
21081                  } elseif (isset($this->pageopen[$i])) {
21082                      unset($this->pageopen[$i]);
21083                  }
21084              }
21085              // remove last page
21086              unset($this->pages[$this->numpages]);
21087              unset($this->pagedim[$this->numpages]);
21088              unset($this->pagelen[$this->numpages]);
21089              unset($this->intmrk[$this->numpages]);
21090              unset($this->bordermrk[$this->numpages]);
21091              unset($this->cntmrk[$this->numpages]);
21092              foreach ($this->pageobjects[$this->numpages] as $oid) {
21093                  if (isset($this->offsets[$oid])){
21094                      unset($this->offsets[$oid]);
21095                  }
21096              }
21097              unset($this->pageobjects[$this->numpages]);
21098              if (isset($this->footerpos[$this->numpages])) {
21099                  unset($this->footerpos[$this->numpages]);
21100              }
21101              if (isset($this->footerlen[$this->numpages])) {
21102                  unset($this->footerlen[$this->numpages]);
21103              }
21104              if (isset($this->transfmrk[$this->numpages])) {
21105                  unset($this->transfmrk[$this->numpages]);
21106              }
21107              if (isset($this->PageAnnots[$this->numpages])) {
21108                  unset($this->PageAnnots[$this->numpages]);
21109              }
21110              if (isset($this->newpagegroup[$this->numpages])) {
21111                  unset($this->newpagegroup[$this->numpages]);
21112              }
21113              if ($this->currpagegroup == $this->numpages) {
21114                  $this->currpagegroup = ($this->numpages - 1);
21115              }
21116              if (isset($this->pagegroups[$this->numpages])) {
21117                  unset($this->pagegroups[$this->numpages]);
21118              }
21119              if (isset($this->pageopen[$this->numpages])) {
21120                  unset($this->pageopen[$this->numpages]);
21121              }
21122          }
21123          --$this->numpages;
21124          $this->page = $this->numpages;
21125          // adjust outlines
21126          $tmpoutlines = $this->outlines;
21127          foreach ($tmpoutlines as $key => $outline) {
21128              if (!$outline['f']) {
21129                  if ($outline['p'] > $page) {
21130                      $this->outlines[$key]['p'] = $outline['p'] - 1;
21131                  } elseif ($outline['p'] == $page) {
21132                      unset($this->outlines[$key]);
21133                  }
21134              }
21135          }
21136          // adjust dests
21137          $tmpdests = $this->dests;
21138          foreach ($tmpdests as $key => $dest) {
21139              if (!$dest['f']) {
21140                  if ($dest['p'] > $page) {
21141                      $this->dests[$key]['p'] = $dest['p'] - 1;
21142                  } elseif ($dest['p'] == $page) {
21143                      unset($this->dests[$key]);
21144                  }
21145              }
21146          }
21147          // adjust links
21148          $tmplinks = $this->links;
21149          foreach ($tmplinks as $key => $link) {
21150              if (!$link['f']) {
21151                  if ($link['p'] > $page) {
21152                      $this->links[$key]['p'] = $link['p'] - 1;
21153                  } elseif ($link['p'] == $page) {
21154                      unset($this->links[$key]);
21155                  }
21156              }
21157          }
21158          // adjust javascript
21159          $jpage = $page;
21160          if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21161              foreach($pamatch[0] as $pk => $pmatch) {
21162                  $pagenum = intval($pamatch[3][$pk]) + 1;
21163                  if ($pagenum >= $jpage) {
21164                      $newpage = ($pagenum - 1);
21165                  } elseif ($pagenum == $jpage) {
21166                      $newpage = 1;
21167                  } else {
21168                      $newpage = $pagenum;
21169                  }
21170                  --$newpage;
21171                  $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21172                  $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21173              }
21174              unset($pamatch);
21175          }
21176          // return to last page
21177          if ($this->numpages > 0) {
21178              $this->lastPage(true);
21179          }
21180          return true;
21181      }
21182  
21183      /**
21184       * Clone the specified page to a new page.
21185       * @param $page (int) number of page to copy (0 = current page)
21186       * @return true in case of success, false in case of error.
21187       * @public
21188       * @since 4.9.015 (2010-04-20)
21189       */
21190  	public function copyPage($page=0) {
21191          if ($page == 0) {
21192              // default value
21193              $page = $this->page;
21194          }
21195          if (($page < 1) OR ($page > $this->numpages)) {
21196              return false;
21197          }
21198          // close the last page
21199          $this->endPage();
21200          // copy all page-related states
21201          ++$this->numpages;
21202          $this->page = $this->numpages;
21203          $this->setPageBuffer($this->page, $this->getPageBuffer($page));
21204          $this->pagedim[$this->page] = $this->pagedim[$page];
21205          $this->pagelen[$this->page] = $this->pagelen[$page];
21206          $this->intmrk[$this->page] = $this->intmrk[$page];
21207          $this->bordermrk[$this->page] = $this->bordermrk[$page];
21208          $this->cntmrk[$this->page] = $this->cntmrk[$page];
21209          $this->pageobjects[$this->page] = $this->pageobjects[$page];
21210          $this->pageopen[$this->page] = false;
21211          if (isset($this->footerpos[$page])) {
21212              $this->footerpos[$this->page] = $this->footerpos[$page];
21213          }
21214          if (isset($this->footerlen[$page])) {
21215              $this->footerlen[$this->page] = $this->footerlen[$page];
21216          }
21217          if (isset($this->transfmrk[$page])) {
21218              $this->transfmrk[$this->page] = $this->transfmrk[$page];
21219          }
21220          if (isset($this->PageAnnots[$page])) {
21221              $this->PageAnnots[$this->page] = $this->PageAnnots[$page];
21222          }
21223          if (isset($this->newpagegroup[$page])) {
21224              // start a new group
21225              $this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
21226              $this->currpagegroup = $this->newpagegroup[$this->page];
21227              $this->pagegroups[$this->currpagegroup] = 1;
21228          } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
21229              ++$this->pagegroups[$this->currpagegroup];
21230          }
21231          // copy outlines
21232          $tmpoutlines = $this->outlines;
21233          foreach ($tmpoutlines as $key => $outline) {
21234              if ($outline['p'] == $page) {
21235                  $this->outlines[] = array('t' => $outline['t'], 'l' => $outline['l'], 'x' => $outline['x'], 'y' => $outline['y'], 'p' => $this->page, 'f' => $outline['f'], 's' => $outline['s'], 'c' => $outline['c']);
21236              }
21237          }
21238          // copy links
21239          $tmplinks = $this->links;
21240          foreach ($tmplinks as $key => $link) {
21241              if ($link['p'] == $page) {
21242                  $this->links[] = array('p' => $this->page, 'y' => $link['y'], 'f' => $link['f']);
21243              }
21244          }
21245          // return to last page
21246          $this->lastPage(true);
21247          return true;
21248      }
21249  
21250      /**
21251       * Output a Table of Content Index (TOC).
21252       * This method must be called after all Bookmarks were set.
21253       * Before calling this method you have to open the page using the addTOCPage() method.
21254       * After calling this method you have to call endTOCPage() to close the TOC page.
21255       * You can override this method to achieve different styles.
21256       * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21257       * @param $numbersfont (string) set the font for page numbers (please use monospaced font for better alignment).
21258       * @param $filler (string) string used to fill the space between text and page number.
21259       * @param $toc_name (string) name to use for TOC bookmark.
21260       * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21261       * @param $color (array) RGB color array for bookmark title (values from 0 to 255).
21262       * @public
21263       * @author Nicola Asuni
21264       * @since 4.5.000 (2009-01-02)
21265       * @see addTOCPage(), endTOCPage(), addHTMLTOC()
21266       */
21267  	public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
21268          $fontsize = $this->FontSizePt;
21269          $fontfamily = $this->FontFamily;
21270          $fontstyle = $this->FontStyle;
21271          $w = $this->w - $this->lMargin - $this->rMargin;
21272          $spacer = $this->GetStringWidth(chr(32)) * 4;
21273          $lmargin = $this->lMargin;
21274          $rmargin = $this->rMargin;
21275          $x_start = $this->GetX();
21276          $page_first = $this->page;
21277          $current_page = $this->page;
21278          $page_fill_start = false;
21279          $page_fill_end = false;
21280          $current_column = $this->current_column;
21281          if (TCPDF_STATIC::empty_string($numbersfont)) {
21282              $numbersfont = $this->default_monospaced_font;
21283          }
21284          if (TCPDF_STATIC::empty_string($filler)) {
21285              $filler = ' ';
21286          }
21287          if (TCPDF_STATIC::empty_string($page)) {
21288              $gap = ' ';
21289          } else {
21290              $gap = '';
21291              if ($page < 1) {
21292                  $page = 1;
21293              }
21294          }
21295          $this->SetFont($numbersfont, $fontstyle, $fontsize);
21296          $numwidth = $this->GetStringWidth('00000');
21297          $maxpage = 0; //used for pages on attached documents
21298          foreach ($this->outlines as $key => $outline) {
21299              // check for extra pages (used for attachments)
21300              if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
21301                  $outline['p'] += ($this->page - $page_first);
21302              }
21303              if ($this->rtl) {
21304                  $aligntext = 'R';
21305                  $alignnum = 'L';
21306              } else {
21307                  $aligntext = 'L';
21308                  $alignnum = 'R';
21309              }
21310              if ($outline['l'] == 0) {
21311                  $this->SetFont($fontfamily, $outline['s'].'B', $fontsize);
21312              } else {
21313                  $this->SetFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
21314              }
21315              $this->SetTextColorArray($outline['c']);
21316              // check for page break
21317              $this->checkPageBreak(2 * $this->getCellHeight($this->FontSize));
21318              // set margins and X position
21319              if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
21320                  $this->lMargin = $lmargin;
21321                  $this->rMargin = $rmargin;
21322              } else {
21323                  if ($this->current_column != $current_column) {
21324                      if ($this->rtl) {
21325                          $x_start = $this->w - $this->columns[$this->current_column]['x'];
21326                      } else {
21327                          $x_start = $this->columns[$this->current_column]['x'];
21328                      }
21329                  }
21330                  $lmargin = $this->lMargin;
21331                  $rmargin = $this->rMargin;
21332                  $current_page = $this->page;
21333                  $current_column = $this->current_column;
21334              }
21335              $this->SetX($x_start);
21336              $indent = ($spacer * $outline['l']);
21337              if ($this->rtl) {
21338                  $this->x -= $indent;
21339                  $this->rMargin = $this->w - $this->x;
21340              } else {
21341                  $this->x += $indent;
21342                  $this->lMargin = $this->x;
21343              }
21344              $link = $this->AddLink();
21345              $this->SetLink($link, $outline['y'], $outline['p']);
21346              // write the text
21347              if ($this->rtl) {
21348                  $txt = ' '.$outline['t'];
21349              } else {
21350                  $txt = $outline['t'].' ';
21351              }
21352              $this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
21353              if ($this->rtl) {
21354                  $tw = $this->x - $this->lMargin;
21355              } else {
21356                  $tw = $this->w - $this->rMargin - $this->x;
21357              }
21358              $this->SetFont($numbersfont, $fontstyle, $fontsize);
21359              if (TCPDF_STATIC::empty_string($page)) {
21360                  $pagenum = $outline['p'];
21361              } else {
21362                  // placemark to be replaced with the correct number
21363                  $pagenum = '{#'.($outline['p']).'}';
21364                  if ($this->isUnicodeFont()) {
21365                      $pagenum = '{'.$pagenum.'}';
21366                  }
21367                  $maxpage = max($maxpage, $outline['p']);
21368              }
21369              $fw = ($tw - $this->GetStringWidth($pagenum.$filler));
21370              $wfiller = $this->GetStringWidth($filler);
21371              if ($wfiller > 0) {
21372                  $numfills = floor($fw / $wfiller);
21373              } else {
21374                  $numfills = 0;
21375              }
21376              if ($numfills > 0) {
21377                  $rowfill = str_repeat($filler, $numfills);
21378              } else {
21379                  $rowfill = '';
21380              }
21381              if ($this->rtl) {
21382                  $pagenum = $pagenum.$gap.$rowfill;
21383              } else {
21384                  $pagenum = $rowfill.$gap.$pagenum;
21385              }
21386              // write the number
21387              $this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
21388          }
21389          $page_last = $this->getPage();
21390          $numpages = ($page_last - $page_first + 1);
21391          // account for booklet mode
21392          if ($this->booklet) {
21393              // check if a blank page is required before TOC
21394              $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21395              $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21396              if ($page_fill_start) {
21397                  // add a page at the end (to be moved before TOC)
21398                  $this->addPage();
21399                  ++$page_last;
21400                  ++$numpages;
21401              }
21402              if ($page_fill_end) {
21403                  // add a page at the end
21404                  $this->addPage();
21405                  ++$page_last;
21406                  ++$numpages;
21407              }
21408          }
21409          $maxpage = max($maxpage, $page_last);
21410          if (!TCPDF_STATIC::empty_string($page)) {
21411              for ($p = $page_first; $p <= $page_last; ++$p) {
21412                  // get page data
21413                  $temppage = $this->getPageBuffer($p);
21414                  for ($n = 1; $n <= $maxpage; ++$n) {
21415                      // update page numbers
21416                      $a = '{#'.$n.'}';
21417                      // get page number aliases
21418                      $pnalias = $this->getInternalPageNumberAliases($a);
21419                      // calculate replacement number
21420                      if (($n >= $page) AND ($n <= $this->numpages)) {
21421                          $np = $n + $numpages;
21422                      } else {
21423                          $np = $n;
21424                      }
21425                      $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21426                      $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21427                      // replace aliases with numbers
21428                      foreach ($pnalias['u'] as $u) {
21429                          $sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
21430                          if ($this->rtl) {
21431                              $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21432                          } else {
21433                              $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21434                          }
21435                          $temppage = str_replace($u, $nr, $temppage);
21436                      }
21437                      foreach ($pnalias['a'] as $a) {
21438                          $sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
21439                          if ($this->rtl) {
21440                              $nr = $na.' '.$sfill;
21441                          } else {
21442                              $nr = $sfill.' '.$na;
21443                          }
21444                          $temppage = str_replace($a, $nr, $temppage);
21445                      }
21446                  }
21447                  // save changes
21448                  $this->setPageBuffer($p, $temppage);
21449              }
21450              // move pages
21451              $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21452              if ($page_fill_start) {
21453                  $this->movePage($page_last, $page_first);
21454              }
21455              for ($i = 0; $i < $numpages; ++$i) {
21456                  $this->movePage($page_last, $page);
21457              }
21458          }
21459      }
21460  
21461      /**
21462       * Output a Table Of Content Index (TOC) using HTML templates.
21463       * This method must be called after all Bookmarks were set.
21464       * Before calling this method you have to open the page using the addTOCPage() method.
21465       * After calling this method you have to call endTOCPage() to close the TOC page.
21466       * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21467       * @param $toc_name (string) name to use for TOC bookmark.
21468       * @param $templates (array) array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
21469       * @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)
21470       * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21471       * @param $color (array) RGB color array for title (values from 0 to 255).
21472       * @public
21473       * @author Nicola Asuni
21474       * @since 5.0.001 (2010-05-06)
21475       * @see addTOCPage(), endTOCPage(), addTOC()
21476       */
21477  	public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
21478          $filler = ' ';
21479          $prev_htmlLinkColorArray = $this->htmlLinkColorArray;
21480          $prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
21481          // set new style for link
21482          $this->htmlLinkColorArray = array();
21483          $this->htmlLinkFontStyle = '';
21484          $page_first = $this->getPage();
21485          $page_fill_start = false;
21486          $page_fill_end = false;
21487          // get the font type used for numbers in each template
21488          $current_font = $this->FontFamily;
21489          foreach ($templates as $level => $html) {
21490              $dom = $this->getHtmlDomArray($html);
21491              foreach ($dom as $key => $value) {
21492                  if ($value['value'] == '#TOC_PAGE_NUMBER#') {
21493                      $this->SetFont($dom[($key - 1)]['fontname']);
21494                      $templates['F'.$level] = $this->isUnicodeFont();
21495                  }
21496              }
21497          }
21498          $this->SetFont($current_font);
21499          $maxpage = 0; //used for pages on attached documents
21500          foreach ($this->outlines as $key => $outline) {
21501              // get HTML template
21502              $row = $templates[$outline['l']];
21503              if (TCPDF_STATIC::empty_string($page)) {
21504                  $pagenum = $outline['p'];
21505              } else {
21506                  // placemark to be replaced with the correct number
21507                  $pagenum = '{#'.($outline['p']).'}';
21508                  if ($templates['F'.$outline['l']]) {
21509                      $pagenum = '{'.$pagenum.'}';
21510                  }
21511                  $maxpage = max($maxpage, $outline['p']);
21512              }
21513              // replace templates with current values
21514              $row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
21515              $row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
21516              // add link to page
21517              $row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
21518              // write bookmark entry
21519              $this->writeHTML($row, false, false, true, false, '');
21520          }
21521          // restore link styles
21522          $this->htmlLinkColorArray = $prev_htmlLinkColorArray;
21523          $this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
21524          // move TOC page and replace numbers
21525          $page_last = $this->getPage();
21526          $numpages = ($page_last - $page_first + 1);
21527          // account for booklet mode
21528          if ($this->booklet) {
21529              // check if a blank page is required before TOC
21530              $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21531              $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21532              if ($page_fill_start) {
21533                  // add a page at the end (to be moved before TOC)
21534                  $this->addPage();
21535                  ++$page_last;
21536                  ++$numpages;
21537              }
21538              if ($page_fill_end) {
21539                  // add a page at the end
21540                  $this->addPage();
21541                  ++$page_last;
21542                  ++$numpages;
21543              }
21544          }
21545          $maxpage = max($maxpage, $page_last);
21546          if (!TCPDF_STATIC::empty_string($page)) {
21547              for ($p = $page_first; $p <= $page_last; ++$p) {
21548                  // get page data
21549                  $temppage = $this->getPageBuffer($p);
21550                  for ($n = 1; $n <= $maxpage; ++$n) {
21551                      // update page numbers
21552                      $a = '{#'.$n.'}';
21553                      // get page number aliases
21554                      $pnalias = $this->getInternalPageNumberAliases($a);
21555                      // calculate replacement number
21556                      if ($n >= $page) {
21557                          $np = $n + $numpages;
21558                      } else {
21559                          $np = $n;
21560                      }
21561                      $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21562                      $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21563                      // replace aliases with numbers
21564                      foreach ($pnalias['u'] as $u) {
21565                          if ($correct_align) {
21566                              $sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
21567                              if ($this->rtl) {
21568                                  $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21569                              } else {
21570                                  $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21571                              }
21572                          } else {
21573                              $nr = $nu;
21574                          }
21575                          $temppage = str_replace($u, $nr, $temppage);
21576                      }
21577                      foreach ($pnalias['a'] as $a) {
21578                          if ($correct_align) {
21579                              $sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
21580                              if ($this->rtl) {
21581                                  $nr = $na.' '.$sfill;
21582                              } else {
21583                                  $nr = $sfill.' '.$na;
21584                              }
21585                          } else {
21586                              $nr = $na;
21587                          }
21588                          $temppage = str_replace($a, $nr, $temppage);
21589                      }
21590                  }
21591                  // save changes
21592                  $this->setPageBuffer($p, $temppage);
21593              }
21594              // move pages
21595              $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21596              if ($page_fill_start) {
21597                  $this->movePage($page_last, $page_first);
21598              }
21599              for ($i = 0; $i < $numpages; ++$i) {
21600                  $this->movePage($page_last, $page);
21601              }
21602          }
21603      }
21604  
21605      /**
21606       * Stores a copy of the current TCPDF object used for undo operation.
21607       * @public
21608       * @since 4.5.029 (2009-03-19)
21609       */
21610  	public function startTransaction() {
21611          if (isset($this->objcopy)) {
21612              // remove previous copy
21613              $this->commitTransaction();
21614          }
21615          // record current page number and Y position
21616          $this->start_transaction_page = $this->page;
21617          $this->start_transaction_y = $this->y;
21618          // clone current object
21619          $this->objcopy = TCPDF_STATIC::objclone($this);
21620      }
21621  
21622      /**
21623       * Delete the copy of the current TCPDF object used for undo operation.
21624       * @public
21625       * @since 4.5.029 (2009-03-19)
21626       */
21627  	public function commitTransaction() {
21628          if (isset($this->objcopy)) {
21629              $this->objcopy->_destroy(true, true);
21630              unset($this->objcopy);
21631          }
21632      }
21633  
21634      /**
21635       * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
21636       * @param $self (boolean) if true restores current class object to previous state without the need of reassignment via the returned value.
21637       * @return TCPDF object.
21638       * @public
21639       * @since 4.5.029 (2009-03-19)
21640       */
21641  	public function rollbackTransaction($self=false) {
21642          if (isset($this->objcopy)) {
21643              $this->_destroy(true, true);
21644              if ($self) {
21645                  $objvars = get_object_vars($this->objcopy);
21646                  foreach ($objvars as $key => $value) {
21647                      $this->$key = $value;
21648                  }
21649              }
21650              return $this->objcopy;
21651          }
21652          return $this;
21653      }
21654  
21655      // --- MULTI COLUMNS METHODS -----------------------
21656  
21657      /**
21658       * Set multiple columns of the same size
21659       * @param $numcols (int) number of columns (set to zero to disable columns mode)
21660       * @param $width (int) column width
21661       * @param $y (int) column starting Y position (leave empty for current Y position)
21662       * @public
21663       * @since 4.9.001 (2010-03-28)
21664       */
21665  	public function setEqualColumns($numcols=0, $width=0, $y='') {
21666          $this->columns = array();
21667          if ($numcols < 2) {
21668              $numcols = 0;
21669              $this->columns = array();
21670          } else {
21671              // maximum column width
21672              $maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
21673              if (($width == 0) OR ($width > $maxwidth)) {
21674                  $width = $maxwidth;
21675              }
21676              if (TCPDF_STATIC::empty_string($y)) {
21677                  $y = $this->y;
21678              }
21679              // space between columns
21680              $space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
21681              // fill the columns array (with, space, starting Y position)
21682              for ($i = 0; $i < $numcols; ++$i) {
21683                  $this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
21684              }
21685          }
21686          $this->num_columns = $numcols;
21687          $this->current_column = 0;
21688          $this->column_start_page = $this->page;
21689          $this->selectColumn(0);
21690      }
21691  
21692      /**
21693       * Remove columns and reset page margins.
21694       * @public
21695       * @since 5.9.072 (2011-04-26)
21696       */
21697  	public function resetColumns() {
21698          $this->lMargin = $this->original_lMargin;
21699          $this->rMargin = $this->original_rMargin;
21700          $this->setEqualColumns();
21701      }
21702  
21703      /**
21704       * Set columns array.
21705       * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
21706       * @param $columns (array)
21707       * @public
21708       * @since 4.9.001 (2010-03-28)
21709       */
21710  	public function setColumnsArray($columns) {
21711          $this->columns = $columns;
21712          $this->num_columns = count($columns);
21713          $this->current_column = 0;
21714          $this->column_start_page = $this->page;
21715          $this->selectColumn(0);
21716      }
21717  
21718      /**
21719       * Set position at a given column
21720       * @param $col (int) column number (from 0 to getNumberOfColumns()-1); empty string = current column.
21721       * @public
21722       * @since 4.9.001 (2010-03-28)
21723       */
21724  	public function selectColumn($col='') {
21725          if (is_string($col)) {
21726              $col = $this->current_column;
21727          } elseif ($col >= $this->num_columns) {
21728              $col = 0;
21729          }
21730          $xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
21731          $enable_thead = false;
21732          if ($this->num_columns > 1) {
21733              if ($col != $this->current_column) {
21734                  // move Y pointer at the top of the column
21735                  if ($this->column_start_page == $this->page) {
21736                      $this->y = $this->columns[$col]['y'];
21737                  } else {
21738                      $this->y = $this->tMargin;
21739                  }
21740                  // Avoid to write table headers more than once
21741                  if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
21742                      $enable_thead = true;
21743                      $this->maxselcol['page'] = $this->page;
21744                      $this->maxselcol['column'] = $col;
21745                  }
21746              }
21747              $xshift = $this->colxshift;
21748              // set X position of the current column by case
21749              $listindent = ($this->listindentlevel * $this->listindent);
21750              // calculate column X position
21751              $colpos = 0;
21752              for ($i = 0; $i < $col; ++$i) {
21753                  $colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
21754              }
21755              if ($this->rtl) {
21756                  $x = $this->w - $this->original_rMargin - $colpos;
21757                  $this->rMargin = ($this->w - $x + $listindent);
21758                  $this->lMargin = ($x - $this->columns[$col]['w']);
21759                  $this->x = $x - $listindent;
21760              } else {
21761                  $x = $this->original_lMargin + $colpos;
21762                  $this->lMargin = ($x + $listindent);
21763                  $this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
21764                  $this->x = $x + $listindent;
21765              }
21766              $this->columns[$col]['x'] = $x;
21767          }
21768          $this->current_column = $col;
21769          // fix for HTML mode
21770          $this->newline = true;
21771          // print HTML table header (if any)
21772          if ((!TCPDF_STATIC::empty_string($this->thead)) AND (!$this->inthead)) {
21773              if ($enable_thead) {
21774                  // print table header
21775                  $this->writeHTML($this->thead, false, false, false, false, '');
21776                  $this->y += $xshift['s']['V'];
21777                  // store end of header position
21778                  if (!isset($this->columns[$col]['th'])) {
21779                      $this->columns[$col]['th'] = array();
21780                  }
21781                  $this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
21782                  $this->lasth = 0;
21783              } elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
21784                  $this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
21785              }
21786          }
21787          // account for an html table cell over multiple columns
21788          if ($this->rtl) {
21789              $this->rMargin += $xshift['x'];
21790              $this->x -= ($xshift['x'] + $xshift['p']['R']);
21791          } else {
21792              $this->lMargin += $xshift['x'];
21793              $this->x += $xshift['x'] + $xshift['p']['L'];
21794          }
21795      }
21796  
21797      /**
21798       * Return the current column number
21799       * @return int current column number
21800       * @public
21801       * @since 5.5.011 (2010-07-08)
21802       */
21803  	public function getColumn() {
21804          return $this->current_column;
21805      }
21806  
21807      /**
21808       * Return the current number of columns.
21809       * @return int number of columns
21810       * @public
21811       * @since 5.8.018 (2010-08-25)
21812       */
21813  	public function getNumberOfColumns() {
21814          return $this->num_columns;
21815      }
21816  
21817      /**
21818       * Set Text rendering mode.
21819       * @param $stroke (int) outline size in user units (0 = disable).
21820       * @param $fill (boolean) if true fills the text (default).
21821       * @param $clip (boolean) if true activate clipping mode
21822       * @public
21823       * @since 4.9.008 (2009-04-02)
21824       */
21825  	public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
21826          // Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
21827          // convert text rendering parameters
21828          if ($stroke < 0) {
21829              $stroke = 0;
21830          }
21831          if ($fill === true) {
21832              if ($stroke > 0) {
21833                  if ($clip === true) {
21834                      // Fill, then stroke text and add to path for clipping
21835                      $textrendermode = 6;
21836                  } else {
21837                      // Fill, then stroke text
21838                      $textrendermode = 2;
21839                  }
21840                  $textstrokewidth = $stroke;
21841              } else {
21842                  if ($clip === true) {
21843                      // Fill text and add to path for clipping
21844                      $textrendermode = 4;
21845                  } else {
21846                      // Fill text
21847                      $textrendermode = 0;
21848                  }
21849              }
21850          } else {
21851              if ($stroke > 0) {
21852                  if ($clip === true) {
21853                      // Stroke text and add to path for clipping
21854                      $textrendermode = 5;
21855                  } else {
21856                      // Stroke text
21857                      $textrendermode = 1;
21858                  }
21859                  $textstrokewidth = $stroke;
21860              } else {
21861                  if ($clip === true) {
21862                      // Add text to path for clipping
21863                      $textrendermode = 7;
21864                  } else {
21865                      // Neither fill nor stroke text (invisible)
21866                      $textrendermode = 3;
21867                  }
21868              }
21869          }
21870          $this->textrendermode = $textrendermode;
21871          $this->textstrokewidth = $stroke;
21872      }
21873  
21874      /**
21875       * Set parameters for drop shadow effect for text.
21876       * @param $params (array) Array of parameters: enabled (boolean) set to true to enable shadow; depth_w (float) shadow width in user units; depth_h (float) shadow height in user units; color (array) shadow color or false to use the stroke color; opacity (float) Alpha value: real value from 0 (transparent) to 1 (opaque); blend_mode (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity.
21877       * @since 5.9.174 (2012-07-25)
21878       * @public
21879      */
21880  	public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
21881          if (isset($params['enabled'])) {
21882              $this->txtshadow['enabled'] = $params['enabled']?true:false;
21883          } else {
21884              $this->txtshadow['enabled'] = false;
21885          }
21886          if (isset($params['depth_w'])) {
21887              $this->txtshadow['depth_w'] = floatval($params['depth_w']);
21888          } else {
21889              $this->txtshadow['depth_w'] = 0;
21890          }
21891          if (isset($params['depth_h'])) {
21892              $this->txtshadow['depth_h'] = floatval($params['depth_h']);
21893          } else {
21894              $this->txtshadow['depth_h'] = 0;
21895          }
21896          if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
21897              $this->txtshadow['color'] = $params['color'];
21898          } else {
21899              $this->txtshadow['color'] = $this->strokecolor;
21900          }
21901          if (isset($params['opacity'])) {
21902              $this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
21903          } else {
21904              $this->txtshadow['opacity'] = 1;
21905          }
21906          if (isset($params['blend_mode']) AND in_array($params['blend_mode'], array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
21907              $this->txtshadow['blend_mode'] = $params['blend_mode'];
21908          } else {
21909              $this->txtshadow['blend_mode'] = 'Normal';
21910          }
21911          if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
21912              $this->txtshadow['enabled'] = false;
21913          }
21914      }
21915  
21916      /**
21917       * Return the text shadow parameters array.
21918       * @return Array of parameters.
21919       * @since 5.9.174 (2012-07-25)
21920       * @public
21921       */
21922  	public function getTextShadow() {
21923          return $this->txtshadow;
21924      }
21925  
21926      /**
21927       * Returns an array of chars containing soft hyphens.
21928       * @param $word (array) array of chars
21929       * @param $patterns (array) Array of hypenation patterns.
21930       * @param $dictionary (array) Array of words to be returned without applying the hyphenation algorithm.
21931       * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
21932       * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
21933       * @param $charmin (int) Minimum word length to apply the hyphenation algorithm.
21934       * @param $charmax (int) Maximum length of broken piece of word.
21935       * @return array text with soft hyphens
21936       * @author Nicola Asuni
21937       * @since 4.9.012 (2010-04-12)
21938       * @protected
21939       */
21940  	protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
21941          $hyphenword = array(); // hyphens positions
21942          $numchars = count($word);
21943          if ($numchars <= $charmin) {
21944              return $word;
21945          }
21946          $word_string = TCPDF_FONTS::UTF8ArrSubString($word, '', '', $this->isunicode);
21947          // some words will be returned as-is
21948          $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})(\]?)$/';
21949          if (preg_match($pattern, $word_string) > 0) {
21950              // email
21951              return $word;
21952          }
21953          $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})(\]?)$/';
21954          if (preg_match($pattern, $word_string) > 0) {
21955              // URL
21956              return $word;
21957          }
21958          if (isset($dictionary[$word_string])) {
21959              return TCPDF_FONTS::UTF8StringToArray($dictionary[$word_string], $this->isunicode, $this->CurrentFont);
21960          }
21961          // surround word with '_' characters
21962          $tmpword = array_merge(array(46), $word, array(46));
21963          $tmpnumchars = $numchars + 2;
21964          $maxpos = $tmpnumchars - 1;
21965          for ($pos = 0; $pos < $maxpos; ++$pos) {
21966              $imax = min(($tmpnumchars - $pos), $charmax);
21967              for ($i = 1; $i <= $imax; ++$i) {
21968                  $subword = strtolower(TCPDF_FONTS::UTF8ArrSubString($tmpword, $pos, ($pos + $i), $this->isunicode));
21969                  if (isset($patterns[$subword])) {
21970                      $pattern = TCPDF_FONTS::UTF8StringToArray($patterns[$subword], $this->isunicode, $this->CurrentFont);
21971                      $pattern_length = count($pattern);
21972                      $digits = 1;
21973                      for ($j = 0; $j < $pattern_length; ++$j) {
21974                          // check if $pattern[$j] is a number = hyphenation level (only numbers from 1 to 5 are valid)
21975                          if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
21976                              if ($j == 0) {
21977                                  $zero = $pos - 1;
21978                              } else {
21979                                  $zero = $pos + $j - $digits;
21980                              }
21981                              // get hyphenation level
21982                              $level = ($pattern[$j] - 48);
21983                              // if two levels from two different patterns match at the same point, the higher one is selected.
21984                              if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] < $level)) {
21985                                  $hyphenword[$zero] = $level;
21986                              }
21987                              ++$digits;
21988                          }
21989                      }
21990                  }
21991              }
21992          }
21993          $inserted = 0;
21994          $maxpos = $numchars - $rightmin;
21995          for ($i = $leftmin; $i <= $maxpos; ++$i) {
21996              // only odd levels indicate allowed hyphenation points
21997              if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
21998                  // 173 = soft hyphen character
21999                  array_splice($word, $i + $inserted, 0, 173);
22000                  ++$inserted;
22001              }
22002          }
22003          return $word;
22004      }
22005  
22006      /**
22007       * Returns text with soft hyphens.
22008       * @param $text (string) text to process
22009       * @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/
22010       * @param $dictionary (array) Array of words to be returned without applying the hyphenation algorithm.
22011       * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
22012       * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
22013       * @param $charmin (int) Minimum word length to apply the hyphenation algorithm.
22014       * @param $charmax (int) Maximum length of broken piece of word.
22015       * @return array text with soft hyphens
22016       * @author Nicola Asuni
22017       * @since 4.9.012 (2010-04-12)
22018       * @public
22019       */
22020  	public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22021          $text = $this->unhtmlentities($text);
22022          $word = array(); // last word
22023          $txtarr = array(); // text to be returned
22024          $intag = false; // true if we are inside an HTML tag
22025          $skip = false; // true to skip hyphenation
22026          if (!is_array($patterns)) {
22027              $patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patterns);
22028          }
22029          // get array of characters
22030          $unichars = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
22031          // for each char
22032          foreach ($unichars as $char) {
22033              if ((!$intag) AND (!$skip) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
22034                  // letter character
22035                  $word[] = $char;
22036              } else {
22037                  // other type of character
22038                  if (!TCPDF_STATIC::empty_string($word)) {
22039                      // hypenate the word
22040                      $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22041                      $word = array();
22042                  }
22043                  $txtarr[] = $char;
22044                  if (chr($char) == '<') {
22045                      // we are inside an HTML tag
22046                      $intag = true;
22047                  } elseif ($intag AND (chr($char) == '>')) {
22048                      // end of HTML tag
22049                      $intag = false;
22050                      // check for style tag
22051                      $expected = array(115, 116, 121, 108, 101); // = 'style'
22052                      $current = array_slice($txtarr, -6, 5); // last 5 chars
22053                      $compare = array_diff($expected, $current);
22054                      if (empty($compare)) {
22055                          // check if it is a closing tag
22056                          $expected = array(47); // = '/'
22057                          $current = array_slice($txtarr, -7, 1);
22058                          $compare = array_diff($expected, $current);
22059                          if (empty($compare)) {
22060                              // closing style tag
22061                              $skip = false;
22062                          } else {
22063                              // opening style tag
22064                              $skip = true;
22065                          }
22066                      }
22067                  }
22068              }
22069          }
22070          if (!TCPDF_STATIC::empty_string($word)) {
22071              // hypenate the word
22072              $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22073          }
22074          // convert char array to string and return
22075          return TCPDF_FONTS::UTF8ArrSubString($txtarr, '', '', $this->isunicode);
22076      }
22077  
22078      /**
22079       * Enable/disable rasterization of vector images using ImageMagick library.
22080       * @param $mode (boolean) if true enable rasterization, false otherwise.
22081       * @public
22082       * @since 5.0.000 (2010-04-27)
22083       */
22084  	public function setRasterizeVectorImages($mode) {
22085          $this->rasterize_vector_images = $mode;
22086      }
22087  
22088      /**
22089       * Enable or disable default option for font subsetting.
22090       * @param $enable (boolean) if true enable font subsetting by default.
22091       * @author Nicola Asuni
22092       * @public
22093       * @since 5.3.002 (2010-06-07)
22094       */
22095  	public function setFontSubsetting($enable=true) {
22096          if ($this->pdfa_mode) {
22097              $this->font_subsetting = false;
22098          } else {
22099              $this->font_subsetting = $enable ? true : false;
22100          }
22101      }
22102  
22103      /**
22104       * Return the default option for font subsetting.
22105       * @return boolean default font subsetting state.
22106       * @author Nicola Asuni
22107       * @public
22108       * @since 5.3.002 (2010-06-07)
22109       */
22110  	public function getFontSubsetting() {
22111          return $this->font_subsetting;
22112      }
22113  
22114      /**
22115       * Left trim the input string
22116       * @param $str (string) string to trim
22117       * @param $replace (string) string that replace spaces.
22118       * @return left trimmed string
22119       * @author Nicola Asuni
22120       * @public
22121       * @since 5.8.000 (2010-08-11)
22122       */
22123  	public function stringLeftTrim($str, $replace='') {
22124          return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
22125      }
22126  
22127      /**
22128       * Right trim the input string
22129       * @param $str (string) string to trim
22130       * @param $replace (string) string that replace spaces.
22131       * @return right trimmed string
22132       * @author Nicola Asuni
22133       * @public
22134       * @since 5.8.000 (2010-08-11)
22135       */
22136  	public function stringRightTrim($str, $replace='') {
22137          return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
22138      }
22139  
22140      /**
22141       * Trim the input string
22142       * @param $str (string) string to trim
22143       * @param $replace (string) string that replace spaces.
22144       * @return trimmed string
22145       * @author Nicola Asuni
22146       * @public
22147       * @since 5.8.000 (2010-08-11)
22148       */
22149  	public function stringTrim($str, $replace='') {
22150          $str = $this->stringLeftTrim($str, $replace);
22151          $str = $this->stringRightTrim($str, $replace);
22152          return $str;
22153      }
22154  
22155      /**
22156       * Return true if the current font is unicode type.
22157       * @return true for unicode font, false otherwise.
22158       * @author Nicola Asuni
22159       * @public
22160       * @since 5.8.002 (2010-08-14)
22161       */
22162  	public function isUnicodeFont() {
22163          return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
22164      }
22165  
22166      /**
22167       * Return normalized font name
22168       * @param $fontfamily (string) property string containing font family names
22169       * @return string normalized font name
22170       * @author Nicola Asuni
22171       * @public
22172       * @since 5.8.004 (2010-08-17)
22173       */
22174  	public function getFontFamilyName($fontfamily) {
22175          // remove spaces and symbols
22176          $fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
22177          // extract all font names
22178          $fontslist = preg_split('/[,]/', $fontfamily);
22179          // find first valid font name
22180          foreach ($fontslist as $font) {
22181              // replace font variations
22182              $font = preg_replace('/regular$/', '', $font);
22183              $font = preg_replace('/italic$/', 'I', $font);
22184              $font = preg_replace('/oblique$/', 'I', $font);
22185              $font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
22186              // replace common family names and core fonts
22187              $pattern = array();
22188              $replacement = array();
22189              $pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
22190              $replacement[] = 'times';
22191              $pattern[] = '/^sansserif/';
22192              $replacement[] = 'helvetica';
22193              $pattern[] = '/^monospace/';
22194              $replacement[] = 'courier';
22195              $font = preg_replace($pattern, $replacement, $font);
22196              if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
22197                  return $font;
22198              }
22199          }
22200          // return current font as default
22201          return $this->CurrentFont['fontkey'];
22202      }
22203  
22204      /**
22205       * Start a new XObject Template.
22206       * 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).
22207       * 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.
22208       * Note: X,Y coordinates will be reset to 0,0.
22209       * @param $w (int) Template width in user units (empty string or zero = page width less margins).
22210       * @param $h (int) Template height in user units (empty string or zero = page height less margins).
22211       * @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).
22212       * @return int the XObject Template ID in case of success or false in case of error.
22213       * @author Nicola Asuni
22214       * @public
22215       * @since 5.8.017 (2010-08-24)
22216       * @see endTemplate(), printTemplate()
22217       */
22218  	public function startTemplate($w=0, $h=0, $group=false) {
22219          if ($this->inxobj) {
22220              // we are already inside an XObject template
22221              return false;
22222          }
22223          $this->inxobj = true;
22224          ++$this->n;
22225          // XObject ID
22226          $this->xobjid = 'XT'.$this->n;
22227          // object ID
22228          $this->xobjects[$this->xobjid] = array('n' => $this->n);
22229          // store current graphic state
22230          $this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
22231          // initialize data
22232          $this->xobjects[$this->xobjid]['intmrk'] = 0;
22233          $this->xobjects[$this->xobjid]['transfmrk'] = array();
22234          $this->xobjects[$this->xobjid]['outdata'] = '';
22235          $this->xobjects[$this->xobjid]['xobjects'] = array();
22236          $this->xobjects[$this->xobjid]['images'] = array();
22237          $this->xobjects[$this->xobjid]['fonts'] = array();
22238          $this->xobjects[$this->xobjid]['annotations'] = array();
22239          $this->xobjects[$this->xobjid]['extgstates'] = array();
22240          $this->xobjects[$this->xobjid]['gradients'] = array();
22241          $this->xobjects[$this->xobjid]['spot_colors'] = array();
22242          // set new environment
22243          $this->num_columns = 1;
22244          $this->current_column = 0;
22245          $this->SetAutoPageBreak(false);
22246          if (($w === '') OR ($w <= 0)) {
22247              $w = $this->w - $this->lMargin - $this->rMargin;
22248          }
22249          if (($h === '') OR ($h <= 0)) {
22250              $h = $this->h - $this->tMargin - $this->bMargin;
22251          }
22252          $this->xobjects[$this->xobjid]['x'] = 0;
22253          $this->xobjects[$this->xobjid]['y'] = 0;
22254          $this->xobjects[$this->xobjid]['w'] = $w;
22255          $this->xobjects[$this->xobjid]['h'] = $h;
22256          $this->w = $w;
22257          $this->h = $h;
22258          $this->wPt = $this->w * $this->k;
22259          $this->hPt = $this->h * $this->k;
22260          $this->fwPt = $this->wPt;
22261          $this->fhPt = $this->hPt;
22262          $this->x = 0;
22263          $this->y = 0;
22264          $this->lMargin = 0;
22265          $this->rMargin = 0;
22266          $this->tMargin = 0;
22267          $this->bMargin = 0;
22268          // set group mode
22269          $this->xobjects[$this->xobjid]['group'] = $group;
22270          return $this->xobjid;
22271      }
22272  
22273      /**
22274       * End the current XObject Template started with startTemplate() and restore the previous graphic state.
22275       * 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).
22276       * 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.
22277       * @return int the XObject Template ID in case of success or false in case of error.
22278       * @author Nicola Asuni
22279       * @public
22280       * @since 5.8.017 (2010-08-24)
22281       * @see startTemplate(), printTemplate()
22282       */
22283  	public function endTemplate() {
22284          if (!$this->inxobj) {
22285              // we are not inside a template
22286              return false;
22287          }
22288          $this->inxobj = false;
22289          // restore previous graphic state
22290          $this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
22291          return $this->xobjid;
22292      }
22293  
22294      /**
22295       * Print an XObject Template.
22296       * You can print an XObject Template inside the currently opened Template.
22297       * 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).
22298       * 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.
22299       * @param $id (string) The ID of XObject Template to print.
22300       * @param $x (int) X position in user units (empty string = current x position)
22301       * @param $y (int) Y position in user units (empty string = current y position)
22302       * @param $w (int) Width in user units (zero = remaining page width)
22303       * @param $h (int) Height in user units (zero = remaining page height)
22304       * @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>
22305       * @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>
22306       * @param $fitonpage (boolean) If true the template is resized to not exceed page dimensions.
22307       * @author Nicola Asuni
22308       * @public
22309       * @since 5.8.017 (2010-08-24)
22310       * @see startTemplate(), endTemplate()
22311       */
22312  	public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
22313          if ($this->state != 2) {
22314               return;
22315          }
22316          if (!isset($this->xobjects[$id])) {
22317              $this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
22318          }
22319          if ($this->inxobj) {
22320              if ($id == $this->xobjid) {
22321                  // close current template
22322                  $this->endTemplate();
22323              } else {
22324                  // use the template as resource for the template currently opened
22325                  $this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
22326              }
22327          }
22328          // set default values
22329          if ($x === '') {
22330              $x = $this->x;
22331          }
22332          if ($y === '') {
22333              $y = $this->y;
22334          }
22335          // check page for no-write regions and adapt page margins if necessary
22336          list($x, $y) = $this->checkPageRegions($h, $x, $y);
22337          $ow = $this->xobjects[$id]['w'];
22338          if ($ow <= 0) {
22339              $ow = 1;
22340          }
22341          $oh = $this->xobjects[$id]['h'];
22342          if ($oh <= 0) {
22343              $oh = 1;
22344          }
22345          // calculate template width and height on document
22346          if (($w <= 0) AND ($h <= 0)) {
22347              $w = $ow;
22348              $h = $oh;
22349          } elseif ($w <= 0) {
22350              $w = $h * $ow / $oh;
22351          } elseif ($h <= 0) {
22352              $h = $w * $oh / $ow;
22353          }
22354          // fit the template on available space
22355          list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22356          // set page alignment
22357          $rb_y = $y + $h;
22358          // set alignment
22359          if ($this->rtl) {
22360              if ($palign == 'L') {
22361                  $xt = $this->lMargin;
22362              } elseif ($palign == 'C') {
22363                  $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22364              } elseif ($palign == 'R') {
22365                  $xt = $this->w - $this->rMargin - $w;
22366              } else {
22367                  $xt = $x - $w;
22368              }
22369              $rb_x = $xt;
22370          } else {
22371              if ($palign == 'L') {
22372                  $xt = $this->lMargin;
22373              } elseif ($palign == 'C') {
22374                  $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22375              } elseif ($palign == 'R') {
22376                  $xt = $this->w - $this->rMargin - $w;
22377              } else {
22378                  $xt = $x;
22379              }
22380              $rb_x = $xt + $w;
22381          }
22382          // print XObject Template + Transformation matrix
22383          $this->StartTransform();
22384          // translate and scale
22385          $sx = ($w / $ow);
22386          $sy = ($h / $oh);
22387          $tm = array();
22388          $tm[0] = $sx;
22389          $tm[1] = 0;
22390          $tm[2] = 0;
22391          $tm[3] = $sy;
22392          $tm[4] = $xt * $this->k;
22393          $tm[5] = ($this->h - $h - $y) * $this->k;
22394          $this->Transform($tm);
22395          // set object
22396          $this->_out('/'.$id.' Do');
22397          $this->StopTransform();
22398          // add annotations
22399          if (!empty($this->xobjects[$id]['annotations'])) {
22400              foreach ($this->xobjects[$id]['annotations'] as $annot) {
22401                  // transform original coordinates
22402                  $coordlt = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
22403                  $ax = ($coordlt[4] / $this->k);
22404                  $ay = ($this->h - $h - ($coordlt[5] / $this->k));
22405                  $coordrb = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
22406                  $aw = ($coordrb[4] / $this->k) - $ax;
22407                  $ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
22408                  $this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
22409              }
22410          }
22411          // set pointer to align the next text/objects
22412          switch($align) {
22413              case 'T': {
22414                  $this->y = $y;
22415                  $this->x = $rb_x;
22416                  break;
22417              }
22418              case 'M': {
22419                  $this->y = $y + round($h/2);
22420                  $this->x = $rb_x;
22421                  break;
22422              }
22423              case 'B': {
22424                  $this->y = $rb_y;
22425                  $this->x = $rb_x;
22426                  break;
22427              }
22428              case 'N': {
22429                  $this->SetY($rb_y);
22430                  break;
22431              }
22432              default:{
22433                  break;
22434              }
22435          }
22436      }
22437  
22438      /**
22439       * Set the percentage of character stretching.
22440       * @param $perc (int) percentage of stretching (100 = no stretching)
22441       * @author Nicola Asuni
22442       * @public
22443       * @since 5.9.000 (2010-09-29)
22444       */
22445  	public function setFontStretching($perc=100) {
22446          $this->font_stretching = $perc;
22447      }
22448  
22449      /**
22450       * Get the percentage of character stretching.
22451       * @return float stretching value
22452       * @author Nicola Asuni
22453       * @public
22454       * @since 5.9.000 (2010-09-29)
22455       */
22456  	public function getFontStretching() {
22457          return $this->font_stretching;
22458      }
22459  
22460      /**
22461       * Set the amount to increase or decrease the space between characters in a text.
22462       * @param $spacing (float) amount to increase or decrease the space between characters in a text (0 = default spacing)
22463       * @author Nicola Asuni
22464       * @public
22465       * @since 5.9.000 (2010-09-29)
22466       */
22467  	public function setFontSpacing($spacing=0) {
22468          $this->font_spacing = $spacing;
22469      }
22470  
22471      /**
22472       * Get the amount to increase or decrease the space between characters in a text.
22473       * @return int font spacing (tracking) value
22474       * @author Nicola Asuni
22475       * @public
22476       * @since 5.9.000 (2010-09-29)
22477       */
22478  	public function getFontSpacing() {
22479          return $this->font_spacing;
22480      }
22481  
22482      /**
22483       * Return an array of no-write page regions
22484       * @return array of no-write page regions
22485       * @author Nicola Asuni
22486       * @public
22487       * @since 5.9.003 (2010-10-13)
22488       * @see setPageRegions(), addPageRegion()
22489       */
22490  	public function getPageRegions() {
22491          return $this->page_regions;
22492      }
22493  
22494      /**
22495       * Set no-write regions on page.
22496       * 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.
22497       * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22498       * You can set multiple regions for the same page.
22499       * @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.
22500       * @author Nicola Asuni
22501       * @public
22502       * @since 5.9.003 (2010-10-13)
22503       * @see addPageRegion(), getPageRegions()
22504       */
22505  	public function setPageRegions($regions=array()) {
22506          // empty current regions array
22507          $this->page_regions = array();
22508          // add regions
22509          foreach ($regions as $data) {
22510              $this->addPageRegion($data);
22511          }
22512      }
22513  
22514      /**
22515       * Add a single no-write region on selected page.
22516       * 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.
22517       * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22518       * You can set multiple regions for the same page.
22519       * @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).
22520       * @author Nicola Asuni
22521       * @public
22522       * @since 5.9.003 (2010-10-13)
22523       * @see setPageRegions(), getPageRegions()
22524       */
22525  	public function addPageRegion($region) {
22526          if (!isset($region['page']) OR empty($region['page'])) {
22527              $region['page'] = $this->page;
22528          }
22529          if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
22530              AND isset($region['yt'])  AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
22531              AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
22532              $this->page_regions[] = $region;
22533          }
22534      }
22535  
22536      /**
22537       * Remove a single no-write region.
22538       * @param $key (int) region key
22539       * @author Nicola Asuni
22540       * @public
22541       * @since 5.9.003 (2010-10-13)
22542       * @see setPageRegions(), getPageRegions()
22543       */
22544  	public function removePageRegion($key) {
22545          if (isset($this->page_regions[$key])) {
22546              unset($this->page_regions[$key]);
22547          }
22548      }
22549  
22550      /**
22551       * Check page for no-write regions and adapt current coordinates and page margins if necessary.
22552       * 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.
22553       * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22554       * @param $h (float) height of the text/image/object to print in user units
22555       * @param $x (float) current X coordinate in user units
22556       * @param $y (float) current Y coordinate in user units
22557       * @return array($x, $y)
22558       * @author Nicola Asuni
22559       * @protected
22560       * @since 5.9.003 (2010-10-13)
22561       */
22562  	protected function checkPageRegions($h, $x, $y) {
22563          // set default values
22564          if ($x === '') {
22565              $x = $this->x;
22566          }
22567          if ($y === '') {
22568              $y = $this->y;
22569          }
22570          if (!$this->check_page_regions OR empty($this->page_regions)) {
22571              // no page regions defined
22572              return array($x, $y);
22573          }
22574          if (empty($h)) {
22575              $h = $this->getCellHeight($this->FontSize);
22576          }
22577          // check for page break
22578          if ($this->checkPageBreak($h, $y)) {
22579              // the content will be printed on a new page
22580              $x = $this->x;
22581              $y = $this->y;
22582          }
22583          if ($this->num_columns > 1) {
22584              if ($this->rtl) {
22585                  $this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22586              } else {
22587                  $this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22588              }
22589          } else {
22590              if ($this->rtl) {
22591                  $this->lMargin = max($this->clMargin, $this->original_lMargin);
22592              } else {
22593                  $this->rMargin = max($this->crMargin, $this->original_rMargin);
22594              }
22595          }
22596          // adjust coordinates and page margins
22597          foreach ($this->page_regions as $regid => $regdata) {
22598              if ($regdata['page'] == $this->page) {
22599                  // check region boundaries
22600                  if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
22601                      // Y is inside the region
22602                      $minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
22603                      $yt = max($y, $regdata['yt']);
22604                      $yb = min(($yt + $h), $regdata['yb']);
22605                      $xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
22606                      $xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
22607                      if ($regdata['side'] == 'L') { // left side
22608                          $new_margin = max($xt, $xb);
22609                          if ($this->lMargin < $new_margin) {
22610                              if ($this->rtl) {
22611                                  // adjust left page margin
22612                                  $this->lMargin = max(0, $new_margin);
22613                              }
22614                              if ($x < $new_margin) {
22615                                  // adjust x position
22616                                  $x = $new_margin;
22617                                  if ($new_margin > ($this->w - $this->rMargin)) {
22618                                      // adjust y position
22619                                      $y = $regdata['yb'] - $h;
22620                                  }
22621                              }
22622                          }
22623                      } elseif ($regdata['side'] == 'R') { // right side
22624                          $new_margin = min($xt, $xb);
22625                          if (($this->w - $this->rMargin) > $new_margin) {
22626                              if (!$this->rtl) {
22627                                  // adjust right page margin
22628                                  $this->rMargin = max(0, ($this->w - $new_margin));
22629                              }
22630                              if ($x > $new_margin) {
22631                                  // adjust x position
22632                                  $x = $new_margin;
22633                                  if ($new_margin > $this->lMargin) {
22634                                      // adjust y position
22635                                      $y = $regdata['yb'] - $h;
22636                                  }
22637                              }
22638                          }
22639                      }
22640                  }
22641              }
22642          }
22643          return array($x, $y);
22644      }
22645  
22646      // --- SVG METHODS ---------------------------------------------------------
22647  
22648      /**
22649       * Embedd a Scalable Vector Graphics (SVG) image.
22650       * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
22651       * @param $file (string) Name of the SVG file or a '@' character followed by the SVG data string.
22652       * @param $x (float) Abscissa of the upper-left corner.
22653       * @param $y (float) Ordinate of the upper-left corner.
22654       * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
22655       * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
22656       * @param $link (mixed) URL or identifier returned by AddLink().
22657       * @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.
22658       * @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>
22659       * @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)))
22660       * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
22661       * @author Nicola Asuni
22662       * @since 5.0.000 (2010-05-02)
22663       * @public
22664       */
22665  	public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
22666          if ($this->state != 2) {
22667               return;
22668          }
22669          // reset SVG vars
22670          $this->svggradients = array();
22671          $this->svggradientid = 0;
22672          $this->svgdefsmode = false;
22673          $this->svgdefs = array();
22674          $this->svgclipmode = false;
22675          $this->svgclippaths = array();
22676          $this->svgcliptm = array();
22677          $this->svgclipid = 0;
22678          $this->svgtext = '';
22679          $this->svgtextmode = array();
22680          if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
22681              // convert SVG to raster image using GD or ImageMagick libraries
22682              return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22683          }
22684          if ($file[0] === '@') { // image from string
22685              $this->svgdir = '';
22686              $svgdata = substr($file, 1);
22687          } else { // SVG file
22688              $this->svgdir = dirname($file);
22689              $svgdata = TCPDF_STATIC::fileGetContents($file);
22690          }
22691          if ($svgdata === FALSE) {
22692              $this->Error('SVG file not found: '.$file);
22693          }
22694          if ($x === '') {
22695              $x = $this->x;
22696          }
22697          if ($y === '') {
22698              $y = $this->y;
22699          }
22700          // check page for no-write regions and adapt page margins if necessary
22701          list($x, $y) = $this->checkPageRegions($h, $x, $y);
22702          $k = $this->k;
22703          $ox = 0;
22704          $oy = 0;
22705          $ow = $w;
22706          $oh = $h;
22707          $aspect_ratio_align = 'xMidYMid';
22708          $aspect_ratio_ms = 'meet';
22709          $regs = array();
22710          // get original image width and height
22711          preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
22712          if (isset($regs[1]) AND !empty($regs[1])) {
22713              $tmp = array();
22714              if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22715                  $ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22716              }
22717              $tmp = array();
22718              if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22719                  $oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22720              }
22721              $tmp = array();
22722              if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22723                  $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22724              }
22725              $tmp = array();
22726              if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22727                  $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22728              }
22729              $tmp = array();
22730              $view_box = array();
22731              if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
22732                  if (count($tmp) == 5) {
22733                      array_shift($tmp);
22734                      foreach ($tmp as $key => $val) {
22735                          $view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
22736                      }
22737                      $ox = $view_box[0];
22738                      $oy = $view_box[1];
22739                  }
22740                  // get aspect ratio
22741                  $tmp = array();
22742                  if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22743                      $aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
22744                      switch (count($aspect_ratio)) {
22745                          case 3: {
22746                              $aspect_ratio_align = $aspect_ratio[1];
22747                              $aspect_ratio_ms = $aspect_ratio[2];
22748                              break;
22749                          }
22750                          case 2: {
22751                              $aspect_ratio_align = $aspect_ratio[0];
22752                              $aspect_ratio_ms = $aspect_ratio[1];
22753                              break;
22754                          }
22755                          case 1: {
22756                              $aspect_ratio_align = $aspect_ratio[0];
22757                              $aspect_ratio_ms = 'meet';
22758                              break;
22759                          }
22760                      }
22761                  }
22762              }
22763          }
22764          if ($ow <= 0) {
22765              $ow = 1;
22766          }
22767          if ($oh <= 0) {
22768              $oh = 1;
22769          }
22770          // calculate image width and height on document
22771          if (($w <= 0) AND ($h <= 0)) {
22772              // convert image size to document unit
22773              $w = $ow;
22774              $h = $oh;
22775          } elseif ($w <= 0) {
22776              $w = $h * $ow / $oh;
22777          } elseif ($h <= 0) {
22778              $h = $w * $oh / $ow;
22779          }
22780          // fit the image on available space
22781          list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22782          if ($this->rasterize_vector_images) {
22783              // convert SVG to raster image using GD or ImageMagick libraries
22784              return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22785          }
22786          // set alignment
22787          $this->img_rb_y = $y + $h;
22788          // set alignment
22789          if ($this->rtl) {
22790              if ($palign == 'L') {
22791                  $ximg = $this->lMargin;
22792              } elseif ($palign == 'C') {
22793                  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22794              } elseif ($palign == 'R') {
22795                  $ximg = $this->w - $this->rMargin - $w;
22796              } else {
22797                  $ximg = $x - $w;
22798              }
22799              $this->img_rb_x = $ximg;
22800          } else {
22801              if ($palign == 'L') {
22802                  $ximg = $this->lMargin;
22803              } elseif ($palign == 'C') {
22804                  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22805              } elseif ($palign == 'R') {
22806                  $ximg = $this->w - $this->rMargin - $w;
22807              } else {
22808                  $ximg = $x;
22809              }
22810              $this->img_rb_x = $ximg + $w;
22811          }
22812          // store current graphic vars
22813          $gvars = $this->getGraphicVars();
22814          // store SVG position and scale factors
22815          $svgoffset_x = ($ximg - $ox) * $this->k;
22816          $svgoffset_y = -($y - $oy) * $this->k;
22817          if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
22818              $ow = $view_box[2];
22819              $oh = $view_box[3];
22820          } else {
22821              if ($ow <= 0) {
22822                  $ow = $w;
22823              }
22824              if ($oh <= 0) {
22825                  $oh = $h;
22826              }
22827          }
22828          $svgscale_x = $w / $ow;
22829          $svgscale_y = $h / $oh;
22830          // scaling and alignment
22831          if ($aspect_ratio_align != 'none') {
22832              // store current scaling values
22833              $svgscale_old_x = $svgscale_x;
22834              $svgscale_old_y = $svgscale_y;
22835              // force uniform scaling
22836              if ($aspect_ratio_ms == 'slice') {
22837                  // the entire viewport is covered by the viewBox
22838                  if ($svgscale_x > $svgscale_y) {
22839                      $svgscale_y = $svgscale_x;
22840                  } elseif ($svgscale_x < $svgscale_y) {
22841                      $svgscale_x = $svgscale_y;
22842                  }
22843              } else { // meet
22844                  // the entire viewBox is visible within the viewport
22845                  if ($svgscale_x < $svgscale_y) {
22846                      $svgscale_y = $svgscale_x;
22847                  } elseif ($svgscale_x > $svgscale_y) {
22848                      $svgscale_x = $svgscale_y;
22849                  }
22850              }
22851              // correct X alignment
22852              switch (substr($aspect_ratio_align, 1, 3)) {
22853                  case 'Min': {
22854                      // do nothing
22855                      break;
22856                  }
22857                  case 'Max': {
22858                      $svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
22859                      break;
22860                  }
22861                  default:
22862                  case 'Mid': {
22863                      $svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
22864                      break;
22865                  }
22866              }
22867              // correct Y alignment
22868              switch (substr($aspect_ratio_align, 5)) {
22869                  case 'Min': {
22870                      // do nothing
22871                      break;
22872                  }
22873                  case 'Max': {
22874                      $svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
22875                      break;
22876                  }
22877                  default:
22878                  case 'Mid': {
22879                      $svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
22880                      break;
22881                  }
22882              }
22883          }
22884          // store current page break mode
22885          $page_break_mode = $this->AutoPageBreak;
22886          $page_break_margin = $this->getBreakMargin();
22887          $cell_padding = $this->cell_padding;
22888          $this->SetCellPadding(0);
22889          $this->SetAutoPageBreak(false);
22890          // save the current graphic state
22891          $this->_out('q'.$this->epsmarker);
22892          // set initial clipping mask
22893          $this->Rect($ximg, $y, $w, $h, 'CNZ', array(), array());
22894          // scale and translate
22895          $e = $ox * $this->k * (1 - $svgscale_x);
22896          $f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
22897          $this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
22898          // creates a new XML parser to be used by the other XML functions
22899          $this->parser = xml_parser_create('UTF-8');
22900          // the following function allows to use parser inside object
22901          xml_set_object($this->parser, $this);
22902          // disable case-folding for this XML parser
22903          xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
22904          // sets the element handler functions for the XML parser
22905          xml_set_element_handler($this->parser, 'startSVGElementHandler', 'endSVGElementHandler');
22906          // sets the character data handler function for the XML parser
22907          xml_set_character_data_handler($this->parser, 'segSVGContentHandler');
22908          // start parsing an XML document
22909          if (!xml_parse($this->parser, $svgdata)) {
22910              $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));
22911              $this->Error($error_message);
22912          }
22913          // free this XML parser
22914          xml_parser_free($this->parser);
22915          // restore previous graphic state
22916          $this->_out($this->epsmarker.'Q');
22917          // restore graphic vars
22918          $this->setGraphicVars($gvars);
22919          $this->lasth = $gvars['lasth'];
22920          if (!empty($border)) {
22921              $bx = $this->x;
22922              $by = $this->y;
22923              $this->x = $ximg;
22924              if ($this->rtl) {
22925                  $this->x += $w;
22926              }
22927              $this->y = $y;
22928              $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
22929              $this->x = $bx;
22930              $this->y = $by;
22931          }
22932          if ($link) {
22933              $this->Link($ximg, $y, $w, $h, $link, 0);
22934          }
22935          // set pointer to align the next text/objects
22936          switch($align) {
22937              case 'T':{
22938                  $this->y = $y;
22939                  $this->x = $this->img_rb_x;
22940                  break;
22941              }
22942              case 'M':{
22943                  $this->y = $y + round($h/2);
22944                  $this->x = $this->img_rb_x;
22945                  break;
22946              }
22947              case 'B':{
22948                  $this->y = $this->img_rb_y;
22949                  $this->x = $this->img_rb_x;
22950                  break;
22951              }
22952              case 'N':{
22953                  $this->SetY($this->img_rb_y);
22954                  break;
22955              }
22956              default:{
22957                  // restore pointer to starting position
22958                  $this->x = $gvars['x'];
22959                  $this->y = $gvars['y'];
22960                  $this->page = $gvars['page'];
22961                  $this->current_column = $gvars['current_column'];
22962                  $this->tMargin = $gvars['tMargin'];
22963                  $this->bMargin = $gvars['bMargin'];
22964                  $this->w = $gvars['w'];
22965                  $this->h = $gvars['h'];
22966                  $this->wPt = $gvars['wPt'];
22967                  $this->hPt = $gvars['hPt'];
22968                  $this->fwPt = $gvars['fwPt'];
22969                  $this->fhPt = $gvars['fhPt'];
22970                  break;
22971              }
22972          }
22973          $this->endlinex = $this->img_rb_x;
22974          // restore page break
22975          $this->SetAutoPageBreak($page_break_mode, $page_break_margin);
22976          $this->cell_padding = $cell_padding;
22977      }
22978  
22979      /**
22980       * Convert SVG transformation matrix to PDF.
22981       * @param $tm (array) original SVG transformation matrix
22982       * @return array transformation matrix
22983       * @protected
22984       * @since 5.0.000 (2010-05-02)
22985       */
22986  	protected function convertSVGtMatrix($tm) {
22987          $a = $tm[0];
22988          $b = -$tm[1];
22989          $c = -$tm[2];
22990          $d = $tm[3];
22991          $e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
22992          $f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
22993          $x = 0;
22994          $y = $this->h * $this->k;
22995          $e = ($x * (1 - $a)) - ($y * $c) + $e;
22996          $f = ($y * (1 - $d)) - ($x * $b) + $f;
22997          return array($a, $b, $c, $d, $e, $f);
22998      }
22999  
23000      /**
23001       * Apply SVG graphic transformation matrix.
23002       * @param $tm (array) original SVG transformation matrix
23003       * @protected
23004       * @since 5.0.000 (2010-05-02)
23005       */
23006  	protected function SVGTransform($tm) {
23007          $this->Transform($this->convertSVGtMatrix($tm));
23008      }
23009  
23010      /**
23011       * Apply the requested SVG styles (*** TO BE COMPLETED ***)
23012       * @param $svgstyle (array) array of SVG styles to apply
23013       * @param $prevsvgstyle (array) array of previous SVG style
23014       * @param $x (int) X origin of the bounding box
23015       * @param $y (int) Y origin of the bounding box
23016       * @param $w (int) width of the bounding box
23017       * @param $h (int) height of the bounding box
23018       * @param $clip_function (string) clip function
23019       * @param $clip_params (array) array of parameters for clipping function
23020       * @return object style
23021       * @author Nicola Asuni
23022       * @since 5.0.000 (2010-05-02)
23023       * @protected
23024       */
23025  	protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
23026          if ($this->state != 2) {
23027               return;
23028          }
23029          $objstyle = '';
23030          $minlen = (0.01 / $this->k); // minimum acceptable length
23031          if (!isset($svgstyle['opacity'])) {
23032              return $objstyle;
23033          }
23034          // clip-path
23035          $regs = array();
23036          if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
23037              $clip_path = $this->svgclippaths[$regs[1]];
23038              foreach ($clip_path as $cp) {
23039                  $this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
23040              }
23041          }
23042          // opacity
23043          if ($svgstyle['opacity'] != 1) {
23044              $this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
23045          }
23046          // color
23047          $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
23048          $this->SetFillColorArray($fill_color);
23049          // text color
23050          $text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
23051          $this->SetTextColorArray($text_color);
23052          // clip
23053          if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
23054              $top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
23055              $right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
23056              $bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
23057              $left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
23058              $cx = $x + $left;
23059              $cy = $y + $top;
23060              $cw = $w - $left - $right;
23061              $ch = $h - $top - $bottom;
23062              if ($svgstyle['clip-rule'] == 'evenodd') {
23063                  $clip_rule = 'CNZ';
23064              } else {
23065                  $clip_rule = 'CEO';
23066              }
23067              $this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
23068          }
23069          // fill
23070          $regs = array();
23071          if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
23072              // gradient
23073              $gradient = $this->svggradients[$regs[1]];
23074              if (isset($gradient['xref'])) {
23075                  // reference to another gradient definition
23076                  $newgradient = $this->svggradients[$gradient['xref']];
23077                  $newgradient['coords'] = $gradient['coords'];
23078                  $newgradient['mode'] = $gradient['mode'];
23079                  $newgradient['type'] = $gradient['type'];
23080                  $newgradient['gradientUnits'] = $gradient['gradientUnits'];
23081                  if (isset($gradient['gradientTransform'])) {
23082                      $newgradient['gradientTransform'] = $gradient['gradientTransform'];
23083                  }
23084                  $gradient = $newgradient;
23085              }
23086              //save current Graphic State
23087              $this->_outSaveGraphicsState();
23088              //set clipping area
23089              if (!empty($clip_function) AND method_exists($this, $clip_function)) {
23090                  $bbox = call_user_func_array(array($this, $clip_function), $clip_params);
23091                  if ((!isset($gradient['type']) OR ($gradient['type'] != 3)) AND is_array($bbox) AND (count($bbox) == 4)) {
23092                      list($x, $y, $w, $h) = $bbox;
23093                  }
23094              }
23095              if ($gradient['mode'] == 'measure') {
23096                  if (!isset($gradient['coords'][4])) {
23097                      $gradient['coords'][4] = 0.5;
23098                  }
23099                  if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
23100                      $gtm = $gradient['gradientTransform'];
23101                      // apply transformation matrix
23102                      $xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
23103                      $ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
23104                      $xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
23105                      $yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
23106                      $r = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
23107                      $gradient['coords'][0] = $xa;
23108                      $gradient['coords'][1] = $ya;
23109                      $gradient['coords'][2] = $xb;
23110                      $gradient['coords'][3] = $yb;
23111                      $gradient['coords'][4] = $r;
23112                  }
23113                  // convert SVG coordinates to user units
23114                  $gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
23115                  $gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
23116                  $gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
23117                  $gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
23118                  $gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
23119                  if ($w <= $minlen) {
23120                      $w = $minlen;
23121                  }
23122                  if ($h <= $minlen) {
23123                      $h = $minlen;
23124                  }
23125                  // shift units
23126                  if ($gradient['gradientUnits'] == 'objectBoundingBox') {
23127                      // convert to SVG coordinate system
23128                      $gradient['coords'][0] += $x;
23129                      $gradient['coords'][1] += $y;
23130                      $gradient['coords'][2] += $x;
23131                      $gradient['coords'][3] += $y;
23132                  }
23133                  // calculate percentages
23134                  $gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
23135                  $gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
23136                  $gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
23137                  $gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
23138                  $gradient['coords'][4] /= $w;
23139              } elseif ($gradient['mode'] == 'percentage') {
23140                  foreach($gradient['coords'] as $key => $val) {
23141                      $gradient['coords'][$key] = (intval($val) / 100);
23142                      if ($val < 0) {
23143                          $gradient['coords'][$key] = 0;
23144                      } elseif ($val > 1) {
23145                          $gradient['coords'][$key] = 1;
23146                      }
23147                  }
23148              }
23149              if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
23150                  // single color (no shading)
23151                  $gradient['coords'][0] = 1;
23152                  $gradient['coords'][1] = 0;
23153                  $gradient['coords'][2] = 0.999;
23154                  $gradient['coords'][3] = 0;
23155              }
23156              // swap Y coordinates
23157              $tmp = $gradient['coords'][1];
23158              $gradient['coords'][1] = $gradient['coords'][3];
23159              $gradient['coords'][3] = $tmp;
23160              // set transformation map for gradient
23161              $cy = ($this->h - $y);
23162              if ($gradient['type'] == 3) {
23163                  // circular gradient
23164                  $cy -= ($gradient['coords'][1] * ($w + $h));
23165                  $h = $w = max($w, $h);
23166              } else {
23167                  $cy -= $h;
23168              }
23169              $this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), ($cy * $this->k)));
23170              if (count($gradient['stops']) > 1) {
23171                  $this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
23172              }
23173          } elseif ($svgstyle['fill'] != 'none') {
23174              $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
23175              if ($svgstyle['fill-opacity'] != 1) {
23176                  $this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
23177              }
23178              $this->SetFillColorArray($fill_color);
23179              if ($svgstyle['fill-rule'] == 'evenodd') {
23180                  $objstyle .= 'F*';
23181              } else {
23182                  $objstyle .= 'F';
23183              }
23184          }
23185          // stroke
23186          if ($svgstyle['stroke'] != 'none') {
23187              if ($svgstyle['stroke-opacity'] != 1) {
23188                  $this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
23189              } elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['stroke'], $rgba_matches)) {
23190                  $this->setAlpha($rgba_matches[1], 'Normal', $this->alpha['ca'], false);
23191              }
23192              $stroke_style = array(
23193                  'color' => TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors),
23194                  'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
23195                  'cap' => $svgstyle['stroke-linecap'],
23196                  'join' => $svgstyle['stroke-linejoin']
23197                  );
23198              if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
23199                  $stroke_style['dash'] = $svgstyle['stroke-dasharray'];
23200              }
23201              $this->SetLineStyle($stroke_style);
23202              $objstyle .= 'D';
23203          }
23204          // font
23205          $regs = array();
23206          if (!empty($svgstyle['font'])) {
23207              if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
23208                  $font_family = $this->getFontFamilyName($regs[1]);
23209              } else {
23210                  $font_family = $svgstyle['font-family'];
23211              }
23212              if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23213                  $font_size = trim($regs[1]);
23214              } else {
23215                  $font_size = $svgstyle['font-size'];
23216              }
23217              if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23218                  $font_style = trim($regs[1]);
23219              } else {
23220                  $font_style = $svgstyle['font-style'];
23221              }
23222              if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23223                  $font_weight = trim($regs[1]);
23224              } else {
23225                  $font_weight = $svgstyle['font-weight'];
23226              }
23227              if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23228                  $font_stretch = trim($regs[1]);
23229              } else {
23230                  $font_stretch = $svgstyle['font-stretch'];
23231              }
23232              if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23233                  $font_spacing = trim($regs[1]);
23234              } else {
23235                  $font_spacing = $svgstyle['letter-spacing'];
23236              }
23237          } else {
23238              $font_family = $this->getFontFamilyName($svgstyle['font-family']);
23239              $font_size = $svgstyle['font-size'];
23240              $font_style = $svgstyle['font-style'];
23241              $font_weight = $svgstyle['font-weight'];
23242              $font_stretch = $svgstyle['font-stretch'];
23243              $font_spacing = $svgstyle['letter-spacing'];
23244          }
23245          $font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit);
23246          $font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
23247          $font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
23248          switch ($font_style) {
23249              case 'italic': {
23250                  $font_style = 'I';
23251                  break;
23252              }
23253              case 'oblique': {
23254                  $font_style = 'I';
23255                  break;
23256              }
23257              default:
23258              case 'normal': {
23259                  $font_style = '';
23260                  break;
23261              }
23262          }
23263          switch ($font_weight) {
23264              case 'bold':
23265              case 'bolder': {
23266                  $font_style .= 'B';
23267                  break;
23268              }
23269              case 'normal': {
23270                  if ((substr($font_family, -1) == 'I') AND (substr($font_family, -2, 1) == 'B')) {
23271                      $font_family = substr($font_family, 0, -2).'I';
23272                  } elseif (substr($font_family, -1) == 'B') {
23273                      $font_family = substr($font_family, 0, -1);
23274                  }
23275                  break;
23276              }
23277          }
23278          switch ($svgstyle['text-decoration']) {
23279              case 'underline': {
23280                  $font_style .= 'U';
23281                  break;
23282              }
23283              case 'overline': {
23284                  $font_style .= 'O';
23285                  break;
23286              }
23287              case 'line-through': {
23288                  $font_style .= 'D';
23289                  break;
23290              }
23291              default:
23292              case 'none': {
23293                  break;
23294              }
23295          }
23296          $this->SetFont($font_family, $font_style, $font_size);
23297          $this->setFontStretching($font_stretch);
23298          $this->setFontSpacing($font_spacing);
23299          return $objstyle;
23300      }
23301  
23302      /**
23303       * Draws an SVG path
23304       * @param $d (string) attribute d of the path SVG element
23305       * @param $style (string) Style of rendering. Possible values are:
23306       * <ul>
23307       *     <li>D or empty string: Draw (default).</li>
23308       *     <li>F: Fill.</li>
23309       *     <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23310       *     <li>DF or FD: Draw and fill.</li>
23311       *     <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23312       *     <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
23313       *     <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
23314       * </ul>
23315       * @return array of container box measures (x, y, w, h)
23316       * @author Nicola Asuni
23317       * @since 5.0.000 (2010-05-02)
23318       * @protected
23319       */
23320  	protected function SVGPath($d, $style='') {
23321          if ($this->state != 2) {
23322               return;
23323          }
23324          // set fill/stroke style
23325          $op = TCPDF_STATIC::getPathPaintOperator($style, '');
23326          if (empty($op)) {
23327              return;
23328          }
23329          $paths = array();
23330          $d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
23331          preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
23332          $x = 0;
23333          $y = 0;
23334          $x1 = 0;
23335          $y1 = 0;
23336          $x2 = 0;
23337          $y2 = 0;
23338          $xmin = 2147483647;
23339          $xmax = 0;
23340          $ymin = 2147483647;
23341          $ymax = 0;
23342          $relcoord = false;
23343          $minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
23344          $firstcmd = true; // used to print first point
23345          // draw curve pieces
23346          foreach ($paths as $key => $val) {
23347              // get curve type
23348              $cmd = trim($val[1]);
23349              if (strtolower($cmd) == $cmd) {
23350                  // use relative coordinated instead of absolute
23351                  $relcoord = true;
23352                  $xoffset = $x;
23353                  $yoffset = $y;
23354              } else {
23355                  $relcoord = false;
23356                  $xoffset = 0;
23357                  $yoffset = 0;
23358              }
23359              $params = array();
23360              if (isset($val[2])) {
23361                  // get curve parameters
23362                  $rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
23363                  $params = array();
23364                  foreach ($rawparams as $ck => $cp) {
23365                      $params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
23366                      if (abs($params[$ck]) < $minlen) {
23367                          // approximate little values to zero
23368                          $params[$ck] = 0;
23369                      }
23370                  }
23371              }
23372              // store current origin point
23373              $x0 = $x;
23374              $y0 = $y;
23375              switch (strtoupper($cmd)) {
23376                  case 'M': { // moveto
23377                      foreach ($params as $ck => $cp) {
23378                          if (($ck % 2) == 0) {
23379                              $x = $cp + $xoffset;
23380                          } else {
23381                              $y = $cp + $yoffset;
23382                              if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23383                                  if ($ck == 1) {
23384                                      $this->_outPoint($x, $y);
23385                                      $firstcmd = false;
23386                                  } else {
23387                                      $this->_outLine($x, $y);
23388                                  }
23389                                  $x0 = $x;
23390                                  $y0 = $y;
23391                              }
23392                              $xmin = min($xmin, $x);
23393                              $ymin = min($ymin, $y);
23394                              $xmax = max($xmax, $x);
23395                              $ymax = max($ymax, $y);
23396                              if ($relcoord) {
23397                                  $xoffset = $x;
23398                                  $yoffset = $y;
23399                              }
23400                          }
23401                      }
23402                      break;
23403                  }
23404                  case 'L': { // lineto
23405                      foreach ($params as $ck => $cp) {
23406                          if (($ck % 2) == 0) {
23407                              $x = $cp + $xoffset;
23408                          } else {
23409                              $y = $cp + $yoffset;
23410                              if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23411                                  $this->_outLine($x, $y);
23412                                  $x0 = $x;
23413                                  $y0 = $y;
23414                              }
23415                              $xmin = min($xmin, $x);
23416                              $ymin = min($ymin, $y);
23417                              $xmax = max($xmax, $x);
23418                              $ymax = max($ymax, $y);
23419                              if ($relcoord) {
23420                                  $xoffset = $x;
23421                                  $yoffset = $y;
23422                              }
23423                          }
23424                      }
23425                      break;
23426                  }
23427                  case 'H': { // horizontal lineto
23428                      foreach ($params as $ck => $cp) {
23429                          $x = $cp + $xoffset;
23430                          if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23431                              $this->_outLine($x, $y);
23432                              $x0 = $x;
23433                              $y0 = $y;
23434                          }
23435                          $xmin = min($xmin, $x);
23436                          $xmax = max($xmax, $x);
23437                          if ($relcoord) {
23438                              $xoffset = $x;
23439                          }
23440                      }
23441                      break;
23442                  }
23443                  case 'V': { // vertical lineto
23444                      foreach ($params as $ck => $cp) {
23445                          $y = $cp + $yoffset;
23446                          if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23447                              $this->_outLine($x, $y);
23448                              $x0 = $x;
23449                              $y0 = $y;
23450                          }
23451                          $ymin = min($ymin, $y);
23452                          $ymax = max($ymax, $y);
23453                          if ($relcoord) {
23454                              $yoffset = $y;
23455                          }
23456                      }
23457                      break;
23458                  }
23459                  case 'C': { // curveto
23460                      foreach ($params as $ck => $cp) {
23461                          $params[$ck] = $cp;
23462                          if ((($ck + 1) % 6) == 0) {
23463                              $x1 = $params[($ck - 5)] + $xoffset;
23464                              $y1 = $params[($ck - 4)] + $yoffset;
23465                              $x2 = $params[($ck - 3)] + $xoffset;
23466                              $y2 = $params[($ck - 2)] + $yoffset;
23467                              $x = $params[($ck - 1)] + $xoffset;
23468                              $y = $params[($ck)] + $yoffset;
23469                              $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23470                              $xmin = min($xmin, $x, $x1, $x2);
23471                              $ymin = min($ymin, $y, $y1, $y2);
23472                              $xmax = max($xmax, $x, $x1, $x2);
23473                              $ymax = max($ymax, $y, $y1, $y2);
23474                              if ($relcoord) {
23475                                  $xoffset = $x;
23476                                  $yoffset = $y;
23477                              }
23478                          }
23479                      }
23480                      break;
23481                  }
23482                  case 'S': { // shorthand/smooth curveto
23483                      foreach ($params as $ck => $cp) {
23484                          $params[$ck] = $cp;
23485                          if ((($ck + 1) % 4) == 0) {
23486                              if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
23487                                  $x1 = (2 * $x) - $x2;
23488                                  $y1 = (2 * $y) - $y2;
23489                              } else {
23490                                  $x1 = $x;
23491                                  $y1 = $y;
23492                              }
23493                              $x2 = $params[($ck - 3)] + $xoffset;
23494                              $y2 = $params[($ck - 2)] + $yoffset;
23495                              $x = $params[($ck - 1)] + $xoffset;
23496                              $y = $params[($ck)] + $yoffset;
23497                              $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23498                              $xmin = min($xmin, $x, $x1, $x2);
23499                              $ymin = min($ymin, $y, $y1, $y2);
23500                              $xmax = max($xmax, $x, $x1, $x2);
23501                              $ymax = max($ymax, $y, $y1, $y2);
23502                              if ($relcoord) {
23503                                  $xoffset = $x;
23504                                  $yoffset = $y;
23505                              }
23506                          }
23507                      }
23508                      break;
23509                  }
23510                  case 'Q': { // quadratic Bezier curveto
23511                      foreach ($params as $ck => $cp) {
23512                          $params[$ck] = $cp;
23513                          if ((($ck + 1) % 4) == 0) {
23514                              // convert quadratic points to cubic points
23515                              $x1 = $params[($ck - 3)] + $xoffset;
23516                              $y1 = $params[($ck - 2)] + $yoffset;
23517                              $xa = ($x + (2 * $x1)) / 3;
23518                              $ya = ($y + (2 * $y1)) / 3;
23519                              $x = $params[($ck - 1)] + $xoffset;
23520                              $y = $params[($ck)] + $yoffset;
23521                              $xb = ($x + (2 * $x1)) / 3;
23522                              $yb = ($y + (2 * $y1)) / 3;
23523                              $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23524                              $xmin = min($xmin, $x, $xa, $xb);
23525                              $ymin = min($ymin, $y, $ya, $yb);
23526                              $xmax = max($xmax, $x, $xa, $xb);
23527                              $ymax = max($ymax, $y, $ya, $yb);
23528                              if ($relcoord) {
23529                                  $xoffset = $x;
23530                                  $yoffset = $y;
23531                              }
23532                          }
23533                      }
23534                      break;
23535                  }
23536                  case 'T': { // shorthand/smooth quadratic Bezier curveto
23537                      foreach ($params as $ck => $cp) {
23538                          $params[$ck] = $cp;
23539                          if (($ck % 2) != 0) {
23540                              if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
23541                                  $x1 = (2 * $x) - $x1;
23542                                  $y1 = (2 * $y) - $y1;
23543                              } else {
23544                                  $x1 = $x;
23545                                  $y1 = $y;
23546                              }
23547                              // convert quadratic points to cubic points
23548                              $xa = ($x + (2 * $x1)) / 3;
23549                              $ya = ($y + (2 * $y1)) / 3;
23550                              $x = $params[($ck - 1)] + $xoffset;
23551                              $y = $params[($ck)] + $yoffset;
23552                              $xb = ($x + (2 * $x1)) / 3;
23553                              $yb = ($y + (2 * $y1)) / 3;
23554                              $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23555                              $xmin = min($xmin, $x, $xa, $xb);
23556                              $ymin = min($ymin, $y, $ya, $yb);
23557                              $xmax = max($xmax, $x, $xa, $xb);
23558                              $ymax = max($ymax, $y, $ya, $yb);
23559                              if ($relcoord) {
23560                                  $xoffset = $x;
23561                                  $yoffset = $y;
23562                              }
23563                          }
23564                      }
23565                      break;
23566                  }
23567                  case 'A': { // elliptical arc
23568                      foreach ($params as $ck => $cp) {
23569                          $params[$ck] = $cp;
23570                          if ((($ck + 1) % 7) == 0) {
23571                              $x0 = $x;
23572                              $y0 = $y;
23573                              $rx = abs($params[($ck - 6)]);
23574                              $ry = abs($params[($ck - 5)]);
23575                              $ang = -$rawparams[($ck - 4)];
23576                              $angle = deg2rad($ang);
23577                              $fa = $rawparams[($ck - 3)]; // large-arc-flag
23578                              $fs = $rawparams[($ck - 2)]; // sweep-flag
23579                              $x = $params[($ck - 1)] + $xoffset;
23580                              $y = $params[$ck] + $yoffset;
23581                              if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
23582                                  // endpoints are almost identical
23583                                  $xmin = min($xmin, $x);
23584                                  $ymin = min($ymin, $y);
23585                                  $xmax = max($xmax, $x);
23586                                  $ymax = max($ymax, $y);
23587                              } else {
23588                                  $cos_ang = cos($angle);
23589                                  $sin_ang = sin($angle);
23590                                  $a = (($x0 - $x) / 2);
23591                                  $b = (($y0 - $y) / 2);
23592                                  $xa = ($a * $cos_ang) - ($b * $sin_ang);
23593                                  $ya = ($a * $sin_ang) + ($b * $cos_ang);
23594                                  $rx2 = $rx * $rx;
23595                                  $ry2 = $ry * $ry;
23596                                  $xa2 = $xa * $xa;
23597                                  $ya2 = $ya * $ya;
23598                                  $delta = ($xa2 / $rx2) + ($ya2 / $ry2);
23599                                  if ($delta > 1) {
23600                                      $rx *= sqrt($delta);
23601                                      $ry *= sqrt($delta);
23602                                      $rx2 = $rx * $rx;
23603                                      $ry2 = $ry * $ry;
23604                                  }
23605                                  $numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
23606                                  if ($numerator < 0) {
23607                                      $root = 0;
23608                                  } else {
23609                                      $root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
23610                                  }
23611                                  if ($fa == $fs){
23612                                      $root *= -1;
23613                                  }
23614                                  $cax = $root * (($rx * $ya) / $ry);
23615                                  $cay = -$root * (($ry * $xa) / $rx);
23616                                  // coordinates of ellipse center
23617                                  $cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
23618                                  $cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
23619                                  // get angles
23620                                  $angs = TCPDF_STATIC::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
23621                                  $dang = TCPDF_STATIC::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
23622                                  if (($fs == 0) AND ($dang > 0)) {
23623                                      $dang -= (2 * M_PI);
23624                                  } elseif (($fs == 1) AND ($dang < 0)) {
23625                                      $dang += (2 * M_PI);
23626                                  }
23627                                  $angf = $angs - $dang;
23628                                  if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
23629                                      // reverse angles
23630                                      $tmp = $angs;
23631                                      $angs = $angf;
23632                                      $angf = $tmp;
23633                                  }
23634                                  $angs = round(rad2deg($angs), 6);
23635                                  $angf = round(rad2deg($angf), 6);
23636                                  // covent angles to positive values
23637                                  if (($angs < 0) AND ($angf < 0)) {
23638                                      $angs += 360;
23639                                      $angf += 360;
23640                                  }
23641                                  $pie = false;
23642                                  if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
23643                                      $pie = true;
23644                                  }
23645                                  list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
23646                                  $xmin = min($xmin, $x, $axmin);
23647                                  $ymin = min($ymin, $y, $aymin);
23648                                  $xmax = max($xmax, $x, $axmax);
23649                                  $ymax = max($ymax, $y, $aymax);
23650                              }
23651                              if ($relcoord) {
23652                                  $xoffset = $x;
23653                                  $yoffset = $y;
23654                              }
23655                          }
23656                      }
23657                      break;
23658                  }
23659                  case 'Z': {
23660                      $this->_out('h');
23661                      break;
23662                  }
23663              }
23664              $firstcmd = false;
23665          } // end foreach
23666          if (!empty($op)) {
23667              $this->_out($op);
23668          }
23669          return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
23670      }
23671  
23672      /**
23673       * Return the tag name without the namespace
23674       * @param $name (string) Tag name
23675       * @protected
23676       */
23677  	protected function removeTagNamespace($name) {
23678          if(strpos($name, ':') !== false) {
23679              $parts = explode(':', $name);
23680              return $parts[(sizeof($parts) - 1)];
23681          }
23682          return $name;
23683      }
23684          
23685      /**
23686       * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
23687       * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
23688       * @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.
23689       * @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.
23690       * @param $ctm (array) tranformation matrix for clipping mode (starting transformation matrix).
23691       * @author Nicola Asuni
23692       * @since 5.0.000 (2010-05-02)
23693       * @protected
23694       */
23695  	protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
23696          $name = $this->removeTagNamespace($name);
23697          // check if we are in clip mode
23698          if ($this->svgclipmode) {
23699              $this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
23700              return;
23701          }
23702          if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
23703              if (isset($attribs['id'])) {
23704                  $attribs['child_elements'] = array();
23705                  $this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23706                  return;
23707              }
23708              if (end($this->svgdefs) !== FALSE) {
23709                  $last_svgdefs_id = key($this->svgdefs);
23710                  if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
23711                      $attribs['id'] = 'DF_'.(count($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements']) + 1);
23712                      $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23713                      return;
23714                  }
23715              }
23716              return;
23717          }
23718          $clipping = false;
23719          if ($parser == 'clip-path') {
23720              // set clipping mode
23721              $clipping = true;
23722          }
23723          // get styling properties
23724          $prev_svgstyle = $this->svgstyles[max(0,(count($this->svgstyles) - 1))]; // previous style
23725          $svgstyle = $this->svgstyles[0]; // set default style
23726          if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
23727              // default fill attribute for clipping
23728              $attribs['fill'] = 'none';
23729          }
23730          if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
23731              // fix style for regular expression
23732              $attribs['style'] = ';'.$attribs['style'];
23733          }
23734          foreach ($prev_svgstyle as $key => $val) {
23735              if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
23736                  // inherit previous value
23737                  $svgstyle[$key] = $val;
23738              }
23739              if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
23740                  // specific attribute settings
23741                  if ($attribs[$key] == 'inherit') {
23742                      $svgstyle[$key] = $val;
23743                  } else {
23744                      $svgstyle[$key] = $attribs[$key];
23745                  }
23746              } elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
23747                  // CSS style syntax
23748                  $attrval = array();
23749                  if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
23750                      if ($attrval[1] == 'inherit') {
23751                          $svgstyle[$key] = $val;
23752                      } else {
23753                          $svgstyle[$key] = $attrval[1];
23754                      }
23755                  }
23756              }
23757          }
23758          // transformation matrix
23759          if (!empty($ctm)) {
23760              $tm = $ctm;
23761          } else {
23762              $tm = array(1,0,0,1,0,0);
23763          }
23764          if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
23765              $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
23766          }
23767          $svgstyle['transfmatrix'] = $tm;
23768          $invisible = false;
23769          if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
23770              // the current graphics element is invisible (nothing is painted)
23771              $invisible = true;
23772          }
23773          // process tag
23774          switch($name) {
23775              case 'defs': {
23776                  $this->svgdefsmode = true;
23777                  break;
23778              }
23779              // clipPath
23780              case 'clipPath': {
23781                  if ($invisible) {
23782                      break;
23783                  }
23784                  $this->svgclipmode = true;
23785                  if (!isset($attribs['id'])) {
23786                      $attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
23787                  }
23788                  $this->svgclipid = $attribs['id'];
23789                  $this->svgclippaths[$this->svgclipid] = array();
23790                  $this->svgcliptm[$this->svgclipid] = $tm;
23791                  break;
23792              }
23793              case 'svg': {
23794                  // start of SVG object
23795                  if(++$this->svg_tag_depth <= 1) {
23796                      break;
23797                  }
23798                  // inner SVG
23799                  array_push($this->svgstyles, $svgstyle);
23800                  $this->StartTransform();
23801                  $svgX = (isset($attribs['x'])?$attribs['x']:0);
23802                  $svgY = (isset($attribs['y'])?$attribs['y']:0);
23803                  $svgW = (isset($attribs['width'])?$attribs['width']:0);
23804                  $svgH = (isset($attribs['height'])?$attribs['height']:0);
23805                  // set x, y position using transform matrix
23806                  $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array( 1, 0, 0, 1, $svgX, $svgY));
23807                  $this->SVGTransform($tm);
23808                  // set clipping for width and height
23809                  $x = 0;
23810                  $y = 0;
23811                  $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):$this->w);
23812                  $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):$this->h);
23813                  // draw clipping rect
23814                  $this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
23815                  // parse viewbox, calculate extra transformation matrix
23816                  if (isset($attribs['viewBox'])) {
23817                      $tmp = array();
23818                      preg_match_all("/[0-9]+/", $attribs['viewBox'], $tmp);
23819                      $tmp = $tmp[0];
23820                      if (sizeof($tmp) == 4) {
23821                          $vx = $tmp[0];
23822                          $vy = $tmp[1];
23823                          $vw = $tmp[2];
23824                          $vh = $tmp[3];
23825                          // get aspect ratio
23826                          $tmp = array();
23827                          $aspectX = 'xMid';
23828                          $aspectY = 'YMid';
23829                          $fit = 'meet';
23830                          if (isset($attribs['preserveAspectRatio'])) {
23831                              if($attribs['preserveAspectRatio'] == 'none') {
23832                                  $fit = 'none';
23833                              } else {
23834                                  preg_match_all('/[a-zA-Z]+/', $attribs['preserveAspectRatio'], $tmp);
23835                                  $tmp = $tmp[0];
23836                                  if ((sizeof($tmp) == 2) AND (strlen($tmp[0]) == 8) AND (in_array($tmp[1], array('meet', 'slice', 'none')))) {
23837                                      $aspectX = substr($tmp[0], 0, 4);
23838                                      $aspectY = substr($tmp[0], 4, 4);
23839                                      $fit = $tmp[1];
23840                                  }
23841                              }
23842                          }
23843                          $wr = ($svgW / $vw);
23844                          $hr = ($svgH / $vh);
23845                          $ax = $ay = 0;
23846                          if ((($fit == 'meet') AND ($hr < $wr)) OR (($fit == 'slice') AND ($hr > $wr))) {
23847                              if ($aspectX == 'xMax') {
23848                                  $ax = (($vw * ($wr / $hr)) - $vw);
23849                              }
23850                              if ($aspectX == 'xMid') {
23851                                  $ax = ((($vw * ($wr / $hr)) - $vw) / 2);
23852                              }
23853                              $wr = $hr;
23854                          } elseif ((($fit == 'meet') AND ($hr > $wr)) OR (($fit == 'slice') AND ($hr < $wr))) {
23855                              if ($aspectY == 'YMax') {
23856                                  $ay = (($vh * ($hr / $wr)) - $vh);
23857                              }
23858                              if ($aspectY == 'YMid') {
23859                                  $ay = ((($vh * ($hr / $wr)) - $vh) / 2);
23860                              }
23861                              $hr = $wr;
23862                          }
23863                          $newtm = array($wr, 0, 0, $hr, (($wr * ($ax - $vx)) - $svgX), (($hr * ($ay - $vy)) - $svgY));
23864                          $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, $newtm);
23865                          $this->SVGTransform($tm);
23866                      }
23867                  }
23868                  $this->setSVGStyles($svgstyle, $prev_svgstyle);
23869                  break;
23870              }
23871              case 'g': {
23872                  // group together related graphics elements
23873                  array_push($this->svgstyles, $svgstyle);
23874                  $this->StartTransform();
23875                  $x = (isset($attribs['x'])?$attribs['x']:0);
23876                  $y = (isset($attribs['y'])?$attribs['y']:0);
23877                  $w = 1;//(isset($attribs['width'])?$attribs['width']:1);
23878                  $h = 1;//(isset($attribs['height'])?$attribs['height']:1);
23879                  $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
23880                  $this->SVGTransform($tm);
23881                  $this->setSVGStyles($svgstyle, $prev_svgstyle);
23882                  break;
23883              }
23884              case 'linearGradient': {
23885                  if ($this->pdfa_mode) {
23886                      break;
23887                  }
23888                  if (!isset($attribs['id'])) {
23889                      $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
23890                  }
23891                  $this->svggradientid = $attribs['id'];
23892                  $this->svggradients[$this->svggradientid] = array();
23893                  $this->svggradients[$this->svggradientid]['type'] = 2;
23894                  $this->svggradients[$this->svggradientid]['stops'] = array();
23895                  if (isset($attribs['gradientUnits'])) {
23896                      $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
23897                  } else {
23898                      $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
23899                  }
23900                  //$attribs['spreadMethod']
23901                  if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
23902                      OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
23903                          OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
23904                          OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
23905                          OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
23906                      $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
23907                  } else {
23908                      $this->svggradients[$this->svggradientid]['mode'] = 'measure';
23909                  }
23910                  $x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
23911                  $y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
23912                  $x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
23913                  $y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
23914                  if (isset($attribs['gradientTransform'])) {
23915                      $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
23916                  }
23917                  $this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
23918                  if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
23919                      // gradient is defined on another place
23920                      $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
23921                  }
23922                  break;
23923              }
23924              case 'radialGradient': {
23925                  if ($this->pdfa_mode) {
23926                      break;
23927                  }
23928                  if (!isset($attribs['id'])) {
23929                      $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
23930                  }
23931                  $this->svggradientid = $attribs['id'];
23932                  $this->svggradients[$this->svggradientid] = array();
23933                  $this->svggradients[$this->svggradientid]['type'] = 3;
23934                  $this->svggradients[$this->svggradientid]['stops'] = array();
23935                  if (isset($attribs['gradientUnits'])) {
23936                      $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
23937                  } else {
23938                      $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
23939                  }
23940                  //$attribs['spreadMethod']
23941                  if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
23942                      OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
23943                      OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')))) {
23944                      $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
23945                  } elseif (isset($attribs['r']) AND is_numeric($attribs['r']) AND ($attribs['r']) <= 1) {
23946                      $this->svggradients[$this->svggradientid]['mode'] = 'ratio';
23947                  } else {
23948                      $this->svggradients[$this->svggradientid]['mode'] = 'measure';
23949                  }
23950                  $cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
23951                  $cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
23952                  $fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
23953                  $fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
23954                  $r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
23955                  if (isset($attribs['gradientTransform'])) {
23956                      $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
23957                  }
23958                  $this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
23959                  if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
23960                      // gradient is defined on another place
23961                      $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
23962                  }
23963                  break;
23964              }
23965              case 'stop': {
23966                  // gradient stops
23967                  if (substr($attribs['offset'], -1) == '%') {
23968                      $offset = floatval(substr($attribs['offset'], -1)) / 100;
23969                  } else {
23970                      $offset = floatval($attribs['offset']);
23971                      if ($offset > 1) {
23972                          $offset /= 100;
23973                      }
23974                  }
23975                  $stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
23976                  $opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
23977                  $this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
23978                  break;
23979              }
23980              // paths
23981              case 'path': {
23982                  if ($invisible) {
23983                      break;
23984                  }
23985                  if (isset($attribs['d'])) {
23986                      $d = trim($attribs['d']);
23987                      if (!empty($d)) {
23988                          $x = (isset($attribs['x'])?$attribs['x']:0);
23989                          $y = (isset($attribs['y'])?$attribs['y']:0);
23990                          $w = (isset($attribs['width'])?$attribs['width']:1);
23991                          $h = (isset($attribs['height'])?$attribs['height']:1);
23992                          $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
23993                          if ($clipping) {
23994                              $this->SVGTransform($tm);
23995                              $this->SVGPath($d, 'CNZ');
23996                          } else {
23997                              $this->StartTransform();
23998                              $this->SVGTransform($tm);
23999                              $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
24000                              if (!empty($obstyle)) {
24001                                  $this->SVGPath($d, $obstyle);
24002                              }
24003                              $this->StopTransform();
24004                          }
24005                      }
24006                  }
24007                  break;
24008              }
24009              // shapes
24010              case 'rect': {
24011                  if ($invisible) {
24012                      break;
24013                  }
24014                  $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24015                  $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24016                  $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24017                  $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24018                  $rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
24019                  $ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
24020                  if ($clipping) {
24021                      $this->SVGTransform($tm);
24022                      $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
24023                  } else {
24024                      $this->StartTransform();
24025                      $this->SVGTransform($tm);
24026                      $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
24027                      if (!empty($obstyle)) {
24028                          $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
24029                      }
24030                      $this->StopTransform();
24031                  }
24032                  break;
24033              }
24034              case 'circle': {
24035                  if ($invisible) {
24036                      break;
24037                  }
24038                  $r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
24039                  $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24040                  $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24041                  $x = ($cx - $r);
24042                  $y = ($cy - $r);
24043                  $w = (2 * $r);
24044                  $h = $w;
24045                  if ($clipping) {
24046                      $this->SVGTransform($tm);
24047                      $this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
24048                  } else {
24049                      $this->StartTransform();
24050                      $this->SVGTransform($tm);
24051                      $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
24052                      if (!empty($obstyle)) {
24053                          $this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
24054                      }
24055                      $this->StopTransform();
24056                  }
24057                  break;
24058              }
24059              case 'ellipse': {
24060                  if ($invisible) {
24061                      break;
24062                  }
24063                  $rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
24064                  $ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
24065                  $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24066                  $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24067                  $x = ($cx - $rx);
24068                  $y = ($cy - $ry);
24069                  $w = (2 * $rx);
24070                  $h = (2 * $ry);
24071                  if ($clipping) {
24072                      $this->SVGTransform($tm);
24073                      $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
24074                  } else {
24075                      $this->StartTransform();
24076                      $this->SVGTransform($tm);
24077                      $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
24078                      if (!empty($obstyle)) {
24079                          $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
24080                      }
24081                      $this->StopTransform();
24082                  }
24083                  break;
24084              }
24085              case 'line': {
24086                  if ($invisible) {
24087                      break;
24088                  }
24089                  $x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
24090                  $y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
24091                  $x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
24092                  $y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
24093                  $x = $x1;
24094                  $y = $y1;
24095                  $w = abs($x2 - $x1);
24096                  $h = abs($y2 - $y1);
24097                  if (!$clipping) {
24098                      $this->StartTransform();
24099                      $this->SVGTransform($tm);
24100                      $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
24101                      $this->Line($x1, $y1, $x2, $y2);
24102                      $this->StopTransform();
24103                  }
24104                  break;
24105              }
24106              case 'polyline':
24107              case 'polygon': {
24108                  if ($invisible) {
24109                      break;
24110                  }
24111                  $points = (isset($attribs['points'])?$attribs['points']:'0 0');
24112                  $points = trim($points);
24113                  // note that point may use a complex syntax not covered here
24114                  $points = preg_split('/[\,\s]+/si', $points);
24115                  if (count($points) < 4) {
24116                      break;
24117                  }
24118                  $p = array();
24119                  $xmin = 2147483647;
24120                  $xmax = 0;
24121                  $ymin = 2147483647;
24122                  $ymax = 0;
24123                  foreach ($points as $key => $val) {
24124                      $p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24125                      if (($key % 2) == 0) {
24126                          // X coordinate
24127                          $xmin = min($xmin, $p[$key]);
24128                          $xmax = max($xmax, $p[$key]);
24129                      } else {
24130                          // Y coordinate
24131                          $ymin = min($ymin, $p[$key]);
24132                          $ymax = max($ymax, $p[$key]);
24133                      }
24134                  }
24135                  $x = $xmin;
24136                  $y = $ymin;
24137                  $w = ($xmax - $xmin);
24138                  $h = ($ymax - $ymin);
24139                  if ($name == 'polyline') {
24140                      $this->StartTransform();
24141                      $this->SVGTransform($tm);
24142                      $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
24143                      if (!empty($obstyle)) {
24144                          $this->PolyLine($p, $obstyle, array(), array());
24145                      }
24146                      $this->StopTransform();
24147                  } else { // polygon
24148                      if ($clipping) {
24149                          $this->SVGTransform($tm);
24150                          $this->Polygon($p, 'CNZ', array(), array(), true);
24151                      } else {
24152                          $this->StartTransform();
24153                          $this->SVGTransform($tm);
24154                          $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
24155                          if (!empty($obstyle)) {
24156                              $this->Polygon($p, $obstyle, array(), array(), true);
24157                          }
24158                          $this->StopTransform();
24159                      }
24160                  }
24161                  break;
24162              }
24163              // image
24164              case 'image': {
24165                  if ($invisible) {
24166                      break;
24167                  }
24168                  if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
24169                      break;
24170                  }
24171                  $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24172                  $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24173                  $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24174                  $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24175                  $img = $attribs['xlink:href'];
24176                  if (!$clipping) {
24177                      $this->StartTransform();
24178                      $this->SVGTransform($tm);
24179                      $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
24180                      if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
24181                          // embedded image encoded as base64
24182                          $img = '@'.base64_decode(substr($img, strlen($m[0])));
24183                      } else {
24184                          // fix image path
24185                          if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
24186                              // replace relative path with full server path
24187                              $img = $this->svgdir.'/'.$img;
24188                          }
24189                          if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
24190                              $findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
24191                              if (($findroot === false) OR ($findroot > 1)) {
24192                                  if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
24193                                      $img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
24194                                  } else {
24195                                      $img = $_SERVER['DOCUMENT_ROOT'].$img;
24196                                  }
24197                              }
24198                          }
24199                          $img = urldecode($img);
24200                          $testscrtype = @parse_url($img);
24201                          if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
24202                              // convert URL to server path
24203                              $img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
24204                          }
24205                      }
24206                      // get image type
24207                      $imgtype = TCPDF_IMAGES::getImageFileType($img);
24208                      if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
24209                          $this->ImageEps($img, $x, $y, $w, $h);
24210                      } elseif ($imgtype == 'svg') {
24211                          // store SVG vars
24212                          $svggradients = $this->svggradients;
24213                          $svggradientid = $this->svggradientid;
24214                          $svgdefsmode = $this->svgdefsmode;
24215                          $svgdefs = $this->svgdefs;
24216                          $svgclipmode = $this->svgclipmode;
24217                          $svgclippaths = $this->svgclippaths;
24218                          $svgcliptm = $this->svgcliptm;
24219                          $svgclipid = $this->svgclipid;
24220                          $svgtext = $this->svgtext;
24221                          $svgtextmode = $this->svgtextmode;
24222                          $this->ImageSVG($img, $x, $y, $w, $h);
24223                          // restore SVG vars
24224                          $this->svggradients = $svggradients;
24225                          $this->svggradientid = $svggradientid;
24226                          $this->svgdefsmode = $svgdefsmode;
24227                          $this->svgdefs = $svgdefs;
24228                          $this->svgclipmode = $svgclipmode;
24229                          $this->svgclippaths = $svgclippaths;
24230                          $this->svgcliptm = $svgcliptm;
24231                          $this->svgclipid = $svgclipid;
24232                          $this->svgtext = $svgtext;
24233                          $this->svgtextmode = $svgtextmode;
24234                      } else {
24235                          $this->Image($img, $x, $y, $w, $h);
24236                      }
24237                      $this->StopTransform();
24238                  }
24239                  break;
24240              }
24241              // text
24242              case 'text':
24243              case 'tspan': {
24244                  if (isset($this->svgtextmode['text-anchor']) AND !empty($this->svgtext)) {
24245                      // @TODO: unsupported feature
24246                  }
24247                  // only basic support - advanced features must be implemented
24248                  $this->svgtextmode['invisible'] = $invisible;
24249                  if ($invisible) {
24250                      break;
24251                  }
24252                  array_push($this->svgstyles, $svgstyle);
24253                  if (isset($attribs['x'])) {
24254                      $x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
24255                  } elseif ($name == 'tspan') {
24256                      $x = $this->x;
24257                  } else {
24258                      $x = 0;
24259                  }
24260                  if (isset($attribs['dx'])) {
24261                      $x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
24262                  }
24263                  if (isset($attribs['y'])) {
24264                      $y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
24265                  } elseif ($name == 'tspan') {
24266                      $y = $this->y;
24267                  } else {
24268                      $y = 0;
24269                  }
24270                  if (isset($attribs['dy'])) {
24271                      $y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
24272                  }
24273                  $svgstyle['text-color'] = $svgstyle['fill'];
24274                  $this->svgtext = '';
24275                  if (isset($svgstyle['text-anchor'])) {
24276                      $this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
24277                  } else {
24278                      $this->svgtextmode['text-anchor'] = 'start';
24279                  }
24280                  if (isset($svgstyle['direction'])) {
24281                      if ($svgstyle['direction'] == 'rtl') {
24282                          $this->svgtextmode['rtl'] = true;
24283                      } else {
24284                          $this->svgtextmode['rtl'] = false;
24285                      }
24286                  } else {
24287                      $this->svgtextmode['rtl'] = false;
24288                  }
24289                  if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
24290                      $this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
24291                  } else {
24292                      $this->svgtextmode['stroke'] = false;
24293                  }
24294                  $this->StartTransform();
24295                  $this->SVGTransform($tm);
24296                  $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
24297                  $this->x = $x;
24298                  $this->y = $y;
24299                  break;
24300              }
24301              // use
24302              case 'use': {
24303                  if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24304                      $svgdefid = substr($attribs['xlink:href'], 1);
24305                      if (isset($this->svgdefs[$svgdefid])) {
24306                          $use = $this->svgdefs[$svgdefid];
24307                          if (isset($attribs['xlink:href'])) {
24308                              unset($attribs['xlink:href']);
24309                          }
24310                          if (isset($attribs['id'])) {
24311                              unset($attribs['id']);
24312                          }
24313                          if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
24314                              $attribs['x'] += $use['attribs']['x'];
24315                          }
24316                          if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
24317                              $attribs['y'] += $use['attribs']['y'];
24318                          }
24319                          if (empty($attribs['style'])) {
24320                              $attribs['style'] = '';
24321                          }
24322                          if (!empty($use['attribs']['style'])) {
24323                              // merge styles
24324                              $attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
24325                          }
24326                          $attribs = array_merge($use['attribs'], $attribs);
24327                          $this->startSVGElementHandler($parser, $use['name'], $attribs);
24328                          return;
24329                      }
24330                  }
24331                  break;
24332              }
24333              default: {
24334                  break;
24335              }
24336          } // end of switch
24337          // process child elements
24338          if (!empty($attribs['child_elements'])) {
24339              $child_elements = $attribs['child_elements'];
24340              unset($attribs['child_elements']);
24341              foreach($child_elements as $child_element) {
24342                  if (empty($child_element['attribs']['closing_tag'])) {
24343                      $this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
24344                  } else {
24345                      if (isset($child_element['attribs']['content'])) {
24346                          $this->svgtext = $child_element['attribs']['content'];
24347                      }
24348                      $this->endSVGElementHandler('child-tag', $child_element['name']);
24349                  }
24350              }
24351          }
24352      }
24353  
24354      /**
24355       * Sets the closing SVG element handler function for the XML parser.
24356       * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24357       * @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.
24358       * @author Nicola Asuni
24359       * @since 5.0.000 (2010-05-02)
24360       * @protected
24361       */
24362  	protected function endSVGElementHandler($parser, $name) {
24363          $name = $this->removeTagNamespace($name);
24364          if ($this->svgdefsmode AND !in_array($name, array('defs', 'clipPath', 'linearGradient', 'radialGradient', 'stop'))) {;
24365              if (end($this->svgdefs) !== FALSE) {
24366                  $last_svgdefs_id = key($this->svgdefs);
24367                  if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
24368                      foreach($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'] as $child_element) {
24369                          if (isset($child_element['attribs']['id']) AND ($child_element['name'] == $name)) {
24370                              $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$child_element['attribs']['id'].'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24371                              return;
24372                          }
24373                      }
24374                      if ($this->svgdefs[$last_svgdefs_id]['name'] == $name) {
24375                          $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$last_svgdefs_id.'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24376                          return;
24377                      }
24378                  }
24379              }
24380              return;
24381          }
24382          switch($name) {
24383              case 'defs': {
24384                  $this->svgdefsmode = false;
24385                  break;
24386              }
24387              // clipPath
24388              case 'clipPath': {
24389                  $this->svgclipmode = false;
24390                  break;
24391              }
24392              case 'svg': {
24393                  if (--$this->svg_tag_depth <= 0) {
24394                      break;
24395                  }
24396              }
24397              case 'g': {
24398                  // ungroup: remove last style from array
24399                  array_pop($this->svgstyles);
24400                  $this->StopTransform();
24401                  break;
24402              }
24403              case 'text':
24404              case 'tspan': {
24405                  if ($this->svgtextmode['invisible']) {
24406                      // This implementation must be fixed to following the rule:
24407                      // 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.
24408                      break;
24409                  }
24410                  // print text
24411                  $text = $this->svgtext;
24412                  //$text = $this->stringTrim($text);
24413                  $textlen = $this->GetStringWidth($text);
24414                  if ($this->svgtextmode['text-anchor'] != 'start') {
24415                      // check if string is RTL text
24416                      if ($this->svgtextmode['text-anchor'] == 'end') {
24417                          if ($this->svgtextmode['rtl']) {
24418                              $this->x += $textlen;
24419                          } else {
24420                              $this->x -= $textlen;
24421                          }
24422                      } elseif ($this->svgtextmode['text-anchor'] == 'middle') {
24423                          if ($this->svgtextmode['rtl']) {
24424                              $this->x += ($textlen / 2);
24425                          } else {
24426                              $this->x -= ($textlen / 2);
24427                          }
24428                      }
24429                  }
24430                  $textrendermode = $this->textrendermode;
24431                  $textstrokewidth = $this->textstrokewidth;
24432                  $this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
24433                  if ($name == 'text') {
24434                      // store current coordinates
24435                      $tmpx = $this->x;
24436                      $tmpy = $this->y;
24437                  }
24438                  // print the text
24439                  $this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
24440                  if ($name == 'text') {
24441                      // restore coordinates
24442                      $this->x = $tmpx;
24443                      $this->y = $tmpy;
24444                  }
24445                  // restore previous rendering mode
24446                  $this->textrendermode = $textrendermode;
24447                  $this->textstrokewidth = $textstrokewidth;
24448                  $this->svgtext = '';
24449                  $this->StopTransform();
24450                  if (!$this->svgdefsmode) {
24451                      array_pop($this->svgstyles);
24452                  }
24453                  break;
24454              }
24455              default: {
24456                  break;
24457              }
24458          }
24459      }
24460  
24461      /**
24462       * Sets the character data handler function for the XML parser.
24463       * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24464       * @param $data (string) The second parameter, data, contains the character data as a string.
24465       * @author Nicola Asuni
24466       * @since 5.0.000 (2010-05-02)
24467       * @protected
24468       */
24469  	protected function segSVGContentHandler($parser, $data) {
24470          $this->svgtext .= $data;
24471      }
24472  
24473      // --- END SVG METHODS -----------------------------------------------------
24474  
24475  } // END OF TCPDF CLASS
24476  
24477  //============================================================+
24478  // END OF FILE
24479  //============================================================+


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1