[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

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

   1  <?php
   2  //============================================================+
   3  // File name   : tcpdf.php
   4  // Version     : 6.0.093
   5  // Begin       : 2002-08-03
   6  // Last Update : 2014-09-02
   7  // Author      : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - [email protected]
   8  // License     : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
   9  // -------------------------------------------------------------------
  10  // Copyright (C) 2002-2014 Nicola Asuni - Tecnick.com LTD
  11  //
  12  // This file is part of TCPDF software library.
  13  //
  14  // TCPDF is free software: you can ioredistribute it and/or modify it
  15  // under the terms of the GNU Lesser General Public License as
  16  // published by the Free Software Foundation, either version 3 of the
  17  // License, or (at your option) any later version.
  18  //
  19  // TCPDF is distributed in the hope that it will be useful, but
  20  // WITHOUT ANY WARRANTY; without even the implied warranty of
  21  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  22  // See the GNU Lesser General Public License for more details.
  23  //
  24  // You should have received a copy of the License
  25  // along with TCPDF. If not, see
  26  // <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
  27  //
  28  // See LICENSE.TXT file for more information.
  29  // -------------------------------------------------------------------
  30  //
  31  // Description :
  32  //   This is a PHP class for generating PDF documents without requiring external extensions.
  33  //
  34  // NOTE:
  35  //   This class was originally derived in 2002 from the Public
  36  //   Domain FPDF class by Olivier Plathey (http://www.fpdf.org),
  37  //   but now is almost entirely rewritten and contains thousands of
  38  //   new lines of code and hundreds new features.
  39  //
  40  // Main features:
  41  //  * no external libraries are required for the basic functions;
  42  //  * all standard page formats, custom page formats, custom margins and units of measure;
  43  //  * UTF-8 Unicode and Right-To-Left languages;
  44  //  * TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;
  45  //  * font subsetting;
  46  //  * methods to publish some XHTML + CSS code, Javascript and Forms;
  47  //  * images, graphic (geometric figures) and transformation methods;
  48  //  * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)
  49  //  * 1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extention, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;
  50  //  * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;
  51  //  * automatic page header and footer management;
  52  //  * document encryption up to 256 bit and digital signature certifications;
  53  //  * transactions to UNDO commands;
  54  //  * PDF annotations, including links, text and file attachments;
  55  //  * text rendering modes (fill, stroke and clipping);
  56  //  * multiple columns mode;
  57  //  * no-write page regions;
  58  //  * bookmarks, named destinations and table of content;
  59  //  * text hyphenation;
  60  //  * text stretching and spacing (tracking);
  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 Extention, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;</li>
  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.0.093
 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.0.093
 132   * @author Nicola Asuni - [email protected]
 133   */
 134  class TCPDF {
 135  
 136      // Protected properties
 137  
 138      /**
 139       * Current page number.
 140       * @protected
 141       */
 142      protected $page;
 143  
 144      /**
 145       * Current object number.
 146       * @protected
 147       */
 148      protected $n;
 149  
 150      /**
 151       * Array of object offsets.
 152       * @protected
 153       */
 154      protected $offsets = array();
 155  
 156      /**
 157       * Array of object IDs for each page.
 158       * @protected
 159       */
 160      protected $pageobjects = array();
 161  
 162      /**
 163       * Buffer holding in-memory PDF.
 164       * @protected
 165       */
 166      protected $buffer;
 167  
 168      /**
 169       * Array containing pages.
 170       * @protected
 171       */
 172      protected $pages = array();
 173  
 174      /**
 175       * Current document state.
 176       * @protected
 177       */
 178      protected $state;
 179  
 180      /**
 181       * Compression flag.
 182       * @protected
 183       */
 184      protected $compress;
 185  
 186      /**
 187       * Current page orientation (P = Portrait, L = Landscape).
 188       * @protected
 189       */
 190      protected $CurOrientation;
 191  
 192      /**
 193       * Page dimensions.
 194       * @protected
 195       */
 196      protected $pagedim = array();
 197  
 198      /**
 199       * Scale factor (number of points in user unit).
 200       * @protected
 201       */
 202      protected $k;
 203  
 204      /**
 205       * Width of page format in points.
 206       * @protected
 207       */
 208      protected $fwPt;
 209  
 210      /**
 211       * Height of page format in points.
 212       * @protected
 213       */
 214      protected $fhPt;
 215  
 216      /**
 217       * Current width of page in points.
 218       * @protected
 219       */
 220      protected $wPt;
 221  
 222      /**
 223       * Current height of page in points.
 224       * @protected
 225       */
 226      protected $hPt;
 227  
 228      /**
 229       * Current width of page in user unit.
 230       * @protected
 231       */
 232      protected $w;
 233  
 234      /**
 235       * Current height of page in user unit.
 236       * @protected
 237       */
 238      protected $h;
 239  
 240      /**
 241       * Left margin.
 242       * @protected
 243       */
 244      protected $lMargin;
 245  
 246      /**
 247       * Right margin.
 248       * @protected
 249       */
 250      protected $rMargin;
 251  
 252      /**
 253       * Cell left margin (used by regions).
 254       * @protected
 255       */
 256      protected $clMargin;
 257  
 258      /**
 259       * Cell right margin (used by regions).
 260       * @protected
 261       */
 262      protected $crMargin;
 263  
 264      /**
 265       * Top margin.
 266       * @protected
 267       */
 268      protected $tMargin;
 269  
 270      /**
 271       * Page break margin.
 272       * @protected
 273       */
 274      protected $bMargin;
 275  
 276      /**
 277       * Array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
 278       * @since 5.9.000 (2010-10-03)
 279       * @protected
 280       */
 281      protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
 282  
 283      /**
 284       * Array of cell margins ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
 285       * @since 5.9.000 (2010-10-04)
 286       * @protected
 287       */
 288      protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
 289  
 290      /**
 291       * Current horizontal position in user unit for cell positioning.
 292       * @protected
 293       */
 294      protected $x;
 295  
 296      /**
 297       * Current vertical position in user unit for cell positioning.
 298       * @protected
 299       */
 300      protected $y;
 301  
 302      /**
 303       * Height of last cell printed.
 304       * @protected
 305       */
 306      protected $lasth;
 307  
 308      /**
 309       * Line width in user unit.
 310       * @protected
 311       */
 312      protected $LineWidth;
 313  
 314      /**
 315       * Array of standard font names.
 316       * @protected
 317       */
 318      protected $CoreFonts;
 319  
 320      /**
 321       * Array of used fonts.
 322       * @protected
 323       */
 324      protected $fonts = array();
 325  
 326      /**
 327       * Array of font files.
 328       * @protected
 329       */
 330      protected $FontFiles = array();
 331  
 332      /**
 333       * Array of encoding differences.
 334       * @protected
 335       */
 336      protected $diffs = array();
 337  
 338      /**
 339       * Array of used images.
 340       * @protected
 341       */
 342      protected $images = array();
 343  
 344      /**
 345       * Array of cached files.
 346       * @protected
 347       */
 348      protected $cached_files = array();
 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       * If true enables disk caching.
1188       * @protected
1189       * @since 4.5.000 (2008-12-31)
1190       */
1191      protected $diskcache = false;
1192  
1193      /**
1194       * Counts the number of fonts.
1195       * @protected
1196       * @since 4.5.000 (2009-01-02)
1197       */
1198      protected $numfonts = 0;
1199  
1200      /**
1201       * Store the font keys.
1202       * @protected
1203       * @since 4.5.000 (2009-01-02)
1204       */
1205      protected $fontkeys = array();
1206  
1207      /**
1208       * Store the font object IDs.
1209       * @protected
1210       * @since 4.8.001 (2009-09-09)
1211       */
1212      protected $font_obj_ids = array();
1213  
1214      /**
1215       * Store the fage status (true when opened, false when closed).
1216       * @protected
1217       * @since 4.5.000 (2009-01-02)
1218       */
1219      protected $pageopen = array();
1220  
1221      /**
1222       * Default monospace font.
1223       * @protected
1224       * @since 4.5.025 (2009-03-10)
1225       */
1226      protected $default_monospaced_font = 'courier';
1227  
1228      /**
1229       * Cloned copy of the current class object.
1230       * @protected
1231       * @since 4.5.029 (2009-03-19)
1232       */
1233      protected $objcopy;
1234  
1235      /**
1236       * Array used to store the lengths of cache files.
1237       * @protected
1238       * @since 4.5.029 (2009-03-19)
1239       */
1240      protected $cache_file_length = array();
1241  
1242      /**
1243       * Table header content to be repeated on each new page.
1244       * @protected
1245       * @since 4.5.030 (2009-03-20)
1246       */
1247      protected $thead = '';
1248  
1249      /**
1250       * Margins used for table header.
1251       * @protected
1252       * @since 4.5.030 (2009-03-20)
1253       */
1254      protected $theadMargins = array();
1255  
1256      /**
1257       * Boolean flag to enable document digital signature.
1258       * @protected
1259       * @since 4.6.005 (2009-04-24)
1260       */
1261      protected $sign = false;
1262  
1263      /**
1264       * Digital signature data.
1265       * @protected
1266       * @since 4.6.005 (2009-04-24)
1267       */
1268      protected $signature_data = array();
1269  
1270      /**
1271       * Digital signature max length.
1272       * @protected
1273       * @since 4.6.005 (2009-04-24)
1274       */
1275      protected $signature_max_length = 11742;
1276  
1277      /**
1278       * Data for digital signature appearance.
1279       * @protected
1280       * @since 5.3.011 (2010-06-16)
1281       */
1282      protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1283  
1284      /**
1285       * Array of empty digital signature appearances.
1286       * @protected
1287       * @since 5.9.101 (2011-07-06)
1288       */
1289      protected $empty_signature_appearance = array();
1290  
1291      /**
1292       * Boolean flag to enable document timestamping with TSA.
1293       * @protected
1294       * @since 6.0.085 (2014-06-19)
1295       */
1296      protected $tsa_timestamp = false;
1297  
1298      /**
1299       * Timestamping data.
1300       * @protected
1301       * @since 6.0.085 (2014-06-19)
1302       */
1303      protected $tsa_data = array();
1304  
1305      /**
1306       * Regular expression used to find blank characters (required for word-wrapping).
1307       * @protected
1308       * @since 4.6.006 (2009-04-28)
1309       */
1310      protected $re_spaces = '/[^\S\xa0]/';
1311  
1312      /**
1313       * Array of $re_spaces parts.
1314       * @protected
1315       * @since 5.5.011 (2010-07-09)
1316       */
1317      protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1318  
1319      /**
1320       * Digital signature object ID.
1321       * @protected
1322       * @since 4.6.022 (2009-06-23)
1323       */
1324      protected $sig_obj_id = 0;
1325  
1326      /**
1327       * ID of page objects.
1328       * @protected
1329       * @since 4.7.000 (2009-08-29)
1330       */
1331      protected $page_obj_id = array();
1332  
1333      /**
1334       * List of form annotations IDs.
1335       * @protected
1336       * @since 4.8.000 (2009-09-07)
1337       */
1338      protected $form_obj_id = array();
1339  
1340      /**
1341       * 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.
1342       * @protected
1343       * @since 4.8.000 (2009-09-07)
1344       */
1345      protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1346  
1347      /**
1348       * Javascript objects array.
1349       * @protected
1350       * @since 4.8.000 (2009-09-07)
1351       */
1352      protected $js_objects = array();
1353  
1354      /**
1355       * Current form action (used during XHTML rendering).
1356       * @protected
1357       * @since 4.8.000 (2009-09-07)
1358       */
1359      protected $form_action = '';
1360  
1361      /**
1362       * Current form encryption type (used during XHTML rendering).
1363       * @protected
1364       * @since 4.8.000 (2009-09-07)
1365       */
1366      protected $form_enctype = 'application/x-www-form-urlencoded';
1367  
1368      /**
1369       * Current method to submit forms.
1370       * @protected
1371       * @since 4.8.000 (2009-09-07)
1372       */
1373      protected $form_mode = 'post';
1374  
1375      /**
1376       * List of fonts used on form fields (fontname => fontkey).
1377       * @protected
1378       * @since 4.8.001 (2009-09-09)
1379       */
1380      protected $annotation_fonts = array();
1381  
1382      /**
1383       * List of radio buttons parent objects.
1384       * @protected
1385       * @since 4.8.001 (2009-09-09)
1386       */
1387      protected $radiobutton_groups = array();
1388  
1389      /**
1390       * List of radio group objects IDs.
1391       * @protected
1392       * @since 4.8.001 (2009-09-09)
1393       */
1394      protected $radio_groups = array();
1395  
1396      /**
1397       * Text indentation value (used for text-indent CSS attribute).
1398       * @protected
1399       * @since 4.8.006 (2009-09-23)
1400       */
1401      protected $textindent = 0;
1402  
1403      /**
1404       * Store page number when startTransaction() is called.
1405       * @protected
1406       * @since 4.8.006 (2009-09-23)
1407       */
1408      protected $start_transaction_page = 0;
1409  
1410      /**
1411       * Store Y position when startTransaction() is called.
1412       * @protected
1413       * @since 4.9.001 (2010-03-28)
1414       */
1415      protected $start_transaction_y = 0;
1416  
1417      /**
1418       * True when we are printing the thead section on a new page.
1419       * @protected
1420       * @since 4.8.027 (2010-01-25)
1421       */
1422      protected $inthead = false;
1423  
1424      /**
1425       * Array of column measures (width, space, starting Y position).
1426       * @protected
1427       * @since 4.9.001 (2010-03-28)
1428       */
1429      protected $columns = array();
1430  
1431      /**
1432       * Number of colums.
1433       * @protected
1434       * @since 4.9.001 (2010-03-28)
1435       */
1436      protected $num_columns = 1;
1437  
1438      /**
1439       * Current column number.
1440       * @protected
1441       * @since 4.9.001 (2010-03-28)
1442       */
1443      protected $current_column = 0;
1444  
1445      /**
1446       * Starting page for columns.
1447       * @protected
1448       * @since 4.9.001 (2010-03-28)
1449       */
1450      protected $column_start_page = 0;
1451  
1452      /**
1453       * Maximum page and column selected.
1454       * @protected
1455       * @since 5.8.000 (2010-08-11)
1456       */
1457      protected $maxselcol = array('page' => 0, 'column' => 0);
1458  
1459      /**
1460       * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
1461       * @protected
1462       * @since 5.8.000 (2010-08-11)
1463       */
1464      protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1465  
1466      /**
1467       * 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.
1468       * @protected
1469       * @since 4.9.008 (2010-04-03)
1470       */
1471      protected $textrendermode = 0;
1472  
1473      /**
1474       * Text stroke width in doc units.
1475       * @protected
1476       * @since 4.9.008 (2010-04-03)
1477       */
1478      protected $textstrokewidth = 0;
1479  
1480      /**
1481       * Current stroke color.
1482       * @protected
1483       * @since 4.9.008 (2010-04-03)
1484       */
1485      protected $strokecolor;
1486  
1487      /**
1488       * Default unit of measure for document.
1489       * @protected
1490       * @since 5.0.000 (2010-04-22)
1491       */
1492      protected $pdfunit = 'mm';
1493  
1494      /**
1495       * Boolean flag true when we are on TOC (Table Of Content) page.
1496       * @protected
1497       */
1498      protected $tocpage = false;
1499  
1500      /**
1501       * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1502       * @protected
1503       * @since 5.0.000 (2010-04-26)
1504       */
1505      protected $rasterize_vector_images = false;
1506  
1507      /**
1508       * Boolean flag: if true enables font subsetting by default.
1509       * @protected
1510       * @since 5.3.002 (2010-06-07)
1511       */
1512      protected $font_subsetting = true;
1513  
1514      /**
1515       * Array of default graphic settings.
1516       * @protected
1517       * @since 5.5.008 (2010-07-02)
1518       */
1519      protected $default_graphic_vars = array();
1520  
1521      /**
1522       * Array of XObjects.
1523       * @protected
1524       * @since 5.8.014 (2010-08-23)
1525       */
1526      protected $xobjects = array();
1527  
1528      /**
1529       * Boolean value true when we are inside an XObject.
1530       * @protected
1531       * @since 5.8.017 (2010-08-24)
1532       */
1533      protected $inxobj = false;
1534  
1535      /**
1536       * Current XObject ID.
1537       * @protected
1538       * @since 5.8.017 (2010-08-24)
1539       */
1540      protected $xobjid = '';
1541  
1542      /**
1543       * Percentage of character stretching.
1544       * @protected
1545       * @since 5.9.000 (2010-09-29)
1546       */
1547      protected $font_stretching = 100;
1548  
1549      /**
1550       * Increases or decreases the space between characters in a text by the specified amount (tracking).
1551       * @protected
1552       * @since 5.9.000 (2010-09-29)
1553       */
1554      protected $font_spacing = 0;
1555  
1556      /**
1557       * Array of no-write regions.
1558       * ('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)
1559       * @protected
1560       * @since 5.9.003 (2010-10-14)
1561       */
1562      protected $page_regions = array();
1563  
1564      /**
1565       * Boolean value true when page region check is active.
1566       * @protected
1567       */
1568      protected $check_page_regions = true;
1569  
1570      /**
1571       * Array of PDF layers data.
1572       * @protected
1573       * @since 5.9.102 (2011-07-13)
1574       */
1575      protected $pdflayers = array();
1576  
1577      /**
1578       * A dictionary of names and corresponding destinations (Dests key on document Catalog).
1579       * @protected
1580       * @since 5.9.097 (2011-06-23)
1581       */
1582      protected $dests = array();
1583  
1584      /**
1585       * Object ID for Named Destinations
1586       * @protected
1587       * @since 5.9.097 (2011-06-23)
1588       */
1589      protected $n_dests;
1590  
1591      /**
1592       * Embedded Files Names
1593       * @protected
1594       * @since 5.9.204 (2013-01-23)
1595       */
1596      protected $efnames = array();
1597  
1598      /**
1599       * Directory used for the last SVG image.
1600       * @protected
1601       * @since 5.0.000 (2010-05-05)
1602       */
1603      protected $svgdir = '';
1604  
1605      /**
1606       *  Deafult unit of measure for SVG.
1607       * @protected
1608       * @since 5.0.000 (2010-05-02)
1609       */
1610      protected $svgunit = 'px';
1611  
1612      /**
1613       * Array of SVG gradients.
1614       * @protected
1615       * @since 5.0.000 (2010-05-02)
1616       */
1617      protected $svggradients = array();
1618  
1619      /**
1620       * ID of last SVG gradient.
1621       * @protected
1622       * @since 5.0.000 (2010-05-02)
1623       */
1624      protected $svggradientid = 0;
1625  
1626      /**
1627       * Boolean value true when in SVG defs group.
1628       * @protected
1629       * @since 5.0.000 (2010-05-02)
1630       */
1631      protected $svgdefsmode = false;
1632  
1633      /**
1634       * Array of SVG defs.
1635       * @protected
1636       * @since 5.0.000 (2010-05-02)
1637       */
1638      protected $svgdefs = array();
1639  
1640      /**
1641       * Boolean value true when in SVG clipPath tag.
1642       * @protected
1643       * @since 5.0.000 (2010-04-26)
1644       */
1645      protected $svgclipmode = false;
1646  
1647      /**
1648       * Array of SVG clipPath commands.
1649       * @protected
1650       * @since 5.0.000 (2010-05-02)
1651       */
1652      protected $svgclippaths = array();
1653  
1654      /**
1655       * Array of SVG clipPath tranformation matrix.
1656       * @protected
1657       * @since 5.8.022 (2010-08-31)
1658       */
1659      protected $svgcliptm = array();
1660  
1661      /**
1662       * ID of last SVG clipPath.
1663       * @protected
1664       * @since 5.0.000 (2010-05-02)
1665       */
1666      protected $svgclipid = 0;
1667  
1668      /**
1669       * SVG text.
1670       * @protected
1671       * @since 5.0.000 (2010-05-02)
1672       */
1673      protected $svgtext = '';
1674  
1675      /**
1676       * SVG text properties.
1677       * @protected
1678       * @since 5.8.013 (2010-08-23)
1679       */
1680      protected $svgtextmode = array();
1681  
1682      /**
1683       * Array of SVG properties.
1684       * @protected
1685       * @since 5.0.000 (2010-05-02)
1686       */
1687      protected $svgstyles = array(array(
1688          'alignment-baseline' => 'auto',
1689          'baseline-shift' => 'baseline',
1690          'clip' => 'auto',
1691          'clip-path' => 'none',
1692          'clip-rule' => 'nonzero',
1693          'color' => 'black',
1694          'color-interpolation' => 'sRGB',
1695          'color-interpolation-filters' => 'linearRGB',
1696          'color-profile' => 'auto',
1697          'color-rendering' => 'auto',
1698          'cursor' => 'auto',
1699          'direction' => 'ltr',
1700          'display' => 'inline',
1701          'dominant-baseline' => 'auto',
1702          'enable-background' => 'accumulate',
1703          'fill' => 'black',
1704          'fill-opacity' => 1,
1705          'fill-rule' => 'nonzero',
1706          'filter' => 'none',
1707          'flood-color' => 'black',
1708          'flood-opacity' => 1,
1709          'font' => '',
1710          'font-family' => 'helvetica',
1711          'font-size' => 'medium',
1712          'font-size-adjust' => 'none',
1713          'font-stretch' => 'normal',
1714          'font-style' => 'normal',
1715          'font-variant' => 'normal',
1716          'font-weight' => 'normal',
1717          'glyph-orientation-horizontal' => '0deg',
1718          'glyph-orientation-vertical' => 'auto',
1719          'image-rendering' => 'auto',
1720          'kerning' => 'auto',
1721          'letter-spacing' => 'normal',
1722          'lighting-color' => 'white',
1723          'marker' => '',
1724          'marker-end' => 'none',
1725          'marker-mid' => 'none',
1726          'marker-start' => 'none',
1727          'mask' => 'none',
1728          'opacity' => 1,
1729          'overflow' => 'auto',
1730          'pointer-events' => 'visiblePainted',
1731          'shape-rendering' => 'auto',
1732          'stop-color' => 'black',
1733          'stop-opacity' => 1,
1734          'stroke' => 'none',
1735          'stroke-dasharray' => 'none',
1736          'stroke-dashoffset' => 0,
1737          'stroke-linecap' => 'butt',
1738          'stroke-linejoin' => 'miter',
1739          'stroke-miterlimit' => 4,
1740          'stroke-opacity' => 1,
1741          'stroke-width' => 1,
1742          'text-anchor' => 'start',
1743          'text-decoration' => 'none',
1744          'text-rendering' => 'auto',
1745          'unicode-bidi' => 'normal',
1746          'visibility' => 'visible',
1747          'word-spacing' => 'normal',
1748          'writing-mode' => 'lr-tb',
1749          'text-color' => 'black',
1750          'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1751          ));
1752  
1753      /**
1754       * If true force sRGB color profile for all document.
1755       * @protected
1756       * @since 5.9.121 (2011-09-28)
1757       */
1758      protected $force_srgb = false;
1759  
1760      /**
1761       * If true set the document to PDF/A mode.
1762       * @protected
1763       * @since 5.9.121 (2011-09-27)
1764       */
1765      protected $pdfa_mode = false;
1766  
1767      /**
1768       * Document creation date-time
1769       * @protected
1770       * @since 5.9.152 (2012-03-22)
1771       */
1772      protected $doc_creation_timestamp;
1773  
1774      /**
1775       * Document modification date-time
1776       * @protected
1777       * @since 5.9.152 (2012-03-22)
1778       */
1779      protected $doc_modification_timestamp;
1780  
1781      /**
1782       * Custom XMP data.
1783       * @protected
1784       * @since 5.9.128 (2011-10-06)
1785       */
1786      protected $custom_xmp = '';
1787  
1788      /**
1789       * Overprint mode array.
1790       * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1791       * @protected
1792       * @since 5.9.152 (2012-03-23)
1793       */
1794      protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1795  
1796      /**
1797       * Alpha mode array.
1798       * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1799       * @protected
1800       * @since 5.9.152 (2012-03-23)
1801       */
1802      protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1803  
1804      /**
1805       * Define the page boundaries boxes to be set on document.
1806       * @protected
1807       * @since 5.9.152 (2012-03-23)
1808       */
1809      protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1810  
1811      /**
1812       * If true print TCPDF meta link.
1813       * @protected
1814       * @since 5.9.152 (2012-03-23)
1815       */
1816      protected $tcpdflink = true;
1817  
1818      /**
1819       * Cache array for computed GD gamma values.
1820       * @protected
1821       * @since 5.9.1632 (2012-06-05)
1822       */
1823      protected $gdgammacache = array();
1824  
1825      //------------------------------------------------------------
1826      // METHODS
1827      //------------------------------------------------------------
1828  
1829      /**
1830       * This is the class constructor.
1831       * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1832       * 
1833       * 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.
1834       * 
1835       * @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>
1836       * @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.
1837       * @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().
1838       * @param $unicode (boolean) TRUE means that the input text is unicode (default = true)
1839       * @param $encoding (string) Charset encoding (used only when converting back html entities); default is UTF-8.
1840       * @param $diskcache (boolean) If TRUE reduce the RAM memory usage by caching temporary data on filesystem (slower).
1841       * @param $pdfa (boolean) If TRUE set the document to PDF/A mode.
1842       * @public
1843       * @see getPageSizeFromFormat(), setPageFormat()
1844       */
1845  	public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
1846          /* Set internal character encoding to ASCII */
1847          if (function_exists('mb_internal_encoding') AND mb_internal_encoding()) {
1848              $this->internal_encoding = mb_internal_encoding();
1849              mb_internal_encoding('ASCII');
1850          }
1851          // set file ID for trailer
1852          $serformat = (is_array($format) ? json_encode($format) : $format);
1853          $this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
1854          $this->font_obj_ids = array();
1855          $this->page_obj_id = array();
1856          $this->form_obj_id = array();
1857          // set pdf/a mode
1858          $this->pdfa_mode = $pdfa;
1859          $this->force_srgb = false;
1860          // set disk caching
1861          $this->diskcache = $diskcache ? true : false;
1862          // set language direction
1863          $this->rtl = false;
1864          $this->tmprtl = false;
1865          // some checks
1866          $this->_dochecks();
1867          // initialization of properties
1868          $this->isunicode = $unicode;
1869          $this->page = 0;
1870          $this->transfmrk[0] = array();
1871          $this->pagedim = array();
1872          $this->n = 2;
1873          $this->buffer = '';
1874          $this->pages = array();
1875          $this->state = 0;
1876          $this->fonts = array();
1877          $this->FontFiles = array();
1878          $this->diffs = array();
1879          $this->images = array();
1880          $this->links = array();
1881          $this->gradients = array();
1882          $this->InFooter = false;
1883          $this->lasth = 0;
1884          $this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
1885          $this->FontStyle = '';
1886          $this->FontSizePt = 12;
1887          $this->underline = false;
1888          $this->overline = false;
1889          $this->linethrough = false;
1890          $this->DrawColor = '0 G';
1891          $this->FillColor = '0 g';
1892          $this->TextColor = '0 g';
1893          $this->ColorFlag = false;
1894          $this->pdflayers = array();
1895          // encryption values
1896          $this->encrypted = false;
1897          $this->last_enc_key = '';
1898          // standard Unicode fonts
1899          $this->CoreFonts = array(
1900              'courier'=>'Courier',
1901              'courierB'=>'Courier-Bold',
1902              'courierI'=>'Courier-Oblique',
1903              'courierBI'=>'Courier-BoldOblique',
1904              'helvetica'=>'Helvetica',
1905              'helveticaB'=>'Helvetica-Bold',
1906              'helveticaI'=>'Helvetica-Oblique',
1907              'helveticaBI'=>'Helvetica-BoldOblique',
1908              'times'=>'Times-Roman',
1909              'timesB'=>'Times-Bold',
1910              'timesI'=>'Times-Italic',
1911              'timesBI'=>'Times-BoldItalic',
1912              'symbol'=>'Symbol',
1913              'zapfdingbats'=>'ZapfDingbats'
1914          );
1915          // set scale factor
1916          $this->setPageUnit($unit);
1917          // set page format and orientation
1918          $this->setPageFormat($format, $orientation);
1919          // page margins (1 cm)
1920          $margin = 28.35 / $this->k;
1921          $this->SetMargins($margin, $margin);
1922          $this->clMargin = $this->lMargin;
1923          $this->crMargin = $this->rMargin;
1924          // internal cell padding
1925          $cpadding = $margin / 10;
1926          $this->setCellPaddings($cpadding, 0, $cpadding, 0);
1927          // cell margins
1928          $this->setCellMargins(0, 0, 0, 0);
1929          // line width (0.2 mm)
1930          $this->LineWidth = 0.57 / $this->k;
1931          $this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
1932          $this->linestyleCap = '0 J';
1933          $this->linestyleJoin = '0 j';
1934          $this->linestyleDash = '[] 0 d';
1935          // automatic page break
1936          $this->SetAutoPageBreak(true, (2 * $margin));
1937          // full width display mode
1938          $this->SetDisplayMode('fullwidth');
1939          // compression
1940          $this->SetCompression();
1941          // set default PDF version number
1942          $this->setPDFVersion();
1943          $this->tcpdflink = true;
1944          $this->encoding = $encoding;
1945          $this->HREF = array();
1946          $this->getFontsList();
1947          $this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
1948          $this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
1949          $this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
1950          $this->extgstates = array();
1951          $this->setTextShadow();
1952          // signature
1953          $this->sign = false;
1954          $this->tsa_timestamp = false;
1955          $this->tsa_data = array();
1956          $this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
1957          $this->empty_signature_appearance = array();
1958          // user's rights
1959          $this->ur['enabled'] = false;
1960          $this->ur['document'] = '/FullSave';
1961          $this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
1962          $this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
1963          $this->ur['signature'] = '/Modify';
1964          $this->ur['ef'] = '/Create/Delete/Modify/Import';
1965          $this->ur['formex'] = '';
1966          // set default JPEG quality
1967          $this->jpeg_quality = 75;
1968          // initialize some settings
1969          TCPDF_FONTS::utf8Bidi(array(''), '', false, $this->isunicode, $this->CurrentFont);
1970          // set default font
1971          $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
1972          $this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1973          $this->setFooterFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1974          // check if PCRE Unicode support is enabled
1975          if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
1976              // PCRE unicode support is turned ON
1977              // \s     : any whitespace character
1978              // \p{Z}  : any separator
1979              // \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
1980              // \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
1981              //$this->setSpacesRE('/(?!\xa0)[\s\p{Z}\p{Lo}]/u');
1982              $this->setSpacesRE('/(?!\xa0)[\s\p{Z}]/u');
1983          } else {
1984              // PCRE unicode support is turned OFF
1985              $this->setSpacesRE('/[^\S\xa0]/');
1986          }
1987          $this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1988          // set document creation and modification timestamp
1989          $this->doc_creation_timestamp = time();
1990          $this->doc_modification_timestamp = $this->doc_creation_timestamp;
1991          // get default graphic vars
1992          $this->default_graphic_vars = $this->getGraphicVars();
1993          $this->header_xobj_autoreset = false;
1994          $this->custom_xmp = '';
1995      }
1996  
1997      /**
1998       * Default destructor.
1999       * @public
2000       * @since 1.53.0.TC016
2001       */
2002  	public function __destruct() {
2003          // restore internal encoding
2004          if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) {
2005              mb_internal_encoding($this->internal_encoding);
2006          }
2007          // unset all class variables
2008          $this->_destroy(true);
2009      }
2010  
2011      /**
2012       * Set the units of measure for the document.
2013       * @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.
2014       * @public
2015       * @since 3.0.015 (2008-06-06)
2016       */
2017  	public function setPageUnit($unit) {
2018          $unit = strtolower($unit);
2019          //Set scale factor
2020          switch ($unit) {
2021              // points
2022              case 'px':
2023              case 'pt': {
2024                  $this->k = 1;
2025                  break;
2026              }
2027              // millimeters
2028              case 'mm': {
2029                  $this->k = $this->dpi / 25.4;
2030                  break;
2031              }
2032              // centimeters
2033              case 'cm': {
2034                  $this->k = $this->dpi / 2.54;
2035                  break;
2036              }
2037              // inches
2038              case 'in': {
2039                  $this->k = $this->dpi;
2040                  break;
2041              }
2042              // unsupported unit
2043              default : {
2044                  $this->Error('Incorrect unit: '.$unit);
2045                  break;
2046              }
2047          }
2048          $this->pdfunit = $unit;
2049          if (isset($this->CurOrientation)) {
2050              $this->setPageOrientation($this->CurOrientation);
2051          }
2052      }
2053  
2054      /**
2055       * Change the format of the current page
2056       * @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>
2057       * <li>['format'] = page format name (one of the above);</li>
2058       * <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>
2059       * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2060       * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2061       * <li>['MediaBox']['llx'] : lower-left x coordinate</li>
2062       * <li>['MediaBox']['lly'] : lower-left y coordinate</li>
2063       * <li>['MediaBox']['urx'] : upper-right x coordinate</li>
2064       * <li>['MediaBox']['ury'] : upper-right y coordinate</li>
2065       * <li>['CropBox'] : the visible region of default user space:</li>
2066       * <li>['CropBox']['llx'] : lower-left x coordinate</li>
2067       * <li>['CropBox']['lly'] : lower-left y coordinate</li>
2068       * <li>['CropBox']['urx'] : upper-right x coordinate</li>
2069       * <li>['CropBox']['ury'] : upper-right y coordinate</li>
2070       * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2071       * <li>['BleedBox']['llx'] : lower-left x coordinate</li>
2072       * <li>['BleedBox']['lly'] : lower-left y coordinate</li>
2073       * <li>['BleedBox']['urx'] : upper-right x coordinate</li>
2074       * <li>['BleedBox']['ury'] : upper-right y coordinate</li>
2075       * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2076       * <li>['TrimBox']['llx'] : lower-left x coordinate</li>
2077       * <li>['TrimBox']['lly'] : lower-left y coordinate</li>
2078       * <li>['TrimBox']['urx'] : upper-right x coordinate</li>
2079       * <li>['TrimBox']['ury'] : upper-right y coordinate</li>
2080       * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2081       * <li>['ArtBox']['llx'] : lower-left x coordinate</li>
2082       * <li>['ArtBox']['lly'] : lower-left y coordinate</li>
2083       * <li>['ArtBox']['urx'] : upper-right x coordinate</li>
2084       * <li>['ArtBox']['ury'] : upper-right y coordinate</li>
2085       * <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>
2086       * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2087       * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2088       * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2089       * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2090       * <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>
2091       * <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>
2092       * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2093       * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2094       * <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>
2095       * <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>
2096       * <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>
2097       * <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>
2098       * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2099       * </ul>
2100       * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul>
2101       * <li>P or Portrait (default)</li>
2102       * <li>L or Landscape</li>
2103       * <li>'' (empty string) for automatic orientation</li>
2104       * </ul>
2105       * @protected
2106       * @since 3.0.015 (2008-06-06)
2107       * @see getPageSizeFromFormat()
2108       */
2109  	protected function setPageFormat($format, $orientation='P') {
2110          if (!empty($format) AND isset($this->pagedim[$this->page])) {
2111              // remove inherited values
2112              unset($this->pagedim[$this->page]);
2113          }
2114          if (is_string($format)) {
2115              // get page measures from format name
2116              $pf = TCPDF_STATIC::getPageSizeFromFormat($format);
2117              $this->fwPt = $pf[0];
2118              $this->fhPt = $pf[1];
2119          } else {
2120              // the boundaries of the physical medium on which the page shall be displayed or printed
2121              if (isset($format['MediaBox'])) {
2122                  $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);
2123                  $this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2124                  $this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2125              } else {
2126                  if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2127                      $pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2128                  } else {
2129                      if (!isset($format['format'])) {
2130                          // default value
2131                          $format['format'] = 'A4';
2132                      }
2133                      $pf = TCPDF_STATIC::getPageSizeFromFormat($format['format']);
2134                  }
2135                  $this->fwPt = $pf[0];
2136                  $this->fhPt = $pf[1];
2137                  $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2138              }
2139              // the visible region of default user space
2140              if (isset($format['CropBox'])) {
2141                  $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);
2142              }
2143              // the region to which the contents of the page shall be clipped when output in a production environment
2144              if (isset($format['BleedBox'])) {
2145                  $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);
2146              }
2147              // the intended dimensions of the finished page after trimming
2148              if (isset($format['TrimBox'])) {
2149                  $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);
2150              }
2151              // the page's meaningful content (including potential white space)
2152              if (isset($format['ArtBox'])) {
2153                  $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);
2154              }
2155              // specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2156              if (isset($format['BoxColorInfo'])) {
2157                  $this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2158              }
2159              if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2160                  // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2161                  $this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2162              }
2163              if (isset($format['PZ'])) {
2164                  // The page's preferred zoom (magnification) factor
2165                  $this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2166              }
2167              if (isset($format['trans'])) {
2168                  // The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2169                  if (isset($format['trans']['Dur'])) {
2170                      // The page's display duration
2171                      $this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2172                  }
2173                  $stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2174                  if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2175                      // The transition style that shall be used when moving to this page from another during a presentation
2176                      $this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2177                      $valid_effect = array('Split', 'Blinds');
2178                      $valid_vals = array('H', 'V');
2179                      if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2180                          $this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2181                      }
2182                      $valid_effect = array('Split', 'Box', 'Fly');
2183                      $valid_vals = array('I', 'O');
2184                      if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2185                          $this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2186                      }
2187                      $valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2188                      if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2189                          if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2190                              OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2191                              OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2192                              $this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2193                          }
2194                      }
2195                      if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2196                          $this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2197                      }
2198                      if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2199                          $this->pagedim[$this->page]['trans']['B'] = 'true';
2200                      }
2201                  } else {
2202                      $this->pagedim[$this->page]['trans']['S'] = 'R';
2203                  }
2204                  if (isset($format['trans']['D'])) {
2205                      // The duration of the transition effect, in seconds
2206                      $this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2207                  } else {
2208                      $this->pagedim[$this->page]['trans']['D'] = 1;
2209                  }
2210              }
2211          }
2212          $this->setPageOrientation($orientation);
2213      }
2214  
2215      /**
2216       * Set page orientation.
2217       * @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>
2218       * @param $autopagebreak (boolean) Boolean indicating if auto-page-break mode should be on or off.
2219       * @param $bottommargin (float) bottom margin of the page.
2220       * @public
2221       * @since 3.0.015 (2008-06-06)
2222       */
2223  	public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
2224          if (!isset($this->pagedim[$this->page]['MediaBox'])) {
2225              // the boundaries of the physical medium on which the page shall be displayed or printed
2226              $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2227          }
2228          if (!isset($this->pagedim[$this->page]['CropBox'])) {
2229              // the visible region of default user space
2230              $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);
2231          }
2232          if (!isset($this->pagedim[$this->page]['BleedBox'])) {
2233              // the region to which the contents of the page shall be clipped when output in a production environment
2234              $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);
2235          }
2236          if (!isset($this->pagedim[$this->page]['TrimBox'])) {
2237              // the intended dimensions of the finished page after trimming
2238              $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);
2239          }
2240          if (!isset($this->pagedim[$this->page]['ArtBox'])) {
2241              // the page's meaningful content (including potential white space)
2242              $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);
2243          }
2244          if (!isset($this->pagedim[$this->page]['Rotate'])) {
2245              // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2246              $this->pagedim[$this->page]['Rotate'] = 0;
2247          }
2248          if (!isset($this->pagedim[$this->page]['PZ'])) {
2249              // The page's preferred zoom (magnification) factor
2250              $this->pagedim[$this->page]['PZ'] = 1;
2251          }
2252          if ($this->fwPt > $this->fhPt) {
2253              // landscape
2254              $default_orientation = 'L';
2255          } else {
2256              // portrait
2257              $default_orientation = 'P';
2258          }
2259          $valid_orientations = array('P', 'L');
2260          if (empty($orientation)) {
2261              $orientation = $default_orientation;
2262          } else {
2263              $orientation = strtoupper($orientation[0]);
2264          }
2265          if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2266              $this->CurOrientation = $orientation;
2267              $this->wPt = $this->fhPt;
2268              $this->hPt = $this->fwPt;
2269          } else {
2270              $this->CurOrientation = $default_orientation;
2271              $this->wPt = $this->fwPt;
2272              $this->hPt = $this->fhPt;
2273          }
2274          if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
2275              // swap X and Y coordinates (change page orientation)
2276              $this->pagedim = TCPDF_STATIC::swapPageBoxCoordinates($this->page, $this->pagedim);
2277          }
2278          $this->w = ($this->wPt / $this->k);
2279          $this->h = ($this->hPt / $this->k);
2280          if (TCPDF_STATIC::empty_string($autopagebreak)) {
2281              if (isset($this->AutoPageBreak)) {
2282                  $autopagebreak = $this->AutoPageBreak;
2283              } else {
2284                  $autopagebreak = true;
2285              }
2286          }
2287          if (TCPDF_STATIC::empty_string($bottommargin)) {
2288              if (isset($this->bMargin)) {
2289                  $bottommargin = $this->bMargin;
2290              } else {
2291                  // default value = 2 cm
2292                  $bottommargin = 2 * 28.35 / $this->k;
2293              }
2294          }
2295          $this->SetAutoPageBreak($autopagebreak, $bottommargin);
2296          // store page dimensions
2297          $this->pagedim[$this->page]['w'] = $this->wPt;
2298          $this->pagedim[$this->page]['h'] = $this->hPt;
2299          $this->pagedim[$this->page]['wk'] = $this->w;
2300          $this->pagedim[$this->page]['hk'] = $this->h;
2301          $this->pagedim[$this->page]['tm'] = $this->tMargin;
2302          $this->pagedim[$this->page]['bm'] = $bottommargin;
2303          $this->pagedim[$this->page]['lm'] = $this->lMargin;
2304          $this->pagedim[$this->page]['rm'] = $this->rMargin;
2305          $this->pagedim[$this->page]['pb'] = $autopagebreak;
2306          $this->pagedim[$this->page]['or'] = $this->CurOrientation;
2307          $this->pagedim[$this->page]['olm'] = $this->original_lMargin;
2308          $this->pagedim[$this->page]['orm'] = $this->original_rMargin;
2309      }
2310  
2311      /**
2312       * Set regular expression to detect withespaces or word separators.
2313       * The pattern delimiter must be the forward-slash character "/".
2314       * Some example patterns are:
2315       * <pre>
2316       * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
2317       * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
2318       * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
2319       * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
2320       *      \s     : any whitespace character
2321       *      \p{Z}  : any separator
2322       *      \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2323       *      \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2324       * </pre>
2325       * @param $re (string) regular expression (leave empty for default).
2326       * @public
2327       * @since 4.6.016 (2009-06-15)
2328       */
2329  	public function setSpacesRE($re='/[^\S\xa0]/') {
2330          $this->re_spaces = $re;
2331          $re_parts = explode('/', $re);
2332          // get pattern parts
2333          $this->re_space = array();
2334          if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2335              $this->re_space['p'] = $re_parts[1];
2336          } else {
2337              $this->re_space['p'] = '[\s]';
2338          }
2339          // set pattern modifiers
2340          if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2341              $this->re_space['m'] = $re_parts[2];
2342          } else {
2343              $this->re_space['m'] = '';
2344          }
2345      }
2346  
2347      /**
2348       * Enable or disable Right-To-Left language mode
2349       * @param $enable (Boolean) if true enable Right-To-Left language mode.
2350       * @param $resetx (Boolean) if true reset the X position on direction change.
2351       * @public
2352       * @since 2.0.000 (2008-01-03)
2353       */
2354  	public function setRTL($enable, $resetx=true) {
2355          $enable = $enable ? true : false;
2356          $resetx = ($resetx AND ($enable != $this->rtl));
2357          $this->rtl = $enable;
2358          $this->tmprtl = false;
2359          if ($resetx) {
2360              $this->Ln(0);
2361          }
2362      }
2363  
2364      /**
2365       * Return the RTL status
2366       * @return boolean
2367       * @public
2368       * @since 4.0.012 (2008-07-24)
2369       */
2370  	public function getRTL() {
2371          return $this->rtl;
2372      }
2373  
2374      /**
2375       * Force temporary RTL language direction
2376       * @param $mode (mixed) can be false, 'L' for LTR or 'R' for RTL
2377       * @public
2378       * @since 2.1.000 (2008-01-09)
2379       */
2380  	public function setTempRTL($mode) {
2381          $newmode = false;
2382          switch (strtoupper($mode)) {
2383              case 'LTR':
2384              case 'L': {
2385                  if ($this->rtl) {
2386                      $newmode = 'L';
2387                  }
2388                  break;
2389              }
2390              case 'RTL':
2391              case 'R': {
2392                  if (!$this->rtl) {
2393                      $newmode = 'R';
2394                  }
2395                  break;
2396              }
2397              case false:
2398              default: {
2399                  $newmode = false;
2400                  break;
2401              }
2402          }
2403          $this->tmprtl = $newmode;
2404      }
2405  
2406      /**
2407       * Return the current temporary RTL status
2408       * @return boolean
2409       * @public
2410       * @since 4.8.014 (2009-11-04)
2411       */
2412  	public function isRTLTextDir() {
2413          return ($this->rtl OR ($this->tmprtl == 'R'));
2414      }
2415  
2416      /**
2417       * Set the last cell height.
2418       * @param $h (float) cell height.
2419       * @author Nicola Asuni
2420       * @public
2421       * @since 1.53.0.TC034
2422       */
2423  	public function setLastH($h) {
2424          $this->lasth = $h;
2425      }
2426  
2427      /**
2428       * Return the cell height
2429       * @param $fontsize (int) Font size in internal units
2430       * @param $padding (boolean) If true add cell padding
2431       * @public
2432       */
2433  	public function getCellHeight($fontsize, $padding=TRUE) {
2434          $height = ($fontsize * $this->cell_height_ratio);
2435          if ($padding) {
2436              $height += ($this->cell_padding['T'] + $this->cell_padding['B']);
2437          }
2438          return round($height, 6);
2439      }
2440  
2441      /**
2442       * Reset the last cell height.
2443       * @public
2444       * @since 5.9.000 (2010-10-03)
2445       */
2446  	public function resetLastH() {
2447          $this->lasth = $this->getCellHeight($this->FontSize);
2448      }
2449  
2450      /**
2451       * Get the last cell height.
2452       * @return last cell height
2453       * @public
2454       * @since 4.0.017 (2008-08-05)
2455       */
2456  	public function getLastH() {
2457          return $this->lasth;
2458      }
2459  
2460      /**
2461       * Set the adjusting factor to convert pixels to user units.
2462       * @param $scale (float) adjusting factor to convert pixels to user units.
2463       * @author Nicola Asuni
2464       * @public
2465       * @since 1.5.2
2466       */
2467  	public function setImageScale($scale) {
2468          $this->imgscale = $scale;
2469      }
2470  
2471      /**
2472       * Returns the adjusting factor to convert pixels to user units.
2473       * @return float adjusting factor to convert pixels to user units.
2474       * @author Nicola Asuni
2475       * @public
2476       * @since 1.5.2
2477       */
2478  	public function getImageScale() {
2479          return $this->imgscale;
2480      }
2481  
2482      /**
2483       * Returns an array of page dimensions:
2484       * <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>
2485       * @param $pagenum (int) page number (empty = current page)
2486       * @return array of page dimensions.
2487       * @author Nicola Asuni
2488       * @public
2489       * @since 4.5.027 (2009-03-16)
2490       */
2491  	public function getPageDimensions($pagenum='') {
2492          if (empty($pagenum)) {
2493              $pagenum = $this->page;
2494          }
2495          return $this->pagedim[$pagenum];
2496      }
2497  
2498      /**
2499       * Returns the page width in units.
2500       * @param $pagenum (int) page number (empty = current page)
2501       * @return int page width.
2502       * @author Nicola Asuni
2503       * @public
2504       * @since 1.5.2
2505       * @see getPageDimensions()
2506       */
2507  	public function getPageWidth($pagenum='') {
2508          if (empty($pagenum)) {
2509              return $this->w;
2510          }
2511          return $this->pagedim[$pagenum]['w'];
2512      }
2513  
2514      /**
2515       * Returns the page height in units.
2516       * @param $pagenum (int) page number (empty = current page)
2517       * @return int page height.
2518       * @author Nicola Asuni
2519       * @public
2520       * @since 1.5.2
2521       * @see getPageDimensions()
2522       */
2523  	public function getPageHeight($pagenum='') {
2524          if (empty($pagenum)) {
2525              return $this->h;
2526          }
2527          return $this->pagedim[$pagenum]['h'];
2528      }
2529  
2530      /**
2531       * Returns the page break margin.
2532       * @param $pagenum (int) page number (empty = current page)
2533       * @return int page break margin.
2534       * @author Nicola Asuni
2535       * @public
2536       * @since 1.5.2
2537       * @see getPageDimensions()
2538       */
2539  	public function getBreakMargin($pagenum='') {
2540          if (empty($pagenum)) {
2541              return $this->bMargin;
2542          }
2543          return $this->pagedim[$pagenum]['bm'];
2544      }
2545  
2546      /**
2547       * Returns the scale factor (number of points in user unit).
2548       * @return int scale factor.
2549       * @author Nicola Asuni
2550       * @public
2551       * @since 1.5.2
2552       */
2553  	public function getScaleFactor() {
2554          return $this->k;
2555      }
2556  
2557      /**
2558       * Defines the left, top and right margins.
2559       * @param $left (float) Left margin.
2560       * @param $top (float) Top margin.
2561       * @param $right (float) Right margin. Default value is the left one.
2562       * @param $keepmargins (boolean) if true overwrites the default page margins
2563       * @public
2564       * @since 1.0
2565       * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
2566       */
2567  	public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
2568          //Set left, top and right margins
2569          $this->lMargin = $left;
2570          $this->tMargin = $top;
2571          if ($right == -1) {
2572              $right = $left;
2573          }
2574          $this->rMargin = $right;
2575          if ($keepmargins) {
2576              // overwrite original values
2577              $this->original_lMargin = $this->lMargin;
2578              $this->original_rMargin = $this->rMargin;
2579          }
2580      }
2581  
2582      /**
2583       * 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.
2584       * @param $margin (float) The margin.
2585       * @public
2586       * @since 1.4
2587       * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2588       */
2589  	public function SetLeftMargin($margin) {
2590          //Set left margin
2591          $this->lMargin = $margin;
2592          if (($this->page > 0) AND ($this->x < $margin)) {
2593              $this->x = $margin;
2594          }
2595      }
2596  
2597      /**
2598       * Defines the top margin. The method can be called before creating the first page.
2599       * @param $margin (float) The margin.
2600       * @public
2601       * @since 1.5
2602       * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2603       */
2604  	public function SetTopMargin($margin) {
2605          //Set top margin
2606          $this->tMargin = $margin;
2607          if (($this->page > 0) AND ($this->y < $margin)) {
2608              $this->y = $margin;
2609          }
2610      }
2611  
2612      /**
2613       * Defines the right margin. The method can be called before creating the first page.
2614       * @param $margin (float) The margin.
2615       * @public
2616       * @since 1.5
2617       * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
2618       */
2619  	public function SetRightMargin($margin) {
2620          $this->rMargin = $margin;
2621          if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
2622              $this->x = $this->w - $margin;
2623          }
2624      }
2625  
2626      /**
2627       * Set the same internal Cell padding for top, right, bottom, left-
2628       * @param $pad (float) internal padding.
2629       * @public
2630       * @since 2.1.000 (2008-01-09)
2631       * @see getCellPaddings(), setCellPaddings()
2632       */
2633  	public function SetCellPadding($pad) {
2634          if ($pad >= 0) {
2635              $this->cell_padding['L'] = $pad;
2636              $this->cell_padding['T'] = $pad;
2637              $this->cell_padding['R'] = $pad;
2638              $this->cell_padding['B'] = $pad;
2639          }
2640      }
2641  
2642      /**
2643       * Set the internal Cell paddings.
2644       * @param $left (float) left padding
2645       * @param $top (float) top padding
2646       * @param $right (float) right padding
2647       * @param $bottom (float) bottom padding
2648       * @public
2649       * @since 5.9.000 (2010-10-03)
2650       * @see getCellPaddings(), SetCellPadding()
2651       */
2652  	public function setCellPaddings($left='', $top='', $right='', $bottom='') {
2653          if (($left !== '') AND ($left >= 0)) {
2654              $this->cell_padding['L'] = $left;
2655          }
2656          if (($top !== '') AND ($top >= 0)) {
2657              $this->cell_padding['T'] = $top;
2658          }
2659          if (($right !== '') AND ($right >= 0)) {
2660              $this->cell_padding['R'] = $right;
2661          }
2662          if (($bottom !== '') AND ($bottom >= 0)) {
2663              $this->cell_padding['B'] = $bottom;
2664          }
2665      }
2666  
2667      /**
2668       * Get the internal Cell padding array.
2669       * @return array of padding values
2670       * @public
2671       * @since 5.9.000 (2010-10-03)
2672       * @see setCellPaddings(), SetCellPadding()
2673       */
2674  	public function getCellPaddings() {
2675          return $this->cell_padding;
2676      }
2677  
2678      /**
2679       * Set the internal Cell margins.
2680       * @param $left (float) left margin
2681       * @param $top (float) top margin
2682       * @param $right (float) right margin
2683       * @param $bottom (float) bottom margin
2684       * @public
2685       * @since 5.9.000 (2010-10-03)
2686       * @see getCellMargins()
2687       */
2688  	public function setCellMargins($left='', $top='', $right='', $bottom='') {
2689          if (($left !== '') AND ($left >= 0)) {
2690              $this->cell_margin['L'] = $left;
2691          }
2692          if (($top !== '') AND ($top >= 0)) {
2693              $this->cell_margin['T'] = $top;
2694          }
2695          if (($right !== '') AND ($right >= 0)) {
2696              $this->cell_margin['R'] = $right;
2697          }
2698          if (($bottom !== '') AND ($bottom >= 0)) {
2699              $this->cell_margin['B'] = $bottom;
2700          }
2701      }
2702  
2703      /**
2704       * Get the internal Cell margin array.
2705       * @return array of margin values
2706       * @public
2707       * @since 5.9.000 (2010-10-03)
2708       * @see setCellMargins()
2709       */
2710  	public function getCellMargins() {
2711          return $this->cell_margin;
2712      }
2713  
2714      /**
2715       * Adjust the internal Cell padding array to take account of the line width.
2716       * @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)))
2717       * @return array of adjustments
2718       * @public
2719       * @since 5.9.000 (2010-10-03)
2720       */
2721  	protected function adjustCellPadding($brd=0) {
2722          if (empty($brd)) {
2723              return;
2724          }
2725          if (is_string($brd)) {
2726              // convert string to array
2727              $slen = strlen($brd);
2728              $newbrd = array();
2729              for ($i = 0; $i < $slen; ++$i) {
2730                  $newbrd[$brd[$i]] = true;
2731              }
2732              $brd = $newbrd;
2733          } elseif (($brd === 1) OR ($brd === true) OR (is_numeric($brd) AND (intval($brd) > 0))) {
2734              $brd = array('LRTB' => true);
2735          }
2736          if (!is_array($brd)) {
2737              return;
2738          }
2739          // store current cell padding
2740          $cp = $this->cell_padding;
2741          // select border mode
2742          if (isset($brd['mode'])) {
2743              $mode = $brd['mode'];
2744              unset($brd['mode']);
2745          } else {
2746              $mode = 'normal';
2747          }
2748          // process borders
2749          foreach ($brd as $border => $style) {
2750              $line_width = $this->LineWidth;
2751              if (is_array($style) AND isset($style['width'])) {
2752                  // get border width
2753                  $line_width = $style['width'];
2754              }
2755              $adj = 0; // line width inside the cell
2756              switch ($mode) {
2757                  case 'ext': {
2758                      $adj = 0;
2759                      break;
2760                  }
2761                  case 'int': {
2762                      $adj = $line_width;
2763                      break;
2764                  }
2765                  case 'normal':
2766                  default: {
2767                      $adj = ($line_width / 2);
2768                      break;
2769                  }
2770              }
2771              // correct internal cell padding if required to avoid overlap between text and lines
2772              if ((strpos($border,'T') !== false) AND ($this->cell_padding['T'] < $adj)) {
2773                  $this->cell_padding['T'] = $adj;
2774              }
2775              if ((strpos($border,'R') !== false) AND ($this->cell_padding['R'] < $adj)) {
2776                  $this->cell_padding['R'] = $adj;
2777              }
2778              if ((strpos($border,'B') !== false) AND ($this->cell_padding['B'] < $adj)) {
2779                  $this->cell_padding['B'] = $adj;
2780              }
2781              if ((strpos($border,'L') !== false) AND ($this->cell_padding['L'] < $adj)) {
2782                  $this->cell_padding['L'] = $adj;
2783              }
2784          }
2785          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']));
2786      }
2787  
2788      /**
2789       * 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.
2790       * @param $auto (boolean) Boolean indicating if mode should be on or off.
2791       * @param $margin (float) Distance from the bottom of the page.
2792       * @public
2793       * @since 1.0
2794       * @see Cell(), MultiCell(), AcceptPageBreak()
2795       */
2796  	public function SetAutoPageBreak($auto, $margin=0) {
2797          $this->AutoPageBreak = $auto ? true : false;
2798          $this->bMargin = $margin;
2799          $this->PageBreakTrigger = $this->h - $margin;
2800      }
2801  
2802      /**
2803       * Return the auto-page-break mode (true or false).
2804       * @return boolean auto-page-break mode
2805       * @public
2806       * @since 5.9.088
2807       */
2808  	public function getAutoPageBreak() {
2809          return $this->AutoPageBreak;
2810      }
2811  
2812      /**
2813       * Defines the way the document is to be displayed by the viewer.
2814       * @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>
2815       * @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>
2816       * @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>
2817       * @public
2818       * @since 1.2
2819       */
2820  	public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
2821          if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
2822              $this->ZoomMode = $zoom;
2823          } else {
2824              $this->Error('Incorrect zoom display mode: '.$zoom);
2825          }
2826          $this->LayoutMode = TCPDF_STATIC::getPageLayoutMode($layout);
2827          $this->PageMode = TCPDF_STATIC::getPageMode($mode);
2828      }
2829  
2830      /**
2831       * 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.
2832       * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
2833       * @param $compress (boolean) Boolean indicating if compression must be enabled.
2834       * @public
2835       * @since 1.4
2836       */
2837  	public function SetCompression($compress=true) {
2838          if (function_exists('gzcompress')) {
2839              $this->compress = $compress ? true : false;
2840          } else {
2841              $this->compress = false;
2842          }
2843      }
2844  
2845      /**
2846       * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
2847       * @param $mode (boolean) If true force sRGB output intent.
2848       * @public
2849       * @since 5.9.121 (2011-09-28)
2850       */
2851  	public function setSRGBmode($mode=false) {
2852          $this->force_srgb = $mode ? true : false;
2853      }
2854  
2855      /**
2856       * Turn on/off Unicode mode for document information dictionary (meta tags).
2857       * This has effect only when unicode mode is set to false.
2858       * @param $unicode (boolean) if true set the meta information in Unicode
2859       * @since 5.9.027 (2010-12-01)
2860       * @public
2861       */
2862  	public function SetDocInfoUnicode($unicode=true) {
2863          $this->docinfounicode = $unicode ? true : false;
2864      }
2865  
2866      /**
2867       * Defines the title of the document.
2868       * @param $title (string) The title.
2869       * @public
2870       * @since 1.2
2871       * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
2872       */
2873  	public function SetTitle($title) {
2874          $this->title = $title;
2875      }
2876  
2877      /**
2878       * Defines the subject of the document.
2879       * @param $subject (string) The subject.
2880       * @public
2881       * @since 1.2
2882       * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
2883       */
2884  	public function SetSubject($subject) {
2885          $this->subject = $subject;
2886      }
2887  
2888      /**
2889       * Defines the author of the document.
2890       * @param $author (string) The name of the author.
2891       * @public
2892       * @since 1.2
2893       * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
2894       */
2895  	public function SetAuthor($author) {
2896          $this->author = $author;
2897      }
2898  
2899      /**
2900       * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
2901       * @param $keywords (string) The list of keywords.
2902       * @public
2903       * @since 1.2
2904       * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
2905       */
2906  	public function SetKeywords($keywords) {
2907          $this->keywords = $keywords;
2908      }
2909  
2910      /**
2911       * Defines the creator of the document. This is typically the name of the application that generates the PDF.
2912       * @param $creator (string) The name of the creator.
2913       * @public
2914       * @since 1.2
2915       * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
2916       */
2917  	public function SetCreator($creator) {
2918          $this->creator = $creator;
2919      }
2920  
2921      /**
2922       * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
2923       * @param $msg (string) The error message
2924       * @public
2925       * @since 1.0
2926       */
2927  	public function Error($msg) {
2928          // unset all class variables
2929          $this->_destroy(true);
2930          if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
2931              die('<strong>TCPDF ERROR: </strong>'.$msg);
2932          } else {
2933              throw new Exception('TCPDF ERROR: '.$msg);
2934          }
2935      }
2936  
2937      /**
2938       * This method begins the generation of the PDF document.
2939       * It is not necessary to call it explicitly because AddPage() does it automatically.
2940       * Note: no page is created by this method
2941       * @public
2942       * @since 1.0
2943       * @see AddPage(), Close()
2944       */
2945  	public function Open() {
2946          $this->state = 1;
2947      }
2948  
2949      /**
2950       * Terminates the PDF document.
2951       * It is not necessary to call this method explicitly because Output() does it automatically.
2952       * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
2953       * @public
2954       * @since 1.0
2955       * @see Open(), Output()
2956       */
2957  	public function Close() {
2958          if ($this->state == 3) {
2959              return;
2960          }
2961          if ($this->page == 0) {
2962              $this->AddPage();
2963          }
2964          $this->endLayer();
2965          if ($this->tcpdflink) {
2966              // save current graphic settings
2967              $gvars = $this->getGraphicVars();
2968              $this->setEqualColumns();
2969              $this->lastpage(true);
2970              $this->SetAutoPageBreak(false);
2971              $this->x = 0;
2972              $this->y = $this->h - (1 / $this->k);
2973              $this->lMargin = 0;
2974              $this->_outSaveGraphicsState();
2975              $font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
2976              $this->SetFont($font, '', 1);
2977              $this->setTextRenderingMode(0, false, false);
2978              $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";
2979              $lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
2980              $this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
2981              $this->_outRestoreGraphicsState();
2982              // restore graphic settings
2983              $this->setGraphicVars($gvars);
2984          }
2985          // close page
2986          $this->endPage();
2987          // close document
2988          $this->_enddoc();
2989          // unset all class variables (except critical ones)
2990          $this->_destroy(false);
2991      }
2992  
2993      /**
2994       * Move pointer at the specified document page and update page dimensions.
2995       * @param $pnum (int) page number (1 ... numpages)
2996       * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
2997       * @public
2998       * @since 2.1.000 (2008-01-07)
2999       * @see getPage(), lastpage(), getNumPages()
3000       */
3001  	public function setPage($pnum, $resetmargins=false) {
3002          if (($pnum == $this->page) AND ($this->state == 2)) {
3003              return;
3004          }
3005          if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3006              $this->state = 2;
3007              // save current graphic settings
3008              //$gvars = $this->getGraphicVars();
3009              $oldpage = $this->page;
3010              $this->page = $pnum;
3011              $this->wPt = $this->pagedim[$this->page]['w'];
3012              $this->hPt = $this->pagedim[$this->page]['h'];
3013              $this->w = $this->pagedim[$this->page]['wk'];
3014              $this->h = $this->pagedim[$this->page]['hk'];
3015              $this->tMargin = $this->pagedim[$this->page]['tm'];
3016              $this->bMargin = $this->pagedim[$this->page]['bm'];
3017              $this->original_lMargin = $this->pagedim[$this->page]['olm'];
3018              $this->original_rMargin = $this->pagedim[$this->page]['orm'];
3019              $this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3020              $this->CurOrientation = $this->pagedim[$this->page]['or'];
3021              $this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3022              // restore graphic settings
3023              //$this->setGraphicVars($gvars);
3024              if ($resetmargins) {
3025                  $this->lMargin = $this->pagedim[$this->page]['olm'];
3026                  $this->rMargin = $this->pagedim[$this->page]['orm'];
3027                  $this->SetY($this->tMargin);
3028              } else {
3029                  // account for booklet mode
3030                  if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3031                      $deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3032                      $this->lMargin += $deltam;
3033                      $this->rMargin -= $deltam;
3034                  }
3035              }
3036          } else {
3037              $this->Error('Wrong page number on setPage() function: '.$pnum);
3038          }
3039      }
3040  
3041      /**
3042       * Reset pointer to the last document page.
3043       * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
3044       * @public
3045       * @since 2.0.000 (2008-01-04)
3046       * @see setPage(), getPage(), getNumPages()
3047       */
3048  	public function lastPage($resetmargins=false) {
3049          $this->setPage($this->getNumPages(), $resetmargins);
3050      }
3051  
3052      /**
3053       * Get current document page number.
3054       * @return int page number
3055       * @public
3056       * @since 2.1.000 (2008-01-07)
3057       * @see setPage(), lastpage(), getNumPages()
3058       */
3059  	public function getPage() {
3060          return $this->page;
3061      }
3062  
3063      /**
3064       * Get the total number of insered pages.
3065       * @return int number of pages
3066       * @public
3067       * @since 2.1.000 (2008-01-07)
3068       * @see setPage(), getPage(), lastpage()
3069       */
3070  	public function getNumPages() {
3071          return $this->numpages;
3072      }
3073  
3074      /**
3075       * Adds a new TOC (Table Of Content) page to the document.
3076       * @param $orientation (string) page orientation.
3077       * @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().
3078       * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3079       * @public
3080       * @since 5.0.001 (2010-05-06)
3081       * @see AddPage(), startPage(), endPage(), endTOCPage()
3082       */
3083  	public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3084          $this->AddPage($orientation, $format, $keepmargins, true);
3085      }
3086  
3087      /**
3088       * Terminate the current TOC (Table Of Content) page
3089       * @public
3090       * @since 5.0.001 (2010-05-06)
3091       * @see AddPage(), startPage(), endPage(), addTOCPage()
3092       */
3093  	public function endTOCPage() {
3094          $this->endPage(true);
3095      }
3096  
3097      /**
3098       * 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).
3099       * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3100       * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3101       * @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().
3102       * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3103       * @param $tocpage (boolean) if true set the tocpage state to true (the added page will be used to display Table Of Content).
3104       * @public
3105       * @since 1.0
3106       * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3107       */
3108  	public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3109          if ($this->inxobj) {
3110              // we are inside an XObject template
3111              return;
3112          }
3113          if (!isset($this->original_lMargin) OR $keepmargins) {
3114              $this->original_lMargin = $this->lMargin;
3115          }
3116          if (!isset($this->original_rMargin) OR $keepmargins) {
3117              $this->original_rMargin = $this->rMargin;
3118          }
3119          // terminate previous page
3120          $this->endPage();
3121          // start new page
3122          $this->startPage($orientation, $format, $tocpage);
3123      }
3124  
3125      /**
3126       * Terminate the current page
3127       * @param $tocpage (boolean) if true set the tocpage state to false (end the page used to display Table Of Content).
3128       * @public
3129       * @since 4.2.010 (2008-11-14)
3130       * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3131       */
3132  	public function endPage($tocpage=false) {
3133          // check if page is already closed
3134          if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3135              return;
3136          }
3137          // print page footer
3138          $this->setFooter();
3139          // close page
3140          $this->_endpage();
3141          // mark page as closed
3142          $this->pageopen[$this->page] = false;
3143          if ($tocpage) {
3144              $this->tocpage = false;
3145          }
3146      }
3147  
3148      /**
3149       * Starts a new page to the document. The page must be closed using the endPage() function.
3150       * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3151       * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3152       * @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().
3153       * @param $tocpage (boolean) if true the page is designated to contain the Table-Of-Content.
3154       * @since 4.2.010 (2008-11-14)
3155       * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3156       * @public
3157       */
3158  	public function startPage($orientation='', $format='', $tocpage=false) {
3159          if ($tocpage) {
3160              $this->tocpage = true;
3161          }
3162          // move page numbers of documents to be attached
3163          if ($this->tocpage) {
3164              // move reference to unexistent pages (used for page attachments)
3165              // adjust outlines
3166              $tmpoutlines = $this->outlines;
3167              foreach ($tmpoutlines as $key => $outline) {
3168                  if (!$outline['f'] AND ($outline['p'] > $this->numpages)) {
3169                      $this->outlines[$key]['p'] = ($outline['p'] + 1);
3170                  }
3171              }
3172              // adjust dests
3173              $tmpdests = $this->dests;
3174              foreach ($tmpdests as $key => $dest) {
3175                  if (!$dest['f'] AND ($dest['p'] > $this->numpages)) {
3176                      $this->dests[$key]['p'] = ($dest['p'] + 1);
3177                  }
3178              }
3179              // adjust links
3180              $tmplinks = $this->links;
3181              foreach ($tmplinks as $key => $link) {
3182                  if (!$link['f'] AND ($link['p'] > $this->numpages)) {
3183                      $this->links[$key]['p'] = ($link['p'] + 1);
3184                  }
3185              }
3186          }
3187          if ($this->numpages > $this->page) {
3188              // this page has been already added
3189              $this->setPage($this->page + 1);
3190              $this->SetY($this->tMargin);
3191              return;
3192          }
3193          // start a new page
3194          if ($this->state == 0) {
3195              $this->Open();
3196          }
3197          ++$this->numpages;
3198          $this->swapMargins($this->booklet);
3199          // save current graphic settings
3200          $gvars = $this->getGraphicVars();
3201          // start new page
3202          $this->_beginpage($orientation, $format);
3203          // mark page as open
3204          $this->pageopen[$this->page] = true;
3205          // restore graphic settings
3206          $this->setGraphicVars($gvars);
3207          // mark this point
3208          $this->setPageMark();
3209          // print page header
3210          $this->setHeader();
3211          // restore graphic settings
3212          $this->setGraphicVars($gvars);
3213          // mark this point
3214          $this->setPageMark();
3215          // print table header (if any)
3216          $this->setTableHeader();
3217          // set mark for empty page check
3218          $this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
3219      }
3220  
3221      /**
3222       * Set start-writing mark on current page stream used to put borders and fills.
3223       * Borders and fills are always created after content and inserted on the position marked by this method.
3224       * This function must be called after calling Image() function for a background image.
3225       * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
3226       * @public
3227       * @since 4.0.016 (2008-07-30)
3228       */
3229  	public function setPageMark() {
3230          $this->intmrk[$this->page] = $this->pagelen[$this->page];
3231          $this->bordermrk[$this->page] = $this->intmrk[$this->page];
3232          $this->setContentMark();
3233      }
3234  
3235      /**
3236       * Set start-writing mark on selected page.
3237       * Borders and fills are always created after content and inserted on the position marked by this method.
3238       * @param $page (int) page number (default is the current page)
3239       * @protected
3240       * @since 4.6.021 (2009-07-20)
3241       */
3242  	protected function setContentMark($page=0) {
3243          if ($page <= 0) {
3244              $page = $this->page;
3245          }
3246          if (isset($this->footerlen[$page])) {
3247              $this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3248          } else {
3249              $this->cntmrk[$page] = $this->pagelen[$page];
3250          }
3251      }
3252  
3253      /**
3254       * Set header data.
3255       * @param $ln (string) header image logo
3256       * @param $lw (string) header image logo width in mm
3257       * @param $ht (string) string to print as title on document header
3258       * @param $hs (string) string to print on document header
3259       * @param $tc (array) RGB array color for text.
3260       * @param $lc (array) RGB array color for line.
3261       * @public
3262       */
3263  	public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
3264          $this->header_logo = $ln;
3265          $this->header_logo_width = $lw;
3266          $this->header_title = $ht;
3267          $this->header_string = $hs;
3268          $this->header_text_color = $tc;
3269          $this->header_line_color = $lc;
3270      }
3271  
3272      /**
3273       * Set footer data.
3274       * @param $tc (array) RGB array color for text.
3275       * @param $lc (array) RGB array color for line.
3276       * @public
3277       */
3278  	public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
3279          $this->footer_text_color = $tc;
3280          $this->footer_line_color = $lc;
3281      }
3282  
3283      /**
3284       * Returns header data:
3285       * <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>
3286       * @return array()
3287       * @public
3288       * @since 4.0.012 (2008-07-24)
3289       */
3290  	public function getHeaderData() {
3291          $ret = array();
3292          $ret['logo'] = $this->header_logo;
3293          $ret['logo_width'] = $this->header_logo_width;
3294          $ret['title'] = $this->header_title;
3295          $ret['string'] = $this->header_string;
3296          $ret['text_color'] = $this->header_text_color;
3297          $ret['line_color'] = $this->header_line_color;
3298          return $ret;
3299      }
3300  
3301      /**
3302       * Set header margin.
3303       * (minimum distance between header and top page margin)
3304       * @param $hm (int) distance in user units
3305       * @public
3306       */
3307  	public function setHeaderMargin($hm=10) {
3308          $this->header_margin = $hm;
3309      }
3310  
3311      /**
3312       * Returns header margin in user units.
3313       * @return float
3314       * @since 4.0.012 (2008-07-24)
3315       * @public
3316       */
3317  	public function getHeaderMargin() {
3318          return $this->header_margin;
3319      }
3320  
3321      /**
3322       * Set footer margin.
3323       * (minimum distance between footer and bottom page margin)
3324       * @param $fm (int) distance in user units
3325       * @public
3326       */
3327  	public function setFooterMargin($fm=10) {
3328          $this->footer_margin = $fm;
3329      }
3330  
3331      /**
3332       * Returns footer margin in user units.
3333       * @return float
3334       * @since 4.0.012 (2008-07-24)
3335       * @public
3336       */
3337  	public function getFooterMargin() {
3338          return $this->footer_margin;
3339      }
3340      /**
3341       * Set a flag to print page header.
3342       * @param $val (boolean) set to true to print the page header (default), false otherwise.
3343       * @public
3344       */
3345  	public function setPrintHeader($val=true) {
3346          $this->print_header = $val ? true : false;
3347      }
3348  
3349      /**
3350       * Set a flag to print page footer.
3351       * @param $val (boolean) set to true to print the page footer (default), false otherwise.
3352       * @public
3353       */
3354  	public function setPrintFooter($val=true) {
3355          $this->print_footer = $val ? true : false;
3356      }
3357  
3358      /**
3359       * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
3360       * @return float
3361       * @public
3362       */
3363  	public function getImageRBX() {
3364          return $this->img_rb_x;
3365      }
3366  
3367      /**
3368       * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
3369       * @return float
3370       * @public
3371       */
3372  	public function getImageRBY() {
3373          return $this->img_rb_y;
3374      }
3375  
3376      /**
3377       * Reset the xobject template used by Header() method.
3378       * @public
3379       */
3380  	public function resetHeaderTemplate() {
3381          $this->header_xobjid = false;
3382      }
3383  
3384      /**
3385       * Set a flag to automatically reset the xobject template used by Header() method at each page.
3386       * @param $val (boolean) set to true to reset Header xobject template at each page, false otherwise.
3387       * @public
3388       */
3389  	public function setHeaderTemplateAutoreset($val=true) {
3390          $this->header_xobj_autoreset = $val ? true : false;
3391      }
3392  
3393      /**
3394       * This method is used to render the page header.
3395       * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3396       * @public
3397       */
3398  	public function Header() {
3399          if ($this->header_xobjid === false) {
3400              // start a new XObject Template
3401              $this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
3402              $headerfont = $this->getHeaderFont();
3403              $headerdata = $this->getHeaderData();
3404              $this->y = $this->header_margin;
3405              if ($this->rtl) {
3406                  $this->x = $this->w - $this->original_rMargin;
3407              } else {
3408                  $this->x = $this->original_lMargin;
3409              }
3410              if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
3411                  $imgtype = TCPDF_IMAGES::getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
3412                  if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
3413                      $this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3414                  } elseif ($imgtype == 'svg') {
3415                      $this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3416                  } else {
3417                      $this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3418                  }
3419                  $imgy = $this->getImageRBY();
3420              } else {
3421                  $imgy = $this->y;
3422              }
3423              $cell_height = $this->getCellHeight($headerfont[2] / $this->k);
3424              // set starting margin for text data cell
3425              if ($this->getRTL()) {
3426                  $header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
3427              } else {
3428                  $header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
3429              }
3430              $cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
3431              $this->SetTextColorArray($this->header_text_color);
3432              // header title
3433              $this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
3434              $this->SetX($header_x);
3435              $this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
3436              // header string
3437              $this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
3438              $this->SetX($header_x);
3439              $this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
3440              // print an ending header line
3441              $this->SetLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
3442              $this->SetY((2.835 / $this->k) + max($imgy, $this->y));
3443              if ($this->rtl) {
3444                  $this->SetX($this->original_rMargin);
3445              } else {
3446                  $this->SetX($this->original_lMargin);
3447              }
3448              $this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
3449              $this->endTemplate();
3450          }
3451          // print header template
3452          $x = 0;
3453          $dx = 0;
3454          if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
3455              // adjust margins for booklet mode
3456              $dx = ($this->original_lMargin - $this->original_rMargin);
3457          }
3458          if ($this->rtl) {
3459              $x = $this->w + $dx;
3460          } else {
3461              $x = 0 + $dx;
3462          }
3463          $this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
3464          if ($this->header_xobj_autoreset) {
3465              // reset header xobject template at each page
3466              $this->header_xobjid = false;
3467          }
3468      }
3469  
3470      /**
3471       * This method is used to render the page footer.
3472       * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3473       * @public
3474       */
3475  	public function Footer() {
3476          $cur_y = $this->y;
3477          $this->SetTextColorArray($this->footer_text_color);
3478          //set style for cell border
3479          $line_width = (0.85 / $this->k);
3480          $this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
3481          //print document barcode
3482          $barcode = $this->getBarcode();
3483          if (!empty($barcode)) {
3484              $this->Ln($line_width);
3485              $barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
3486              $style = array(
3487                  'position' => $this->rtl?'R':'L',
3488                  'align' => $this->rtl?'R':'L',
3489                  'stretch' => false,
3490                  'fitwidth' => true,
3491                  'cellfitalign' => '',
3492                  'border' => false,
3493                  'padding' => 0,
3494                  'fgcolor' => array(0,0,0),
3495                  'bgcolor' => false,
3496                  'text' => false
3497              );
3498              $this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
3499          }
3500          $w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
3501          if (empty($this->pagegroups)) {
3502              $pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
3503          } else {
3504              $pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
3505          }
3506          $this->SetY($cur_y);
3507          //Print page number
3508          if ($this->getRTL()) {
3509              $this->SetX($this->original_rMargin);
3510              $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
3511          } else {
3512              $this->SetX($this->original_lMargin);
3513              $this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
3514          }
3515      }
3516  
3517      /**
3518       * This method is used to render the page header.
3519       * @protected
3520       * @since 4.0.012 (2008-07-24)
3521       */
3522  	protected function setHeader() {
3523          if (!$this->print_header OR ($this->state != 2)) {
3524              return;
3525          }
3526          $this->InHeader = true;
3527          $this->setGraphicVars($this->default_graphic_vars);
3528          $temp_thead = $this->thead;
3529          $temp_theadMargins = $this->theadMargins;
3530          $lasth = $this->lasth;
3531          $newline = $this->newline;
3532          $this->_outSaveGraphicsState();
3533          $this->rMargin = $this->original_rMargin;
3534          $this->lMargin = $this->original_lMargin;
3535          $this->SetCellPadding(0);
3536          //set current position
3537          if ($this->rtl) {
3538              $this->SetXY($this->original_rMargin, $this->header_margin);
3539          } else {
3540              $this->SetXY($this->original_lMargin, $this->header_margin);
3541          }
3542          $this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
3543          $this->Header();
3544          //restore position
3545          if ($this->rtl) {
3546              $this->SetXY($this->original_rMargin, $this->tMargin);
3547          } else {
3548              $this->SetXY($this->original_lMargin, $this->tMargin);
3549          }
3550          $this->_outRestoreGraphicsState();
3551          $this->lasth = $lasth;
3552          $this->thead = $temp_thead;
3553          $this->theadMargins = $temp_theadMargins;
3554          $this->newline = $newline;
3555          $this->InHeader = false;
3556      }
3557  
3558      /**
3559       * This method is used to render the page footer.
3560       * @protected
3561       * @since 4.0.012 (2008-07-24)
3562       */
3563  	protected function setFooter() {
3564          if ($this->state != 2) {
3565              return;
3566          }
3567          $this->InFooter = true;
3568          // save current graphic settings
3569          $gvars = $this->getGraphicVars();
3570          // mark this point
3571          $this->footerpos[$this->page] = $this->pagelen[$this->page];
3572          $this->_out("\n");
3573          if ($this->print_footer) {
3574              $this->setGraphicVars($this->default_graphic_vars);
3575              $this->current_column = 0;
3576              $this->num_columns = 1;
3577              $temp_thead = $this->thead;
3578              $temp_theadMargins = $this->theadMargins;
3579              $lasth = $this->lasth;
3580              $this->_outSaveGraphicsState();
3581              $this->rMargin = $this->original_rMargin;
3582              $this->lMargin = $this->original_lMargin;
3583              $this->SetCellPadding(0);
3584              //set current position
3585              $footer_y = $this->h - $this->footer_margin;
3586              if ($this->rtl) {
3587                  $this->SetXY($this->original_rMargin, $footer_y);
3588              } else {
3589                  $this->SetXY($this->original_lMargin, $footer_y);
3590              }
3591              $this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
3592              $this->Footer();
3593              //restore position
3594              if ($this->rtl) {
3595                  $this->SetXY($this->original_rMargin, $this->tMargin);
3596              } else {
3597                  $this->SetXY($this->original_lMargin, $this->tMargin);
3598              }
3599              $this->_outRestoreGraphicsState();
3600              $this->lasth = $lasth;
3601              $this->thead = $temp_thead;
3602              $this->theadMargins = $temp_theadMargins;
3603          }
3604          // restore graphic settings
3605          $this->setGraphicVars($gvars);
3606          $this->current_column = $gvars['current_column'];
3607          $this->num_columns = $gvars['num_columns'];
3608          // calculate footer length
3609          $this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
3610          $this->InFooter = false;
3611      }
3612  
3613      /**
3614       * Check if we are on the page body (excluding page header and footer).
3615       * @return true if we are not in page header nor in page footer, false otherwise.
3616       * @protected
3617       * @since 5.9.091 (2011-06-15)
3618       */
3619  	protected function inPageBody() {
3620          return (($this->InHeader === false) AND ($this->InFooter === false));
3621      }
3622  
3623      /**
3624       * This method is used to render the table header on new page (if any).
3625       * @protected
3626       * @since 4.5.030 (2009-03-25)
3627       */
3628  	protected function setTableHeader() {
3629          if ($this->num_columns > 1) {
3630              // multi column mode
3631              return;
3632          }
3633          if (isset($this->theadMargins['top'])) {
3634              // restore the original top-margin
3635              $this->tMargin = $this->theadMargins['top'];
3636              $this->pagedim[$this->page]['tm'] = $this->tMargin;
3637              $this->y = $this->tMargin;
3638          }
3639          if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
3640              // set margins
3641              $prev_lMargin = $this->lMargin;
3642              $prev_rMargin = $this->rMargin;
3643              $prev_cell_padding = $this->cell_padding;
3644              $this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
3645              $this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
3646              $this->cell_padding = $this->theadMargins['cell_padding'];
3647              if ($this->rtl) {
3648                  $this->x = $this->w - $this->rMargin;
3649              } else {
3650                  $this->x = $this->lMargin;
3651              }
3652              // account for special "cell" mode
3653              if ($this->theadMargins['cell']) {
3654                  if ($this->rtl) {
3655                      $this->x -= $this->cell_padding['R'];
3656                  } else {
3657                      $this->x += $this->cell_padding['L'];
3658                  }
3659              }
3660              $gvars = $this->getGraphicVars();
3661              if (!empty($this->theadMargins['gvars'])) {
3662                  // set the correct graphic style
3663                  $this->setGraphicVars($this->theadMargins['gvars']);
3664                  $this->rMargin = $gvars['rMargin'];
3665                  $this->lMargin = $gvars['lMargin'];
3666              }
3667              // print table header
3668              $this->writeHTML($this->thead, false, false, false, false, '');
3669              $this->setGraphicVars($gvars);
3670              // set new top margin to skip the table headers
3671              if (!isset($this->theadMargins['top'])) {
3672                  $this->theadMargins['top'] = $this->tMargin;
3673              }
3674              // store end of header position
3675              if (!isset($this->columns[0]['th'])) {
3676                  $this->columns[0]['th'] = array();
3677              }
3678              $this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
3679              $this->tMargin = $this->y;
3680              $this->pagedim[$this->page]['tm'] = $this->tMargin;
3681              $this->lasth = 0;
3682              $this->lMargin = $prev_lMargin;
3683              $this->rMargin = $prev_rMargin;
3684              $this->cell_padding = $prev_cell_padding;
3685          }
3686      }
3687  
3688      /**
3689       * Returns the current page number.
3690       * @return int page number
3691       * @public
3692       * @since 1.0
3693       * @see getAliasNbPages()
3694       */
3695  	public function PageNo() {
3696          return $this->page;
3697      }
3698  
3699      /**
3700       * Returns the array of spot colors.
3701       * @return (array) Spot colors array.
3702       * @public
3703       * @since 6.0.038 (2013-09-30)
3704       */
3705  	public function getAllSpotColors() {
3706          return $this->spot_colors;
3707      }
3708  
3709      /**
3710       * Defines a new spot color.
3711       * It can be expressed in RGB components or gray scale.
3712       * The method can be called before the first page is created and the value is retained from page to page.
3713       * @param $name (string) Full name of the spot color.
3714       * @param $c (float) Cyan color for CMYK. Value between 0 and 100.
3715       * @param $m (float) Magenta color for CMYK. Value between 0 and 100.
3716       * @param $y (float) Yellow color for CMYK. Value between 0 and 100.
3717       * @param $k (float) Key (Black) color for CMYK. Value between 0 and 100.
3718       * @public
3719       * @since 4.0.024 (2008-09-12)
3720       * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3721       */
3722  	public function AddSpotColor($name, $c, $m, $y, $k) {
3723          if (!isset($this->spot_colors[$name])) {
3724              $i = (1 + count($this->spot_colors));
3725              $this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
3726          }
3727      }
3728  
3729      /**
3730       * Set the spot color for the specified type ('draw', 'fill', 'text').
3731       * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3732       * @param $name (string) Name of the spot color.
3733       * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3734       * @return (string) PDF color command.
3735       * @public
3736       * @since 5.9.125 (2011-10-03)
3737       */
3738  	public function setSpotColor($type, $name, $tint=100) {
3739          $spotcolor = TCPDF_COLORS::getSpotColor($name, $this->spot_colors);
3740          if ($spotcolor === false) {
3741              $this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
3742          }
3743          $tint = (max(0, min(100, $tint)) / 100);
3744          $pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
3745          switch ($type) {
3746              case 'draw': {
3747                  $pdfcolor .= sprintf('CS %F SCN', $tint);
3748                  $this->DrawColor = $pdfcolor;
3749                  $this->strokecolor = $spotcolor;
3750                  break;
3751              }
3752              case 'fill': {
3753                  $pdfcolor .= sprintf('cs %F scn', $tint);
3754                  $this->FillColor = $pdfcolor;
3755                  $this->bgcolor = $spotcolor;
3756                  break;
3757              }
3758              case 'text': {
3759                  $pdfcolor .= sprintf('cs %F scn', $tint);
3760                  $this->TextColor = $pdfcolor;
3761                  $this->fgcolor = $spotcolor;
3762                  break;
3763              }
3764          }
3765          $this->ColorFlag = ($this->FillColor != $this->TextColor);
3766          if ($this->state == 2) {
3767              $this->_out($pdfcolor);
3768          }
3769          if ($this->inxobj) {
3770              // we are inside an XObject template
3771              $this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
3772          }
3773          return $pdfcolor;
3774      }
3775  
3776      /**
3777       * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
3778       * @param $name (string) Name of the spot color.
3779       * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3780       * @public
3781       * @since 4.0.024 (2008-09-12)
3782       * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3783       */
3784  	public function SetDrawSpotColor($name, $tint=100) {
3785          $this->setSpotColor('draw', $name, $tint);
3786      }
3787  
3788      /**
3789       * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
3790       * @param $name (string) Name of the spot color.
3791       * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3792       * @public
3793       * @since 4.0.024 (2008-09-12)
3794       * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
3795       */
3796  	public function SetFillSpotColor($name, $tint=100) {
3797          $this->setSpotColor('fill', $name, $tint);
3798      }
3799  
3800      /**
3801       * Defines the spot color used for text.
3802       * @param $name (string) Name of the spot color.
3803       * @param $tint (int) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3804       * @public
3805       * @since 4.0.024 (2008-09-12)
3806       * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
3807       */
3808  	public function SetTextSpotColor($name, $tint=100) {
3809          $this->setSpotColor('text', $name, $tint);
3810      }
3811  
3812      /**
3813       * Set the color array for the specified type ('draw', 'fill', 'text').
3814       * It can be expressed in RGB, CMYK or GRAY SCALE components.
3815       * The method can be called before the first page is created and the value is retained from page to page.
3816       * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3817       * @param $color (array) Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
3818       * @param $ret (boolean) If true do not send the PDF command.
3819       * @return (string) The PDF command or empty string.
3820       * @public
3821       * @since 3.1.000 (2008-06-11)
3822       */
3823  	public function setColorArray($type, $color, $ret=false) {
3824          if (is_array($color)) {
3825              $color = array_values($color);
3826              // component: grey, RGB red or CMYK cyan
3827              $c = isset($color[0]) ? $color[0] : -1;
3828              // component: RGB green or CMYK magenta
3829              $m = isset($color[1]) ? $color[1] : -1;
3830              // component: RGB blue or CMYK yellow
3831              $y = isset($color[2]) ? $color[2] : -1;
3832              // component: CMYK black
3833              $k = isset($color[3]) ? $color[3] : -1;
3834              // color name
3835              $name = isset($color[4]) ? $color[4] : '';
3836              if ($c >= 0) {
3837                  return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
3838              }
3839          }
3840          return '';
3841      }
3842  
3843      /**
3844       * Defines the color used for all drawing operations (lines, rectangles and cell borders).
3845       * It can be expressed in RGB, CMYK or GRAY SCALE components.
3846       * The method can be called before the first page is created and the value is retained from page to page.
3847       * @param $color (array) Array of colors (1, 3 or 4 values).
3848       * @param $ret (boolean) If true do not send the PDF command.
3849       * @return string the PDF command
3850       * @public
3851       * @since 3.1.000 (2008-06-11)
3852       * @see SetDrawColor()
3853       */
3854  	public function SetDrawColorArray($color, $ret=false) {
3855          return $this->setColorArray('draw', $color, $ret);
3856      }
3857  
3858      /**
3859       * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
3860       * It can be expressed in RGB, CMYK or GRAY SCALE components.
3861       * The method can be called before the first page is created and the value is retained from page to page.
3862       * @param $color (array) Array of colors (1, 3 or 4 values).
3863       * @param $ret (boolean) If true do not send the PDF command.
3864       * @public
3865       * @since 3.1.000 (2008-6-11)
3866       * @see SetFillColor()
3867       */
3868  	public function SetFillColorArray($color, $ret=false) {
3869          return $this->setColorArray('fill', $color, $ret);
3870      }
3871  
3872      /**
3873       * Defines the color used for text. It can be expressed in RGB components or gray scale.
3874       * The method can be called before the first page is created and the value is retained from page to page.
3875       * @param $color (array) Array of colors (1, 3 or 4 values).
3876       * @param $ret (boolean) If true do not send the PDF command.
3877       * @public
3878       * @since 3.1.000 (2008-6-11)
3879       * @see SetFillColor()
3880       */
3881  	public function SetTextColorArray($color, $ret=false) {
3882          return $this->setColorArray('text', $color, $ret);
3883      }
3884  
3885      /**
3886       * Defines the color used by the specified type ('draw', 'fill', 'text').
3887       * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3888       * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3889       * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3890       * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3891       * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3892       * @param $ret (boolean) If true do not send the command.
3893       * @param $name (string) spot color name (if any)
3894       * @return (string) The PDF command or empty string.
3895       * @public
3896       * @since 5.9.125 (2011-10-03)
3897       */
3898  	public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3899          // set default values
3900          if (!is_numeric($col1)) {
3901              $col1 = 0;
3902          }
3903          if (!is_numeric($col2)) {
3904              $col2 = -1;
3905          }
3906          if (!is_numeric($col3)) {
3907              $col3 = -1;
3908          }
3909          if (!is_numeric($col4)) {
3910              $col4 = -1;
3911          }
3912          // set color by case
3913          $suffix = '';
3914          if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
3915              // Grey scale
3916              $col1 = max(0, min(255, $col1));
3917              $intcolor = array('G' => $col1);
3918              $pdfcolor = sprintf('%F ', ($col1 / 255));
3919              $suffix = 'g';
3920          } elseif ($col4 == -1) {
3921              // RGB
3922              $col1 = max(0, min(255, $col1));
3923              $col2 = max(0, min(255, $col2));
3924              $col3 = max(0, min(255, $col3));
3925              $intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
3926              $pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
3927              $suffix = 'rg';
3928          } else {
3929              $col1 = max(0, min(100, $col1));
3930              $col2 = max(0, min(100, $col2));
3931              $col3 = max(0, min(100, $col3));
3932              $col4 = max(0, min(100, $col4));
3933              if (empty($name)) {
3934                  // CMYK
3935                  $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
3936                  $pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
3937                  $suffix = 'k';
3938              } else {
3939                  // SPOT COLOR
3940                  $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
3941                  $this->AddSpotColor($name, $col1, $col2, $col3, $col4);
3942                  $pdfcolor = $this->setSpotColor($type, $name, 100);
3943              }
3944          }
3945          switch ($type) {
3946              case 'draw': {
3947                  $pdfcolor .= strtoupper($suffix);
3948                  $this->DrawColor = $pdfcolor;
3949                  $this->strokecolor = $intcolor;
3950                  break;
3951              }
3952              case 'fill': {
3953                  $pdfcolor .= $suffix;
3954                  $this->FillColor = $pdfcolor;
3955                  $this->bgcolor = $intcolor;
3956                  break;
3957              }
3958              case 'text': {
3959                  $pdfcolor .= $suffix;
3960                  $this->TextColor = $pdfcolor;
3961                  $this->fgcolor = $intcolor;
3962                  break;
3963              }
3964          }
3965          $this->ColorFlag = ($this->FillColor != $this->TextColor);
3966          if (($type != 'text') AND ($this->state == 2)) {
3967              if (!$ret) {
3968                  $this->_out($pdfcolor);
3969              }
3970              return $pdfcolor;
3971          }
3972          return '';
3973      }
3974  
3975      /**
3976       * 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.
3977       * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3978       * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3979       * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3980       * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3981       * @param $ret (boolean) If true do not send the command.
3982       * @param $name (string) spot color name (if any)
3983       * @return string the PDF command
3984       * @public
3985       * @since 1.3
3986       * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
3987       */
3988  	public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3989          return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
3990      }
3991  
3992      /**
3993       * 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.
3994       * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3995       * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3996       * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3997       * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3998       * @param $ret (boolean) If true do not send the command.
3999       * @param $name (string) Spot color name (if any).
4000       * @return (string) The PDF command.
4001       * @public
4002       * @since 1.3
4003       * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
4004       */
4005  	public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4006          return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
4007      }
4008  
4009      /**
4010       * 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.
4011       * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4012       * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4013       * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4014       * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
4015       * @param $ret (boolean) If true do not send the command.
4016       * @param $name (string) Spot color name (if any).
4017       * @return (string) Empty string.
4018       * @public
4019       * @since 1.3
4020       * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4021       */
4022  	public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4023          return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4024      }
4025  
4026      /**
4027       * Returns the length of a string in user unit. A font must be selected.<br>
4028       * @param $s (string) The string whose length is to be computed
4029       * @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.
4030       * @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.
4031       * @param $fontsize (float) Font size in points. The default value is the current size.
4032       * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4033       * @return mixed int total string length or array of characted widths
4034       * @author Nicola Asuni
4035       * @public
4036       * @since 1.2
4037       */
4038  	public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4039          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);
4040      }
4041  
4042      /**
4043       * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4044       * @param $sa (string) The array of chars whose total length is to be computed
4045       * @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.
4046       * @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.
4047       * @param $fontsize (float) Font size in points. The default value is the current size.
4048       * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4049       * @return mixed int total string length or array of characted widths
4050       * @author Nicola Asuni
4051       * @public
4052       * @since 2.4.000 (2008-03-06)
4053       */
4054  	public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4055          // store current values
4056          if (!TCPDF_STATIC::empty_string($fontname)) {
4057              $prev_FontFamily = $this->FontFamily;
4058              $prev_FontStyle = $this->FontStyle;
4059              $prev_FontSizePt = $this->FontSizePt;
4060              $this->SetFont($fontname, $fontstyle, $fontsize, '', 'default', false);
4061          }
4062          // convert UTF-8 array to Latin1 if required
4063          if ($this->isunicode AND (!$this->isUnicodeFont())) {
4064              $sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
4065          }
4066          $w = 0; // total width
4067          $wa = array(); // array of characters widths
4068          foreach ($sa as $ck => $char) {
4069              // character width
4070              $cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4071              $wa[] = $cw;
4072              $w += $cw;
4073          }
4074          // restore previous values
4075          if (!TCPDF_STATIC::empty_string($fontname)) {
4076              $this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4077          }
4078          if ($getarray) {
4079              return $wa;
4080          }
4081          return $w;
4082      }
4083  
4084      /**
4085       * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
4086       * @param $char (int) The char code whose length is to be returned
4087       * @param $notlast (boolean) If false ignore the font-spacing.
4088       * @return float char width
4089       * @author Nicola Asuni
4090       * @public
4091       * @since 2.4.000 (2008-03-06)
4092       */
4093  	public function GetCharWidth($char, $notlast=true) {
4094          // get raw width
4095          $chw = $this->getRawCharWidth($char);
4096          if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
4097              // increase/decrease font spacing
4098              $chw += $this->font_spacing;
4099          }
4100          if ($this->font_stretching != 100) {
4101              // fixed stretching mode
4102              $chw *= ($this->font_stretching / 100);
4103          }
4104          return $chw;
4105      }
4106  
4107      /**
4108       * Returns the length of the char in user unit for the current font.
4109       * @param $char (int) The char code whose length is to be returned
4110       * @return float char width
4111       * @author Nicola Asuni
4112       * @public
4113       * @since 5.9.000 (2010-09-28)
4114       */
4115  	public function getRawCharWidth($char) {
4116          if ($char == 173) {
4117              // SHY character will not be printed
4118              return (0);
4119          }
4120          if (isset($this->CurrentFont['cw'][$char])) {
4121              $w = $this->CurrentFont['cw'][$char];
4122          } elseif (isset($this->CurrentFont['dw'])) {
4123              // default width
4124              $w = $this->CurrentFont['dw'];
4125          } elseif (isset($this->CurrentFont['cw'][32])) {
4126              // default width
4127              $w = $this->CurrentFont['cw'][32];
4128          } else {
4129              $w = 600;
4130          }
4131          return $this->getAbsFontMeasure($w);
4132      }
4133  
4134      /**
4135       * Returns the numbero of characters in a string.
4136       * @param $s (string) The input string.
4137       * @return int number of characters
4138       * @public
4139       * @since 2.0.0001 (2008-01-07)
4140       */
4141  	public function GetNumChars($s) {
4142          if ($this->isUnicodeFont()) {
4143              return count(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont));
4144          }
4145          return strlen($s);
4146      }
4147  
4148      /**
4149       * Fill the list of available fonts ($this->fontlist).
4150       * @protected
4151       * @since 4.0.013 (2008-07-28)
4152       */
4153  	protected function getFontsList() {
4154          if (($fontsdir = opendir(TCPDF_FONTS::_getfontpath())) !== false) {
4155              while (($file = readdir($fontsdir)) !== false) {
4156                  if (substr($file, -4) == '.php') {
4157                      array_push($this->fontlist, strtolower(basename($file, '.php')));
4158                  }
4159              }
4160              closedir($fontsdir);
4161          }
4162      }
4163  
4164      /**
4165       * Returns the unicode caracter specified by the value
4166       * @param $c (int) UTF-8 value
4167       * @return Returns the specified character.
4168       * @since 2.3.000 (2008-03-05)
4169       * @public
4170       * @deprecated
4171       */
4172  	public function unichr($c) {
4173          return TCPDF_FONTS::unichr($c, $this->isunicode);
4174      }
4175  
4176      /**
4177       * Convert and add the selected TrueType or Type1 font to the fonts folder (that must be writeable).
4178       * @param $fontfile (string) Font file (full path).
4179       * @param $fonttype (string) Font type. Leave empty for autodetect mode. Valid values are: TrueTypeUnicode, TrueType, Type1, CID0JP = CID-0 Japanese, CID0KR = CID-0 Korean, CID0CS = CID-0 Chinese Simplified, CID0CT = CID-0 Chinese Traditional.
4180       * @param $enc (string) Name of the encoding table to use. Leave empty for default mode. Omit this parameter for TrueType Unicode and symbolic fonts like Symbol or ZapfDingBats.
4181       * @param $flags (int) Unsigned 32-bit integer containing flags specifying various characteristics of the font (PDF32000:2008 - 9.8.2 Font Descriptor Flags): +1 for fixed font; +4 for symbol or +32 for non-symbol; +64 for italic. Fixed and Italic mode are generally autodetected so you have to set it to 32 = non-symbolic font (default) or 4 = symbolic font.
4182       * @param $outpath (string) Output path for generated font files (must be writeable by the web server). Leave empty for default font folder.
4183       * @param $platid (int) Platform ID for CMAP table to extract (when building a Unicode font for Windows this value should be 3, for Macintosh should be 1).
4184       * @param $encid (int) Encoding ID for CMAP table to extract (when building a Unicode font for Windows this value should be 1, for Macintosh should be 0). When Platform ID is 3, legal values for Encoding ID are: 0=Symbol, 1=Unicode, 2=ShiftJIS, 3=PRC, 4=Big5, 5=Wansung, 6=Johab, 7=Reserved, 8=Reserved, 9=Reserved, 10=UCS-4.
4185       * @param $addcbbox (boolean) If true includes the character bounding box information on the php font file.
4186       * @return (string) TCPDF font name.
4187       * @author Nicola Asuni
4188       * @since 5.9.123 (2010-09-30)
4189       * @public
4190       * @deprecated
4191       */
4192  	public function addTTFfont($fontfile, $fonttype='', $enc='', $flags=32, $outpath='', $platid=3, $encid=1, $addcbbox=false) {
4193          return TCPDF_FONTS::addTTFfont($fontfile, $fonttype, $enc, $flags, $outpath, $platid, $encid, $addcbbox);
4194      }
4195  
4196      /**
4197       * Imports a TrueType, Type1, core, or CID0 font and makes it available.
4198       * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
4199       * 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.
4200       * @param $family (string) Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
4201       * @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>
4202       * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4203       * @return array containing the font data, or false in case of error.
4204       * @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.
4205       * @public
4206       * @since 1.5
4207       * @see SetFont(), setFontSubsetting()
4208       */
4209  	public function AddFont($family, $style='', $fontfile='', $subset='default') {
4210          if ($subset === 'default') {
4211              $subset = $this->font_subsetting;
4212          }
4213          if ($this->pdfa_mode) {
4214              $subset = false;
4215          }
4216          if (TCPDF_STATIC::empty_string($family)) {
4217              if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
4218                  $family = $this->FontFamily;
4219              } else {
4220                  $this->Error('Empty font family');
4221              }
4222          }
4223          // move embedded styles on $style
4224          if (substr($family, -1) == 'I') {
4225              $style .= 'I';
4226              $family = substr($family, 0, -1);
4227          }
4228          if (substr($family, -1) == 'B') {
4229              $style .= 'B';
4230              $family = substr($family, 0, -1);
4231          }
4232          // normalize family name
4233          $family = strtolower($family);
4234          if ((!$this->isunicode) AND ($family == 'arial')) {
4235              $family = 'helvetica';
4236          }
4237          if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4238              $style = '';
4239          }
4240          if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
4241              // all fonts must be embedded
4242              $family = 'pdfa'.$family;
4243          }
4244          $tempstyle = strtoupper($style);
4245          $style = '';
4246          // underline
4247          if (strpos($tempstyle, 'U') !== false) {
4248              $this->underline = true;
4249          } else {
4250              $this->underline = false;
4251          }
4252          // line-through (deleted)
4253          if (strpos($tempstyle, 'D') !== false) {
4254              $this->linethrough = true;
4255          } else {
4256              $this->linethrough = false;
4257          }
4258          // overline
4259          if (strpos($tempstyle, 'O') !== false) {
4260              $this->overline = true;
4261          } else {
4262              $this->overline = false;
4263          }
4264          // bold
4265          if (strpos($tempstyle, 'B') !== false) {
4266              $style .= 'B';
4267          }
4268          // oblique
4269          if (strpos($tempstyle, 'I') !== false) {
4270              $style .= 'I';
4271          }
4272          $bistyle = $style;
4273          $fontkey = $family.$style;
4274          $font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
4275          $fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4276          // check if the font has been already added
4277          $fb = $this->getFontBuffer($fontkey);
4278          if ($fb !== false) {
4279              if ($this->inxobj) {
4280                  // we are inside an XObject template
4281                  $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
4282              }
4283              return $fontdata;
4284          }
4285          // get specified font directory (if any)
4286          $fontdir = false;
4287          if (!TCPDF_STATIC::empty_string($fontfile)) {
4288              $fontdir = dirname($fontfile);
4289              if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
4290                  $fontdir = '';
4291              } else {
4292                  $fontdir .= '/';
4293              }
4294          }
4295          // true when the font style variation is missing
4296          $missing_style = false;
4297          // search and include font file
4298          if (TCPDF_STATIC::empty_string($fontfile) OR (!@file_exists($fontfile))) {
4299              // build a standard filenames for specified font
4300              $tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
4301              $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4302              if (TCPDF_STATIC::empty_string($fontfile)) {
4303                  $missing_style = true;
4304                  // try to remove the style part
4305                  $tmp_fontfile = str_replace(' ', '', $family).'.php';
4306                  $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4307              }
4308          }
4309          // include font file
4310          if (!TCPDF_STATIC::empty_string($fontfile) AND (@file_exists($fontfile))) {
4311              include($fontfile);
4312          } else {
4313              $this->Error('Could not include font definition file: '.$family.'');
4314          }
4315          // check font parameters
4316          if ((!isset($type)) OR (!isset($cw))) {
4317              $this->Error('The font definition file has a bad format: '.$fontfile.'');
4318          }
4319          // SET default parameters
4320          if (!isset($file) OR TCPDF_STATIC::empty_string($file)) {
4321              $file = '';
4322          }
4323          if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
4324              $enc = '';
4325          }
4326          if (!isset($cidinfo) OR TCPDF_STATIC::empty_string($cidinfo)) {
4327              $cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
4328              $cidinfo['uni2cid'] = array();
4329          }
4330          if (!isset($ctg) OR TCPDF_STATIC::empty_string($ctg)) {
4331              $ctg = '';
4332          }
4333          if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
4334              $desc = array();
4335          }
4336          if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
4337              $up = -100;
4338          }
4339          if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
4340              $ut = 50;
4341          }
4342          if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
4343              $cw = array();
4344          }
4345          if (!isset($dw) OR TCPDF_STATIC::empty_string($dw)) {
4346              // set default width
4347              if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4348                  $dw = $desc['MissingWidth'];
4349              } elseif (isset($cw[32])) {
4350                  $dw = $cw[32];
4351              } else {
4352                  $dw = 600;
4353              }
4354          }
4355          ++$this->numfonts;
4356          if ($type == 'core') {
4357              $name = $this->CoreFonts[$fontkey];
4358              $subset = false;
4359          } elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4360              $subset = false;
4361          } elseif ($type == 'TrueTypeUnicode') {
4362              $enc = 'Identity-H';
4363          } elseif ($type == 'cidfont0') {
4364              if ($this->pdfa_mode) {
4365                  $this->Error('All fonts must be embedded in PDF/A mode!');
4366              }
4367          } else {
4368              $this->Error('Unknow font type: '.$type.'');
4369          }
4370          // set name if unset
4371          if (!isset($name) OR empty($name)) {
4372              $name = $fontkey;
4373          }
4374          // create artificial font style variations if missing (only works with non-embedded fonts)
4375          if (($type != 'core') AND $missing_style) {
4376              // style variations
4377              $styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4378              $name .= $styles[$bistyle];
4379              // artificial bold
4380              if (strpos($bistyle, 'B') !== false) {
4381                  if (isset($desc['StemV'])) {
4382                      // from normal to bold
4383                      $desc['StemV'] = round($desc['StemV'] * 1.75);
4384                  } else {
4385                      // bold
4386                      $desc['StemV'] = 123;
4387                  }
4388              }
4389              // artificial italic
4390              if (strpos($bistyle, 'I') !== false) {
4391                  if (isset($desc['ItalicAngle'])) {
4392                      $desc['ItalicAngle'] -= 11;
4393                  } else {
4394                      $desc['ItalicAngle'] = -11;
4395                  }
4396                  if (isset($desc['Flags'])) {
4397                      $desc['Flags'] |= 64; //bit 7
4398                  } else {
4399                      $desc['Flags'] = 64;
4400                  }
4401              }
4402          }
4403          // check if the array of characters bounding boxes is defined
4404          if (!isset($cbbox)) {
4405              $cbbox = array();
4406          }
4407          // initialize subsetchars
4408          $subsetchars = array_fill(0, 255, true);
4409          $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));
4410          if ($this->inxobj) {
4411              // we are inside an XObject template
4412              $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
4413          }
4414          if (isset($diff) AND (!empty($diff))) {
4415              //Search existing encodings
4416              $d = 0;
4417              $nb = count($this->diffs);
4418              for ($i=1; $i <= $nb; ++$i) {
4419                  if ($this->diffs[$i] == $diff) {
4420                      $d = $i;
4421                      break;
4422                  }
4423              }
4424              if ($d == 0) {
4425                  $d = $nb + 1;
4426                  $this->diffs[$d] = $diff;
4427              }
4428              $this->setFontSubBuffer($fontkey, 'diff', $d);
4429          }
4430          if (!TCPDF_STATIC::empty_string($file)) {
4431              if (!isset($this->FontFiles[$file])) {
4432                  if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4433                      $this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4434                  } elseif ($type != 'core') {
4435                      $this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4436                  }
4437              } else {
4438                  // update fontkeys that are sharing this font file
4439                  $this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
4440                  if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
4441                      $this->FontFiles[$file]['fontkeys'][] = $fontkey;
4442                  }
4443              }
4444          }
4445          return $fontdata;
4446      }
4447  
4448      /**
4449       * Sets the font used to print character strings.
4450       * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
4451       * The method can be called before the first page is created and the font is retained from page to page.
4452       * If you just wish to change the current font size, it is simpler to call SetFontSize().
4453       * 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 />
4454       * @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.
4455       * @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.
4456       * @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
4457       * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4458       * @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.
4459       * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4460       * @author Nicola Asuni
4461       * @public
4462       * @since 1.0
4463       * @see AddFont(), SetFontSize()
4464       */
4465  	public function SetFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
4466          //Select a font; size given in points
4467          if ($size === null) {
4468              $size = $this->FontSizePt;
4469          }
4470          if ($size < 0) {
4471              $size = 0;
4472          }
4473          // try to add font (if not already added)
4474          $fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4475          $this->FontFamily = $fontdata['family'];
4476          $this->FontStyle = $fontdata['style'];
4477          if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
4478              // save subset chars of the previous font
4479              $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
4480          }
4481          $this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
4482          $this->SetFontSize($size, $out);
4483      }
4484  
4485      /**
4486       * Defines the size of the current font.
4487       * @param $size (float) The font size in points.
4488       * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4489       * @public
4490       * @since 1.0
4491       * @see SetFont()
4492       */
4493  	public function SetFontSize($size, $out=true) {
4494          // font size in points
4495          $this->FontSizePt = $size;
4496          // font size in user units
4497          $this->FontSize = $size / $this->k;
4498          // calculate some font metrics
4499          if (isset($this->CurrentFont['desc']['FontBBox'])) {
4500              $bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4501              $font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4502          } else {
4503              $font_height = $size * 1.219;
4504          }
4505          if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
4506              $font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
4507          }
4508          if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
4509              $font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
4510          }
4511          if (!isset($font_ascent) AND !isset($font_descent)) {
4512              // core font
4513              $font_ascent = 0.76 * $font_height;
4514              $font_descent = $font_height - $font_ascent;
4515          } elseif (!isset($font_descent)) {
4516              $font_descent = $font_height - $font_ascent;
4517          } elseif (!isset($font_ascent)) {
4518              $font_ascent = $font_height - $font_descent;
4519          }
4520          $this->FontAscent = ($font_ascent / $this->k);
4521          $this->FontDescent = ($font_descent / $this->k);
4522          if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
4523              $this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4524          }
4525      }
4526  
4527      /**
4528       * Returns the bounding box of the current font in user units.
4529       * @return array
4530       * @public
4531       * @since 5.9.152 (2012-03-23)
4532       */
4533  	public function getFontBBox() {
4534          $fbbox = array();
4535          if (isset($this->CurrentFont['desc']['FontBBox'])) {
4536              $tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4537              $fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
4538          } else {
4539              // Find max width
4540              if (isset($this->CurrentFont['desc']['MaxWidth'])) {
4541                  $maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
4542              } else {
4543                  $maxw = 0;
4544                  if (isset($this->CurrentFont['desc']['MissingWidth'])) {
4545                      $maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
4546                  }
4547                  if (isset($this->CurrentFont['desc']['AvgWidth'])) {
4548                      $maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
4549                  }
4550                  if (isset($this->CurrentFont['dw'])) {
4551                      $maxw = max($maxw, $this->CurrentFont['dw']);
4552                  }
4553                  foreach ($this->CurrentFont['cw'] as $char => $w) {
4554                      $maxw = max($maxw, $w);
4555                  }
4556                  if ($maxw == 0) {
4557                      $maxw = 600;
4558                  }
4559                  $maxw = $this->getAbsFontMeasure($maxw);
4560              }
4561              $fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
4562          }
4563          return $fbbox;
4564      }
4565  
4566      /**
4567       * Convert a relative font measure into absolute value.
4568       * @param $s (int) Font measure.
4569       * @return float Absolute measure.
4570       * @since 5.9.186 (2012-09-13)
4571       */
4572  	public function getAbsFontMeasure($s) {
4573          return ($s * $this->FontSize / 1000);
4574      }
4575  
4576      /**
4577       * Returns the glyph bounding box of the specified character in the current font in user units.
4578       * @param $char (int) Input character code.
4579       * @return mixed array(xMin, yMin, xMax, yMax) or FALSE if not defined.
4580       * @since 5.9.186 (2012-09-13)
4581       */
4582  	public function getCharBBox($char) {
4583          if (isset($this->CurrentFont['cbbox'][$char])) {
4584              return array_map(array($this,'getAbsFontMeasure'), $this->CurrentFont['cbbox'][intval($char)]);
4585          }
4586          return false;
4587      }
4588  
4589      /**
4590       * Return the font descent value
4591       * @param $font (string) font name
4592       * @param $style (string) font style
4593       * @param $size (float) The size (in points)
4594       * @return int font descent
4595       * @public
4596       * @author Nicola Asuni
4597       * @since 4.9.003 (2010-03-30)
4598       */
4599  	public function getFontDescent($font, $style='', $size=0) {
4600          $fontdata = $this->AddFont($font, $style);
4601          $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4602          if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
4603              $descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
4604          } else {
4605              $descent = (1.219 * 0.24 * $size);
4606          }
4607          return ($descent / $this->k);
4608      }
4609  
4610      /**
4611       * Return the font ascent value.
4612       * @param $font (string) font name
4613       * @param $style (string) font style
4614       * @param $size (float) The size (in points)
4615       * @return int font ascent
4616       * @public
4617       * @author Nicola Asuni
4618       * @since 4.9.003 (2010-03-30)
4619       */
4620  	public function getFontAscent($font, $style='', $size=0) {
4621          $fontdata = $this->AddFont($font, $style);
4622          $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4623          if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
4624              $ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
4625          } else {
4626              $ascent = 1.219 * 0.76 * $size;
4627          }
4628          return ($ascent / $this->k);
4629      }
4630  
4631      /**
4632       * Return true in the character is present in the specified font.
4633       * @param $char (mixed) Character to check (integer value or string)
4634       * @param $font (string) Font name (family name).
4635       * @param $style (string) Font style.
4636       * @return (boolean) true if the char is defined, false otherwise.
4637       * @public
4638       * @since 5.9.153 (2012-03-28)
4639       */
4640  	public function isCharDefined($char, $font='', $style='') {
4641          if (is_string($char)) {
4642              // get character code
4643              $char = TCPDF_FONTS::UTF8StringToArray($char, $this->isunicode, $this->CurrentFont);
4644              $char = $char[0];
4645          }
4646          if (TCPDF_STATIC::empty_string($font)) {
4647              if (TCPDF_STATIC::empty_string($style)) {
4648                  return (isset($this->CurrentFont['cw'][intval($char)]));
4649              }
4650              $font = $this->FontFamily;
4651          }
4652          $fontdata = $this->AddFont($font, $style);
4653          $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4654          return (isset($fontinfo['cw'][intval($char)]));
4655      }
4656  
4657      /**
4658       * Replace missing font characters on selected font with specified substitutions.
4659       * @param $text (string) Text to process.
4660       * @param $font (string) Font name (family name).
4661       * @param $style (string) Font style.
4662       * @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.
4663       * @return (string) Processed text.
4664       * @public
4665       * @since 5.9.153 (2012-03-28)
4666       */
4667  	public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
4668          if (empty($subs)) {
4669              return $text;
4670          }
4671          if (TCPDF_STATIC::empty_string($font)) {
4672              $font = $this->FontFamily;
4673          }
4674          $fontdata = $this->AddFont($font, $style);
4675          $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4676          $uniarr = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
4677          foreach ($uniarr as $k => $chr) {
4678              if (!isset($fontinfo['cw'][$chr])) {
4679                  // this character is missing on the selected font
4680                  if (isset($subs[$chr])) {
4681                      // we have available substitutions
4682                      if (is_array($subs[$chr])) {
4683                          foreach($subs[$chr] as $s) {
4684                              if (isset($fontinfo['cw'][$s])) {
4685                                  $uniarr[$k] = $s;
4686                                  break;
4687                              }
4688                          }
4689                      } elseif (isset($fontinfo['cw'][$subs[$chr]])) {
4690                          $uniarr[$k] = $subs[$chr];
4691                      }
4692                  }
4693              }
4694          }
4695          return TCPDF_FONTS::UniArrSubString(TCPDF_FONTS::UTF8ArrayToUniArray($uniarr, $this->isunicode));
4696      }
4697  
4698      /**
4699       * Defines the default monospaced font.
4700       * @param $font (string) Font name.
4701       * @public
4702       * @since 4.5.025
4703       */
4704  	public function SetDefaultMonospacedFont($font) {
4705          $this->default_monospaced_font = $font;
4706      }
4707  
4708      /**
4709       * 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 />
4710       * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
4711       * @public
4712       * @since 1.5
4713       * @see Cell(), Write(), Image(), Link(), SetLink()
4714       */
4715  	public function AddLink() {
4716          // create a new internal link
4717          $n = count($this->links) + 1;
4718          $this->links[$n] = array('p' => 0, 'y' => 0, 'f' => false);
4719          return $n;
4720      }
4721  
4722      /**
4723       * Defines the page and position a link points to.
4724       * @param $link (int) The link identifier returned by AddLink()
4725       * @param $y (float) Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
4726       * @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. 
4727       * @public
4728       * @since 1.5
4729       * @see AddLink()
4730       */
4731  	public function SetLink($link, $y=0, $page=-1) {
4732          $fixed = false;
4733          if (!empty($page) AND ($page[0] == '*')) {
4734              $page = intval(substr($page, 1));
4735              // this page number will not be changed when moving/add/deleting pages
4736              $fixed = true;
4737          }
4738          if ($page < 0) {
4739              $page = $this->page;
4740          }
4741          if ($y == -1) {
4742              $y = $this->y;
4743          }
4744          $this->links[$link] = array('p' => $page, 'y' => $y, 'f' => $fixed);
4745      }
4746  
4747      /**
4748       * Puts a link on a rectangular area of the page.
4749       * 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.
4750       * @param $x (float) Abscissa of the upper-left corner of the rectangle
4751       * @param $y (float) Ordinate of the upper-left corner of the rectangle
4752       * @param $w (float) Width of the rectangle
4753       * @param $h (float) Height of the rectangle
4754       * @param $link (mixed) URL or identifier returned by AddLink()
4755       * @param $spaces (int) number of spaces on the text to link
4756       * @public
4757       * @since 1.5
4758       * @see AddLink(), Annotation(), Cell(), Write(), Image()
4759       */
4760  	public function Link($x, $y, $w, $h, $link, $spaces=0) {
4761          $this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
4762      }
4763  
4764      /**
4765       * Puts a markup annotation on a rectangular area of the page.
4766       * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
4767       * @param $x (float) Abscissa of the upper-left corner of the rectangle
4768       * @param $y (float) Ordinate of the upper-left corner of the rectangle
4769       * @param $w (float) Width of the rectangle
4770       * @param $h (float) Height of the rectangle
4771       * @param $text (string) annotation text or alternate content
4772       * @param $opt (array) array of options (see section 8.4 of PDF reference 1.7).
4773       * @param $spaces (int) number of spaces on the text to link
4774       * @public
4775       * @since 4.0.018 (2008-08-06)
4776       */
4777  	public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
4778          if ($this->inxobj) {
4779              // store parameters for later use on template
4780              $this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
4781              return;
4782          }
4783          if ($x === '') {
4784              $x = $this->x;
4785          }
4786          if ($y === '') {
4787              $y = $this->y;
4788          }
4789          // check page for no-write regions and adapt page margins if necessary
4790          list($x, $y) = $this->checkPageRegions($h, $x, $y);
4791          // recalculate coordinates to account for graphic transformations
4792          if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
4793              for ($i=$this->transfmatrix_key; $i > 0; --$i) {
4794                  $maxid = count($this->transfmatrix[$i]) - 1;
4795                  for ($j=$maxid; $j >= 0; --$j) {
4796                      $ctm = $this->transfmatrix[$i][$j];
4797                      if (isset($ctm['a'])) {
4798                          $x = $x * $this->k;
4799                          $y = ($this->h - $y) * $this->k;
4800                          $w = $w * $this->k;
4801                          $h = $h * $this->k;
4802                          // top left
4803                          $xt = $x;
4804                          $yt = $y;
4805                          $x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4806                          $y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4807                          // top right
4808                          $xt = $x + $w;
4809                          $yt = $y;
4810                          $x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4811                          $y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4812                          // bottom left
4813                          $xt = $x;
4814                          $yt = $y - $h;
4815                          $x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4816                          $y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4817                          // bottom right
4818                          $xt = $x + $w;
4819                          $yt = $y - $h;
4820                          $x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4821                          $y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4822                          // new coordinates (rectangle area)
4823                          $x = min($x1, $x2, $x3, $x4);
4824                          $y = max($y1, $y2, $y3, $y4);
4825                          $w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
4826                          $h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
4827                          $x = $x / $this->k;
4828                          $y = $this->h - ($y / $this->k);
4829                      }
4830                  }
4831              }
4832          }
4833          if ($this->page <= 0) {
4834              $page = 1;
4835          } else {
4836              $page = $this->page;
4837          }
4838          if (!isset($this->PageAnnots[$page])) {
4839              $this->PageAnnots[$page] = array();
4840          }
4841          $this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
4842          if (!$this->pdfa_mode) {
4843              if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
4844                  AND (@file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
4845                  AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
4846                  $this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
4847              }
4848          }
4849          // Add widgets annotation's icons
4850          if (isset($opt['mk']['i']) AND @file_exists($opt['mk']['i'])) {
4851              $this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
4852          }
4853          if (isset($opt['mk']['ri']) AND @file_exists($opt['mk']['ri'])) {
4854              $this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4855          }
4856          if (isset($opt['mk']['ix']) AND @file_exists($opt['mk']['ix'])) {
4857              $this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4858          }
4859      }
4860  
4861      /**
4862       * Embedd the attached files.
4863       * @since 4.4.000 (2008-12-07)
4864       * @protected
4865       * @see Annotation()
4866       */
4867  	protected function _putEmbeddedFiles() {
4868          if ($this->pdfa_mode) {
4869              // embedded files are not allowed in PDF/A mode
4870              return;
4871          }
4872          reset($this->embeddedfiles);
4873          foreach ($this->embeddedfiles as $filename => $filedata) {
4874              $data = TCPDF_STATIC::fileGetContents($filedata['file']);
4875              if ($data !== FALSE) {
4876                  $rawsize = strlen($data);
4877                  if ($rawsize > 0) {
4878                      // update name tree
4879                      $this->efnames[$filename] = $filedata['f'].' 0 R';
4880                      // embedded file specification object
4881                      $out = $this->_getobj($filedata['f'])."\n";
4882                      $out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']).' /EF <</F '.$filedata['n'].' 0 R>> >>';
4883                      $out .= "\n".'endobj';
4884                      $this->_out($out);
4885                      // embedded file object
4886                      $filter = '';
4887                      if ($this->compress) {
4888                          $data = gzcompress($data);
4889                          $filter = ' /Filter /FlateDecode';
4890                      }
4891                      $stream = $this->_getrawstream($data, $filedata['n']);
4892                      $out = $this->_getobj($filedata['n'])."\n";
4893                      $out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
4894                      $out .= ' stream'."\n".$stream."\n".'endstream';
4895                      $out .= "\n".'endobj';
4896                      $this->_out($out);
4897                  }
4898              }
4899          }
4900      }
4901  
4902      /**
4903       * Prints a text cell at the specified position.
4904       * This method allows to place a string precisely on the page.
4905       * @param $x (float) Abscissa of the cell origin
4906       * @param $y (float) Ordinate of the cell origin
4907       * @param $txt (string) String to print
4908       * @param $fstroke (int) outline size in user units (false = disable)
4909       * @param $fclip (boolean) if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
4910       * @param $ffill (boolean) if true fills the text
4911       * @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)))
4912       * @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.
4913       * @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>
4914       * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
4915       * @param $link (mixed) URL or identifier returned by AddLink().
4916       * @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.
4917       * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
4918       * @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>
4919       * @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>
4920       * @param $rtloff (boolean) if true uses the page top-left corner as origin of axis for $x and $y initial position.
4921       * @public
4922       * @since 1.0
4923       * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
4924       */
4925  	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) {
4926          $textrendermode = $this->textrendermode;
4927          $textstrokewidth = $this->textstrokewidth;
4928          $this->setTextRenderingMode($fstroke, $ffill, $fclip);
4929          $this->SetXY($x, $y, $rtloff);
4930          $this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
4931          // restore previous rendering mode
4932          $this->textrendermode = $textrendermode;
4933          $this->textstrokewidth = $textstrokewidth;
4934      }
4935  
4936      /**
4937       * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
4938       * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
4939       * This method is called automatically and should not be called directly by the application.
4940       * @return boolean
4941       * @public
4942       * @since 1.4
4943       * @see SetAutoPageBreak()
4944       */
4945  	public function AcceptPageBreak() {
4946          if ($this->num_columns > 1) {
4947              // multi column mode
4948              if ($this->current_column < ($this->num_columns - 1)) {
4949                  // go to next column
4950                  $this->selectColumn($this->current_column + 1);
4951              } elseif ($this->AutoPageBreak) {
4952                  // add a new page
4953                  $this->AddPage();
4954                  // set first column
4955                  $this->selectColumn(0);
4956              }
4957              // avoid page breaking from checkPageBreak()
4958              return false;
4959          }
4960          return $this->AutoPageBreak;
4961      }
4962  
4963      /**
4964       * Add page if needed.
4965       * @param $h (float) Cell height. Default value: 0.
4966       * @param $y (mixed) starting y position, leave empty for current position.
4967       * @param $addpage (boolean) if true add a page, otherwise only return the true/false state
4968       * @return boolean true in case of page break, false otherwise.
4969       * @since 3.2.000 (2008-07-01)
4970       * @protected
4971       */
4972  	protected function checkPageBreak($h=0, $y='', $addpage=true) {
4973          if (TCPDF_STATIC::empty_string($y)) {
4974              $y = $this->y;
4975          }
4976          $current_page = $this->page;
4977          if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
4978              if ($addpage) {
4979                  //Automatic page break
4980                  $x = $this->x;
4981                  $this->AddPage($this->CurOrientation);
4982                  $this->y = $this->tMargin;
4983                  $oldpage = $this->page - 1;
4984                  if ($this->rtl) {
4985                      if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
4986                          $this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
4987                      } else {
4988                          $this->x = $x;
4989                      }
4990                  } else {
4991                      if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
4992                          $this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
4993                      } else {
4994                          $this->x = $x;
4995                      }
4996                  }
4997              }
4998              return true;
4999          }
5000          if ($current_page != $this->page) {
5001              // account for columns mode
5002              return true;
5003          }
5004          return false;
5005      }
5006  
5007      /**
5008       * 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 />
5009       * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5010       * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
5011       * @param $h (float) Cell height. Default value: 0.
5012       * @param $txt (string) String to print. Default value: empty string.
5013       * @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)))
5014       * @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.
5015       * @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>
5016       * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5017       * @param $link (mixed) URL or identifier returned by AddLink().
5018       * @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.
5019       * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5020       * @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>
5021       * @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>
5022       * @public
5023       * @since 1.0
5024       * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
5025       */
5026  	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') {
5027          $prev_cell_margin = $this->cell_margin;
5028          $prev_cell_padding = $this->cell_padding;
5029          $this->adjustCellPadding($border);
5030          if (!$ignore_min_height) {
5031              $min_cell_height = $this->getCellHeight($this->FontSize);
5032              if ($h < $min_cell_height) {
5033                  $h = $min_cell_height;
5034              }
5035          }
5036          $this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5037          // apply text shadow if enabled
5038          if ($this->txtshadow['enabled']) {
5039              // save data
5040              $x = $this->x;
5041              $y = $this->y;
5042              $bc = $this->bgcolor;
5043              $fc = $this->fgcolor;
5044              $sc = $this->strokecolor;
5045              $alpha = $this->alpha;
5046              // print shadow
5047              $this->x += $this->txtshadow['depth_w'];
5048              $this->y += $this->txtshadow['depth_h'];
5049              $this->SetFillColorArray($this->txtshadow['color']);
5050              $this->SetTextColorArray($this->txtshadow['color']);
5051              $this->SetDrawColorArray($this->txtshadow['color']);
5052              if ($this->txtshadow['opacity'] != $alpha['CA']) {
5053                  $this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
5054              }
5055              if ($this->state == 2) {
5056                  $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5057              }
5058              //restore data
5059              $this->x = $x;
5060              $this->y = $y;
5061              $this->SetFillColorArray($bc);
5062              $this->SetTextColorArray($fc);
5063              $this->SetDrawColorArray($sc);
5064              if ($this->txtshadow['opacity'] != $alpha['CA']) {
5065                  $this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
5066              }
5067          }
5068          if ($this->state == 2) {
5069              $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5070          }
5071          $this->cell_padding = $prev_cell_padding;
5072          $this->cell_margin = $prev_cell_margin;
5073      }
5074  
5075      /**
5076       * 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 />
5077       * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5078       * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
5079       * @param $h (float) Cell height. Default value: 0.
5080       * @param $txt (string) String to print. Default value: empty string.
5081       * @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)))
5082       * @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.
5083       * @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>
5084       * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5085       * @param $link (mixed) URL or identifier returned by AddLink().
5086       * @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.
5087       * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5088       * @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>
5089       * @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>
5090       * @return string containing cell code
5091       * @protected
5092       * @since 1.0
5093       * @see Cell()
5094       */
5095  	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') {
5096          // replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
5097          $txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
5098          $prev_cell_margin = $this->cell_margin;
5099          $prev_cell_padding = $this->cell_padding;
5100          $txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
5101          $rs = ''; //string to be returned
5102          $this->adjustCellPadding($border);
5103          if (!$ignore_min_height) {
5104              $min_cell_height = $this->getCellHeight($this->FontSize);
5105              if ($h < $min_cell_height) {
5106                  $h = $min_cell_height;
5107              }
5108          }
5109          $k = $this->k;
5110          // check page for no-write regions and adapt page margins if necessary
5111          list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
5112          if ($this->rtl) {
5113              $x = $this->x - $this->cell_margin['R'];
5114          } else {
5115              $x = $this->x + $this->cell_margin['L'];
5116          }
5117          $y = $this->y + $this->cell_margin['T'];
5118          $prev_font_stretching = $this->font_stretching;
5119          $prev_font_spacing = $this->font_spacing;
5120          // cell vertical alignment
5121          switch ($calign) {
5122              case 'A': {
5123                  // font top
5124                  switch ($valign) {
5125                      case 'T': {
5126                          // top
5127                          $y -= $this->cell_padding['T'];
5128                          break;
5129                      }
5130                      case 'B': {
5131                          // bottom
5132                          $y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5133                          break;
5134                      }
5135                      default:
5136                      case 'C':
5137                      case 'M': {
5138                          // center
5139                          $y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5140                          break;
5141                      }
5142                  }
5143                  break;
5144              }
5145              case 'L': {
5146                  // font baseline
5147                  switch ($valign) {
5148                      case 'T': {
5149                          // top
5150                          $y -= ($this->cell_padding['T'] + $this->FontAscent);
5151                          break;
5152                      }
5153                      case 'B': {
5154                          // bottom
5155                          $y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5156                          break;
5157                      }
5158                      default:
5159                      case 'C':
5160                      case 'M': {
5161                          // center
5162                          $y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5163                          break;
5164                      }
5165                  }
5166                  break;
5167              }
5168              case 'D': {
5169                  // font bottom
5170                  switch ($valign) {
5171                      case 'T': {
5172                          // top
5173                          $y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5174                          break;
5175                      }
5176                      case 'B': {
5177                          // bottom
5178                          $y -= ($h - $this->cell_padding['B']);
5179                          break;
5180                      }
5181                      default:
5182                      case 'C':
5183                      case 'M': {
5184                          // center
5185                          $y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5186                          break;
5187                      }
5188                  }
5189                  break;
5190              }
5191              case 'B': {
5192                  // cell bottom
5193                  $y -= $h;
5194                  break;
5195              }
5196              case 'C':
5197              case 'M': {
5198                  // cell center
5199                  $y -= ($h / 2);
5200                  break;
5201              }
5202              default:
5203              case 'T': {
5204                  // cell top
5205                  break;
5206              }
5207          }
5208          // text vertical alignment
5209          switch ($valign) {
5210              case 'T': {
5211                  // top
5212                  $yt = $y + $this->cell_padding['T'];
5213                  break;
5214              }
5215              case 'B': {
5216                  // bottom
5217                  $yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5218                  break;
5219              }
5220              default:
5221              case 'C':
5222              case 'M': {
5223                  // center
5224                  $yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5225                  break;
5226              }
5227          }
5228          $basefonty = $yt + $this->FontAscent;
5229          if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5230              if ($this->rtl) {
5231                  $w = $x - $this->lMargin;
5232              } else {
5233                  $w = $this->w - $this->rMargin - $x;
5234              }
5235          }
5236          $s = '';
5237          // fill and borders
5238          if (is_string($border) AND (strlen($border) == 4)) {
5239              // full border
5240              $border = 1;
5241          }
5242          if ($fill OR ($border == 1)) {
5243              if ($fill) {
5244                  $op = ($border == 1) ? 'B' : 'f';
5245              } else {
5246                  $op = 'S';
5247              }
5248              if ($this->rtl) {
5249                  $xk = (($x - $w) * $k);
5250              } else {
5251                  $xk = ($x * $k);
5252              }
5253              $s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5254          }
5255          // draw borders
5256          $s .= $this->getCellBorder($x, $y, $w, $h, $border);
5257          if ($txt != '') {
5258              $txt2 = $txt;
5259              if ($this->isunicode) {
5260                  if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
5261                      $txt2 = TCPDF_FONTS::UTF8ToLatin1($txt2, $this->isunicode, $this->CurrentFont);
5262                  } else {
5263                      $unicode = TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont); // array of UTF-8 unicode values
5264                      $unicode = TCPDF_FONTS::utf8Bidi($unicode, '', $this->tmprtl, $this->isunicode, $this->CurrentFont);
5265                      // replace thai chars (if any)
5266                      if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
5267                          // number of chars
5268                          $numchars = count($unicode);
5269                          // po pla, for far, for fan
5270                          $longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
5271                          // do chada, to patak
5272                          $lowtail = array(0x0e0e, 0x0e0f);
5273                          // mai hun arkad, sara i, sara ii, sara ue, sara uee
5274                          $upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
5275                          // mai ek, mai tho, mai tri, mai chattawa, karan
5276                          $tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
5277                          // sara u, sara uu, pinthu
5278                          $lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
5279                          $output = array();
5280                          for ($i = 0; $i < $numchars; $i++) {
5281                              if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
5282                                  $ch0 = $unicode[$i];
5283                                  $ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
5284                                  $ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
5285                                  $chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
5286                                  if (in_array($ch0, $tonemark)) {
5287                                      if ($chn == 0x0e33) {
5288                                          // sara um
5289                                          if (in_array($ch1, $longtail)) {
5290                                              // tonemark at upper left
5291                                              $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5292                                          } else {
5293                                              // tonemark at upper right (normal position)
5294                                              $output[] = $ch0;
5295                                          }
5296                                      } elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
5297                                          // tonemark at lower left
5298                                          $output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
5299                                      } elseif (in_array($ch1, $upvowel)) {
5300                                          if (in_array($ch2, $longtail)) {
5301                                              // tonemark at upper left
5302                                              $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5303                                          } else {
5304                                              // tonemark at upper right (normal position)
5305                                              $output[] = $ch0;
5306                                          }
5307                                      } else {
5308                                          // tonemark at lower right
5309                                          $output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
5310                                      }
5311                                  } elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
5312                                      // add lower left nikhahit and sara aa
5313                                      if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
5314                                          $output[] = 0xf711;
5315                                          $this->CurrentFont['subsetchars'][0xf711] = true;
5316                                          $output[] = 0x0e32;
5317                                          $this->CurrentFont['subsetchars'][0x0e32] = true;
5318                                      } else {
5319                                          $output[] = $ch0;
5320                                      }
5321                                  } elseif (in_array($ch1, $longtail)) {
5322                                      if ($ch0 == 0x0e31) {
5323                                          // lower left mai hun arkad
5324                                          $output[] = $this->replaceChar($ch0, 0xf710);
5325                                      } elseif (in_array($ch0, $upvowel)) {
5326                                          // lower left
5327                                          $output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
5328                                      } elseif ($ch0 == 0x0e47) {
5329                                          // lower left mai tai koo
5330                                          $output[] = $this->replaceChar($ch0, 0xf712);
5331                                      } else {
5332                                          // normal character
5333                                          $output[] = $ch0;
5334                                      }
5335                                  } elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
5336                                      // lower vowel
5337                                      $output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
5338                                  } elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
5339                                      // yo ying without lower part
5340                                      $output[] = $this->replaceChar($ch0, 0xf70f);
5341                                  } elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
5342                                      // tho santan without lower part
5343                                      $output[] = $this->replaceChar($ch0, 0xf700);
5344                                  } else {
5345                                      $output[] = $ch0;
5346                                  }
5347                              } else {
5348                                  // non-thai character
5349                                  $output[] = $unicode[$i];
5350                              }
5351                          }
5352                          $unicode = $output;
5353                          // update font subsetchars
5354                          $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
5355                      } // end of K_THAI_TOPCHARS
5356                      $txt2 = TCPDF_FONTS::arrUTF8ToUTF16BE($unicode, false);
5357                  }
5358              }
5359              $txt2 = TCPDF_STATIC::_escape($txt2);
5360              // get current text width (considering general font stretching and spacing)
5361              $txwidth = $this->GetStringWidth($txt);
5362              $width = $txwidth;
5363              // check for stretch mode
5364              if ($stretch > 0) {
5365                  // calculate ratio between cell width and text width
5366                  if ($width <= 0) {
5367                      $ratio = 1;
5368                  } else {
5369                      $ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
5370                  }
5371                  // check if stretching is required
5372                  if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
5373                      // the text will be stretched to fit cell width
5374                      if ($stretch > 2) {
5375                          // set new character spacing
5376                          $this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
5377                      } else {
5378                          // set new horizontal stretching
5379                          $this->font_stretching *= $ratio;
5380                      }
5381                      // recalculate text width (the text fills the entire cell)
5382                      $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5383                      // reset alignment
5384                      $align = '';
5385                  }
5386              }
5387              if ($this->font_stretching != 100) {
5388                  // apply font stretching
5389                  $rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
5390              }
5391              if ($this->font_spacing != 0) {
5392                  // increase/decrease font spacing
5393                  $rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
5394              }
5395              if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5396                  $s .= 'q '.$this->TextColor.' ';
5397              }
5398              // rendering mode
5399              $s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
5400              // count number of spaces
5401              $ns = substr_count($txt, chr(32));
5402              // Justification
5403              $spacewidth = 0;
5404              if (($align == 'J') AND ($ns > 0)) {
5405                  if ($this->isUnicodeFont()) {
5406                      // get string width without spaces
5407                      $width = $this->GetStringWidth(str_replace(' ', '', $txt));
5408                      // calculate average space width
5409                      $spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / ($this->FontSize?$this->FontSize:1);
5410                      if ($this->font_stretching != 100) {
5411                          // word spacing is affected by stretching
5412                          $spacewidth /= ($this->font_stretching / 100);
5413                      }
5414                      // set word position to be used with TJ operator
5415                      $txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
5416                      $unicode_justification = true;
5417                  } else {
5418                      // get string width
5419                      $width = $txwidth;
5420                      // new space width
5421                      $spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
5422                      if ($this->font_stretching != 100) {
5423                          // word spacing (Tw) is affected by stretching
5424                          $spacewidth /= ($this->font_stretching / 100);
5425                      }
5426                      // set word spacing
5427                      $rs .= sprintf('BT %F Tw ET ', $spacewidth);
5428                  }
5429                  $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5430              }
5431              // replace carriage return characters
5432              $txt2 = str_replace("\r", ' ', $txt2);
5433              switch ($align) {
5434                  case 'C': {
5435                      $dx = ($w - $width) / 2;
5436                      break;
5437                  }
5438                  case 'R': {
5439                      if ($this->rtl) {
5440                          $dx = $this->cell_padding['R'];
5441                      } else {
5442                          $dx = $w - $width - $this->cell_padding['R'];
5443                      }
5444                      break;
5445                  }
5446                  case 'L': {
5447                      if ($this->rtl) {
5448                          $dx = $w - $width - $this->cell_padding['L'];
5449                      } else {
5450                          $dx = $this->cell_padding['L'];
5451                      }
5452                      break;
5453                  }
5454                  case 'J':
5455                  default: {
5456                      if ($this->rtl) {
5457                          $dx = $this->cell_padding['R'];
5458                      } else {
5459                          $dx = $this->cell_padding['L'];
5460                      }
5461                      break;
5462                  }
5463              }
5464              if ($this->rtl) {
5465                  $xdx = $x - $dx - $width;
5466              } else {
5467                  $xdx = $x + $dx;
5468              }
5469              $xdk = $xdx * $k;
5470              // print text
5471              $s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
5472              if (isset($uniblock)) {
5473                  // print overlapping characters as separate string
5474                  $xshift = 0; // horizontal shift
5475                  $ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
5476                  $spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
5477                  foreach ($uniblock as $uk => $uniarr) {
5478                      if (($uk % 2) == 0) {
5479                          // x space to skip
5480                          if ($spacewidth != 0) {
5481                              // justification shift
5482                              $xshift += (count(array_keys($uniarr, 32)) * $spw);
5483                          }
5484                          $xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5485                      } else {
5486                          // character to print
5487                          $topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
5488                          $topchr = TCPDF_STATIC::_escape($topchr);
5489                          $s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
5490                      }
5491                  }
5492              }
5493              if ($this->underline) {
5494                  $s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5495              }
5496              if ($this->linethrough) {
5497                  $s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5498              }
5499              if ($this->overline) {
5500                  $s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5501              }
5502              if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5503                  $s .= ' Q';
5504              }
5505              if ($link) {
5506                  $this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5507              }
5508          }
5509          // output cell
5510          if ($s) {
5511              // output cell
5512              $rs .= $s;
5513              if ($this->font_spacing != 0) {
5514                  // reset font spacing mode
5515                  $rs .= ' BT 0 Tc ET';
5516              }
5517              if ($this->font_stretching != 100) {
5518                  // reset font stretching mode
5519                  $rs .= ' BT 100 Tz ET';
5520              }
5521          }
5522          // reset word spacing
5523          if (!$this->isUnicodeFont() AND ($align == 'J')) {
5524              $rs .= ' BT 0 Tw ET';
5525          }
5526          // reset stretching and spacing
5527          $this->font_stretching = $prev_font_stretching;
5528          $this->font_spacing = $prev_font_spacing;
5529          $this->lasth = $h;
5530          if ($ln > 0) {
5531              //Go to the beginning of the next line
5532              $this->y = $y + $h + $this->cell_margin['B'];
5533              if ($ln == 1) {
5534                  if ($this->rtl) {
5535                      $this->x = $this->w - $this->rMargin;
5536                  } else {
5537                      $this->x = $this->lMargin;
5538                  }
5539              }
5540          } else {
5541              // go left or right by case
5542              if ($this->rtl) {
5543                  $this->x = $x - $w - $this->cell_margin['L'];
5544              } else {
5545                  $this->x = $x + $w + $this->cell_margin['R'];
5546              }
5547          }
5548          $gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5549          $rs = $gstyles.$rs;
5550          $this->cell_padding = $prev_cell_padding;
5551          $this->cell_margin = $prev_cell_margin;
5552          return $rs;
5553      }
5554  
5555      /**
5556       * Replace a char if is defined on the current font.
5557       * @param $oldchar (int) Integer code (unicode) of the character to replace.
5558       * @param $newchar (int) Integer code (unicode) of the new character.
5559       * @return int the replaced char or the old char in case the new char i not defined
5560       * @protected
5561       * @since 5.9.167 (2012-06-22)
5562       */
5563  	protected function replaceChar($oldchar, $newchar) {
5564          if ($this->isCharDefined($newchar)) {
5565              // add the new char on the subset list
5566              $this->CurrentFont['subsetchars'][$newchar] = true;
5567              // return the new character
5568              return $newchar;
5569          }
5570          // return the old char
5571          return $oldchar;
5572      }
5573  
5574      /**
5575       * Returns the code to draw the cell border
5576       * @param $x (float) X coordinate.
5577       * @param $y (float) Y coordinate.
5578       * @param $w (float) Cell width.
5579       * @param $h (float) Cell height.
5580       * @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)))
5581       * @return string containing cell border code
5582       * @protected
5583       * @see SetLineStyle()
5584       * @since 5.7.000 (2010-08-02)
5585       */
5586  	protected function getCellBorder($x, $y, $w, $h, $brd) {
5587          $s = ''; // string to be returned
5588          if (empty($brd)) {
5589              return $s;
5590          }
5591          if ($brd == 1) {
5592              $brd = array('LRTB' => true);
5593          }
5594          // calculate coordinates for border
5595          $k = $this->k;
5596          if ($this->rtl) {
5597              $xeL = ($x - $w) * $k;
5598              $xeR = $x * $k;
5599          } else {
5600              $xeL = $x * $k;
5601              $xeR = ($x + $w) * $k;
5602          }
5603          $yeL = (($this->h - ($y + $h)) * $k);
5604          $yeT = (($this->h - $y) * $k);
5605          $xeT = $xeL;
5606          $xeB = $xeR;
5607          $yeR = $yeT;
5608          $yeB = $yeL;
5609          if (is_string($brd)) {
5610              // convert string to array
5611              $slen = strlen($brd);
5612              $newbrd = array();
5613              for ($i = 0; $i < $slen; ++$i) {
5614                  $newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
5615              }
5616              $brd = $newbrd;
5617          }
5618          if (isset($brd['mode'])) {
5619              $mode = $brd['mode'];
5620              unset($brd['mode']);
5621          } else {
5622              $mode = 'normal';
5623          }
5624          foreach ($brd as $border => $style) {
5625              if (is_array($style) AND !empty($style)) {
5626                  // apply border style
5627                  $prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
5628                  $s .= $this->SetLineStyle($style, true)."\n";
5629              }
5630              switch ($mode) {
5631                  case 'ext': {
5632                      $off = (($this->LineWidth / 2) * $k);
5633                      $xL = $xeL - $off;
5634                      $xR = $xeR + $off;
5635                      $yT = $yeT + $off;
5636                      $yL = $yeL - $off;
5637                      $xT = $xL;
5638                      $xB = $xR;
5639                      $yR = $yT;
5640                      $yB = $yL;
5641                      $w += $this->LineWidth;
5642                      $h += $this->LineWidth;
5643                      break;
5644                  }
5645                  case 'int': {
5646                      $off = ($this->LineWidth / 2) * $k;
5647                      $xL = $xeL + $off;
5648                      $xR = $xeR - $off;
5649                      $yT = $yeT - $off;
5650                      $yL = $yeL + $off;
5651                      $xT = $xL;
5652                      $xB = $xR;
5653                      $yR = $yT;
5654                      $yB = $yL;
5655                      $w -= $this->LineWidth;
5656                      $h -= $this->LineWidth;
5657                      break;
5658                  }
5659                  case 'normal':
5660                  default: {
5661                      $xL = $xeL;
5662                      $xT = $xeT;
5663                      $xB = $xeB;
5664                      $xR = $xeR;
5665                      $yL = $yeL;
5666                      $yT = $yeT;
5667                      $yB = $yeB;
5668                      $yR = $yeR;
5669                      break;
5670                  }
5671              }
5672              // draw borders by case
5673              if (strlen($border) == 4) {
5674                  $s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5675              } elseif (strlen($border) == 3) {
5676                  if (strpos($border,'B') === false) { // LTR
5677                      $s .= sprintf('%F %F m ', $xL, $yL);
5678                      $s .= sprintf('%F %F l ', $xT, $yT);
5679                      $s .= sprintf('%F %F l ', $xR, $yR);
5680                      $s .= sprintf('%F %F l ', $xB, $yB);
5681                      $s .= 'S ';
5682                  } elseif (strpos($border,'L') === false) { // TRB
5683                      $s .= sprintf('%F %F m ', $xT, $yT);
5684                      $s .= sprintf('%F %F l ', $xR, $yR);
5685                      $s .= sprintf('%F %F l ', $xB, $yB);
5686                      $s .= sprintf('%F %F l ', $xL, $yL);
5687                      $s .= 'S ';
5688                  } elseif (strpos($border,'T') === false) { // RBL
5689                      $s .= sprintf('%F %F m ', $xR, $yR);
5690                      $s .= sprintf('%F %F l ', $xB, $yB);
5691                      $s .= sprintf('%F %F l ', $xL, $yL);
5692                      $s .= sprintf('%F %F l ', $xT, $yT);
5693                      $s .= 'S ';
5694                  } elseif (strpos($border,'R') === false) { // BLT
5695                      $s .= sprintf('%F %F m ', $xB, $yB);
5696                      $s .= sprintf('%F %F l ', $xL, $yL);
5697                      $s .= sprintf('%F %F l ', $xT, $yT);
5698                      $s .= sprintf('%F %F l ', $xR, $yR);
5699                      $s .= 'S ';
5700                  }
5701              } elseif (strlen($border) == 2) {
5702                  if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5703                      $s .= sprintf('%F %F m ', $xL, $yL);
5704                      $s .= sprintf('%F %F l ', $xT, $yT);
5705                      $s .= sprintf('%F %F l ', $xR, $yR);
5706                      $s .= 'S ';
5707                  } elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5708                      $s .= sprintf('%F %F m ', $xT, $yT);
5709                      $s .= sprintf('%F %F l ', $xR, $yR);
5710                      $s .= sprintf('%F %F l ', $xB, $yB);
5711                      $s .= 'S ';
5712                  } elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5713                      $s .= sprintf('%F %F m ', $xR, $yR);
5714                      $s .= sprintf('%F %F l ', $xB, $yB);
5715                      $s .= sprintf('%F %F l ', $xL, $yL);
5716                      $s .= 'S ';
5717                  } elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5718                      $s .= sprintf('%F %F m ', $xB, $yB);
5719                      $s .= sprintf('%F %F l ', $xL, $yL);
5720                      $s .= sprintf('%F %F l ', $xT, $yT);
5721                      $s .= 'S ';
5722                  } elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5723                      $s .= sprintf('%F %F m ', $xL, $yL);
5724                      $s .= sprintf('%F %F l ', $xT, $yT);
5725                      $s .= 'S ';
5726                      $s .= sprintf('%F %F m ', $xR, $yR);
5727                      $s .= sprintf('%F %F l ', $xB, $yB);
5728                      $s .= 'S ';
5729                  } elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5730                      $s .= sprintf('%F %F m ', $xT, $yT);
5731                      $s .= sprintf('%F %F l ', $xR, $yR);
5732                      $s .= 'S ';
5733                      $s .= sprintf('%F %F m ', $xB, $yB);
5734                      $s .= sprintf('%F %F l ', $xL, $yL);
5735                      $s .= 'S ';
5736                  }
5737              } else { // strlen($border) == 1
5738                  if (strpos($border,'L') !== false) { // L
5739                      $s .= sprintf('%F %F m ', $xL, $yL);
5740                      $s .= sprintf('%F %F l ', $xT, $yT);
5741                      $s .= 'S ';
5742                  } elseif (strpos($border,'T') !== false) { // T
5743                      $s .= sprintf('%F %F m ', $xT, $yT);
5744                      $s .= sprintf('%F %F l ', $xR, $yR);
5745                      $s .= 'S ';
5746                  } elseif (strpos($border,'R') !== false) { // R
5747                      $s .= sprintf('%F %F m ', $xR, $yR);
5748                      $s .= sprintf('%F %F l ', $xB, $yB);
5749                      $s .= 'S ';
5750                  } elseif (strpos($border,'B') !== false) { // B
5751                      $s .= sprintf('%F %F m ', $xB, $yB);
5752                      $s .= sprintf('%F %F l ', $xL, $yL);
5753                      $s .= 'S ';
5754                  }
5755              }
5756              if (is_array($style) AND !empty($style)) {
5757                  // reset border style to previous value
5758                  $s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
5759              }
5760          }
5761          return $s;
5762      }
5763  
5764      /**
5765       * This method allows printing text with line breaks.
5766       * 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 />
5767       * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
5768       * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
5769       * @param $h (float) Cell minimum height. The cell extends automatically if needed.
5770       * @param $txt (string) String to print
5771       * @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)))
5772       * @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>
5773       * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5774       * @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>
5775       * @param $x (float) x position in user units
5776       * @param $y (float) y position in user units
5777       * @param $reseth (boolean) if true reset the last cell height (default true).
5778       * @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.
5779       * @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.
5780       * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
5781       * @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.
5782       * @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.
5783       * @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 wqual to $h.
5784       * @return int Return the number of cells or 1 for html mode.
5785       * @public
5786       * @since 1.3
5787       * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
5788       */
5789  	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) {
5790          $prev_cell_margin = $this->cell_margin;
5791          $prev_cell_padding = $this->cell_padding;
5792          // adjust internal padding
5793          $this->adjustCellPadding($border);
5794          $mc_padding = $this->cell_padding;
5795          $mc_margin = $this->cell_margin;
5796          $this->cell_padding['T'] = 0;
5797          $this->cell_padding['B'] = 0;
5798          $this->setCellMargins(0, 0, 0, 0);
5799          if (TCPDF_STATIC::empty_string($this->lasth) OR $reseth) {
5800              // reset row height
5801              $this->resetLastH();
5802          }
5803          if (!TCPDF_STATIC::empty_string($y)) {
5804              $this->SetY($y);
5805          } else {
5806              $y = $this->GetY();
5807          }
5808          $resth = 0;
5809          if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
5810              // spit cell in more pages/columns
5811              $newh = ($this->PageBreakTrigger - $y);
5812              $resth = ($h - $newh); // cell to be printed on the next page/column
5813              $h = $newh;
5814          }
5815          // get current page number
5816          $startpage = $this->page;
5817          // get current column
5818          $startcolumn = $this->current_column;
5819          if (!TCPDF_STATIC::empty_string($x)) {
5820              $this->SetX($x);
5821          } else {
5822              $x = $this->GetX();
5823          }
5824          // check page for no-write regions and adapt page margins if necessary
5825          list($x, $y) = $this->checkPageRegions(0, $x, $y);
5826          // apply margins
5827          $oy = $y + $mc_margin['T'];
5828          if ($this->rtl) {
5829              $ox = ($this->w - $x - $mc_margin['R']);
5830          } else {
5831              $ox = ($x + $mc_margin['L']);
5832          }
5833          $this->x = $ox;
5834          $this->y = $oy;
5835          // set width
5836          if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5837              if ($this->rtl) {
5838                  $w = ($this->x - $this->lMargin - $mc_margin['L']);
5839              } else {
5840                  $w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
5841              }
5842          }
5843          // store original margin values
5844          $lMargin = $this->lMargin;
5845          $rMargin = $this->rMargin;
5846          if ($this->rtl) {
5847              $this->rMargin = ($this->w - $this->x);
5848              $this->lMargin = ($this->x - $w);
5849          } else {
5850              $this->lMargin = ($this->x);
5851              $this->rMargin = ($this->w - $this->x - $w);
5852          }
5853          $this->clMargin = $this->lMargin;
5854          $this->crMargin = $this->rMargin;
5855          if ($autopadding) {
5856              // add top padding
5857              $this->y += $mc_padding['T'];
5858          }
5859          if ($ishtml) { // ******* Write HTML text
5860              $this->writeHTML($txt, true, false, $reseth, true, $align);
5861              $nl = 1;
5862          } else { // ******* Write simple text
5863              $prev_FontSizePt = $this->FontSizePt;
5864              if ($fitcell) {
5865                  // ajust height values
5866                  $tobottom = ($this->h - $this->y - $this->bMargin - $this->cell_padding['T'] - $this->cell_padding['B']);
5867                  $h = $maxh = max(min($h, $tobottom), min($maxh, $tobottom));
5868              }
5869              // vertical alignment
5870              if ($maxh > 0) {
5871                  // get text height
5872                  $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5873                  if ($fitcell AND ($text_height > $maxh) AND ($this->FontSizePt > 1)) {
5874                      // try to reduce font size to fit text on cell (use a quick search algorithm)
5875                      $fmin = 1;
5876                      $fmax = $this->FontSizePt;
5877                      $diff_epsilon = (1 / $this->k); // one point (min resolution)
5878                      $maxit = (2 * min(100, max(10, intval($fmax)))); // max number of iterations
5879                      while ($maxit >= 0) {
5880                          $fmid = (($fmax + $fmin) / 2);
5881                          $this->SetFontSize($fmid, false);
5882                          $this->resetLastH();
5883                          $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5884                          $diff = ($maxh - $text_height);
5885                          if ($diff >= 0) {
5886                              if ($diff <= $diff_epsilon) {
5887                                  break;
5888                              }
5889                              $fmin = $fmid;
5890                          } else {
5891                              $fmax = $fmid;
5892                          }
5893                          --$maxit;
5894                      }
5895                      if ($maxit < 0) {
5896                          // premature exit, we get the minimum font value to fit the cell
5897                          $this->SetFontSize($fmin);
5898                          $this->resetLastH();
5899                          $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5900                      } else {
5901                          $this->SetFontSize($fmid);
5902                          $this->resetLastH();
5903                      }
5904                  }
5905                  if ($text_height < $maxh) {
5906                      if ($valign == 'M') {
5907                          // text vertically centered
5908                          $this->y += (($maxh - $text_height) / 2);
5909                      } elseif ($valign == 'B') {
5910                          // text vertically aligned on bottom
5911                          $this->y += ($maxh - $text_height);
5912                      }
5913                  }
5914              }
5915              $nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
5916              if ($fitcell) {
5917                  // restore font size
5918                  $this->SetFontSize($prev_FontSizePt);
5919              }
5920          }
5921          if ($autopadding) {
5922              // add bottom padding
5923              $this->y += $mc_padding['B'];
5924          }
5925          // Get end-of-text Y position
5926          $currentY = $this->y;
5927          // get latest page number
5928          $endpage = $this->page;
5929          if ($resth > 0) {
5930              $skip = ($endpage - $startpage);
5931              $tmpresth = $resth;
5932              while ($tmpresth > 0) {
5933                  if ($skip <= 0) {
5934                      // add a page (or trig AcceptPageBreak() for multicolumn mode)
5935                      $this->checkPageBreak($this->PageBreakTrigger + 1);
5936                  }
5937                  if ($this->num_columns > 1) {
5938                      $tmpresth -= ($this->h - $this->y - $this->bMargin);
5939                  } else {
5940                      $tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
5941                  }
5942                  --$skip;
5943              }
5944              $currentY = $this->y;
5945              $endpage = $this->page;
5946          }
5947          // get latest column
5948          $endcolumn = $this->current_column;
5949          if ($this->num_columns == 0) {
5950              $this->num_columns = 1;
5951          }
5952          // disable page regions check
5953          $check_page_regions = $this->check_page_regions;
5954          $this->check_page_regions = false;
5955          // get border modes
5956          $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
5957          $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
5958          $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
5959          // design borders around HTML cells.
5960          for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
5961              $ccode = '';
5962              $this->setPage($page);
5963              if ($this->num_columns < 2) {
5964                  // single-column mode
5965                  $this->SetX($x);
5966                  $this->y = $this->tMargin;
5967              }
5968              // account for margin changes
5969              if ($page > $startpage) {
5970                  if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
5971                      $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
5972                  } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
5973                      $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
5974                  }
5975              }
5976              if ($startpage == $endpage) {
5977                  // single page
5978                  for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
5979                      $this->selectColumn($column);
5980                      if ($this->rtl) {
5981                          $this->x -= $mc_margin['R'];
5982                      } else {
5983                          $this->x += $mc_margin['L'];
5984                      }
5985                      if ($startcolumn == $endcolumn) { // single column
5986                          $cborder = $border;
5987                          $h = max($h, ($currentY - $oy));
5988                          $this->y = $oy;
5989                      } elseif ($column == $startcolumn) { // first column
5990                          $cborder = $border_start;
5991                          $this->y = $oy;
5992                          $h = $this->h - $this->y - $this->bMargin;
5993                      } elseif ($column == $endcolumn) { // end column
5994                          $cborder = $border_end;
5995                          $h = $currentY - $this->y;
5996                          if ($resth > $h) {
5997                              $h = $resth;
5998                          }
5999                      } else { // middle column
6000                          $cborder = $border_middle;
6001                          $h = $this->h - $this->y - $this->bMargin;
6002                          $resth -= $h;
6003                      }
6004                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6005                  } // end for each column
6006              } elseif ($page == $startpage) { // first page
6007                  for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
6008                      $this->selectColumn($column);
6009                      if ($this->rtl) {
6010                          $this->x -= $mc_margin['R'];
6011                      } else {
6012                          $this->x += $mc_margin['L'];
6013                      }
6014                      if ($column == $startcolumn) { // first column
6015                          $cborder = $border_start;
6016                          $this->y = $oy;
6017                          $h = $this->h - $this->y - $this->bMargin;
6018                      } else { // middle column
6019                          $cborder = $border_middle;
6020                          $h = $this->h - $this->y - $this->bMargin;
6021                          $resth -= $h;
6022                      }
6023                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6024                  } // end for each column
6025              } elseif ($page == $endpage) { // last page
6026                  for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
6027                      $this->selectColumn($column);
6028                      if ($this->rtl) {
6029                          $this->x -= $mc_margin['R'];
6030                      } else {
6031                          $this->x += $mc_margin['L'];
6032                      }
6033                      if ($column == $endcolumn) {
6034                          // end column
6035                          $cborder = $border_end;
6036                          $h = $currentY - $this->y;
6037                          if ($resth > $h) {
6038                              $h = $resth;
6039                          }
6040                      } else {
6041                          // middle column
6042                          $cborder = $border_middle;
6043                          $h = $this->h - $this->y - $this->bMargin;
6044                          $resth -= $h;
6045                      }
6046                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6047                  } // end for each column
6048              } else { // middle page
6049                  for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6050                      $this->selectColumn($column);
6051                      if ($this->rtl) {
6052                          $this->x -= $mc_margin['R'];
6053                      } else {
6054                          $this->x += $mc_margin['L'];
6055                      }
6056                      $cborder = $border_middle;
6057                      $h = $this->h - $this->y - $this->bMargin;
6058                      $resth -= $h;
6059                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6060                  } // end for each column
6061              }
6062              if ($cborder OR $fill) {
6063                  $offsetlen = strlen($ccode);
6064                  // draw border and fill
6065                  if ($this->inxobj) {
6066                      // we are inside an XObject template
6067                      if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6068                          $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6069                          $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6070                          $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
6071                      } else {
6072                          $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
6073                          $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
6074                      }
6075                      $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6076                      $pstart = substr($pagebuff, 0, $pagemark);
6077                      $pend = substr($pagebuff, $pagemark);
6078                      $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6079                  } else {
6080                      if (end($this->transfmrk[$this->page]) !== false) {
6081                          $pagemarkkey = key($this->transfmrk[$this->page]);
6082                          $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
6083                          $this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
6084                      } elseif ($this->InFooter) {
6085                          $pagemark = $this->footerpos[$this->page];
6086                          $this->footerpos[$this->page] += $offsetlen;
6087                      } else {
6088                          $pagemark = $this->intmrk[$this->page];
6089                          $this->intmrk[$this->page] += $offsetlen;
6090                      }
6091                      $pagebuff = $this->getPageBuffer($this->page);
6092                      $pstart = substr($pagebuff, 0, $pagemark);
6093                      $pend = substr($pagebuff, $pagemark);
6094                      $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6095                  }
6096              }
6097          } // end for each page
6098          // restore page regions check
6099          $this->check_page_regions = $check_page_regions;
6100          // Get end-of-cell Y position
6101          $currentY = $this->GetY();
6102          // restore previous values
6103          if ($this->num_columns > 1) {
6104              $this->selectColumn();
6105          } else {
6106              // restore original margins
6107              $this->lMargin = $lMargin;
6108              $this->rMargin = $rMargin;
6109              if ($this->page > $startpage) {
6110                  // check for margin variations between pages (i.e. booklet mode)
6111                  $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
6112                  $dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
6113                  if (($dl != 0) OR ($dr != 0)) {
6114                      $this->lMargin += $dl;
6115                      $this->rMargin += $dr;
6116                  }
6117              }
6118          }
6119          if ($ln > 0) {
6120              //Go to the beginning of the next line
6121              $this->SetY($currentY + $mc_margin['B']);
6122              if ($ln == 2) {
6123                  $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6124              }
6125          } else {
6126              // go left or right by case
6127              $this->setPage($startpage);
6128              $this->y = $y;
6129              $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6130          }
6131          $this->setContentMark();
6132          $this->cell_padding = $prev_cell_padding;
6133          $this->cell_margin = $prev_cell_margin;
6134          $this->clMargin = $this->lMargin;
6135          $this->crMargin = $this->rMargin;
6136          return $nl;
6137      }
6138  
6139      /**
6140       * This method return the estimated number of lines for print a simple text string using Multicell() method.
6141       * @param $txt (string) String for calculating his height
6142       * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6143       * @param $reseth (boolean) if true reset the last cell height (default false).
6144       * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6145       * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6146       * @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)))
6147       * @return float Return the minimal height needed for multicell method for printing the $txt param.
6148       * @author Alexander Escalona Fern\E1ndez, Nicola Asuni
6149       * @public
6150       * @since 4.5.011
6151       */
6152  	public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6153          if ($txt === NULL) {
6154              return 0;
6155          }
6156          if ($txt === '') {
6157              // empty string
6158              return 1;
6159          }
6160          // adjust internal padding
6161          $prev_cell_padding = $this->cell_padding;
6162          $prev_lasth = $this->lasth;
6163          if (is_array($cellpadding)) {
6164              $this->cell_padding = $cellpadding;
6165          }
6166          $this->adjustCellPadding($border);
6167          if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
6168              if ($this->rtl) {
6169                  $w = $this->x - $this->lMargin;
6170              } else {
6171                  $w = $this->w - $this->rMargin - $this->x;
6172              }
6173          }
6174          $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6175          if ($reseth) {
6176              // reset row height
6177              $this->resetLastH();
6178          }
6179          $lines = 1;
6180          $sum = 0;
6181          $chars = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont), $txt, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6182          $charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6183          $length = count($chars);
6184          $lastSeparator = -1;
6185          for ($i = 0; $i < $length; ++$i) {
6186              $c = $chars[$i];
6187              $charWidth = $charsWidth[$i];
6188              if (($c != 160)
6189                      AND (($c == 173)
6190                          OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6191                          OR (($c == 45)
6192                              AND ($i > 0) AND ($i < ($length - 1))
6193                              AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i - 1)], $this->isunicode))
6194                              AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6195                          )
6196                      )
6197                  ) {
6198                  $lastSeparator = $i;
6199              }
6200              if ((($sum + $charWidth) > $wmax) OR ($c == 10)) {
6201                  ++$lines;
6202                  if ($c == 10) {
6203                      $lastSeparator = -1;
6204                      $sum = 0;
6205                  } elseif ($lastSeparator != -1) {
6206                      $i = $lastSeparator;
6207                      $lastSeparator = -1;
6208                      $sum = 0;
6209                  } else {
6210                      $sum = $charWidth;
6211                  }
6212              } else {
6213                  $sum += $charWidth;
6214              }
6215          }
6216          if ($chars[($length - 1)] == 10) {
6217              --$lines;
6218          }
6219          $this->cell_padding = $prev_cell_padding;
6220          $this->lasth = $prev_lasth;
6221          return $lines;
6222      }
6223  
6224      /**
6225       * This method return the estimated height needed for printing a simple text string using the Multicell() method.
6226       * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6227       * @pre
6228       *  // store current object
6229       *  $pdf->startTransaction();
6230       *  // store starting values
6231       *  $start_y = $pdf->GetY();
6232       *  $start_page = $pdf->getPage();
6233       *  // call your printing functions with your parameters
6234       *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6235       *  $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);
6236       *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6237       *  // get the new Y
6238       *  $end_y = $pdf->GetY();
6239       *  $end_page = $pdf->getPage();
6240       *  // calculate height
6241       *  $height = 0;
6242       *  if ($end_page == $start_page) {
6243       *      $height = $end_y - $start_y;
6244       *  } else {
6245       *      for ($page=$start_page; $page <= $end_page; ++$page) {
6246       *          $this->setPage($page);
6247       *          if ($page == $start_page) {
6248       *              // first page
6249       *              $height = $this->h - $start_y - $this->bMargin;
6250       *          } elseif ($page == $end_page) {
6251       *              // last page
6252       *              $height = $end_y - $this->tMargin;
6253       *          } else {
6254       *              $height = $this->h - $this->tMargin - $this->bMargin;
6255       *          }
6256       *      }
6257       *  }
6258       *  // restore previous object
6259       *  $pdf = $pdf->rollbackTransaction();
6260       *
6261       * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6262       * @param $txt (string) String for calculating his height
6263       * @param $reseth (boolean) if true reset the last cell height (default false).
6264       * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6265       * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6266       * @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)))
6267       * @return float Return the minimal height needed for multicell method for printing the $txt param.
6268       * @author Nicola Asuni, Alexander Escalona Fern\E1ndez
6269       * @public
6270       */
6271  	public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6272          // adjust internal padding
6273          $prev_cell_padding = $this->cell_padding;
6274          $prev_lasth = $this->lasth;
6275          if (is_array($cellpadding)) {
6276              $this->cell_padding = $cellpadding;
6277          }
6278          $this->adjustCellPadding($border);
6279          $lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6280          $height = $this->getCellHeight(($lines * $this->FontSize), $autopadding);
6281          $this->cell_padding = $prev_cell_padding;
6282          $this->lasth = $prev_lasth;
6283          return $height;
6284      }
6285  
6286      /**
6287       * This method prints text from the current position.<br />
6288       * @param $h (float) Line height
6289       * @param $txt (string) String to print
6290       * @param $link (mixed) URL or identifier returned by AddLink()
6291       * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
6292       * @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>
6293       * @param $ln (boolean) if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
6294       * @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.
6295       * @param $firstline (boolean) if true prints only the first line and return the remaining string.
6296       * @param $firstblock (boolean) if true the string is the starting of a line.
6297       * @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.
6298       * @param $wadj (float) first line width will be reduced by this amount (used in HTML mode).
6299       * @param $margin (array) margin array of the parent container
6300       * @return mixed Return the number of cells or the remaining string if $firstline = true.
6301       * @public
6302       * @since 1.5
6303       */
6304  	public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
6305          // check page for no-write regions and adapt page margins if necessary
6306          list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
6307          if (strlen($txt) == 0) {
6308              // fix empty text
6309              $txt = ' ';
6310          }
6311          if ($margin === '') {
6312              // set default margins
6313              $margin = $this->cell_margin;
6314          }
6315          // remove carriage returns
6316          $s = str_replace("\r", '', $txt);
6317          // check if string contains arabic text
6318          if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $s)) {
6319              $arabic = true;
6320          } else {
6321              $arabic = false;
6322          }
6323          // check if string contains RTL text
6324          if ($arabic OR ($this->tmprtl == 'R') OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $s)) {
6325              $rtlmode = true;
6326          } else {
6327              $rtlmode = false;
6328          }
6329          // get a char width
6330          $chrwidth = $this->GetCharWidth(46); // dot character
6331          // get array of unicode values
6332          $chars = TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont);
6333          // calculate maximum width for a single character on string
6334          $chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
6335          array_walk($chrw, array($this, 'getRawCharWidth'));
6336          $maxchwidth = max($chrw);
6337          // get array of chars
6338          $uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
6339          // get the number of characters
6340          $nb = count($chars);
6341          // replacement for SHY character (minus symbol)
6342          $shy_replacement = 45;
6343          $shy_replacement_char = TCPDF_FONTS::unichr($shy_replacement, $this->isunicode);
6344          // widht for SHY replacement
6345          $shy_replacement_width = $this->GetCharWidth($shy_replacement);
6346          // page width
6347          $pw = $w = $this->w - $this->lMargin - $this->rMargin;
6348          // calculate remaining line width ($w)
6349          if ($this->rtl) {
6350              $w = $this->x - $this->lMargin;
6351          } else {
6352              $w = $this->w - $this->rMargin - $this->x;
6353          }
6354          // max column width
6355          $wmax = ($w - $wadj);
6356          if (!$firstline) {
6357              $wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
6358          }
6359          if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
6360              // the maximum width character do not fit on column
6361              return '';
6362          }
6363          // minimum row height
6364          $row_height = max($h, $this->getCellHeight($this->FontSize));
6365          // max Y
6366          $maxy = $this->y + $maxh - max($row_height, $h);
6367          $start_page = $this->page;
6368          $i = 0; // character position
6369          $j = 0; // current starting position
6370          $sep = -1; // position of the last blank space
6371          $prevsep = $sep; // previous separator
6372          $shy = false; // true if the last blank is a soft hypen (SHY)
6373          $prevshy = $shy; // previous shy mode
6374          $l = 0; // current string length
6375          $nl = 0; //number of lines
6376          $linebreak = false;
6377          $pc = 0; // previous character
6378          // for each character
6379          while ($i < $nb) {
6380              if (($maxh > 0) AND ($this->y > $maxy) ) {
6381                  break;
6382              }
6383              //Get the current character
6384              $c = $chars[$i];
6385              if ($c == 10) { // 10 = "\n" = new line
6386                  //Explicit line break
6387                  if ($align == 'J') {
6388                      if ($this->rtl) {
6389                          $talign = 'R';
6390                      } else {
6391                          $talign = 'L';
6392                      }
6393                  } else {
6394                      $talign = $align;
6395                  }
6396                  $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6397                  if ($firstline) {
6398                      $startx = $this->x;
6399                      $tmparr = array_slice($chars, $j, ($i - $j));
6400                      if ($rtlmode) {
6401                          $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6402                      }
6403                      $linew = $this->GetArrStringWidth($tmparr);
6404                      unset($tmparr);
6405                      if ($this->rtl) {
6406                          $this->endlinex = $startx - $linew;
6407                      } else {
6408                          $this->endlinex = $startx + $linew;
6409                      }
6410                      $w = $linew;
6411                      $tmpcellpadding = $this->cell_padding;
6412                      if ($maxh == 0) {
6413                          $this->SetCellPadding(0);
6414                      }
6415                  }
6416                  if ($firstblock AND $this->isRTLTextDir()) {
6417                      $tmpstr = $this->stringRightTrim($tmpstr);
6418                  }
6419                  // Skip newlines at the begining of a page or column
6420                  if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
6421                      $this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6422                  }
6423                  unset($tmpstr);
6424                  if ($firstline) {
6425                      $this->cell_padding = $tmpcellpadding;
6426                      return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6427                  }
6428                  ++$nl;
6429                  $j = $i + 1;
6430                  $l = 0;
6431                  $sep = -1;
6432                  $prevsep = $sep;
6433                  $shy = false;
6434                  // account for margin changes
6435                  if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6436                      $this->AcceptPageBreak();
6437                      if ($this->rtl) {
6438                          $this->x -= $margin['R'];
6439                      } else {
6440                          $this->x += $margin['L'];
6441                      }
6442                      $this->lMargin += $margin['L'];
6443                      $this->rMargin += $margin['R'];
6444                  }
6445                  $w = $this->getRemainingWidth();
6446                  $wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
6447              } else {
6448                  // 160 is the non-breaking space.
6449                  // 173 is SHY (Soft Hypen).
6450                  // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6451                  // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6452                  // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6453                  if (($c != 160)
6454                      AND (($c == 173)
6455                          OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6456                          OR (($c == 45)
6457                              AND ($i < ($nb - 1))
6458                              AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($pc, $this->isunicode))
6459                              AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6460                          )
6461                      )
6462                  ) {
6463                      // update last blank space position
6464                      $prevsep = $sep;
6465                      $sep = $i;
6466                      // check if is a SHY
6467                      if (($c == 173) OR ($c == 45)) {
6468                          $prevshy = $shy;
6469                          $shy = true;
6470                          if ($pc == 45) {
6471                              $tmp_shy_replacement_width = 0;
6472                              $tmp_shy_replacement_char = '';
6473                          } else {
6474                              $tmp_shy_replacement_width = $shy_replacement_width;
6475                              $tmp_shy_replacement_char = $shy_replacement_char;
6476                          }
6477                      } else {
6478                          $shy = false;
6479                      }
6480                  }
6481                  // update string length
6482                  if ($this->isUnicodeFont() AND ($arabic)) {
6483                      // with bidirectional algorithm some chars may be changed affecting the line length
6484                      // *** very slow ***
6485                      $l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont));
6486                  } else {
6487                      $l += $this->GetCharWidth($c);
6488                  }
6489                  if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) >= $wmax))) {
6490                      if (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) {
6491                          $sep = $prevsep;
6492                          $shy = $prevshy;
6493                      }
6494                      // we have reached the end of column
6495                      if ($sep == -1) {
6496                          // check if the line was already started
6497                          if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
6498                              OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
6499                              // print a void cell and go to next line
6500                              $this->Cell($w, $h, '', 0, 1);
6501                              $linebreak = true;
6502                              if ($firstline) {
6503                                  return (TCPDF_FONTS::UniArrSubString($uchars, $j));
6504                              }
6505                          } else {
6506                              // truncate the word because do not fit on column
6507                              $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6508                              if ($firstline) {
6509                                  $startx = $this->x;
6510                                  $tmparr = array_slice($chars, $j, ($i - $j));
6511                                  if ($rtlmode) {
6512                                      $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6513                                  }
6514                                  $linew = $this->GetArrStringWidth($tmparr);
6515                                  unset($tmparr);
6516                                  if ($this->rtl) {
6517                                      $this->endlinex = $startx - $linew;
6518                                  } else {
6519                                      $this->endlinex = $startx + $linew;
6520                                  }
6521                                  $w = $linew;
6522                                  $tmpcellpadding = $this->cell_padding;
6523                                  if ($maxh == 0) {
6524                                      $this->SetCellPadding(0);
6525                                  }
6526                              }
6527                              if ($firstblock AND $this->isRTLTextDir()) {
6528                                  $tmpstr = $this->stringRightTrim($tmpstr);
6529                              }
6530                              $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6531                              unset($tmpstr);
6532                              if ($firstline) {
6533                                  $this->cell_padding = $tmpcellpadding;
6534                                  return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6535                              }
6536                              $j = $i;
6537                              --$i;
6538                          }
6539                      } else {
6540                          // word wrapping
6541                          if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6542                              $endspace = 1;
6543                          } else {
6544                              $endspace = 0;
6545                          }
6546                          // check the length of the next string
6547                          $strrest = TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace));
6548                          $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $this->stringTrim($strrest));
6549                          if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
6550                              // truncate the word because do not fit on a full page width
6551                              $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6552                              if ($firstline) {
6553                                  $startx = $this->x;
6554                                  $tmparr = array_slice($chars, $j, ($i - $j));
6555                                  if ($rtlmode) {
6556                                      $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6557                                  }
6558                                  $linew = $this->GetArrStringWidth($tmparr);
6559                                  unset($tmparr);
6560                                  if ($this->rtl) {
6561                                      $this->endlinex = ($startx - $linew);
6562                                  } else {
6563                                      $this->endlinex = ($startx + $linew);
6564                                  }
6565                                  $w = $linew;
6566                                  $tmpcellpadding = $this->cell_padding;
6567                                  if ($maxh == 0) {
6568                                      $this->SetCellPadding(0);
6569                                  }
6570                              }
6571                              if ($firstblock AND $this->isRTLTextDir()) {
6572                                  $tmpstr = $this->stringRightTrim($tmpstr);
6573                              }
6574                              $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6575                              unset($tmpstr);
6576                              if ($firstline) {
6577                                  $this->cell_padding = $tmpcellpadding;
6578                                  return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6579                              }
6580                              $j = $i;
6581                              --$i;
6582                          } else {
6583                              // word wrapping
6584                              if ($shy) {
6585                                  // add hypen (minus symbol) at the end of the line
6586                                  $shy_width = $tmp_shy_replacement_width;
6587                                  if ($this->rtl) {
6588                                      $shy_char_left = $tmp_shy_replacement_char;
6589                                      $shy_char_right = '';
6590                                  } else {
6591                                      $shy_char_left = '';
6592                                      $shy_char_right = $tmp_shy_replacement_char;
6593                                  }
6594                              } else {
6595                                  $shy_width = 0;
6596                                  $shy_char_left = '';
6597                                  $shy_char_right = '';
6598                              }
6599                              $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
6600                              if ($firstline) {
6601                                  $startx = $this->x;
6602                                  $tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6603                                  if ($rtlmode) {
6604                                      $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6605                                  }
6606                                  $linew = $this->GetArrStringWidth($tmparr);
6607                                  unset($tmparr);
6608                                  if ($this->rtl) {
6609                                      $this->endlinex = $startx - $linew - $shy_width;
6610                                  } else {
6611                                      $this->endlinex = $startx + $linew + $shy_width;
6612                                  }
6613                                  $w = $linew;
6614                                  $tmpcellpadding = $this->cell_padding;
6615                                  if ($maxh == 0) {
6616                                      $this->SetCellPadding(0);
6617                                  }
6618                              }
6619                              // print the line
6620                              if ($firstblock AND $this->isRTLTextDir()) {
6621                                  $tmpstr = $this->stringRightTrim($tmpstr);
6622                              }
6623                              $this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6624                              unset($tmpstr);
6625                              if ($firstline) {
6626                                  if ($chars[$sep] == 45) {
6627                                      $endspace += 1;
6628                                  }
6629                                  // return the remaining text
6630                                  $this->cell_padding = $tmpcellpadding;
6631                                  return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
6632                              }
6633                              $i = $sep;
6634                              $sep = -1;
6635                              $shy = false;
6636                              $j = ($i + 1);
6637                          }
6638                      }
6639                      // account for margin changes
6640                      if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6641                          $this->AcceptPageBreak();
6642                          if ($this->rtl) {
6643                              $this->x -= $margin['R'];
6644                          } else {
6645                              $this->x += $margin['L'];
6646                          }
6647                          $this->lMargin += $margin['L'];
6648                          $this->rMargin += $margin['R'];
6649                      }
6650                      $w = $this->getRemainingWidth();
6651                      $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6652                      if ($linebreak) {
6653                          $linebreak = false;
6654                      } else {
6655                          ++$nl;
6656                          $l = 0;
6657                      }
6658                  }
6659              }
6660              // save last character
6661              $pc = $c;
6662              ++$i;
6663          } // end while i < nb
6664          // print last substring (if any)
6665          if ($l > 0) {
6666              switch ($align) {
6667                  case 'J':
6668                  case 'C': {
6669                      $w = $w;
6670                      break;
6671                  }
6672                  case 'L': {
6673                      if ($this->rtl) {
6674                          $w = $w;
6675                      } else {
6676                          $w = $l;
6677                      }
6678                      break;
6679                  }
6680                  case 'R': {
6681                      if ($this->rtl) {
6682                          $w = $l;
6683                      } else {
6684                          $w = $w;
6685                      }
6686                      break;
6687                  }
6688                  default: {
6689                      $w = $l;
6690                      break;
6691                  }
6692              }
6693              $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
6694              if ($firstline) {
6695                  $startx = $this->x;
6696                  $tmparr = array_slice($chars, $j, ($nb - $j));
6697                  if ($rtlmode) {
6698                      $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6699                  }
6700                  $linew = $this->GetArrStringWidth($tmparr);
6701                  unset($tmparr);
6702                  if ($this->rtl) {
6703                      $this->endlinex = $startx - $linew;
6704                  } else {
6705                      $this->endlinex = $startx + $linew;
6706                  }
6707                  $w = $linew;
6708                  $tmpcellpadding = $this->cell_padding;
6709                  if ($maxh == 0) {
6710                      $this->SetCellPadding(0);
6711                  }
6712              }
6713              if ($firstblock AND $this->isRTLTextDir()) {
6714                  $tmpstr = $this->stringRightTrim($tmpstr);
6715              }
6716              $this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6717              unset($tmpstr);
6718              if ($firstline) {
6719                  $this->cell_padding = $tmpcellpadding;
6720                  return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
6721              }
6722              ++$nl;
6723          }
6724          if ($firstline) {
6725              return '';
6726          }
6727          return $nl;
6728      }
6729  
6730      /**
6731       * Returns the remaining width between the current position and margins.
6732       * @return int Return the remaining width
6733       * @protected
6734       */
6735  	protected function getRemainingWidth() {
6736          list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
6737          if ($this->rtl) {
6738              return ($this->x - $this->lMargin);
6739          } else {
6740              return ($this->w - $this->rMargin - $this->x);
6741          }
6742      }
6743  
6744      /**
6745       * Set the block dimensions accounting for page breaks and page/column fitting
6746       * @param $w (float) width
6747       * @param $h (float) height
6748       * @param $x (float) X coordinate
6749       * @param $y (float) Y coodiante
6750       * @param $fitonpage (boolean) if true the block is resized to not exceed page dimensions.
6751       * @return array($w, $h, $x, $y)
6752       * @protected
6753       * @since 5.5.009 (2010-07-05)
6754       */
6755  	protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
6756          if ($w <= 0) {
6757              // set maximum width
6758              $w = ($this->w - $this->lMargin - $this->rMargin);
6759              if ($w <= 0) {
6760                  $w = 1;
6761              }
6762          }
6763          if ($h <= 0) {
6764              // set maximum height
6765              $h = ($this->PageBreakTrigger - $this->tMargin);
6766              if ($h <= 0) {
6767                  $h = 1;
6768              }
6769          }
6770          // resize the block to be vertically contained on a single page or single column
6771          if ($fitonpage OR $this->AutoPageBreak) {
6772              $ratio_wh = ($w / $h);
6773              if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
6774                  $h = $this->PageBreakTrigger - $this->tMargin;
6775                  $w = ($h * $ratio_wh);
6776              }
6777              // resize the block to be horizontally contained on a single page or single column
6778              if ($fitonpage) {
6779                  $maxw = ($this->w - $this->lMargin - $this->rMargin);
6780                  if ($w > $maxw) {
6781                      $w = $maxw;
6782                      $h = ($w / $ratio_wh);
6783                  }
6784              }
6785          }
6786          // Check whether we need a new page or new column first as this does not fit
6787          $prev_x = $this->x;
6788          $prev_y = $this->y;
6789          if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
6790              $y = $this->y;
6791              if ($this->rtl) {
6792                  $x += ($prev_x - $this->x);
6793              } else {
6794                  $x += ($this->x - $prev_x);
6795              }
6796              $this->newline = true;
6797          }
6798          // resize the block to be contained on the remaining available page or column space
6799          if ($fitonpage) {
6800              $ratio_wh = ($w / $h);
6801              if (($y + $h) > $this->PageBreakTrigger) {
6802                  $h = $this->PageBreakTrigger - $y;
6803                  $w = ($h * $ratio_wh);
6804              }
6805              if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
6806                  $w = $this->w - $this->rMargin - $x;
6807                  $h = ($w / $ratio_wh);
6808              } elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
6809                  $w = $x - $this->lMargin;
6810                  $h = ($w / $ratio_wh);
6811              }
6812          }
6813          return array($w, $h, $x, $y);
6814      }
6815  
6816      /**
6817       * Puts an image in the page.
6818       * The upper-left corner must be given.
6819       * The dimensions can be specified in different ways:<ul>
6820       * <li>explicit width and height (expressed in user unit)</li>
6821       * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
6822       * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
6823       * 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;
6824       * The format can be specified explicitly or inferred from the file extension.<br />
6825       * It is possible to put a link on the image.<br />
6826       * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
6827       * @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').
6828       * @param $x (float) Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
6829       * @param $y (float) Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
6830       * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
6831       * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
6832       * @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.
6833       * @param $link (mixed) URL or identifier returned by AddLink().
6834       * @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>
6835       * @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).
6836       * @param $dpi (int) dot-per-inch resolution used on resize
6837       * @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>
6838       * @param $ismask (boolean) true if this image is a mask, false otherwise
6839       * @param $imgmask (mixed) image object returned by this function or false
6840       * @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)))
6841       * @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).
6842       * @param $hidden (boolean) If true do not display the image.
6843       * @param $fitonpage (boolean) If true the image is resized to not exceed page dimensions.
6844       * @param $alt (boolean) If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
6845       * @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.
6846       * @return image information
6847       * @public
6848       * @since 1.1
6849       */
6850  	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()) {
6851          if ($this->state != 2) {
6852              return;
6853          }
6854          if ($x === '') {
6855              $x = $this->x;
6856          }
6857          if ($y === '') {
6858              $y = $this->y;
6859          }
6860          // check page for no-write regions and adapt page margins if necessary
6861          list($x, $y) = $this->checkPageRegions($h, $x, $y);
6862          $exurl = ''; // external streams
6863          $imsize = FALSE;
6864          // check if we are passing an image as file or string
6865          if ($file[0] === '@') {
6866              // image from string
6867              $imgdata = substr($file, 1);
6868          } else { // image file
6869              if ($file[0] === '*') {
6870                  // image as external stream
6871                  $file = substr($file, 1);
6872                  $exurl = $file;
6873              }
6874              // check if is a local file
6875              if (!@file_exists($file)) {
6876                  // try to encode spaces on filename
6877                  $tfile = str_replace(' ', '%20', $file);
6878                  if (@file_exists($tfile)) {
6879                      $file = $tfile;
6880                  }
6881              }
6882              if (($imsize = @getimagesize($file)) === FALSE) {
6883                  if (in_array($file, $this->imagekeys)) {
6884                      // get existing image data
6885                      $info = $this->getImageBuffer($file);
6886                      $imsize = array($info['w'], $info['h']);                
6887                  } elseif (strpos($file, '__tcpdf_img') === FALSE) {
6888                      $imgdata = TCPDF_STATIC::fileGetContents($file);
6889                  }
6890              }
6891          }
6892          if (!empty($imgdata)) {
6893              // copy image to cache
6894              $original_file = $file;
6895              $file = TCPDF_STATIC::getObjFilename('img');
6896              $fp = fopen($file, 'w');
6897              if (!$fp) {
6898                  $this->Error('Unable to write file: '.$file);
6899              }
6900              fwrite($fp, $imgdata);
6901              fclose($fp);
6902              unset($imgdata);
6903              $imsize = @getimagesize($file);
6904              if ($imsize === FALSE) {
6905                  unlink($file);
6906                  $file = $original_file;
6907              } else {
6908                  $this->cached_files[] = $file;
6909              }
6910          }
6911          if ($imsize === FALSE) {
6912              if (($w > 0) AND ($h > 0)) {
6913                  // get measures from specified data
6914                  $pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6915                  $ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6916                  $imsize = array($pw, $ph);
6917              } else {
6918                  $this->Error('[Image] Unable to get the size of the image: '.$file);
6919              }
6920          }
6921          // file hash
6922          $filehash = md5($this->file_id.$file);
6923          // get original image width and height in pixels
6924          list($pixw, $pixh) = $imsize;
6925          // calculate image width and height on document
6926          if (($w <= 0) AND ($h <= 0)) {
6927              // convert image size to document unit
6928              $w = $this->pixelsToUnits($pixw);
6929              $h = $this->pixelsToUnits($pixh);
6930          } elseif ($w <= 0) {
6931              $w = $h * $pixw / $pixh;
6932          } elseif ($h <= 0) {
6933              $h = $w * $pixh / $pixw;
6934          } elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
6935              if (strlen($fitbox) !== 2) {
6936                  // set default alignment
6937                  $fitbox = '--';
6938              }
6939              // scale image dimensions proportionally to fit within the ($w, $h) box
6940              if ((($w * $pixh) / ($h * $pixw)) < 1) {
6941                  // store current height
6942                  $oldh = $h;
6943                  // calculate new height
6944                  $h = $w * $pixh / $pixw;
6945                  // height difference
6946                  $hdiff = ($oldh - $h);
6947                  // vertical alignment
6948                  switch (strtoupper($fitbox[1])) {
6949                      case 'T': {
6950                          break;
6951                      }
6952                      case 'M': {
6953                          $y += ($hdiff / 2);
6954                          break;
6955                      }
6956                      case 'B': {
6957                          $y += $hdiff;
6958                          break;
6959                      }
6960                  }
6961              } else {
6962                  // store current width
6963                  $oldw = $w;
6964                  // calculate new width
6965                  $w = $h * $pixw / $pixh;
6966                  // width difference
6967                  $wdiff = ($oldw - $w);
6968                  // horizontal alignment
6969                  switch (strtoupper($fitbox[0])) {
6970                      case 'L': {
6971                          if ($this->rtl) {
6972                              $x -= $wdiff;
6973                          }
6974                          break;
6975                      }
6976                      case 'C': {
6977                          if ($this->rtl) {
6978                              $x -= ($wdiff / 2);
6979                          } else {
6980                              $x += ($wdiff / 2);
6981                          }
6982                          break;
6983                      }
6984                      case 'R': {
6985                          if (!$this->rtl) {
6986                              $x += $wdiff;
6987                          }
6988                          break;
6989                      }
6990                  }
6991              }
6992          }
6993          // fit the image on available space
6994          list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
6995          // calculate new minimum dimensions in pixels
6996          $neww = round($w * $this->k * $dpi / $this->dpi);
6997          $newh = round($h * $this->k * $dpi / $this->dpi);
6998          // check if resize is necessary (resize is used only to reduce the image)
6999          $newsize = ($neww * $newh);
7000          $pixsize = ($pixw * $pixh);
7001          if (intval($resize) == 2) {
7002              $resize = true;
7003          } elseif ($newsize >= $pixsize) {
7004              $resize = false;
7005          }
7006          // check if image has been already added on document
7007          $newimage = true;
7008          if (in_array($file, $this->imagekeys)) {
7009              $newimage = false;
7010              // get existing image data
7011              $info = $this->getImageBuffer($file);
7012              if (strpos($file, '__tcpdf_imgmask_') === FALSE) {
7013                  // check if the newer image is larger
7014                  $oldsize = ($info['w'] * $info['h']);
7015                  if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7016                      $newimage = true;
7017                  }
7018              }
7019          } elseif (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_imgmask_') === FALSE)) {
7020              // create temp image file (without alpha channel)
7021              $tempfile_plain = K_PATH_CACHE.'__tcpdf_imgmask_plain_'.$filehash;
7022              // create temp alpha file
7023              $tempfile_alpha = K_PATH_CACHE.'__tcpdf_imgmask_alpha_'.$filehash;
7024              // check for cached images
7025              if (in_array($tempfile_plain, $this->imagekeys)) {
7026                  // get existing image data
7027                  $info = $this->getImageBuffer($tempfile_plain);
7028                  // check if the newer image is larger
7029                  $oldsize = ($info['w'] * $info['h']);
7030                  if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7031                      $newimage = true;
7032                  } else {
7033                      $newimage = false;
7034                      // embed mask image
7035                      $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7036                      // embed image, masked with previously embedded mask
7037                      return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7038                  }
7039              }
7040          }
7041          if ($newimage) {
7042              //First use of image, get info
7043              $type = strtolower($type);
7044              if ($type == '') {
7045                  $type = TCPDF_IMAGES::getImageFileType($file, $imsize);
7046              } elseif ($type == 'jpg') {
7047                  $type = 'jpeg';
7048              }
7049              $mqr = TCPDF_STATIC::get_mqr();
7050              TCPDF_STATIC::set_mqr(false);
7051              // Specific image handlers (defined on TCPDF_IMAGES CLASS)
7052              $mtd = '_parse'.$type;
7053              // GD image handler function
7054              $gdfunction = 'imagecreatefrom'.$type;
7055              $info = false;
7056              if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
7057                  // TCPDF image functions
7058                  $info = TCPDF_IMAGES::$mtd($file);
7059                  if (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_imgmask_') === FALSE)
7060                      AND (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns'])))) {
7061                      return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
7062                  }
7063              }
7064              if (($info === false) AND function_exists($gdfunction)) {
7065                  try {
7066                      // GD library
7067                      $img = $gdfunction($file);
7068                      if ($img !== false) {
7069                          if ($resize) {
7070                              $imgr = imagecreatetruecolor($neww, $newh);
7071                              if (($type == 'gif') OR ($type == 'png')) {
7072                                  $imgr = TCPDF_IMAGES::setGDImageTransparency($imgr, $img);
7073                              }
7074                              imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7075                              $img = $imgr;
7076                          }
7077                          if (($type == 'gif') OR ($type == 'png')) {
7078                              $info = TCPDF_IMAGES::_toPNG($img);
7079                          } else {
7080                              $info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality);
7081                          }
7082                      }
7083                  } catch(Exception $e) {
7084                      $info = false;
7085                  }
7086              }
7087              if (($info === false) AND extension_loaded('imagick')) {
7088                  try {
7089                      // ImageMagick library
7090                      $img = new Imagick();
7091                      if ($type == 'svg') {
7092                          if ($file[0] === '@') {
7093                              // image from string
7094                              $svgimg = substr($file, 1);
7095                          } else {
7096                              // get SVG file content
7097                              $svgimg = TCPDF_STATIC::fileGetContents($file);
7098                          }
7099                          if ($svgimg !== FALSE) {
7100                              // get width and height
7101                              $regs = array();
7102                              if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7103                                  $svgtag = $regs[1];
7104                                  $tmp = array();
7105                                  if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7106                                      $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7107                                      $owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
7108                                      $svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7109                                  } else {
7110                                      $ow = $w;
7111                                  }
7112                                  $tmp = array();
7113                                  if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7114                                      $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7115                                      $ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
7116                                      $svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7117                                  } else {
7118                                      $oh = $h;
7119                                  }
7120                                  $tmp = array();
7121                                  if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7122                                      $vbw = ($ow * $this->imgscale * $this->k);
7123                                      $vbh = ($oh * $this->imgscale * $this->k);
7124                                      $vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
7125                                      $svgtag = $vbox.$svgtag;
7126                                  }
7127                                  $svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7128                              }
7129                              $img->readImageBlob($svgimg);
7130                          }
7131                      } else {
7132                          $img->readImage($file);
7133                      }
7134                      if ($resize) {
7135                          $img->resizeImage($neww, $newh, 10, 1, false);
7136                      }
7137                      $img->setCompressionQuality($this->jpeg_quality);
7138                      $img->setImageFormat('jpeg');
7139                      $tempname = TCPDF_STATIC::getObjFilename('img');
7140                      $img->writeImage($tempname);
7141                      $info = TCPDF_IMAGES::_parsejpeg($tempname);
7142                      unlink($tempname);
7143                      $img->destroy();
7144                  } catch(Exception $e) {
7145                      $info = false;
7146                  }
7147              }
7148              if ($info === false) {
7149                  // unable to process image
7150                  return;
7151              }
7152              TCPDF_STATIC::set_mqr($mqr);
7153              if ($ismask) {
7154                  // force grayscale
7155                  $info['cs'] = 'DeviceGray';
7156              }
7157              if ($imgmask !== false) {
7158                  $info['masked'] = $imgmask;
7159              }
7160              if (!empty($exurl)) {
7161                  $info['exurl'] = $exurl;
7162              }
7163              // array of alternative images
7164              $info['altimgs'] = $altimgs;
7165              // add image to document
7166              $info['i'] = $this->setImageBuffer($file, $info);
7167          }
7168          // set alignment
7169          $this->img_rb_y = $y + $h;
7170          // set alignment
7171          if ($this->rtl) {
7172              if ($palign == 'L') {
7173                  $ximg = $this->lMargin;
7174              } elseif ($palign == 'C') {
7175                  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7176              } elseif ($palign == 'R') {
7177                  $ximg = $this->w - $this->rMargin - $w;
7178              } else {
7179                  $ximg = $x - $w;
7180              }
7181              $this->img_rb_x = $ximg;
7182          } else {
7183              if ($palign == 'L') {
7184                  $ximg = $this->lMargin;
7185              } elseif ($palign == 'C') {
7186                  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7187              } elseif ($palign == 'R') {
7188                  $ximg = $this->w - $this->rMargin - $w;
7189              } else {
7190                  $ximg = $x;
7191              }
7192              $this->img_rb_x = $ximg + $w;
7193          }
7194          if ($ismask OR $hidden) {
7195              // image is not displayed
7196              return $info['i'];
7197          }
7198          $xkimg = $ximg * $this->k;
7199          if (!$alt) {
7200              // only non-alternative immages will be set
7201              $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']));
7202          }
7203          if (!empty($border)) {
7204              $bx = $this->x;
7205              $by = $this->y;
7206              $this->x = $ximg;
7207              if ($this->rtl) {
7208                  $this->x += $w;
7209              }
7210              $this->y = $y;
7211              $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7212              $this->x = $bx;
7213              $this->y = $by;
7214          }
7215          if ($link) {
7216              $this->Link($ximg, $y, $w, $h, $link, 0);
7217          }
7218          // set pointer to align the next text/objects
7219          switch($align) {
7220              case 'T': {
7221                  $this->y = $y;
7222                  $this->x = $this->img_rb_x;
7223                  break;
7224              }
7225              case 'M': {
7226                  $this->y = $y + round($h/2);
7227                  $this->x = $this->img_rb_x;
7228                  break;
7229              }
7230              case 'B': {
7231                  $this->y = $this->img_rb_y;
7232                  $this->x = $this->img_rb_x;
7233                  break;
7234              }
7235              case 'N': {
7236                  $this->SetY($this->img_rb_y);
7237                  break;
7238              }
7239              default:{
7240                  break;
7241              }
7242          }
7243          $this->endlinex = $this->img_rb_x;
7244          if ($this->inxobj) {
7245              // we are inside an XObject template
7246              $this->xobjects[$this->xobjid]['images'][] = $info['i'];
7247          }
7248          return $info['i'];
7249      }
7250  
7251      /**
7252       * Extract info from a PNG image with alpha channel using the Imagick or GD library.
7253       * @param $file (string) Name of the file containing the image.
7254       * @param $x (float) Abscissa of the upper-left corner.
7255       * @param $y (float) Ordinate of the upper-left corner.
7256       * @param $wpx (float) Original width of the image in pixels.
7257       * @param $hpx (float) original height of the image in pixels.
7258       * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7259       * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7260       * @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.
7261       * @param $link (mixed) URL or identifier returned by AddLink().
7262       * @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>
7263       * @param $resize (boolean) If true resize (reduce) the image to fit $w and $h (requires GD library).
7264       * @param $dpi (int) dot-per-inch resolution used on resize
7265       * @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>
7266       * @param $filehash (string) File hash used to build unique file names.
7267       * @author Nicola Asuni
7268       * @protected
7269       * @since 4.3.007 (2008-12-04)
7270       * @see Image()
7271       */
7272  	protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
7273          // create temp images
7274          if (empty($filehash)) {
7275              $filehash = md5($this->file_id.$file);
7276          }
7277          // create temp image file (without alpha channel)
7278          $tempfile_plain = K_PATH_CACHE.'__tcpdf_imgmask_plain_'.$filehash;
7279          // create temp alpha file
7280          $tempfile_alpha = K_PATH_CACHE.'__tcpdf_imgmask_alpha_'.$filehash;
7281          $parsed = false;
7282          $parse_error = '';
7283          // ImageMagick extension
7284          if (($parsed === false) AND extension_loaded('imagick')) {
7285              try {
7286                  // ImageMagick library
7287                  $img = new Imagick();
7288                  $img->readImage($file);
7289                  // clone image object
7290                  $imga = TCPDF_STATIC::objclone($img);
7291                  // extract alpha channel
7292                  if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
7293                      $img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
7294                  } else {
7295                      $img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7296                      $img->negateImage(true);
7297                  }
7298                  $img->setImageFormat('png');
7299                  $img->writeImage($tempfile_alpha);
7300                  // remove alpha channel
7301                  if (method_exists($imga, 'setImageMatte')) {
7302                      $imga->setImageMatte(false);
7303                  } else {
7304                      $imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7305                  }
7306                  $imga->setImageFormat('png');
7307                  $imga->writeImage($tempfile_plain);
7308                  $parsed = true;
7309              } catch (Exception $e) {
7310                  // Imagemagick fails, try with GD
7311                  $parse_error = 'Imagick library error: '.$e->getMessage();
7312              }
7313          }
7314          // GD extension
7315          if (($parsed === false) AND function_exists('imagecreatefrompng')) {
7316              try {
7317                  // generate images
7318                  $img = imagecreatefrompng($file);
7319                  $imgalpha = imagecreate($wpx, $hpx);
7320                  // generate gray scale palette (0 -> 255)
7321                  for ($c = 0; $c < 256; ++$c) {
7322                      ImageColorAllocate($imgalpha, $c, $c, $c);
7323                  }
7324                  // extract alpha channel
7325                  for ($xpx = 0; $xpx < $wpx; ++$xpx) {
7326                      for ($ypx = 0; $ypx < $hpx; ++$ypx) {
7327                          $color = imagecolorat($img, $xpx, $ypx);
7328                          // get and correct gamma color
7329                          $alpha = $this->getGDgamma($img, $color);
7330                          imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
7331                      }
7332                  }
7333                  imagepng($imgalpha, $tempfile_alpha);
7334                  imagedestroy($imgalpha);
7335                  // extract image without alpha channel
7336                  $imgplain = imagecreatetruecolor($wpx, $hpx);
7337                  imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7338                  imagepng($imgplain, $tempfile_plain);
7339                  imagedestroy($imgplain);
7340                  $parsed = true;
7341              } catch (Exception $e) {
7342                  // GD fails
7343                  $parse_error = 'GD library error: '.$e->getMessage();
7344              }
7345          }
7346          if ($parsed === false) {
7347              if (empty($parse_error)) {
7348                  $this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
7349              } else {
7350                  $this->Error($parse_error);
7351              }
7352          }
7353          // embed mask image
7354          $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7355          // embed image, masked with previously embedded mask
7356          $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7357          // remove temp files
7358          unlink($tempfile_alpha);
7359          unlink($tempfile_plain);
7360      }
7361  
7362      /**
7363       * Get the GD-corrected PNG gamma value from alpha color
7364       * @param $img (int) GD image Resource ID.
7365       * @param $c (int) alpha color
7366       * @protected
7367       * @since 4.3.007 (2008-12-04)
7368       */
7369  	protected function getGDgamma($img, $c) {
7370          if (!isset($this->gdgammacache['#'.$c])) {
7371              $colors = imagecolorsforindex($img, $c);
7372              // GD alpha is only 7 bit (0 -> 127)
7373              $this->gdgammacache['#'.$c] = (((127 - $colors['alpha']) / 127) * 255);
7374              // correct gamma
7375              $this->gdgammacache['#'.$c] = (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255);
7376              // store the latest values on cache to improve performances
7377              if (count($this->gdgammacache) > 8) {
7378                  // remove one element from the cache array
7379                  array_shift($this->gdgammacache);
7380              }
7381          }
7382          return $this->gdgammacache['#'.$c];
7383      }
7384  
7385      /**
7386       * Performs a line break.
7387       * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
7388       * @param $h (float) The height of the break. By default, the value equals the height of the last printed cell.
7389       * @param $cell (boolean) if true add the current left (or right o for RTL) padding to the X coordinate
7390       * @public
7391       * @since 1.0
7392       * @see Cell()
7393       */
7394      public function Ln($h='', $cell=false) {
7395          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'])) {
7396              // revove vertical space from the top of the column
7397              return;
7398          }
7399          if ($cell) {
7400              if ($this->rtl) {
7401                  $cellpadding = $this->cell_padding['R'];
7402              } else {
7403                  $cellpadding = $this->cell_padding['L'];
7404              }
7405          } else {
7406              $cellpadding = 0;
7407          }
7408          if ($this->rtl) {
7409              $this->x = $this->w - $this->rMargin - $cellpadding;
7410          } else {
7411              $this->x = $this->lMargin + $cellpadding;
7412          }
7413          if (is_string($h)) {
7414              $this->y += $this->lasth;
7415          } else {
7416              $this->y += $h;
7417          }
7418          $this->newline = true;
7419      }
7420  
7421      /**
7422       * Returns the relative X value of current position.
7423       * The value is relative to the left border for LTR languages and to the right border for RTL languages.
7424       * @return float
7425       * @public
7426       * @since 1.2
7427       * @see SetX(), GetY(), SetY()
7428       */
7429  	public function GetX() {
7430          //Get x position
7431          if ($this->rtl) {
7432              return ($this->w - $this->x);
7433          } else {
7434              return $this->x;
7435          }
7436      }
7437  
7438      /**
7439       * Returns the absolute X value of current position.
7440       * @return float
7441       * @public
7442       * @since 1.2
7443       * @see SetX(), GetY(), SetY()
7444       */
7445  	public function GetAbsX() {
7446          return $this->x;
7447      }
7448  
7449      /**
7450       * Returns the ordinate of the current position.
7451       * @return float
7452       * @public
7453       * @since 1.0
7454       * @see SetY(), GetX(), SetX()
7455       */
7456  	public function GetY() {
7457          return $this->y;
7458      }
7459  
7460      /**
7461       * Defines the abscissa of the current position.
7462       * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
7463       * @param $x (float) The value of the abscissa in user units.
7464       * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7465       * @public
7466       * @since 1.2
7467       * @see GetX(), GetY(), SetY(), SetXY()
7468       */
7469  	public function SetX($x, $rtloff=false) {
7470          $x = floatval($x);
7471          if (!$rtloff AND $this->rtl) {
7472              if ($x >= 0) {
7473                  $this->x = $this->w - $x;
7474              } else {
7475                  $this->x = abs($x);
7476              }
7477          } else {
7478              if ($x >= 0) {
7479                  $this->x = $x;
7480              } else {
7481                  $this->x = $this->w + $x;
7482              }
7483          }
7484          if ($this->x < 0) {
7485              $this->x = 0;
7486          }
7487          if ($this->x > $this->w) {
7488              $this->x = $this->w;
7489          }
7490      }
7491  
7492      /**
7493       * Moves the current abscissa back to the left margin and sets the ordinate.
7494       * If the passed value is negative, it is relative to the bottom of the page.
7495       * @param $y (float) The value of the ordinate in user units.
7496       * @param $resetx (bool) if true (default) reset the X position.
7497       * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7498       * @public
7499       * @since 1.0
7500       * @see GetX(), GetY(), SetY(), SetXY()
7501       */
7502  	public function SetY($y, $resetx=true, $rtloff=false) {
7503          $y = floatval($y);
7504          if ($resetx) {
7505              //reset x
7506              if (!$rtloff AND $this->rtl) {
7507                  $this->x = $this->w - $this->rMargin;
7508              } else {
7509                  $this->x = $this->lMargin;
7510              }
7511          }
7512          if ($y >= 0) {
7513              $this->y = $y;
7514          } else {
7515              $this->y = $this->h + $y;
7516          }
7517          if ($this->y < 0) {
7518              $this->y = 0;
7519          }
7520          if ($this->y > $this->h) {
7521              $this->y = $this->h;
7522          }
7523      }
7524  
7525      /**
7526       * Defines the abscissa and ordinate of the current position.
7527       * If the passed values are negative, they are relative respectively to the right and bottom of the page.
7528       * @param $x (float) The value of the abscissa.
7529       * @param $y (float) The value of the ordinate.
7530       * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7531       * @public
7532       * @since 1.2
7533       * @see SetX(), SetY()
7534       */
7535  	public function SetXY($x, $y, $rtloff=false) {
7536          $this->SetY($y, false, $rtloff);
7537          $this->SetX($x, $rtloff);
7538      }
7539  
7540      /**
7541       * Set the absolute X coordinate of the current pointer.
7542       * @param $x (float) The value of the abscissa in user units.
7543       * @public
7544       * @since 5.9.186 (2012-09-13)
7545       * @see setAbsX(), setAbsY(), SetAbsXY()
7546       */
7547  	public function SetAbsX($x) {
7548          $this->x = floatval($x);
7549      }
7550  
7551      /**
7552       * Set the absolute Y coordinate of the current pointer.
7553       * @param $y (float) (float) The value of the ordinate in user units.
7554       * @public
7555       * @since 5.9.186 (2012-09-13)
7556       * @see setAbsX(), setAbsY(), SetAbsXY()
7557       */
7558  	public function SetAbsY($y) {
7559          $this->y = floatval($y);
7560      }
7561  
7562      /**
7563       * Set the absolute X and Y coordinates of the current pointer.
7564       * @param $x (float) The value of the abscissa in user units.
7565       * @param $y (float) (float) The value of the ordinate in user units.
7566       * @public
7567       * @since 5.9.186 (2012-09-13)
7568       * @see setAbsX(), setAbsY(), SetAbsXY()
7569       */
7570  	public function SetAbsXY($x, $y) {
7571          $this->SetAbsX($x);
7572          $this->SetAbsY($y);
7573      }
7574  
7575      /**
7576       * Send the document to a given destination: string, local file or browser.
7577       * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
7578       * The method first calls Close() if necessary to terminate the document.
7579       * @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.
7580       * @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>
7581       * @public
7582       * @since 1.0
7583       * @see Close()
7584       */
7585  	public function Output($name='doc.pdf', $dest='I') {
7586          //Output PDF to some destination
7587          //Finish document if necessary
7588          if ($this->state < 3) {
7589              $this->Close();
7590          }
7591          //Normalize parameters
7592          if (is_bool($dest)) {
7593              $dest = $dest ? 'D' : 'F';
7594          }
7595          $dest = strtoupper($dest);
7596          if ($dest[0] != 'F') {
7597              $name = preg_replace('/[\s]+/', '_', $name);
7598              $name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
7599          }
7600          if ($this->sign) {
7601              // *** apply digital signature to the document ***
7602              // get the document content
7603              $pdfdoc = $this->getBuffer();
7604              // remove last newline
7605              $pdfdoc = substr($pdfdoc, 0, -1);
7606              // Remove the original buffer
7607              if (isset($this->diskcache) AND $this->diskcache) {
7608                  // remove buffer file from cache
7609                  unlink($this->buffer);
7610              }
7611              unset($this->buffer);
7612              // remove filler space
7613              $byterange_string_len = strlen(TCPDF_STATIC::$byterange_string);
7614              // define the ByteRange
7615              $byte_range = array();
7616              $byte_range[0] = 0;
7617              $byte_range[1] = strpos($pdfdoc, TCPDF_STATIC::$byterange_string) + $byterange_string_len + 10;
7618              $byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
7619              $byte_range[3] = strlen($pdfdoc) - $byte_range[2];
7620              $pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
7621              // replace the ByteRange
7622              $byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
7623              $byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
7624              $pdfdoc = str_replace(TCPDF_STATIC::$byterange_string, $byterange, $pdfdoc);
7625              // write the document to a temporary folder
7626              $tempdoc = TCPDF_STATIC::getObjFilename('doc');
7627              $f = fopen($tempdoc, 'wb');
7628              if (!$f) {
7629                  $this->Error('Unable to create temporary file: '.$tempdoc);
7630              }
7631              $pdfdoc_length = strlen($pdfdoc);
7632              fwrite($f, $pdfdoc, $pdfdoc_length);
7633              fclose($f);
7634              // get digital signature via openssl library
7635              $tempsign = TCPDF_STATIC::getObjFilename('sig');
7636              if (empty($this->signature_data['extracerts'])) {
7637                  openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
7638              } else {
7639                  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']);
7640              }
7641              unlink($tempdoc);
7642              // read signature
7643              $signature = file_get_contents($tempsign);
7644              unlink($tempsign);
7645              // extract signature
7646              $signature = substr($signature, $pdfdoc_length);
7647              $signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
7648              $tmparr = explode("\n\n", $signature);
7649              $signature = $tmparr[1];
7650              unset($tmparr);
7651              // decode signature
7652              $signature = base64_decode(trim($signature));
7653              // add TSA timestamp to signature
7654              $signature = $this->applyTSA($signature);
7655              // convert signature to hex
7656              $signature = current(unpack('H*', $signature));
7657              $signature = str_pad($signature, $this->signature_max_length, '0');
7658              // disable disk caching
7659              $this->diskcache = false;
7660              // Add signature to the document
7661              $this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
7662              $this->bufferlen = strlen($this->buffer);
7663          }
7664          switch($dest) {
7665              case 'I': {
7666                  // Send PDF to the standard output
7667                  if (ob_get_contents()) {
7668                      $this->Error('Some data has already been output, can\'t send PDF file');
7669                  }
7670                  if (php_sapi_name() != 'cli') {
7671                      // send output to a browser
7672                      header('Content-Type: application/pdf');
7673                      if (headers_sent()) {
7674                          $this->Error('Some data has already been output to browser, can\'t send PDF file');
7675                      }
7676                      header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7677                      //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7678                      header('Pragma: public');
7679                      header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7680                      header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7681                      header('Content-Disposition: inline; filename="'.basename($name).'"');
7682                      TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7683                  } else {
7684                      echo $this->getBuffer();
7685                  }
7686                  break;
7687              }
7688              case 'D': {
7689                  // download PDF as file
7690                  if (ob_get_contents()) {
7691                      $this->Error('Some data has already been output, can\'t send PDF file');
7692                  }
7693                  header('Content-Description: File Transfer');
7694                  if (headers_sent()) {
7695                      $this->Error('Some data has already been output to browser, can\'t send PDF file');
7696                  }
7697                  header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7698                  //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.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($this->getBuffer(), $this->bufferlen);
7715                  break;
7716              }
7717              case 'F':
7718              case 'FI':
7719              case 'FD': {
7720                  // save PDF to a local file
7721                  if ($this->diskcache) {
7722                      copy($this->buffer, $name);
7723                  } else {
7724                      $f = fopen($name, 'wb');
7725                      if (!$f) {
7726                          $this->Error('Unable to create output file: '.$name);
7727                      }
7728                      fwrite($f, $this->getBuffer(), $this->bufferlen);
7729                      fclose($f);
7730                  }
7731                  if ($dest == 'FI') {
7732                      // send headers to browser
7733                      header('Content-Type: application/pdf');
7734                      header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7735                      //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7736                      header('Pragma: public');
7737                      header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7738                      header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7739                      header('Content-Disposition: inline; filename="'.basename($name).'"');
7740                      TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7741                  } elseif ($dest == 'FD') {
7742                      // send headers to browser
7743                      if (ob_get_contents()) {
7744                          $this->Error('Some data has already been output, can\'t send PDF file');
7745                      }
7746                      header('Content-Description: File Transfer');
7747                      if (headers_sent()) {
7748                          $this->Error('Some data has already been output to browser, can\'t send PDF file');
7749                      }
7750                      header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7751                      header('Pragma: public');
7752                      header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7753                      header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7754                      // force download dialog
7755                      if (strpos(php_sapi_name(), 'cgi') === false) {
7756                          header('Content-Type: application/force-download');
7757                          header('Content-Type: application/octet-stream', false);
7758                          header('Content-Type: application/download', false);
7759                          header('Content-Type: application/pdf', false);
7760                      } else {
7761                          header('Content-Type: application/pdf');
7762                      }
7763                      // use the Content-Disposition header to supply a recommended filename
7764                      header('Content-Disposition: attachment; filename="'.basename($name).'"');
7765                      header('Content-Transfer-Encoding: binary');
7766                      TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7767                  }
7768                  break;
7769              }
7770              case 'E': {
7771                  // return PDF as base64 mime multi-part email attachment (RFC 2045)
7772                  $retval = 'Content-Type: application/pdf;'."\r\n";
7773                  $retval .= ' name="'.$name.'"'."\r\n";
7774                  $retval .= 'Content-Transfer-Encoding: base64'."\r\n";
7775                  $retval .= 'Content-Disposition: attachment;'."\r\n";
7776                  $retval .= ' filename="'.$name.'"'."\r\n\r\n";
7777                  $retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
7778                  return $retval;
7779              }
7780              case 'S': {
7781                  // returns PDF as a string
7782                  return $this->getBuffer();
7783              }
7784              default: {
7785                  $this->Error('Incorrect output destination: '.$dest);
7786              }
7787          }
7788          return '';
7789      }
7790  
7791      /**
7792       * Unset all class variables except the following critical variables.
7793       * @param $destroyall (boolean) if true destroys all class variables, otherwise preserves critical variables.
7794       * @param $preserve_objcopy (boolean) if true preserves the objcopy variable
7795       * @public
7796       * @since 4.5.016 (2009-02-24)
7797       */
7798  	public function _destroy($destroyall=false, $preserve_objcopy=false) {
7799          if ($destroyall AND isset($this->diskcache) AND $this->diskcache AND (!$preserve_objcopy) AND (!TCPDF_STATIC::empty_string($this->buffer))) {
7800              // remove buffer file from cache
7801              unlink($this->buffer);
7802          }
7803          if ($destroyall AND !empty($this->cached_files)) {
7804              // remove cached files
7805              foreach ($this->cached_files as $cachefile) {
7806                  if (is_file($cachefile)) {
7807                      unlink($cachefile);
7808                  }
7809              }
7810              unset($this->cached_files);
7811          }
7812          $preserve = array(
7813              'internal_encoding',
7814              'state',
7815              'bufferlen',
7816              'buffer',
7817              'diskcache',
7818              'cached_files',
7819              'sign',
7820              'signature_data',
7821              'signature_max_length',
7822              'byterange_string',
7823              'tsa_timestamp',
7824              'tsa_data'
7825          );
7826          foreach (array_keys(get_object_vars($this)) as $val) {
7827              if ($destroyall OR !in_array($val, $preserve)) {
7828                  if ((!$preserve_objcopy OR ($val != 'objcopy')) AND isset($this->$val)) {
7829                      unset($this->$val);
7830                  }
7831              }
7832          }
7833      }
7834  
7835      /**
7836       * Check for locale-related bug
7837       * @protected
7838       */
7839  	protected function _dochecks() {
7840          //Check for locale-related bug
7841          if (1.1 == 1) {
7842              $this->Error('Don\'t alter the locale before including class file');
7843          }
7844          //Check for decimal separator
7845          if (sprintf('%.1F', 1.0) != '1.0') {
7846              setlocale(LC_NUMERIC, 'C');
7847          }
7848      }
7849  
7850      /**
7851       * Return an array containing variations for the basic page number alias.
7852       * @param $a (string) Base alias.
7853       * @return array of page number aliases
7854       * @protected
7855       */
7856  	protected function getInternalPageNumberAliases($a= '') {
7857          $alias = array();
7858          // build array of Unicode + ASCII variants (the order is important)
7859          $alias = array('u' => array(), 'a' => array());
7860          $u = '{'.$a.'}';
7861          $alias['u'][] = TCPDF_STATIC::_escape($u);
7862          if ($this->isunicode) {
7863              $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($u, $this->isunicode, $this->CurrentFont));
7864              $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($u, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7865              $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($a, $this->isunicode, $this->CurrentFont));
7866              $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($a, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7867          }
7868          $alias['a'][] = TCPDF_STATIC::_escape($a);
7869          return $alias;
7870      }
7871  
7872      /**
7873       * Return an array containing all internal page aliases.
7874       * @return array of page number aliases
7875       * @protected
7876       */
7877  	protected function getAllInternalPageNumberAliases() {
7878          $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);
7879          $pnalias = array();
7880          foreach($basic_alias as $k => $a) {
7881              $pnalias[$k] = $this->getInternalPageNumberAliases($a);
7882          }
7883          return $pnalias;
7884      }
7885  
7886      /**
7887       * Replace right shift page number aliases with spaces to correct right alignment.
7888       * This works perfectly only when using monospaced fonts.
7889       * @param $page (string) Page content.
7890       * @param $aliases (array) Array of page aliases.
7891       * @param $diff (int) initial difference to add.
7892       * @return replaced page content.
7893       * @protected
7894       */
7895  	protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
7896          foreach ($aliases as $type => $alias) {
7897              foreach ($alias as $a) {
7898                  // find position of compensation factor
7899                  $startnum = (strpos($a, ':') + 1);
7900                  $a = substr($a, 0, $startnum);
7901                  if (($pos = strpos($page, $a)) !== false) {
7902                      // end of alias
7903                      $endnum = strpos($page, '}', $pos);
7904                      // string to be replaced
7905                      $aa = substr($page, $pos, ($endnum - $pos + 1));
7906                      // get compensation factor
7907                      $ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
7908                      $ratio = preg_replace('/[^0-9\.]/', '', $ratio);
7909                      $ratio = floatval($ratio);
7910                      if ($type == 'u') {
7911                          $chrdiff = floor(($diff + 12) * $ratio);
7912                          $shift = str_repeat(' ', $chrdiff);
7913                          $shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
7914                      } else {
7915                          $chrdiff = floor(($diff + 11) * $ratio);
7916                          $shift = str_repeat(' ', $chrdiff);
7917                      }
7918                      $page = str_replace($aa, $shift, $page);
7919                  }
7920              }
7921          }
7922          return $page;
7923      }
7924  
7925      /**
7926       * Set page boxes to be included on page descriptions.
7927       * @param $boxes (array) Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
7928       * @protected
7929       */
7930  	protected function setPageBoxTypes($boxes) {
7931          $this->page_boxes = array();
7932          foreach ($boxes as $box) {
7933              if (in_array($box, TCPDF_STATIC::$pageboxes)) {
7934                  $this->page_boxes[] = $box;
7935              }
7936          }
7937      }
7938  
7939      /**
7940       * Output pages (and replace page number aliases).
7941       * @protected
7942       */
7943  	protected function _putpages() {
7944          $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
7945          // get internal aliases for page numbers
7946          $pnalias = $this->getAllInternalPageNumberAliases();
7947          $num_pages = $this->numpages;
7948          $ptpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $num_pages - 1));
7949          $ptpu = TCPDF_FONTS::UTF8ToUTF16BE($ptpa, false, $this->isunicode, $this->CurrentFont);
7950          $ptp_num_chars = $this->GetNumChars($ptpa);
7951          $pagegroupnum = 0;
7952          $groupnum = 0;
7953          $ptgu = 1;
7954          $ptga = 1;
7955          $ptg_num_chars = 1;
7956          for ($n = 1; $n <= $num_pages; ++$n) {
7957              // get current page
7958              $temppage = $this->getPageBuffer($n);
7959              $pagelen = strlen($temppage);
7960              // set replacements for total pages number
7961              $pnpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $n - 1));
7962              $pnpu = TCPDF_FONTS::UTF8ToUTF16BE($pnpa, false, $this->isunicode, $this->CurrentFont);
7963              $pnp_num_chars = $this->GetNumChars($pnpa);
7964              $pdiff = 0; // difference used for right shift alignment of page numbers
7965              $gdiff = 0; // difference used for right shift alignment of page group numbers
7966              if (!empty($this->pagegroups)) {
7967                  if (isset($this->newpagegroup[$n])) {
7968                      $pagegroupnum = 0;
7969                      ++$groupnum;
7970                      $ptga = TCPDF_STATIC::formatPageNumber($this->pagegroups[$groupnum]);
7971                      $ptgu = TCPDF_FONTS::UTF8ToUTF16BE($ptga, false, $this->isunicode, $this->CurrentFont);
7972                      $ptg_num_chars = $this->GetNumChars($ptga);
7973                  }
7974                  ++$pagegroupnum;
7975                  $pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
7976                  $pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
7977                  $png_num_chars = $this->GetNumChars($pnga);
7978                  // replace page numbers
7979                  $replace = array();
7980                  $replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
7981                  $replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
7982                  $replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
7983                  $replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
7984                  list($temppage, $gdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $gdiff);
7985              }
7986              // replace page numbers
7987              $replace = array();
7988              $replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
7989              $replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
7990              $replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
7991              $replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
7992              list($temppage, $pdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $pdiff);
7993              // replace right shift alias
7994              $temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
7995              // replace EPS marker
7996              $temppage = str_replace($this->epsmarker, '', $temppage);
7997              //Page
7998              $this->page_obj_id[$n] = $this->_newobj();
7999              $out = '<<';
8000              $out .= ' /Type /Page';
8001              $out .= ' /Parent 1 0 R';
8002              if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
8003                  $out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
8004              }
8005              $out .= ' /Resources 2 0 R';
8006              foreach ($this->page_boxes as $box) {
8007                  $out .= ' /'.$box;
8008                  $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']);
8009              }
8010              if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
8011                  $out .= ' /BoxColorInfo <<';
8012                  foreach ($this->page_boxes as $box) {
8013                      if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
8014                          $out .= ' /'.$box.' <<';
8015                          if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
8016                              $color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
8017                              $out .= ' /C [';
8018                              $out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
8019                              $out .= ' ]';
8020                          }
8021                          if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
8022                              $out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
8023                          }
8024                          if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
8025                              $out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
8026                          }
8027                          if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
8028                              $dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
8029                              $out .= ' /D [';
8030                              foreach ($dashes as $dash) {
8031                                  $out .= sprintf(' %F', ($dash * $this->k));
8032                              }
8033                              $out .= ' ]';
8034                          }
8035                          $out .= ' >>';
8036                      }
8037                  }
8038                  $out .= ' >>';
8039              }
8040              $out .= ' /Contents '.($this->n + 1).' 0 R';
8041              $out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
8042              if (!$this->pdfa_mode) {
8043                  $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
8044              }
8045              if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
8046                  // page transitions
8047                  if (isset($this->pagedim[$n]['trans']['Dur'])) {
8048                      $out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
8049                  }
8050                  $out .= ' /Trans <<';
8051                  $out .= ' /Type /Trans';
8052                  if (isset($this->pagedim[$n]['trans']['S'])) {
8053                      $out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
8054                  }
8055                  if (isset($this->pagedim[$n]['trans']['D'])) {
8056                      $out .= ' /D '.$this->pagedim[$n]['trans']['D'];
8057                  }
8058                  if (isset($this->pagedim[$n]['trans']['Dm'])) {
8059                      $out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
8060                  }
8061                  if (isset($this->pagedim[$n]['trans']['M'])) {
8062                      $out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
8063                  }
8064                  if (isset($this->pagedim[$n]['trans']['Di'])) {
8065                      $out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
8066                  }
8067                  if (isset($this->pagedim[$n]['trans']['SS'])) {
8068                      $out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
8069                  }
8070                  if (isset($this->pagedim[$n]['trans']['B'])) {
8071                      $out .= ' /B '.$this->pagedim[$n]['trans']['B'];
8072                  }
8073                  $out .= ' >>';
8074              }
8075              $out .= $this->_getannotsrefs($n);
8076              $out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8077              $out .= ' >>';
8078              $out .= "\n".'endobj';
8079              $this->_out($out);
8080              //Page content
8081              $p = ($this->compress) ? gzcompress($temppage) : $temppage;
8082              $this->_newobj();
8083              $p = $this->_getrawstream($p);
8084              $this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8085              if ($this->diskcache) {
8086                  // remove temporary files
8087                  unlink($this->pages[$n]);
8088              }
8089          }
8090          //Pages root
8091          $out = $this->_getobj(1)."\n";
8092          $out .= '<< /Type /Pages /Kids [';
8093          foreach($this->page_obj_id as $page_obj) {
8094              $out .= ' '.$page_obj.' 0 R';
8095          }
8096          $out .= ' ] /Count '.$num_pages.' >>';
8097          $out .= "\n".'endobj';
8098          $this->_out($out);
8099      }
8100  
8101      /**
8102       * Output references to page annotations
8103       * @param $n (int) page number
8104       * @protected
8105       * @author Nicola Asuni
8106       * @since 4.7.000 (2008-08-29)
8107       * @deprecated
8108       */
8109  	protected function _putannotsrefs($n) {
8110          $this->_out($this->_getannotsrefs($n));
8111      }
8112  
8113      /**
8114       * Get references to page annotations.
8115       * @param $n (int) page number
8116       * @return string
8117       * @protected
8118       * @author Nicola Asuni
8119       * @since 5.0.010 (2010-05-17)
8120       */
8121  	protected function _getannotsrefs($n) {
8122          if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
8123              return '';
8124          }
8125          $out = ' /Annots [';
8126          if (isset($this->PageAnnots[$n])) {
8127              foreach ($this->PageAnnots[$n] as $key => $val) {
8128                  if (!in_array($val['n'], $this->radio_groups)) {
8129                      $out .= ' '.$val['n'].' 0 R';
8130                  }
8131              }
8132              // add radiobutton groups
8133              if (isset($this->radiobutton_groups[$n])) {
8134                  foreach ($this->radiobutton_groups[$n] as $key => $data) {
8135                      if (isset($data['n'])) {
8136                          $out .= ' '.$data['n'].' 0 R';
8137                      }
8138                  }
8139              }
8140          }
8141          if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
8142              // set reference for signature object
8143              $out .= ' '.$this->sig_obj_id.' 0 R';
8144          }
8145          if (!empty($this->empty_signature_appearance)) {
8146              foreach ($this->empty_signature_appearance as $esa) {
8147                  if ($esa['page'] == $n) {
8148                      // set reference for empty signature objects
8149                      $out .= ' '.$esa['objid'].' 0 R';
8150                  }
8151              }
8152          }
8153          $out .= ' ]';
8154          return $out;
8155      }
8156  
8157      /**
8158       * Output annotations objects for all pages.
8159       * !!! THIS METHOD IS NOT YET COMPLETED !!!
8160       * See section 12.5 of PDF 32000_2008 reference.
8161       * @protected
8162       * @author Nicola Asuni
8163       * @since 4.0.018 (2008-08-06)
8164       */
8165  	protected function _putannotsobjs() {
8166          // reset object counter
8167          for ($n=1; $n <= $this->numpages; ++$n) {
8168              if (isset($this->PageAnnots[$n])) {
8169                  // set page annotations
8170                  foreach ($this->PageAnnots[$n] as $key => $pl) {
8171                      $annot_obj_id = $this->PageAnnots[$n][$key]['n'];
8172                      // create annotation object for grouping radiobuttons
8173                      if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
8174                          $radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
8175                          $annots = '<<';
8176                          $annots .= ' /Type /Annot';
8177                          $annots .= ' /Subtype /Widget';
8178                          $annots .= ' /Rect [0 0 0 0]';
8179                          if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
8180                              // read only
8181                              $annots .= ' /F 68';
8182                              $annots .= ' /Ff 49153';
8183                          } else {
8184                              $annots .= ' /F 4'; // default print for PDF/A
8185                              $annots .= ' /Ff 49152';
8186                          }
8187                          $annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8188                          if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8189                              $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
8190                          }
8191                          $annots .= ' /FT /Btn';
8192                          $annots .= ' /Kids [';
8193                          $defval = '';
8194                          foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
8195                              if (isset($data['kid'])) {
8196                                  $annots .= ' '.$data['kid'].' 0 R';
8197                                  if ($data['def'] !== 'Off') {
8198                                      $defval = $data['def'];
8199                                  }
8200                              }
8201                          }
8202                          $annots .= ' ]';
8203                          if (!empty($defval)) {
8204                              $annots .= ' /V /'.$defval;
8205                          }
8206                          $annots .= ' >>';
8207                          $this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8208                          $this->form_obj_id[] = $radio_button_obj_id;
8209                          // store object id to be used on Parent entry of Kids
8210                          $this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
8211                      }
8212                      $formfield = false;
8213                      $pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
8214                      $a = $pl['x'] * $this->k;
8215                      $b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
8216                      $c = $pl['w'] * $this->k;
8217                      $d = $pl['h'] * $this->k;
8218                      $rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
8219                      // create new annotation object
8220                      $annots = '<</Type /Annot';
8221                      $annots .= ' /Subtype /'.$pl['opt']['subtype'];
8222                      $annots .= ' /Rect ['.$rect.']';
8223                      $ft = array('Btn', 'Tx', 'Ch', 'Sig');
8224                      if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8225                          $annots .= ' /FT /'.$pl['opt']['ft'];
8226                          $formfield = true;
8227                      }
8228                      $annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8229                      $annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
8230                      $annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8231                      $annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
8232                      if (isset($pl['opt']['f'])) {
8233                          $fval = 0;
8234                          if (is_array($pl['opt']['f'])) {
8235                              foreach ($pl['opt']['f'] as $f) {
8236                                  switch (strtolower($f)) {
8237                                      case 'invisible': {
8238                                          $fval += 1 << 0;
8239                                          break;
8240                                      }
8241                                      case 'hidden': {
8242                                          $fval += 1 << 1;
8243                                          break;
8244                                      }
8245                                      case 'print': {
8246                                          $fval += 1 << 2;
8247                                          break;
8248                                      }
8249                                      case 'nozoom': {
8250                                          $fval += 1 << 3;
8251                                          break;
8252                                      }
8253                                      case 'norotate': {
8254                                          $fval += 1 << 4;
8255                                          break;
8256                                      }
8257                                      case 'noview': {
8258                                          $fval += 1 << 5;
8259                                          break;
8260                                      }
8261                                      case 'readonly': {
8262                                          $fval += 1 << 6;
8263                                          break;
8264                                      }
8265                                      case 'locked': {
8266                                          $fval += 1 << 8;
8267                                          break;
8268                                      }
8269                                      case 'togglenoview': {
8270                                          $fval += 1 << 9;
8271                                          break;
8272                                      }
8273                                      case 'lockedcontents': {
8274                                          $fval += 1 << 10;
8275                                          break;
8276                                      }
8277                                      default: {
8278                                          break;
8279                                      }
8280                                  }
8281                              }
8282                          } else {
8283                              $fval = intval($pl['opt']['f']);
8284                          }
8285                      } else {
8286                          $fval = 4;
8287                      }
8288                      if ($this->pdfa_mode) {
8289                          // force print flag for PDF/A mode
8290                          $fval |= 4;
8291                      }
8292                      $annots .= ' /F '.intval($fval);
8293                      if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8294                          $annots .= ' /AS /'.$pl['opt']['as'];
8295                      }
8296                      if (isset($pl['opt']['ap'])) {
8297                          // appearance stream
8298                          $annots .= ' /AP <<';
8299                          if (is_array($pl['opt']['ap'])) {
8300                              foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8301                                  // $apmode can be: n = normal; r = rollover; d = down;
8302                                  $annots .= ' /'.strtoupper($apmode);
8303                                  if (is_array($apdef)) {
8304                                      $annots .= ' <<';
8305                                      foreach ($apdef as $apstate => $stream) {
8306                                          // reference to XObject that define the appearance for this mode-state
8307                                          $apsobjid = $this->_putAPXObject($c, $d, $stream);
8308                                          $annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8309                                      }
8310                                      $annots .= ' >>';
8311                                  } else {
8312                                      // reference to XObject that define the appearance for this mode
8313                                      $apsobjid = $this->_putAPXObject($c, $d, $apdef);
8314                                      $annots .= ' '.$apsobjid.' 0 R';
8315                                  }
8316                              }
8317                          } else {
8318                              $annots .= $pl['opt']['ap'];
8319                          }
8320                          $annots .= ' >>';
8321                      }
8322                      if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8323                          $annots .= ' /BS <<';
8324                          $annots .= ' /Type /Border';
8325                          if (isset($pl['opt']['bs']['w'])) {
8326                              $annots .= ' /W '.intval($pl['opt']['bs']['w']);
8327                          }
8328                          $bstyles = array('S', 'D', 'B', 'I', 'U');
8329                          if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8330                              $annots .= ' /S /'.$pl['opt']['bs']['s'];
8331                          }
8332                          if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8333                              $annots .= ' /D [';
8334                              foreach ($pl['opt']['bs']['d'] as $cord) {
8335                                  $annots .= ' '.intval($cord);
8336                              }
8337                              $annots .= ']';
8338                          }
8339                          $annots .= ' >>';
8340                      } else {
8341                          $annots .= ' /Border [';
8342                          if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8343                              $annots .= intval($pl['opt']['border'][0]).' ';
8344                              $annots .= intval($pl['opt']['border'][1]).' ';
8345                              $annots .= intval($pl['opt']['border'][2]);
8346                              if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8347                                  $annots .= ' [';
8348                                  foreach ($pl['opt']['border'][3] as $dash) {
8349                                      $annots .= intval($dash).' ';
8350                                  }
8351                                  $annots .= ']';
8352                              }
8353                          } else {
8354                              $annots .= '0 0 0';
8355                          }
8356                          $annots .= ']';
8357                      }
8358                      if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8359                          $annots .= ' /BE <<';
8360                          $bstyles = array('S', 'C');
8361                          if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
8362                              $annots .= ' /S /'.$pl['opt']['bs']['s'];
8363                          } else {
8364                              $annots .= ' /S /S';
8365                          }
8366                          if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8367                              $annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
8368                          }
8369                          $annots .= '>>';
8370                      }
8371                      if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8372                          $annots .= ' /C '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['c']);
8373                      }
8374                      //$annots .= ' /StructParent ';
8375                      //$annots .= ' /OC ';
8376                      $markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8377                      if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8378                          // this is a markup type
8379                          if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8380                              $annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8381                          }
8382                          //$annots .= ' /Popup ';
8383                          if (isset($pl['opt']['ca'])) {
8384                              $annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
8385                          }
8386                          if (isset($pl['opt']['rc'])) {
8387                              $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8388                          }
8389                          $annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
8390                          //$annots .= ' /IRT ';
8391                          if (isset($pl['opt']['subj'])) {
8392                              $annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8393                          }
8394                          //$annots .= ' /RT ';
8395                          //$annots .= ' /IT ';
8396                          //$annots .= ' /ExData ';
8397                      }
8398                      $lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8399                      // Annotation types
8400                      switch (strtolower($pl['opt']['subtype'])) {
8401                          case 'text': {
8402                              if (isset($pl['opt']['open'])) {
8403                                  $annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
8404                              }
8405                              $iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8406                              if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8407                                  $annots .= ' /Name /'.$pl['opt']['name'];
8408                              } else {
8409                                  $annots .= ' /Name /Note';
8410                              }
8411                              $statemodels = array('Marked', 'Review');
8412                              if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
8413                                  $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8414                              } else {
8415                                  $pl['opt']['statemodel'] = 'Marked';
8416                                  $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8417                              }
8418                              if ($pl['opt']['statemodel'] == 'Marked') {
8419                                  $states = array('Accepted', 'Unmarked');
8420                              } else {
8421                                  $states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8422                              }
8423                              if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
8424                                  $annots .= ' /State /'.$pl['opt']['state'];
8425                              } else {
8426                                  if ($pl['opt']['statemodel'] == 'Marked') {
8427                                      $annots .= ' /State /Unmarked';
8428                                  } else {
8429                                      $annots .= ' /State /None';
8430                                  }
8431                              }
8432                              break;
8433                          }
8434                          case 'link': {
8435                              if (is_string($pl['txt'])) {
8436                                  if ($pl['txt'][0] == '#') {
8437                                      // internal destination
8438                                      $annots .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1));
8439                                  } elseif ($pl['txt'][0] == '%') {
8440                                      // embedded PDF file
8441                                      $filename = basename(substr($pl['txt'], 1));
8442                                      $annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
8443                                  } elseif ($pl['txt'][0] == '*') {
8444                                      // embedded generic file
8445                                      $filename = basename(substr($pl['txt'], 1));
8446                                      $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});';
8447                                      $annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
8448                                  } else {
8449                                      $parsedUrl = parse_url($pl['txt']);
8450                                      if (empty($parsedUrl['scheme']) AND (strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
8451                                          // relative link to a PDF file
8452                                          $dest = '[0 /Fit]'; // default page 0
8453                                          if (!empty($parsedUrl['fragment'])) {
8454                                              // check for named destination
8455                                              $tmp = explode('=', $parsedUrl['fragment']);
8456                                              $dest = '('.((count($tmp) == 2) ? $tmp[1] : $tmp[0]).')';
8457                                          }
8458                                          $annots .= ' /A <</S /GoToR /D '.$dest.' /F '.$this->_datastring($this->unhtmlentities($parsedUrl['path']), $annot_obj_id).' /NewWindow true>>';
8459                                      } else {
8460                                          // external URI link
8461                                          $annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8462                                      }
8463                                  }
8464                              } elseif (isset($this->links[$pl['txt']])) {
8465                                  // internal link ID
8466                                  $l = $this->links[$pl['txt']];
8467                                  if (isset($this->page_obj_id[($l['p'])])) {
8468                                      $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)));
8469                                  }
8470                              }
8471                              $hmodes = array('N', 'I', 'O', 'P');
8472                              if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8473                                  $annots .= ' /H /'.$pl['opt']['h'];
8474                              } else {
8475                                  $annots .= ' /H /I';
8476                              }
8477                              //$annots .= ' /PA ';
8478                              //$annots .= ' /Quadpoints ';
8479                              break;
8480                          }
8481                          case 'freetext': {
8482                              if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8483                                  $annots .= ' /DA ('.$pl['opt']['da'].')';
8484                              }
8485                              if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8486                                  $annots .= ' /Q '.intval($pl['opt']['q']);
8487                              }
8488                              if (isset($pl['opt']['rc'])) {
8489                                  $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8490                              }
8491                              if (isset($pl['opt']['ds'])) {
8492                                  $annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8493                              }
8494                              if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8495                                  $annots .= ' /CL [';
8496                                  foreach ($pl['opt']['cl'] as $cl) {
8497                                      $annots .= sprintf('%F ', $cl * $this->k);
8498                                  }
8499                                  $annots .= ']';
8500                              }
8501                              $tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8502                              if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8503                                  $annots .= ' /IT /'.$pl['opt']['it'];
8504                              }
8505                              if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8506                                  $l = $pl['opt']['rd'][0] * $this->k;
8507                                  $r = $pl['opt']['rd'][1] * $this->k;
8508                                  $t = $pl['opt']['rd'][2] * $this->k;
8509                                  $b = $pl['opt']['rd'][3] * $this->k;
8510                                  $annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
8511                              }
8512                              if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8513                                  $annots .= ' /LE /'.$pl['opt']['le'];
8514                              }
8515                              break;
8516                          }
8517                          case 'line': {
8518                              break;
8519                          }
8520                          case 'square': {
8521                              break;
8522                          }
8523                          case 'circle': {
8524                              break;
8525                          }
8526                          case 'polygon': {
8527                              break;
8528                          }
8529                          case 'polyline': {
8530                              break;
8531                          }
8532                          case 'highlight': {
8533                              break;
8534                          }
8535                          case 'underline': {
8536                              break;
8537                          }
8538                          case 'squiggly': {
8539                              break;
8540                          }
8541                          case 'strikeout': {
8542                              break;
8543                          }
8544                          case 'stamp': {
8545                              break;
8546                          }
8547                          case 'caret': {
8548                              break;
8549                          }
8550                          case 'ink': {
8551                              break;
8552                          }
8553                          case 'popup': {
8554                              break;
8555                          }
8556                          case 'fileattachment': {
8557                              if ($this->pdfa_mode) {
8558                                  // embedded files are not allowed in PDF/A mode
8559                                  break;
8560                              }
8561                              if (!isset($pl['opt']['fs'])) {
8562                                  break;
8563                              }
8564                              $filename = basename($pl['opt']['fs']);
8565                              if (isset($this->embeddedfiles[$filename]['f'])) {
8566                                  $annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
8567                                  $iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8568                                  if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8569                                      $annots .= ' /Name /'.$pl['opt']['name'];
8570                                  } else {
8571                                      $annots .= ' /Name /PushPin';
8572                                  }
8573                                  // index (zero-based) of the annotation in the Annots array of this page
8574                                  $this->embeddedfiles[$filename]['a'] = $key;
8575                              }
8576                              break;
8577                          }
8578                          case 'sound': {
8579                              if (!isset($pl['opt']['fs'])) {
8580                                  break;
8581                              }
8582                              $filename = basename($pl['opt']['fs']);
8583                              if (isset($this->embeddedfiles[$filename]['f'])) {
8584                                  // ... TO BE COMPLETED ...
8585                                  // /R /C /B /E /CO /CP
8586                                  $annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
8587                                  $iconsapp = array('Speaker', 'Mic');
8588                                  if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8589                                      $annots .= ' /Name /'.$pl['opt']['name'];
8590                                  } else {
8591                                      $annots .= ' /Name /Speaker';
8592                                  }
8593                              }
8594                              break;
8595                          }
8596                          case 'movie': {
8597                              break;
8598                          }
8599                          case 'widget': {
8600                              $hmode = array('N', 'I', 'O', 'P', 'T');
8601                              if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8602                                  $annots .= ' /H /'.$pl['opt']['h'];
8603                              }
8604                              if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8605                                  $annots .= ' /MK <<';
8606                                  if (isset($pl['opt']['mk']['r'])) {
8607                                      $annots .= ' /R '.$pl['opt']['mk']['r'];
8608                                  }
8609                                  if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8610                                      $annots .= ' /BC '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bc']);
8611                                  }
8612                                  if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8613                                      $annots .= ' /BG '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bg']);
8614                                  }
8615                                  if (isset($pl['opt']['mk']['ca'])) {
8616                                      $annots .= ' /CA '.$pl['opt']['mk']['ca'];
8617                                  }
8618                                  if (isset($pl['opt']['mk']['rc'])) {
8619                                      $annots .= ' /RC '.$pl['opt']['mk']['rc'];
8620                                  }
8621                                  if (isset($pl['opt']['mk']['ac'])) {
8622                                      $annots .= ' /AC '.$pl['opt']['mk']['ac'];
8623                                  }
8624                                  if (isset($pl['opt']['mk']['i'])) {
8625                                      $info = $this->getImageBuffer($pl['opt']['mk']['i']);
8626                                      if ($info !== false) {
8627                                          $annots .= ' /I '.$info['n'].' 0 R';
8628                                      }
8629                                  }
8630                                  if (isset($pl['opt']['mk']['ri'])) {
8631                                      $info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8632                                      if ($info !== false) {
8633                                          $annots .= ' /RI '.$info['n'].' 0 R';
8634                                      }
8635                                  }
8636                                  if (isset($pl['opt']['mk']['ix'])) {
8637                                      $info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8638                                      if ($info !== false) {
8639                                          $annots .= ' /IX '.$info['n'].' 0 R';
8640                                      }
8641                                  }
8642                                  if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8643                                      $annots .= ' /IF <<';
8644                                      $if_sw = array('A', 'B', 'S', 'N');
8645                                      if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8646                                          $annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8647                                      }
8648                                      $if_s = array('A', 'P');
8649                                      if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8650                                          $annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8651                                      }
8652                                      if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8653                                          $annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8654                                      }
8655                                      if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8656                                          $annots .= ' /FB true';
8657                                      }
8658                                      $annots .= '>>';
8659                                  }
8660                                  if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8661                                      $annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8662                                  }
8663                                  $annots .= '>>';
8664                              } // end MK
8665                              // --- Entries for field dictionaries ---
8666                              if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8667                                  // set parent
8668                                  $annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
8669                              }
8670                              if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8671                                  $annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8672                              }
8673                              if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8674                                  $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8675                              }
8676                              if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8677                                  $annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8678                              }
8679                              if (isset($pl['opt']['ff'])) {
8680                                  if (is_array($pl['opt']['ff'])) {
8681                                      // array of bit settings
8682                                      $flag = 0;
8683                                      foreach($pl['opt']['ff'] as $val) {
8684                                          $flag += 1 << ($val - 1);
8685                                      }
8686                                  } else {
8687                                      $flag = intval($pl['opt']['ff']);
8688                                  }
8689                                  $annots .= ' /Ff '.$flag;
8690                              }
8691                              if (isset($pl['opt']['maxlen'])) {
8692                                  $annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
8693                              }
8694                              if (isset($pl['opt']['v'])) {
8695                                  $annots .= ' /V';
8696                                  if (is_array($pl['opt']['v'])) {
8697                                      foreach ($pl['opt']['v'] AS $optval) {
8698                                          if (is_float($optval)) {
8699                                              $optval = sprintf('%F', $optval);
8700                                          }
8701                                          $annots .= ' '.$optval;
8702                                      }
8703                                  } else {
8704                                      $annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8705                                  }
8706                              }
8707                              if (isset($pl['opt']['dv'])) {
8708                                  $annots .= ' /DV';
8709                                  if (is_array($pl['opt']['dv'])) {
8710                                      foreach ($pl['opt']['dv'] AS $optval) {
8711                                          if (is_float($optval)) {
8712                                              $optval = sprintf('%F', $optval);
8713                                          }
8714                                          $annots .= ' '.$optval;
8715                                      }
8716                                  } else {
8717                                      $annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8718                                  }
8719                              }
8720                              if (isset($pl['opt']['rv'])) {
8721                                  $annots .= ' /RV';
8722                                  if (is_array($pl['opt']['rv'])) {
8723                                      foreach ($pl['opt']['rv'] AS $optval) {
8724                                          if (is_float($optval)) {
8725                                              $optval = sprintf('%F', $optval);
8726                                          }
8727                                          $annots .= ' '.$optval;
8728                                      }
8729                                  } else {
8730                                      $annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
8731                                  }
8732                              }
8733                              if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
8734                                  $annots .= ' /A << '.$pl['opt']['a'].' >>';
8735                              }
8736                              if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
8737                                  $annots .= ' /AA << '.$pl['opt']['aa'].' >>';
8738                              }
8739                              if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8740                                  $annots .= ' /DA ('.$pl['opt']['da'].')';
8741                              }
8742                              if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8743                                  $annots .= ' /Q '.intval($pl['opt']['q']);
8744                              }
8745                              if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
8746                                  $annots .= ' /Opt [';
8747                                  foreach($pl['opt']['opt'] AS $copt) {
8748                                      if (is_array($copt)) {
8749                                          $annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
8750                                      } else {
8751                                          $annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8752                                      }
8753                                  }
8754                                  $annots .= ']';
8755                              }
8756                              if (isset($pl['opt']['ti'])) {
8757                                  $annots .= ' /TI '.intval($pl['opt']['ti']);
8758                              }
8759                              if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
8760                                  $annots .= ' /I [';
8761                                  foreach($pl['opt']['i'] AS $copt) {
8762                                      $annots .= intval($copt).' ';
8763                                  }
8764                                  $annots .= ']';
8765                              }
8766                              break;
8767                          }
8768                          case 'screen': {
8769                              break;
8770                          }
8771                          case 'printermark': {
8772                              break;
8773                          }
8774                          case 'trapnet': {
8775                              break;
8776                          }
8777                          case 'watermark': {
8778                              break;
8779                          }
8780                          case '3d': {
8781                              break;
8782                          }
8783                          default: {
8784                              break;
8785                          }
8786                      }
8787                      $annots .= '>>';
8788                      // create new annotation object
8789                      $this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
8790                      if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
8791                          // store reference of form object
8792                          $this->form_obj_id[] = $annot_obj_id;
8793                      }
8794                  }
8795              }
8796          } // end for each page
8797      }
8798  
8799      /**
8800       * Put appearance streams XObject used to define annotation's appearance states.
8801       * @param $w (int) annotation width
8802       * @param $h (int) annotation height
8803       * @param $stream (string) appearance stream
8804       * @return int object ID
8805       * @protected
8806       * @since 4.8.001 (2009-09-09)
8807       */
8808  	protected function _putAPXObject($w=0, $h=0, $stream='') {
8809          $stream = trim($stream);
8810          $out = $this->_getobj()."\n";
8811          $this->xobjects['AX'.$this->n] = array('n' => $this->n);
8812          $out .= '<<';
8813          $out .= ' /Type /XObject';
8814          $out .= ' /Subtype /Form';
8815          $out .= ' /FormType 1';
8816          if ($this->compress) {
8817              $stream = gzcompress($stream);
8818              $out .= ' /Filter /FlateDecode';
8819          }
8820          $rect = sprintf('%F %F', $w, $h);
8821          $out .= ' /BBox [0 0 '.$rect.']';
8822          $out .= ' /Matrix [1 0 0 1 0 0]';
8823          $out .= ' /Resources 2 0 R';
8824          $stream = $this->_getrawstream($stream);
8825          $out .= ' /Length '.strlen($stream);
8826          $out .= ' >>';
8827          $out .= ' stream'."\n".$stream."\n".'endstream';
8828          $out .= "\n".'endobj';
8829          $this->_out($out);
8830          return $this->n;
8831      }
8832  
8833      /**
8834       * Output fonts.
8835       * @author Nicola Asuni
8836       * @protected
8837       */
8838  	protected function _putfonts() {
8839          $nf = $this->n;
8840          foreach ($this->diffs as $diff) {
8841              //Encodings
8842              $this->_newobj();
8843              $this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
8844          }
8845          $mqr = TCPDF_STATIC::get_mqr();
8846          TCPDF_STATIC::set_mqr(false);
8847          foreach ($this->FontFiles as $file => $info) {
8848              // search and get font file to embedd
8849              $fontfile = TCPDF_FONTS::getFontFullPath($file, $info['fontdir']);
8850              if (!TCPDF_STATIC::empty_string($fontfile)) {
8851                  $font = file_get_contents($fontfile);
8852                  $compressed = (substr($file, -2) == '.z');
8853                  if ((!$compressed) AND (isset($info['length2']))) {
8854                      $header = (ord($font[0]) == 128);
8855                      if ($header) {
8856                          // strip first binary header
8857                          $font = substr($font, 6);
8858                      }
8859                      if ($header AND (ord($font[$info['length1']]) == 128)) {
8860                          // strip second binary header
8861                          $font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
8862                      }
8863                  } elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
8864                      if ($compressed) {
8865                          // uncompress font
8866                          $font = gzuncompress($font);
8867                      }
8868                      // merge subset characters
8869                      $subsetchars = array(); // used chars
8870                      foreach ($info['fontkeys'] as $fontkey) {
8871                          $fontinfo = $this->getFontBuffer($fontkey);
8872                          $subsetchars += $fontinfo['subsetchars'];
8873                      }
8874                      // rebuild a font subset
8875                      $font = TCPDF_FONTS::_getTrueTypeFontSubset($font, $subsetchars);
8876                      // calculate new font length
8877                      $info['length1'] = strlen($font);
8878                      if ($compressed) {
8879                          // recompress font
8880                          $font = gzcompress($font);
8881                      }
8882                  }
8883                  $this->_newobj();
8884                  $this->FontFiles[$file]['n'] = $this->n;
8885                  $stream = $this->_getrawstream($font);
8886                  $out = '<< /Length '.strlen($stream);
8887                  if ($compressed) {
8888                      $out .= ' /Filter /FlateDecode';
8889                  }
8890                  $out .= ' /Length1 '.$info['length1'];
8891                  if (isset($info['length2'])) {
8892                      $out .= ' /Length2 '.$info['length2'].' /Length3 0';
8893                  }
8894                  $out .= ' >>';
8895                  $out .= ' stream'."\n".$stream."\n".'endstream';
8896                  $out .= "\n".'endobj';
8897                  $this->_out($out);
8898              }
8899          }
8900          TCPDF_STATIC::set_mqr($mqr);
8901          foreach ($this->fontkeys as $k) {
8902              //Font objects
8903              $font = $this->getFontBuffer($k);
8904              $type = $font['type'];
8905              $name = $font['name'];
8906              if ($type == 'core') {
8907                  // standard core font
8908                  $out = $this->_getobj($this->font_obj_ids[$k])."\n";
8909                  $out .= '<</Type /Font';
8910                  $out .= ' /Subtype /Type1';
8911                  $out .= ' /BaseFont /'.$name;
8912                  $out .= ' /Name /F'.$font['i'];
8913                  if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
8914                      $out .= ' /Encoding /WinAnsiEncoding';
8915                  }
8916                  if ($k == 'helvetica') {
8917                      // add default font for annotations
8918                      $this->annotation_fonts[$k] = $font['i'];
8919                  }
8920                  $out .= ' >>';
8921                  $out .= "\n".'endobj';
8922                  $this->_out($out);
8923              } elseif (($type == 'Type1') OR ($type == 'TrueType')) {
8924                  // additional Type1 or TrueType font
8925                  $out = $this->_getobj($this->font_obj_ids[$k])."\n";
8926                  $out .= '<</Type /Font';
8927                  $out .= ' /Subtype /'.$type;
8928                  $out .= ' /BaseFont /'.$name;
8929                  $out .= ' /Name /F'.$font['i'];
8930                  $out .= ' /FirstChar 32 /LastChar 255';
8931                  $out .= ' /Widths '.($this->n + 1).' 0 R';
8932                  $out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
8933                  if ($font['enc']) {
8934                      if (isset($font['diff'])) {
8935                          $out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
8936                      } else {
8937                          $out .= ' /Encoding /WinAnsiEncoding';
8938                      }
8939                  }
8940                  $out .= ' >>';
8941                  $out .= "\n".'endobj';
8942                  $this->_out($out);
8943                  // Widths
8944                  $this->_newobj();
8945                  $s = '[';
8946                  for ($i = 32; $i < 256; ++$i) {
8947                      if (isset($font['cw'][$i])) {
8948                          $s .= $font['cw'][$i].' ';
8949                      } else {
8950                          $s .= $font['dw'].' ';
8951                      }
8952                  }
8953                  $s .= ']';
8954                  $s .= "\n".'endobj';
8955                  $this->_out($s);
8956                  //Descriptor
8957                  $this->_newobj();
8958                  $s = '<</Type /FontDescriptor /FontName /'.$name;
8959                  foreach ($font['desc'] as $fdk => $fdv) {
8960                      if (is_float($fdv)) {
8961                          $fdv = sprintf('%F', $fdv);
8962                      }
8963                      $s .= ' /'.$fdk.' '.$fdv.'';
8964                  }
8965                  if (!TCPDF_STATIC::empty_string($font['file'])) {
8966                      $s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
8967                  }
8968                  $s .= '>>';
8969                  $s .= "\n".'endobj';
8970                  $this->_out($s);
8971              } else {
8972                  // additional types
8973                  $mtd = '_put'.strtolower($type);
8974                  if (!method_exists($this, $mtd)) {
8975                      $this->Error('Unsupported font type: '.$type);
8976                  }
8977                  $this->$mtd($font);
8978              }
8979          }
8980      }
8981  
8982      /**
8983       * Adds unicode fonts.<br>
8984       * Based on PDF Reference 1.3 (section 5)
8985       * @param $font (array) font data
8986       * @protected
8987       * @author Nicola Asuni
8988       * @since 1.52.0.TC005 (2005-01-05)
8989       */
8990  	protected function _puttruetypeunicode($font) {
8991          $fontname = '';
8992          if ($font['subset']) {
8993              // change name for font subsetting
8994              $subtag = sprintf('%06u', $font['i']);
8995              $subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
8996              $fontname .= $subtag.'+';
8997          }
8998          $fontname .= $font['name'];
8999          // Type0 Font
9000          // A composite font composed of other fonts, organized hierarchically
9001          $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9002          $out .= '<< /Type /Font';
9003          $out .= ' /Subtype /Type0';
9004          $out .= ' /BaseFont /'.$fontname;
9005          $out .= ' /Name /F'.$font['i'];
9006          $out .= ' /Encoding /'.$font['enc'];
9007          $out .= ' /ToUnicode '.($this->n + 1).' 0 R';
9008          $out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
9009          $out .= ' >>';
9010          $out .= "\n".'endobj';
9011          $this->_out($out);
9012          // ToUnicode map for Identity-H
9013          $stream = TCPDF_FONT_DATA::$uni_identity_h;
9014          // ToUnicode Object
9015          $this->_newobj();
9016          $stream = ($this->compress) ? gzcompress($stream) : $stream;
9017          $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9018          $stream = $this->_getrawstream($stream);
9019          $this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
9020          // CIDFontType2
9021          // A CIDFont whose glyph descriptions are based on TrueType font technology
9022          $oid = $this->_newobj();
9023          $out = '<< /Type /Font';
9024          $out .= ' /Subtype /CIDFontType2';
9025          $out .= ' /BaseFont /'.$fontname;
9026          // A dictionary containing entries that define the character collection of the CIDFont.
9027          $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9028          $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9029          $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9030          $out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
9031          $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9032          $out .= ' /DW '.$font['dw']; // default width
9033          $out .= "\n".TCPDF_FONTS::_putfontwidths($font, 0);
9034          if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9035              $out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
9036          }
9037          $out .= ' >>';
9038          $out .= "\n".'endobj';
9039          $this->_out($out);
9040          // Font descriptor
9041          // A font descriptor describing the CIDFont default metrics other than its glyph widths
9042          $this->_newobj();
9043          $out = '<< /Type /FontDescriptor';
9044          $out .= ' /FontName /'.$fontname;
9045          foreach ($font['desc'] as $key => $value) {
9046              if (is_float($value)) {
9047                  $value = sprintf('%F', $value);
9048              }
9049              $out .= ' /'.$key.' '.$value;
9050          }
9051          $fontdir = false;
9052          if (!TCPDF_STATIC::empty_string($font['file'])) {
9053              // A stream containing a TrueType font
9054              $out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
9055              $fontdir = $this->FontFiles[$font['file']]['fontdir'];
9056          }
9057          $out .= ' >>';
9058          $out .= "\n".'endobj';
9059          $this->_out($out);
9060          if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9061              $this->_newobj();
9062              // Embed CIDToGIDMap
9063              // A specification of the mapping from CIDs to glyph indices
9064              // search and get CTG font file to embedd
9065              $ctgfile = strtolower($font['ctg']);
9066              // search and get ctg font file to embedd
9067              $fontfile = TCPDF_FONTS::getFontFullPath($ctgfile, $fontdir);
9068              if (TCPDF_STATIC::empty_string($fontfile)) {
9069                  $this->Error('Font file not found: '.$ctgfile);
9070              }
9071              $stream = $this->_getrawstream(file_get_contents($fontfile));
9072              $out = '<< /Length '.strlen($stream).'';
9073              if (substr($fontfile, -2) == '.z') { // check file extension
9074                  // Decompresses data encoded using the public-domain
9075                  // zlib/deflate compression method, reproducing the
9076                  // original text or binary data
9077                  $out .= ' /Filter /FlateDecode';
9078              }
9079              $out .= ' >>';
9080              $out .= ' stream'."\n".$stream."\n".'endstream';
9081              $out .= "\n".'endobj';
9082              $this->_out($out);
9083          }
9084      }
9085  
9086      /**
9087       * Output CID-0 fonts.
9088       * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
9089       * @param $font (array) font data
9090       * @protected
9091       * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
9092       * @since 3.2.000 (2008-06-23)
9093       */
9094  	protected function _putcidfont0($font) {
9095          $cidoffset = 0;
9096          if (!isset($font['cw'][1])) {
9097              $cidoffset = 31;
9098          }
9099          if (isset($font['cidinfo']['uni2cid'])) {
9100              // convert unicode to cid.
9101              $uni2cid = $font['cidinfo']['uni2cid'];
9102              $cw = array();
9103              foreach ($font['cw'] as $uni => $width) {
9104                  if (isset($uni2cid[$uni])) {
9105                      $cw[($uni2cid[$uni] + $cidoffset)] = $width;
9106                  } elseif ($uni < 256) {
9107                      $cw[$uni] = $width;
9108                  } // else unknown character
9109              }
9110              $font = array_merge($font, array('cw' => $cw));
9111          }
9112          $name = $font['name'];
9113          $enc = $font['enc'];
9114          if ($enc) {
9115              $longname = $name.'-'.$enc;
9116          } else {
9117              $longname = $name;
9118          }
9119          $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9120          $out .= '<</Type /Font';
9121          $out .= ' /Subtype /Type0';
9122          $out .= ' /BaseFont /'.$longname;
9123          $out .= ' /Name /F'.$font['i'];
9124          if ($enc) {
9125              $out .= ' /Encoding /'.$enc;
9126          }
9127          $out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
9128          $out .= ' >>';
9129          $out .= "\n".'endobj';
9130          $this->_out($out);
9131          $oid = $this->_newobj();
9132          $out = '<</Type /Font';
9133          $out .= ' /Subtype /CIDFontType0';
9134          $out .= ' /BaseFont /'.$name;
9135          $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9136          $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9137          $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9138          $out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
9139          $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9140          $out .= ' /DW '.$font['dw'];
9141          $out .= "\n".TCPDF_FONTS::_putfontwidths($font, $cidoffset);
9142          $out .= ' >>';
9143          $out .= "\n".'endobj';
9144          $this->_out($out);
9145          $this->_newobj();
9146          $s = '<</Type /FontDescriptor /FontName /'.$name;
9147          foreach ($font['desc'] as $k => $v) {
9148              if ($k != 'Style') {
9149                  if (is_float($v)) {
9150                      $v = sprintf('%F', $v);
9151                  }
9152                  $s .= ' /'.$k.' '.$v.'';
9153              }
9154          }
9155          $s .= '>>';
9156          $s .= "\n".'endobj';
9157          $this->_out($s);
9158      }
9159  
9160      /**
9161       * Output images.
9162       * @protected
9163       */
9164  	protected function _putimages() {
9165          $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9166          foreach ($this->imagekeys as $file) {
9167              $info = $this->getImageBuffer($file);
9168              // set object for alternate images array
9169              if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
9170                  $altoid = $this->_newobj();
9171                  $out = '[';
9172                  foreach ($info['altimgs'] as $altimage) {
9173                      if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
9174                          $out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
9175                          $out .= ' /DefaultForPrinting';
9176                          if ($altimage[1] === true) {
9177                              $out .= ' true';
9178                          } else {
9179                              $out .= ' false';
9180                          }
9181                          $out .= ' >>';
9182                      }
9183                  }
9184                  $out .= ' ]';
9185                  $out .= "\n".'endobj';
9186                  $this->_out($out);
9187              }
9188              // set image object
9189              $oid = $this->_newobj();
9190              $this->xobjects['I'.$info['i']] = array('n' => $oid);
9191              $this->setImageSubBuffer($file, 'n', $this->n);
9192              $out = '<</Type /XObject';
9193              $out .= ' /Subtype /Image';
9194              $out .= ' /Width '.$info['w'];
9195              $out .= ' /Height '.$info['h'];
9196              if (array_key_exists('masked', $info)) {
9197                  $out .= ' /SMask '.($this->n - 1).' 0 R';
9198              }
9199              // set color space
9200              $icc = false;
9201              if (isset($info['icc']) AND ($info['icc'] !== false)) {
9202                  // ICC Colour Space
9203                  $icc = true;
9204                  $out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
9205              } elseif ($info['cs'] == 'Indexed') {
9206                  // Indexed Colour Space
9207                  $out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
9208              } else {
9209                  // Device Colour Space
9210                  $out .= ' /ColorSpace /'.$info['cs'];
9211              }
9212              if ($info['cs'] == 'DeviceCMYK') {
9213                  $out .= ' /Decode [1 0 1 0 1 0 1 0]';
9214              }
9215              $out .= ' /BitsPerComponent '.$info['bpc'];
9216              if (isset($altoid) AND ($altoid > 0)) {
9217                  // reference to alternate images dictionary
9218                  $out .= ' /Alternates '.$altoid.' 0 R';
9219              }
9220              if (isset($info['exurl']) AND !empty($info['exurl'])) {
9221                  // external stream
9222                  $out .= ' /Length 0';
9223                  $out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
9224                  if (isset($info['f'])) {
9225                      $out .= ' /FFilter /'.$info['f'];
9226                  }
9227                  $out .= ' >>';
9228                  $out .= ' stream'."\n".'endstream';
9229              } else {
9230                  if (isset($info['f'])) {
9231                      $out .= ' /Filter /'.$info['f'];
9232                  }
9233                  if (isset($info['parms'])) {
9234                      $out .= ' '.$info['parms'];
9235                  }
9236                  if (isset($info['trns']) AND is_array($info['trns'])) {
9237                      $trns = '';
9238                      $count_info = count($info['trns']);
9239                      if ($info['cs'] == 'Indexed') {
9240                          $maxval =(pow(2, $info['bpc']) - 1);
9241                          for ($i = 0; $i < $count_info; ++$i) {
9242                              if (($info['trns'][$i] != 0) AND ($info['trns'][$i] != $maxval)) {
9243                                  // this is not a binary type mask @TODO: create a SMask
9244                                  $trns = '';
9245                                  break;
9246                              } elseif (empty($trns) AND ($info['trns'][$i] == 0)) {
9247                                  // store the first fully transparent value
9248                                  $trns .= $i.' '.$i.' ';
9249                              }
9250                          }
9251                      } else {
9252                          // grayscale or RGB
9253                          for ($i = 0; $i < $count_info; ++$i) {
9254                              if ($info['trns'][$i] == 0) {
9255                                  $trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
9256                              }
9257                          }
9258                      }
9259                      // Colour Key Masking
9260                      if (!empty($trns)) {
9261                          $out .= ' /Mask ['.$trns.']';
9262                      }
9263                  }
9264                  $stream = $this->_getrawstream($info['data']);
9265                  $out .= ' /Length '.strlen($stream).' >>';
9266                  $out .= ' stream'."\n".$stream."\n".'endstream';
9267              }
9268              $out .= "\n".'endobj';
9269              $this->_out($out);
9270              if ($icc) {
9271                  // ICC colour profile
9272                  $this->_newobj();
9273                  $icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
9274                  $icc = $this->_getrawstream($icc);
9275                  $this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9276              } elseif ($info['cs'] == 'Indexed') {
9277                  // colour palette
9278                  $this->_newobj();
9279                  $pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
9280                  $pal = $this->_getrawstream($pal);
9281                  $this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
9282              }
9283          }
9284      }
9285  
9286      /**
9287       * Output Form XObjects Templates.
9288       * @author Nicola Asuni
9289       * @since 5.8.017 (2010-08-24)
9290       * @protected
9291       * @see startTemplate(), endTemplate(), printTemplate()
9292       */
9293  	protected function _putxobjects() {
9294          foreach ($this->xobjects as $key => $data) {
9295              if (isset($data['outdata'])) {
9296                  $stream = str_replace($this->epsmarker, '', trim($data['outdata']));
9297                  $out = $this->_getobj($data['n'])."\n";
9298                  $out .= '<<';
9299                  $out .= ' /Type /XObject';
9300                  $out .= ' /Subtype /Form';
9301                  $out .= ' /FormType 1';
9302                  if ($this->compress) {
9303                      $stream = gzcompress($stream);
9304                      $out .= ' /Filter /FlateDecode';
9305                  }
9306                  $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));
9307                  $out .= ' /Matrix [1 0 0 1 0 0]';
9308                  $out .= ' /Resources <<';
9309                  $out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9310                  if (!$this->pdfa_mode) {
9311                      // transparency
9312                      if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
9313                          $out .= ' /ExtGState <<';
9314                          foreach ($data['extgstates'] as $k => $extgstate) {
9315                              if (isset($this->extgstates[$k]['name'])) {
9316                                  $out .= ' /'.$this->extgstates[$k]['name'];
9317                              } else {
9318                                  $out .= ' /GS'.$k;
9319                              }
9320                              $out .= ' '.$this->extgstates[$k]['n'].' 0 R';
9321                          }
9322                          $out .= ' >>';
9323                      }
9324                      if (isset($data['gradients']) AND !empty($data['gradients'])) {
9325                          $gp = '';
9326                          $gs = '';
9327                          foreach ($data['gradients'] as $id => $grad) {
9328                              // gradient patterns
9329                              $gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
9330                              // gradient shadings
9331                              $gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
9332                          }
9333                          $out .= ' /Pattern <<'.$gp.' >>';
9334                          $out .= ' /Shading <<'.$gs.' >>';
9335                      }
9336                  }
9337                  // spot colors
9338                  if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
9339                      $out .= ' /ColorSpace <<';
9340                      foreach ($data['spot_colors'] as $name => $color) {
9341                          $out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
9342                      }
9343                      $out .= ' >>';
9344                  }
9345                  // fonts
9346                  if (!empty($data['fonts'])) {
9347                      $out .= ' /Font <<';
9348                      foreach ($data['fonts'] as $fontkey => $fontid) {
9349                          $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9350                      }
9351                      $out .= ' >>';
9352                  }
9353                  // images or nested xobjects
9354                  if (!empty($data['images']) OR !empty($data['xobjects'])) {
9355                      $out .= ' /XObject <<';
9356                      foreach ($data['images'] as $imgid) {
9357                          $out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
9358                      }
9359                      foreach ($data['xobjects'] as $sub_id => $sub_objid) {
9360                          $out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
9361                      }
9362                      $out .= ' >>';
9363                  }
9364                  $out .= ' >>'; //end resources
9365                  if (isset($data['group']) AND ($data['group'] !== false)) {
9366                      // set transparency group
9367                      $out .= ' /Group << /Type /Group /S /Transparency';
9368                      if (is_array($data['group'])) {
9369                          if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
9370                              $out .= ' /CS /'.$data['group']['CS'];
9371                          }
9372                          if (isset($data['group']['I'])) {
9373                              $out .= ' /I /'.($data['group']['I']===true?'true':'false');
9374                          }
9375                          if (isset($data['group']['K'])) {
9376                              $out .= ' /K /'.($data['group']['K']===true?'true':'false');
9377                          }
9378                      }
9379                      $out .= ' >>';
9380                  }
9381                  $stream = $this->_getrawstream($stream, $data['n']);
9382                  $out .= ' /Length '.strlen($stream);
9383                  $out .= ' >>';
9384                  $out .= ' stream'."\n".$stream."\n".'endstream';
9385                  $out .= "\n".'endobj';
9386                  $this->_out($out);
9387              }
9388          }
9389      }
9390  
9391      /**
9392       * Output Spot Colors Resources.
9393       * @protected
9394       * @since 4.0.024 (2008-09-12)
9395       */
9396  	protected function _putspotcolors() {
9397          foreach ($this->spot_colors as $name => $color) {
9398              $this->_newobj();
9399              $this->spot_colors[$name]['n'] = $this->n;
9400              $out = '[/Separation /'.str_replace(' ', '#20', $name);
9401              $out .= ' /DeviceCMYK <<';
9402              $out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
9403              $out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
9404              $out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
9405              $out .= "\n".'endobj';
9406              $this->_out($out);
9407          }
9408      }
9409  
9410      /**
9411       * Return XObjects Dictionary.
9412       * @return string XObjects dictionary
9413       * @protected
9414       * @since 5.8.014 (2010-08-23)
9415       */
9416  	protected function _getxobjectdict() {
9417          $out = '';
9418          foreach ($this->xobjects as $id => $objid) {
9419              $out .= ' /'.$id.' '.$objid['n'].' 0 R';
9420          }
9421          return $out;
9422      }
9423  
9424      /**
9425       * Output Resources Dictionary.
9426       * @protected
9427       */
9428  	protected function _putresourcedict() {
9429          $out = $this->_getobj(2)."\n";
9430          $out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9431          $out .= ' /Font <<';
9432          foreach ($this->fontkeys as $fontkey) {
9433              $font = $this->getFontBuffer($fontkey);
9434              $out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
9435          }
9436          $out .= ' >>';
9437          $out .= ' /XObject <<';
9438          $out .= $this->_getxobjectdict();
9439          $out .= ' >>';
9440          // layers
9441          if (!empty($this->pdflayers)) {
9442              $out .= ' /Properties <<';
9443              foreach ($this->pdflayers as $layer) {
9444                  $out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
9445              }
9446              $out .= ' >>';
9447          }
9448          if (!$this->pdfa_mode) {
9449              // transparency
9450              if (isset($this->extgstates) AND !empty($this->extgstates)) {
9451                  $out .= ' /ExtGState <<';
9452                  foreach ($this->extgstates as $k => $extgstate) {
9453                      if (isset($extgstate['name'])) {
9454                          $out .= ' /'.$extgstate['name'];
9455                      } else {
9456                          $out .= ' /GS'.$k;
9457                      }
9458                      $out .= ' '.$extgstate['n'].' 0 R';
9459                  }
9460                  $out .= ' >>';
9461              }
9462              if (isset($this->gradients) AND !empty($this->gradients)) {
9463                  $gp = '';
9464                  $gs = '';
9465                  foreach ($this->gradients as $id => $grad) {
9466                      // gradient patterns
9467                      $gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
9468                      // gradient shadings
9469                      $gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
9470                  }
9471                  $out .= ' /Pattern <<'.$gp.' >>';
9472                  $out .= ' /Shading <<'.$gs.' >>';
9473              }
9474          }
9475          // spot colors
9476          if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
9477              $out .= ' /ColorSpace <<';
9478              foreach ($this->spot_colors as $color) {
9479                  $out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
9480              }
9481              $out .= ' >>';
9482          }
9483          $out .= ' >>';
9484          $out .= "\n".'endobj';
9485          $this->_out($out);
9486      }
9487  
9488      /**
9489       * Output Resources.
9490       * @protected
9491       */
9492  	protected function _putresources() {
9493          $this->_putextgstates();
9494          $this->_putocg();
9495          $this->_putfonts();
9496          $this->_putimages();
9497          $this->_putspotcolors();
9498          $this->_putshaders();
9499          $this->_putxobjects();
9500          $this->_putresourcedict();
9501          $this->_putdests();
9502          $this->_putEmbeddedFiles();
9503          $this->_putannotsobjs();
9504          $this->_putjavascript();
9505          $this->_putbookmarks();
9506          $this->_putencryption();
9507      }
9508  
9509      /**
9510       * Adds some Metadata information (Document Information Dictionary)
9511       * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
9512       * @return int object id
9513       * @protected
9514       */
9515  	protected function _putinfo() {
9516          $oid = $this->_newobj();
9517          $out = '<<';
9518          // store current isunicode value
9519          $prev_isunicode = $this->isunicode;
9520          if ($this->docinfounicode) {
9521              $this->isunicode = true;
9522          }
9523          if (!TCPDF_STATIC::empty_string($this->title)) {
9524              // The document's title.
9525              $out .= ' /Title '.$this->_textstring($this->title, $oid);
9526          }
9527          if (!TCPDF_STATIC::empty_string($this->author)) {
9528              // The name of the person who created the document.
9529              $out .= ' /Author '.$this->_textstring($this->author, $oid);
9530          }
9531          if (!TCPDF_STATIC::empty_string($this->subject)) {
9532              // The subject of the document.
9533              $out .= ' /Subject '.$this->_textstring($this->subject, $oid);
9534          }
9535          if (!TCPDF_STATIC::empty_string($this->keywords)) {
9536              // Keywords associated with the document.
9537              $out .= ' /Keywords '.$this->_textstring($this->keywords, $oid);
9538          }
9539          if (!TCPDF_STATIC::empty_string($this->creator)) {
9540              // 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.
9541              $out .= ' /Creator '.$this->_textstring($this->creator, $oid);
9542          }
9543          // restore previous isunicode value
9544          $this->isunicode = $prev_isunicode;
9545          // default producer
9546          $out .= ' /Producer '.$this->_textstring(TCPDF_STATIC::getTCPDFProducer(), $oid);
9547          // The date and time the document was created, in human-readable form
9548          $out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
9549          // The date and time the document was most recently modified, in human-readable form
9550          $out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
9551          // A name object indicating whether the document has been modified to include trapping information
9552          $out .= ' /Trapped /False';
9553          $out .= ' >>';
9554          $out .= "\n".'endobj';
9555          $this->_out($out);
9556          return $oid;
9557      }
9558  
9559      /**
9560       * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
9561       * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9562       * @param $xmp (string) Custom XMP data.
9563       * @since 5.9.128 (2011-10-06)
9564       * @public
9565       */
9566  	public function setExtraXMP($xmp) {
9567          $this->custom_xmp = $xmp;
9568      }
9569  
9570      /**
9571       * Put XMP data object and return ID.
9572       * @return (int) The object ID.
9573       * @since 5.9.121 (2011-09-28)
9574       * @protected
9575       */
9576  	protected function _putXMP() {
9577          $oid = $this->_newobj();
9578          // store current isunicode value
9579          $prev_isunicode = $this->isunicode;
9580          $this->isunicode = true;
9581          $prev_encrypted = $this->encrypted;
9582          $this->encrypted = false;
9583          // set XMP data
9584          $xmp = '<?xpacket begin="'.TCPDF_FONTS::unichr(0xfeff, $this->isunicode).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
9585          $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";
9586          $xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
9587          $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
9588          $xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
9589          $xmp .= "\t\t\t".'<dc:title>'."\n";
9590          $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9591          $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->title).'</rdf:li>'."\n";
9592          $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9593          $xmp .= "\t\t\t".'</dc:title>'."\n";
9594          $xmp .= "\t\t\t".'<dc:creator>'."\n";
9595          $xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
9596          $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->author).'</rdf:li>'."\n";
9597          $xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
9598          $xmp .= "\t\t\t".'</dc:creator>'."\n";
9599          $xmp .= "\t\t\t".'<dc:description>'."\n";
9600          $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9601          $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->subject).'</rdf:li>'."\n";
9602          $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9603          $xmp .= "\t\t\t".'</dc:description>'."\n";
9604          $xmp .= "\t\t\t".'<dc:subject>'."\n";
9605          $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9606          $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->keywords).'</rdf:li>'."\n";
9607          $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9608          $xmp .= "\t\t\t".'</dc:subject>'."\n";
9609          $xmp .= "\t\t".'</rdf:Description>'."\n";
9610          // convert doc creation date format
9611          $dcdate = TCPDF_STATIC::getFormattedDate($this->doc_creation_timestamp);
9612          $doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
9613          $doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
9614          $doccreationdate .= '+'.substr($dcdate, 15, 2).':'.substr($dcdate, 18, 2);
9615          $doccreationdate = TCPDF_STATIC::_escapeXML($doccreationdate);
9616          // convert doc modification date format
9617          $dmdate = TCPDF_STATIC::getFormattedDate($this->doc_modification_timestamp);
9618          $docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
9619          $docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
9620          $docmoddate .= '+'.substr($dmdate, 15, 2).':'.substr($dmdate, 18, 2);
9621          $docmoddate = TCPDF_STATIC::_escapeXML($docmoddate);
9622          $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
9623          $xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
9624          $xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
9625          $xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
9626          $xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
9627          $xmp .= "\t\t".'</rdf:Description>'."\n";
9628          $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
9629          $xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC::_escapeXML($this->keywords).'</pdf:Keywords>'."\n";
9630          $xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC::_escapeXML(TCPDF_STATIC::getTCPDFProducer()).'</pdf:Producer>'."\n";
9631          $xmp .= "\t\t".'</rdf:Description>'."\n";
9632          $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
9633          $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);
9634          $xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
9635          $xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
9636          $xmp .= "\t\t".'</rdf:Description>'."\n";
9637          if ($this->pdfa_mode) {
9638              $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
9639              $xmp .= "\t\t\t".'<pdfaid:part>1</pdfaid:part>'."\n";
9640              $xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
9641              $xmp .= "\t\t".'</rdf:Description>'."\n";
9642          }
9643          // XMP extension schemas
9644          $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";
9645          $xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
9646          $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9647          $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9648          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
9649          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
9650          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
9651          $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9652          $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9653          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
9654          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
9655          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
9656          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9657          $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9658          $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9659          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9660          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
9661          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9662          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9663          $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9664          $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9665          $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9666          $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9667          $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9668          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
9669          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
9670          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
9671          $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9672          $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9673          $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9674          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9675          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
9676          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
9677          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
9678          $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9679          $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9680          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9681          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
9682          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
9683          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9684          $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9685          $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9686          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9687          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
9688          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
9689          $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9690          $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9691          $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9692          $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9693          $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9694          $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9695          $xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
9696          $xmp .= "\t\t".'</rdf:Description>'."\n";
9697          $xmp .= "\t".'</rdf:RDF>'."\n";
9698          $xmp .= $this->custom_xmp;
9699          $xmp .= '</x:xmpmeta>'."\n";
9700          $xmp .= '<?xpacket end="w"?>';
9701          $out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
9702          // restore previous isunicode value
9703          $this->isunicode = $prev_isunicode;
9704          $this->encrypted = $prev_encrypted;
9705          $this->_out($out);
9706          return $oid;
9707      }
9708  
9709      /**
9710       * Output Catalog.
9711       * @return int object id
9712       * @protected
9713       */
9714  	protected function _putcatalog() {
9715          // put XMP
9716          $xmpobj = $this->_putXMP();
9717          // if required, add standard sRGB_IEC61966-2.1 blackscaled ICC colour profile
9718          if ($this->pdfa_mode OR $this->force_srgb) {
9719              $iccobj = $this->_newobj();
9720              $icc = file_get_contents(dirname(__FILE__).'/include/sRGB.icc');
9721              $filter = '';
9722              if ($this->compress) {
9723                  $filter = ' /Filter /FlateDecode';
9724                  $icc = gzcompress($icc);
9725              }
9726              $icc = $this->_getrawstream($icc);
9727              $this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9728          }
9729          // start catalog
9730          $oid = $this->_newobj();
9731          $out = '<< /Type /Catalog';
9732          $out .= ' /Version /'.$this->PDFVersion;
9733          //$out .= ' /Extensions <<>>';
9734          $out .= ' /Pages 1 0 R';
9735          //$out .= ' /PageLabels ' //...;
9736          $out .= ' /Names <<';
9737          if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
9738              $out .= ' /JavaScript '.$this->n_js;
9739          }
9740          if (!empty($this->efnames)) {
9741              $out .= ' /EmbeddedFiles <</Names [';
9742              foreach ($this->efnames AS $fn => $fref) {
9743                  $out .= ' '.$this->_datastring($fn).' '.$fref;
9744              }
9745              $out .= ' ]>>';
9746          }
9747          $out .= ' >>';
9748          if (!empty($this->dests)) {
9749              $out .= ' /Dests '.($this->n_dests).' 0 R';
9750          }
9751          $out .= $this->_putviewerpreferences();
9752          if (isset($this->LayoutMode) AND (!TCPDF_STATIC::empty_string($this->LayoutMode))) {
9753              $out .= ' /PageLayout /'.$this->LayoutMode;
9754          }
9755          if (isset($this->PageMode) AND (!TCPDF_STATIC::empty_string($this->PageMode))) {
9756              $out .= ' /PageMode /'.$this->PageMode;
9757          }
9758          if (count($this->outlines) > 0) {
9759              $out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
9760              $out .= ' /PageMode /UseOutlines';
9761          }
9762          //$out .= ' /Threads []';
9763          if ($this->ZoomMode == 'fullpage') {
9764              $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
9765          } elseif ($this->ZoomMode == 'fullwidth') {
9766              $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
9767          } elseif ($this->ZoomMode == 'real') {
9768              $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
9769          } elseif (!is_string($this->ZoomMode)) {
9770              $out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
9771          }
9772          //$out .= ' /AA <<>>';
9773          //$out .= ' /URI <<>>';
9774          $out .= ' /Metadata '.$xmpobj.' 0 R';
9775          //$out .= ' /StructTreeRoot <<>>';
9776          //$out .= ' /MarkInfo <<>>';
9777          if (isset($this->l['a_meta_language'])) {
9778              $out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
9779          }
9780          //$out .= ' /SpiderInfo <<>>';
9781          // set OutputIntent to sRGB IEC61966-2.1 if required
9782          if ($this->pdfa_mode OR $this->force_srgb) {
9783              $out .= ' /OutputIntents [<<';
9784              $out .= ' /Type /OutputIntent';
9785              $out .= ' /S /GTS_PDFA1';
9786              $out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9787              $out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9788              $out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
9789              $out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9790              $out .= ' /DestOutputProfile '.$iccobj.' 0 R';
9791              $out .= ' >>]';
9792          }
9793          //$out .= ' /PieceInfo <<>>';
9794          if (!empty($this->pdflayers)) {
9795              $lyrobjs = '';
9796              $lyrobjs_off = '';
9797              $lyrobjs_lock = '';
9798              foreach ($this->pdflayers as $layer) {
9799                  $layer_obj_ref = ' '.$layer['objid'].' 0 R';
9800                  $lyrobjs .= $layer_obj_ref;
9801                  if ($layer['view'] === false) {
9802                      $lyrobjs_off .= $layer_obj_ref;
9803                  }
9804                  if ($layer['lock']) {
9805                      $lyrobjs_lock .= $layer_obj_ref;
9806                  }
9807              }
9808              $out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
9809              $out .= ' /D <<';
9810              $out .= ' /Name '.$this->_textstring('Layers', $oid);
9811              $out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
9812              $out .= ' /BaseState /ON';
9813              $out .= ' /OFF ['.$lyrobjs_off.']';
9814              $out .= ' /Locked ['.$lyrobjs_lock.']';
9815              $out .= ' /Intent /View';
9816              $out .= ' /AS [';
9817              $out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
9818              $out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
9819              $out .= ' ]';
9820              $out .= ' /Order ['.$lyrobjs.']';
9821              $out .= ' /ListMode /AllPages';
9822              //$out .= ' /RBGroups ['..']';
9823              //$out .= ' /Locked ['..']';
9824              $out .= ' >>';
9825              $out .= ' >>';
9826          }
9827          // AcroForm
9828          if (!empty($this->form_obj_id)
9829              OR ($this->sign AND isset($this->signature_data['cert_type']))
9830              OR !empty($this->empty_signature_appearance)) {
9831              $out .= ' /AcroForm <<';
9832              $objrefs = '';
9833              if ($this->sign AND isset($this->signature_data['cert_type'])) {
9834                  // set reference for signature object
9835                  $objrefs .= $this->sig_obj_id.' 0 R';
9836              }
9837              if (!empty($this->empty_signature_appearance)) {
9838                  foreach ($this->empty_signature_appearance as $esa) {
9839                      // set reference for empty signature objects
9840                      $objrefs .= ' '.$esa['objid'].' 0 R';
9841                  }
9842              }
9843              if (!empty($this->form_obj_id)) {
9844                  foreach($this->form_obj_id as $objid) {
9845                      $objrefs .= ' '.$objid.' 0 R';
9846                  }
9847              }
9848              $out .= ' /Fields ['.$objrefs.']';
9849              // It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
9850              if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
9851                  $out .= ' /NeedAppearances false';
9852              }
9853              if ($this->sign AND isset($this->signature_data['cert_type'])) {
9854                  if ($this->signature_data['cert_type'] > 0) {
9855                      $out .= ' /SigFlags 3';
9856                  } else {
9857                      $out .= ' /SigFlags 1';
9858                  }
9859              }
9860              //$out .= ' /CO ';
9861              if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
9862                  $out .= ' /DR <<';
9863                  $out .= ' /Font <<';
9864                  foreach ($this->annotation_fonts as $fontkey => $fontid) {
9865                      $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9866                  }
9867                  $out .= ' >> >>';
9868              }
9869              $font = $this->getFontBuffer('helvetica');
9870              $out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
9871              $out .= ' /Q '.(($this->rtl)?'2':'0');
9872              //$out .= ' /XFA ';
9873              $out .= ' >>';
9874              // signatures
9875              if ($this->sign AND isset($this->signature_data['cert_type']) 
9876                  AND (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A'))) {
9877                  if ($this->signature_data['cert_type'] > 0) {
9878                      $out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
9879                  } else {
9880                      $out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
9881                  }
9882              }
9883          }
9884          //$out .= ' /Legal <<>>';
9885          //$out .= ' /Requirements []';
9886          //$out .= ' /Collection <<>>';
9887          //$out .= ' /NeedsRendering true';
9888          $out .= ' >>';
9889          $out .= "\n".'endobj';
9890          $this->_out($out);
9891          return $oid;
9892      }
9893  
9894      /**
9895       * Output viewer preferences.
9896       * @return string for viewer preferences
9897       * @author Nicola asuni
9898       * @since 3.1.000 (2008-06-09)
9899       * @protected
9900       */
9901  	protected function _putviewerpreferences() {
9902          $vp = $this->viewer_preferences;
9903          $out = ' /ViewerPreferences <<';
9904          if ($this->rtl) {
9905              $out .= ' /Direction /R2L';
9906          } else {
9907              $out .= ' /Direction /L2R';
9908          }
9909          if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
9910              $out .= ' /HideToolbar true';
9911          }
9912          if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
9913              $out .= ' /HideMenubar true';
9914          }
9915          if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
9916              $out .= ' /HideWindowUI true';
9917          }
9918          if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
9919              $out .= ' /FitWindow true';
9920          }
9921          if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
9922              $out .= ' /CenterWindow true';
9923          }
9924          if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
9925              $out .= ' /DisplayDocTitle true';
9926          }
9927          if (isset($vp['NonFullScreenPageMode'])) {
9928              $out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
9929          }
9930          if (isset($vp['ViewArea'])) {
9931              $out .= ' /ViewArea /'.$vp['ViewArea'];
9932          }
9933          if (isset($vp['ViewClip'])) {
9934              $out .= ' /ViewClip /'.$vp['ViewClip'];
9935          }
9936          if (isset($vp['PrintArea'])) {
9937              $out .= ' /PrintArea /'.$vp['PrintArea'];
9938          }
9939          if (isset($vp['PrintClip'])) {
9940              $out .= ' /PrintClip /'.$vp['PrintClip'];
9941          }
9942          if (isset($vp['PrintScaling'])) {
9943              $out .= ' /PrintScaling /'.$vp['PrintScaling'];
9944          }
9945          if (isset($vp['Duplex']) AND (!TCPDF_STATIC::empty_string($vp['Duplex']))) {
9946              $out .= ' /Duplex /'.$vp['Duplex'];
9947          }
9948          if (isset($vp['PickTrayByPDFSize'])) {
9949              if ($vp['PickTrayByPDFSize']) {
9950                  $out .= ' /PickTrayByPDFSize true';
9951              } else {
9952                  $out .= ' /PickTrayByPDFSize false';
9953              }
9954          }
9955          if (isset($vp['PrintPageRange'])) {
9956              $PrintPageRangeNum = '';
9957              foreach ($vp['PrintPageRange'] as $k => $v) {
9958                  $PrintPageRangeNum .= ' '.($v - 1).'';
9959              }
9960              $out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
9961          }
9962          if (isset($vp['NumCopies'])) {
9963              $out .= ' /NumCopies '.intval($vp['NumCopies']);
9964          }
9965          $out .= ' >>';
9966          return $out;
9967      }
9968  
9969      /**
9970       * Output PDF File Header (7.5.2).
9971       * @protected
9972       */
9973  	protected function _putheader() {
9974          $this->_out('%PDF-'.$this->PDFVersion);
9975          $this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
9976      }
9977  
9978      /**
9979       * Output end of document (EOF).
9980       * @protected
9981       */
9982  	protected function _enddoc() {
9983          if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
9984              // save subset chars of the previous font
9985              $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
9986          }
9987          $this->state = 1;
9988          $this->_putheader();
9989          $this->_putpages();
9990          $this->_putresources();
9991          // empty signature fields
9992          if (!empty($this->empty_signature_appearance)) {
9993              foreach ($this->empty_signature_appearance as $key => $esa) {
9994                  // widget annotation for empty signature
9995                  $out = $this->_getobj($esa['objid'])."\n";
9996                  $out .= '<< /Type /Annot';
9997                  $out .= ' /Subtype /Widget';
9998                  $out .= ' /Rect ['.$esa['rect'].']';
9999                  $out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
10000                  $out .= ' /F 4';
10001                  $out .= ' /FT /Sig';
10002                  $signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
10003                  $out .= ' /T '.$this->_textstring($signame, $esa['objid']);
10004                  $out .= ' /Ff 0';
10005                  $out .= ' >>';
10006                  $out .= "\n".'endobj';
10007                  $this->_out($out);
10008              }
10009          }
10010          // Signature
10011          if ($this->sign AND isset($this->signature_data['cert_type'])) {
10012              // widget annotation for signature
10013              $out = $this->_getobj($this->sig_obj_id)."\n";
10014              $out .= '<< /Type /Annot';
10015              $out .= ' /Subtype /Widget';
10016              $out .= ' /Rect ['.$this->signature_appearance['rect'].']';
10017              $out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
10018              $out .= ' /F 4';
10019              $out .= ' /FT /Sig';
10020              $out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
10021              $out .= ' /Ff 0';
10022              $out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
10023              $out .= ' >>';
10024              $out .= "\n".'endobj';
10025              $this->_out($out);
10026              // signature
10027              $this->_putsignature();
10028          }
10029          // Info
10030          $objid_info = $this->_putinfo();
10031          // Catalog
10032          $objid_catalog = $this->_putcatalog();
10033          // Cross-ref
10034          $o = $this->bufferlen;
10035          // XREF section
10036          $this->_out('xref');
10037          $this->_out('0 '.($this->n + 1));
10038          $this->_out('0000000000 65535 f ');
10039          $freegen = ($this->n + 2);
10040          for ($i=1; $i <= $this->n; ++$i) {
10041              if (!isset($this->offsets[$i]) AND ($i > 1)) {
10042                  $this->_out(sprintf('0000000000 %05d f ', $freegen));
10043                  ++$freegen;
10044              } else {
10045                  $this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
10046              }
10047          }
10048          // TRAILER
10049          $out = 'trailer'."\n";
10050          $out .= '<<';
10051          $out .= ' /Size '.($this->n + 1);
10052          $out .= ' /Root '.$objid_catalog.' 0 R';
10053          $out .= ' /Info '.$objid_info.' 0 R';
10054          if ($this->encrypted) {
10055              $out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
10056          }
10057          $out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
10058          $out .= ' >>';
10059          $this->_out($out);
10060          $this->_out('startxref');
10061          $this->_out($o);
10062          $this->_out('%%EOF');
10063          $this->state = 3; // end-of-doc
10064          if ($this->diskcache) {
10065              // remove temporary files used for images
10066              foreach ($this->imagekeys as $key) {
10067                  // remove temporary files
10068                  unlink($this->images[$key]);
10069              }
10070              foreach ($this->fontkeys as $key) {
10071                  // remove temporary files
10072                  unlink($this->fonts[$key]);
10073              }
10074          }
10075      }
10076  
10077      /**
10078       * Initialize a new page.
10079       * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
10080       * @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().
10081       * @protected
10082       * @see getPageSizeFromFormat(), setPageFormat()
10083       */
10084  	protected function _beginpage($orientation='', $format='') {
10085          ++$this->page;
10086          $this->pageobjects[$this->page] = array();
10087          $this->setPageBuffer($this->page, '');
10088          // initialize array for graphics tranformation positions inside a page buffer
10089          $this->transfmrk[$this->page] = array();
10090          $this->state = 2;
10091          if (TCPDF_STATIC::empty_string($orientation)) {
10092              if (isset($this->CurOrientation)) {
10093                  $orientation = $this->CurOrientation;
10094              } elseif ($this->fwPt > $this->fhPt) {
10095                  // landscape
10096                  $orientation = 'L';
10097              } else {
10098                  // portrait
10099                  $orientation = 'P';
10100              }
10101          }
10102          if (TCPDF_STATIC::empty_string($format)) {
10103              $this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10104              $this->setPageOrientation($orientation);
10105          } else {
10106              $this->setPageFormat($format, $orientation);
10107          }
10108          if ($this->rtl) {
10109              $this->x = $this->w - $this->rMargin;
10110          } else {
10111              $this->x = $this->lMargin;
10112          }
10113          $this->y = $this->tMargin;
10114          if (isset($this->newpagegroup[$this->page])) {
10115              // start a new group
10116              $this->currpagegroup = $this->newpagegroup[$this->page];
10117              $this->pagegroups[$this->currpagegroup] = 1;
10118          } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
10119              ++$this->pagegroups[$this->currpagegroup];
10120          }
10121      }
10122  
10123      /**
10124       * Mark end of page.
10125       * @protected
10126       */
10127  	protected function _endpage() {
10128          $this->setVisibility('all');
10129          $this->state = 1;
10130      }
10131  
10132      /**
10133       * Begin a new object and return the object number.
10134       * @return int object number
10135       * @protected
10136       */
10137  	protected function _newobj() {
10138          $this->_out($this->_getobj());
10139          return $this->n;
10140      }
10141  
10142      /**
10143       * Return the starting object string for the selected object ID.
10144       * @param $objid (int) Object ID (leave empty to get a new ID).
10145       * @return string the starting object string
10146       * @protected
10147       * @since 5.8.009 (2010-08-20)
10148       */
10149  	protected function _getobj($objid='') {
10150          if ($objid === '') {
10151              ++$this->n;
10152              $objid = $this->n;
10153          }
10154          $this->offsets[$objid] = $this->bufferlen;
10155          $this->pageobjects[$this->page][] = $objid;
10156          return $objid.' 0 obj';
10157      }
10158  
10159      /**
10160       * Underline text.
10161       * @param $x (int) X coordinate
10162       * @param $y (int) Y coordinate
10163       * @param $txt (string) text to underline
10164       * @protected
10165       */
10166  	protected function _dounderline($x, $y, $txt) {
10167          $w = $this->GetStringWidth($txt);
10168          return $this->_dounderlinew($x, $y, $w);
10169      }
10170  
10171      /**
10172       * Underline for rectangular text area.
10173       * @param $x (int) X coordinate
10174       * @param $y (int) Y coordinate
10175       * @param $w (int) width to underline
10176       * @protected
10177       * @since 4.8.008 (2009-09-29)
10178       */
10179  	protected function _dounderlinew($x, $y, $w) {
10180          $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10181          return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
10182      }
10183  
10184      /**
10185       * Line through text.
10186       * @param $x (int) X coordinate
10187       * @param $y (int) Y coordinate
10188       * @param $txt (string) text to linethrough
10189       * @protected
10190       */
10191  	protected function _dolinethrough($x, $y, $txt) {
10192          $w = $this->GetStringWidth($txt);
10193          return $this->_dolinethroughw($x, $y, $w);
10194      }
10195  
10196      /**
10197       * Line through for rectangular text area.
10198       * @param $x (int) X coordinate
10199       * @param $y (int) Y coordinate
10200       * @param $w (int) line length (width)
10201       * @protected
10202       * @since 4.9.008 (2009-09-29)
10203       */
10204  	protected function _dolinethroughw($x, $y, $w) {
10205          $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10206          return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
10207      }
10208  
10209      /**
10210       * Overline text.
10211       * @param $x (int) X coordinate
10212       * @param $y (int) Y coordinate
10213       * @param $txt (string) text to overline
10214       * @protected
10215       * @since 4.9.015 (2010-04-19)
10216       */
10217  	protected function _dooverline($x, $y, $txt) {
10218          $w = $this->GetStringWidth($txt);
10219          return $this->_dooverlinew($x, $y, $w);
10220      }
10221  
10222      /**
10223       * Overline for rectangular text area.
10224       * @param $x (int) X coordinate
10225       * @param $y (int) Y coordinate
10226       * @param $w (int) width to overline
10227       * @protected
10228       * @since 4.9.015 (2010-04-19)
10229       */
10230  	protected function _dooverlinew($x, $y, $w) {
10231          $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10232          return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
10233  
10234      }
10235  
10236      /**
10237       * Format a data string for meta information
10238       * @param $s (string) data string to escape.
10239       * @param $n (int) object ID
10240       * @return string escaped string.
10241       * @protected
10242       */
10243  	protected function _datastring($s, $n=0) {
10244          if ($n == 0) {
10245              $n = $this->n;
10246          }
10247          $s = $this->_encrypt_data($n, $s);
10248          return '('. TCPDF_STATIC::_escape($s).')';
10249      }
10250  
10251      /**
10252       * Set the document creation timestamp
10253       * @param $time (mixed) Document creation timestamp in seconds or date-time string.
10254       * @public
10255       * @since 5.9.152 (2012-03-23)
10256       */
10257  	public function setDocCreationTimestamp($time) {
10258          if (is_string($time)) {
10259              $time = TCPDF_STATIC::getTimestamp($time);
10260          }
10261          $this->doc_creation_timestamp = intval($time);
10262      }
10263  
10264      /**
10265       * Set the document modification timestamp
10266       * @param $time (mixed) Document modification timestamp in seconds or date-time string.
10267       * @public
10268       * @since 5.9.152 (2012-03-23)
10269       */
10270  	public function setDocModificationTimestamp($time) {
10271          if (is_string($time)) {
10272              $time = TCPDF_STATIC::getTimestamp($time);
10273          }
10274          $this->doc_modification_timestamp = intval($time);
10275      }
10276  
10277      /**
10278       * Returns document creation timestamp in seconds.
10279       * @return (int) Creation timestamp in seconds.
10280       * @public
10281       * @since 5.9.152 (2012-03-23)
10282       */
10283  	public function getDocCreationTimestamp() {
10284          return $this->doc_creation_timestamp;
10285      }
10286  
10287      /**
10288       * Returns document modification timestamp in seconds.
10289       * @return (int) Modfication timestamp in seconds.
10290       * @public
10291       * @since 5.9.152 (2012-03-23)
10292       */
10293  	public function getDocModificationTimestamp() {
10294          return $this->doc_modification_timestamp;
10295      }
10296  
10297      /**
10298       * Returns a formatted date for meta information
10299       * @param $n (int) Object ID.
10300       * @param $timestamp (int) Timestamp to convert.
10301       * @return string escaped date string.
10302       * @protected
10303       * @since 4.6.028 (2009-08-25)
10304       */
10305  	protected function _datestring($n=0, $timestamp=0) {
10306          if ((empty($timestamp)) OR ($timestamp < 0)) {
10307              $timestamp = $this->doc_creation_timestamp;
10308          }
10309          return $this->_datastring('D:'.TCPDF_STATIC::getFormattedDate($timestamp), $n);
10310      }
10311  
10312      /**
10313       * Format a text string for meta information
10314       * @param $s (string) string to escape.
10315       * @param $n (int) object ID
10316       * @return string escaped string.
10317       * @protected
10318       */
10319  	protected function _textstring($s, $n=0) {
10320          if ($this->isunicode) {
10321              //Convert string to UTF-16BE
10322              $s = TCPDF_FONTS::UTF8ToUTF16BE($s, true, $this->isunicode, $this->CurrentFont);
10323          }
10324          return $this->_datastring($s, $n);
10325      }
10326  
10327      /**
10328       * THIS METHOD IS DEPRECATED
10329       * Format a text string
10330       * @param $s (string) string to escape.
10331       * @return string escaped string.
10332       * @protected
10333       * @deprecated
10334       */
10335  	protected function _escapetext($s) {
10336          if ($this->isunicode) {
10337              if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
10338                  $s = TCPDF_FONTS::UTF8ToLatin1($s, $this->isunicode, $this->CurrentFont);
10339              } else {
10340                  //Convert string to UTF-16BE and reverse RTL language
10341                  $s = TCPDF_FONTS::utf8StrRev($s, false, $this->tmprtl, $this->isunicode, $this->CurrentFont);
10342              }
10343          }
10344          return TCPDF_STATIC::_escape($s);
10345      }
10346  
10347      /**
10348       * get raw output stream.
10349       * @param $s (string) string to output.
10350       * @param $n (int) object reference for encryption mode
10351       * @protected
10352       * @author Nicola Asuni
10353       * @since 5.5.000 (2010-06-22)
10354       */
10355  	protected function _getrawstream($s, $n=0) {
10356          if ($n <= 0) {
10357              // default to current object
10358              $n = $this->n;
10359          }
10360          return $this->_encrypt_data($n, $s);
10361      }
10362  
10363      /**
10364       * Format output stream (DEPRECATED).
10365       * @param $s (string) string to output.
10366       * @param $n (int) object reference for encryption mode
10367       * @protected
10368       * @deprecated
10369       */
10370  	protected function _getstream($s, $n=0) {
10371          return 'stream'."\n".$this->_getrawstream($s, $n)."\n".'endstream';
10372      }
10373  
10374      /**
10375       * Output a stream (DEPRECATED).
10376       * @param $s (string) string to output.
10377       * @param $n (int) object reference for encryption mode
10378       * @protected
10379       * @deprecated
10380       */
10381  	protected function _putstream($s, $n=0) {
10382          $this->_out($this->_getstream($s, $n));
10383      }
10384  
10385      /**
10386       * Output a string to the document.
10387       * @param $s (string) string to output.
10388       * @protected
10389       */
10390  	protected function _out($s) {
10391          if ($this->state == 2) {
10392              if ($this->inxobj) {
10393                  // we are inside an XObject template
10394                  $this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
10395              } elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
10396                  // puts data before page footer
10397                  $pagebuff = $this->getPageBuffer($this->page);
10398                  $page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
10399                  $footer = substr($pagebuff, -$this->footerlen[$this->page]);
10400                  $this->setPageBuffer($this->page, $page.$s."\n".$footer);
10401                  // update footer position
10402                  $this->footerpos[$this->page] += strlen($s."\n");
10403              } else {
10404                  // set page data
10405                  $this->setPageBuffer($this->page, $s."\n", true);
10406              }
10407          } elseif ($this->state > 0) {
10408              // set general data
10409              $this->setBuffer($s."\n");
10410          }
10411      }
10412  
10413      /**
10414       * Set header font.
10415       * @param $font (array) Array describing the basic font parameters: (family, style, size).
10416       * @public
10417       * @since 1.1
10418       */
10419  	public function setHeaderFont($font) {
10420          $this->header_font = $font;
10421      }
10422  
10423      /**
10424       * Get header font.
10425       * @return array() Array describing the basic font parameters: (family, style, size).
10426       * @public
10427       * @since 4.0.012 (2008-07-24)
10428       */
10429  	public function getHeaderFont() {
10430          return $this->header_font;
10431      }
10432  
10433      /**
10434       * Set footer font.
10435       * @param $font (array) Array describing the basic font parameters: (family, style, size).
10436       * @public
10437       * @since 1.1
10438       */
10439  	public function setFooterFont($font) {
10440          $this->footer_font = $font;
10441      }
10442  
10443      /**
10444       * Get Footer font.
10445       * @return array() Array describing the basic font parameters: (family, style, size).
10446       * @public
10447       * @since 4.0.012 (2008-07-24)
10448       */
10449  	public function getFooterFont() {
10450          return $this->footer_font;
10451      }
10452  
10453      /**
10454       * Set language array.
10455       * @param $language (array)
10456       * @public
10457       * @since 1.1
10458       */
10459  	public function setLanguageArray($language) {
10460          $this->l = $language;
10461          if (isset($this->l['a_meta_dir'])) {
10462              $this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
10463          } else {
10464              $this->rtl = false;
10465          }
10466      }
10467  
10468      /**
10469       * Returns the PDF data.
10470       * @public
10471       */
10472  	public function getPDFData() {
10473          if ($this->state < 3) {
10474              $this->Close();
10475          }
10476          return $this->buffer;
10477      }
10478  
10479      /**
10480       * Output anchor link.
10481       * @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;)
10482       * @param $name (string) link name
10483       * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
10484       * @param $firstline (boolean) if true prints only the first line and return the remaining string.
10485       * @param $color (array) array of RGB text color
10486       * @param $style (string) font style (U, D, B, I)
10487       * @param $firstblock (boolean) if true the string is the starting of a line.
10488       * @return the number of cells used or the remaining text if $firstline = true;
10489       * @public
10490       */
10491  	public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
10492          if (isset($url[1]) AND ($url[0] == '#')) {
10493              // convert url to internal link
10494              $lnkdata = explode(',', $url);
10495              if (isset($lnkdata[0]) ) {
10496                  $page = substr($lnkdata[0], 1);
10497                  if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
10498                      $lnky = floatval($lnkdata[1]);
10499                  } else {
10500                      $lnky = 0;
10501                  }
10502                  $url = $this->AddLink();
10503                  $this->SetLink($url, $lnky, $page);
10504              }
10505          }
10506          // store current settings
10507          $prevcolor = $this->fgcolor;
10508          $prevstyle = $this->FontStyle;
10509          if (empty($color)) {
10510              $this->SetTextColorArray($this->htmlLinkColorArray);
10511          } else {
10512              $this->SetTextColorArray($color);
10513          }
10514          if ($style == -1) {
10515              $this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
10516          } else {
10517              $this->SetFont('', $this->FontStyle.$style);
10518          }
10519          $ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
10520          // restore settings
10521          $this->SetFont('', $prevstyle);
10522          $this->SetTextColorArray($prevcolor);
10523          return $ret;
10524      }
10525  
10526      /**
10527       * Converts pixels to User's Units.
10528       * @param $px (int) pixels
10529       * @return float value in user's unit
10530       * @public
10531       * @see setImageScale(), getImageScale()
10532       */
10533  	public function pixelsToUnits($px) {
10534          return ($px / ($this->imgscale * $this->k));
10535      }
10536  
10537      /**
10538       * Reverse function for htmlentities.
10539       * Convert entities in UTF-8.
10540       * @param $text_to_convert (string) Text to convert.
10541       * @return string converted text string
10542       * @public
10543       */
10544  	public function unhtmlentities($text_to_convert) {
10545          return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
10546      }
10547  
10548      // ENCRYPTION METHODS ----------------------------------
10549  
10550      /**
10551       * Compute encryption key depending on object number where the encrypted data is stored.
10552       * This is used for all strings and streams without crypt filter specifier.
10553       * @param $n (int) object number
10554       * @return int object key
10555       * @protected
10556       * @author Nicola Asuni
10557       * @since 2.0.000 (2008-01-02)
10558       */
10559  	protected function _objectkey($n) {
10560          $objkey = $this->encryptdata['key'].pack('VXxx', $n);
10561          if ($this->encryptdata['mode'] == 2) { // AES-128
10562              // AES padding
10563              $objkey .= "\x73\x41\x6C\x54"; // sAlT
10564          }
10565          $objkey = substr(TCPDF_STATIC::_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
10566          $objkey = substr($objkey, 0, 16);
10567          return $objkey;
10568      }
10569  
10570      /**
10571       * Encrypt the input string.
10572       * @param $n (int) object number
10573       * @param $s (string) data string to encrypt
10574       * @return encrypted string
10575       * @protected
10576       * @author Nicola Asuni
10577       * @since 5.0.005 (2010-05-11)
10578       */
10579  	protected function _encrypt_data($n, $s) {
10580          if (!$this->encrypted) {
10581              return $s;
10582          }
10583          switch ($this->encryptdata['mode']) {
10584              case 0:   // RC4-40
10585              case 1: { // RC4-128
10586                  $s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
10587                  break;
10588              }
10589              case 2: { // AES-128
10590                  $s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
10591                  break;
10592              }
10593              case 3: { // AES-256
10594                  $s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
10595                  break;
10596              }
10597          }
10598          return $s;
10599      }
10600  
10601      /**
10602       * Put encryption on PDF document.
10603       * @protected
10604       * @author Nicola Asuni
10605       * @since 2.0.000 (2008-01-02)
10606       */
10607  	protected function _putencryption() {
10608          if (!$this->encrypted) {
10609              return;
10610          }
10611          $this->encryptdata['objid'] = $this->_newobj();
10612          $out = '<<';
10613          if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
10614              $this->encryptdata['Filter'] = 'Standard';
10615          }
10616          $out .= ' /Filter /'.$this->encryptdata['Filter'];
10617          if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
10618              $out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
10619          }
10620          if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
10621              $this->encryptdata['V'] = 1;
10622          }
10623          // V is a code specifying the algorithm to be used in encrypting and decrypting the document
10624          $out .= ' /V '.$this->encryptdata['V'];
10625          if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
10626              // The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
10627              $out .= ' /Length '.$this->encryptdata['Length'];
10628          } else {
10629              $out .= ' /Length 40';
10630          }
10631          if ($this->encryptdata['V'] >= 4) {
10632              if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
10633                  $this->encryptdata['StmF'] = 'Identity';
10634              }
10635              if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
10636                  // The name of the crypt filter that shall be used when decrypting all strings in the document.
10637                  $this->encryptdata['StrF'] = 'Identity';
10638              }
10639              // A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
10640              if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
10641                  $out .= ' /CF <<';
10642                  $out .= ' /'.$this->encryptdata['StmF'].' <<';
10643                  $out .= ' /Type /CryptFilter';
10644                  if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
10645                      // The method used
10646                      $out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
10647                      if ($this->encryptdata['pubkey']) {
10648                          $out .= ' /Recipients [';
10649                          foreach ($this->encryptdata['Recipients'] as $rec) {
10650                              $out .= ' <'.$rec.'>';
10651                          }
10652                          $out .= ' ]';
10653                          if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
10654                              $out .= ' /EncryptMetadata false';
10655                          } else {
10656                              $out .= ' /EncryptMetadata true';
10657                          }
10658                      }
10659                  } else {
10660                      $out .= ' /CFM /None';
10661                  }
10662                  if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
10663                      // The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
10664                      $out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
10665                  } else {
10666                      $out .= ' /AuthEvent /DocOpen';
10667                  }
10668                  if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
10669                      // The bit length of the encryption key.
10670                      $out .= ' /Length '.$this->encryptdata['CF']['Length'];
10671                  }
10672                  $out .= ' >> >>';
10673              }
10674              // The name of the crypt filter that shall be used by default when decrypting streams.
10675              $out .= ' /StmF /'.$this->encryptdata['StmF'];
10676              // The name of the crypt filter that shall be used when decrypting all strings in the document.
10677              $out .= ' /StrF /'.$this->encryptdata['StrF'];
10678              if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
10679                  // The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
10680                  $out .= ' /EFF /'.$this->encryptdata[''];
10681              }
10682          }
10683          // Additional encryption dictionary entries for the standard security handler
10684          if ($this->encryptdata['pubkey']) {
10685              if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
10686                  $out .= ' /Recipients [';
10687                  foreach ($this->encryptdata['Recipients'] as $rec) {
10688                      $out .= ' <'.$rec.'>';
10689                  }
10690                  $out .= ' ]';
10691              }
10692          } else {
10693              $out .= ' /R';
10694              if ($this->encryptdata['V'] == 5) { // AES-256
10695                  $out .= ' 5';
10696                  $out .= ' /OE ('.TCPDF_STATIC::_escape($this->encryptdata['OE']).')';
10697                  $out .= ' /UE ('.TCPDF_STATIC::_escape($this->encryptdata['UE']).')';
10698                  $out .= ' /Perms ('.TCPDF_STATIC::_escape($this->encryptdata['perms']).')';
10699              } elseif ($this->encryptdata['V'] == 4) { // AES-128
10700                  $out .= ' 4';
10701              } elseif ($this->encryptdata['V'] < 2) { // RC-40
10702                  $out .= ' 2';
10703              } else { // RC-128
10704                  $out .= ' 3';
10705              }
10706              $out .= ' /O ('.TCPDF_STATIC::_escape($this->encryptdata['O']).')';
10707              $out .= ' /U ('.TCPDF_STATIC::_escape($this->encryptdata['U']).')';
10708              $out .= ' /P '.$this->encryptdata['P'];
10709              if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
10710                  $out .= ' /EncryptMetadata false';
10711              } else {
10712                  $out .= ' /EncryptMetadata true';
10713              }
10714          }
10715          $out .= ' >>';
10716          $out .= "\n".'endobj';
10717          $this->_out($out);
10718      }
10719  
10720      /**
10721       * Compute U value (used for encryption)
10722       * @return string U value
10723       * @protected
10724       * @since 2.0.000 (2008-01-02)
10725       * @author Nicola Asuni
10726       */
10727  	protected function _Uvalue() {
10728          if ($this->encryptdata['mode'] == 0) { // RC4-40
10729              return TCPDF_STATIC::_RC4($this->encryptdata['key'], TCPDF_STATIC::$enc_padding, $this->last_enc_key, $this->last_enc_key_c);
10730          } elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
10731              $tmp = TCPDF_STATIC::_md5_16(TCPDF_STATIC::$enc_padding.$this->encryptdata['fileid']);
10732              $enc = TCPDF_STATIC::_RC4($this->encryptdata['key'], $tmp, $this->last_enc_key, $this->last_enc_key_c);
10733              $len = strlen($tmp);
10734              for ($i = 1; $i <= 19; ++$i) {
10735                  $ek = '';
10736                  for ($j = 0; $j < $len; ++$j) {
10737                      $ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
10738                  }
10739                  $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10740              }
10741              $enc .= str_repeat("\x00", 16);
10742              return substr($enc, 0, 32);
10743          } elseif ($this->encryptdata['mode'] == 3) { // AES-256
10744              $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10745              // User Validation Salt
10746              $this->encryptdata['UVS'] = substr($seed, 0, 8);
10747              // User Key Salt
10748              $this->encryptdata['UKS'] = substr($seed, 8, 16);
10749              return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
10750          }
10751      }
10752  
10753      /**
10754       * Compute UE value (used for encryption)
10755       * @return string UE value
10756       * @protected
10757       * @since 5.9.006 (2010-10-19)
10758       * @author Nicola Asuni
10759       */
10760  	protected function _UEvalue() {
10761          $hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
10762          $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
10763          return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
10764      }
10765  
10766      /**
10767       * Compute O value (used for encryption)
10768       * @return string O value
10769       * @protected
10770       * @since 2.0.000 (2008-01-02)
10771       * @author Nicola Asuni
10772       */
10773  	protected function _Ovalue() {
10774          if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
10775              $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['owner_password']);
10776              if ($this->encryptdata['mode'] > 0) {
10777                  for ($i = 0; $i < 50; ++$i) {
10778                      $tmp = TCPDF_STATIC::_md5_16($tmp);
10779                  }
10780              }
10781              $owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
10782              $enc = TCPDF_STATIC::_RC4($owner_key, $this->encryptdata['user_password'], $this->last_enc_key, $this->last_enc_key_c);
10783              if ($this->encryptdata['mode'] > 0) {
10784                  $len = strlen($owner_key);
10785                  for ($i = 1; $i <= 19; ++$i) {
10786                      $ek = '';
10787                      for ($j = 0; $j < $len; ++$j) {
10788                          $ek .= chr(ord($owner_key[$j]) ^ $i);
10789                      }
10790                      $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10791                  }
10792              }
10793              return $enc;
10794          } elseif ($this->encryptdata['mode'] == 3) { // AES-256
10795              $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10796              // Owner Validation Salt
10797              $this->encryptdata['OVS'] = substr($seed, 0, 8);
10798              // Owner Key Salt
10799              $this->encryptdata['OKS'] = substr($seed, 8, 16);
10800              return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
10801          }
10802      }
10803  
10804      /**
10805       * Compute OE value (used for encryption)
10806       * @return string OE value
10807       * @protected
10808       * @since 5.9.006 (2010-10-19)
10809       * @author Nicola Asuni
10810       */
10811  	protected function _OEvalue() {
10812          $hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
10813          $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
10814          return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
10815      }
10816  
10817      /**
10818       * Convert password for AES-256 encryption mode
10819       * @param $password (string) password
10820       * @return string password
10821       * @protected
10822       * @since 5.9.006 (2010-10-19)
10823       * @author Nicola Asuni
10824       */
10825  	protected function _fixAES256Password($password) {
10826          $psw = ''; // password to be returned
10827          $psw_array = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($password, $this->isunicode, $this->CurrentFont), $password, $this->rtl, $this->isunicode, $this->CurrentFont);
10828          foreach ($psw_array as $c) {
10829              $psw .= TCPDF_FONTS::unichr($c, $this->isunicode);
10830          }
10831          return substr($psw, 0, 127);
10832      }
10833  
10834      /**
10835       * Compute encryption key
10836       * @protected
10837       * @since 2.0.000 (2008-01-02)
10838       * @author Nicola Asuni
10839       */
10840  	protected function _generateencryptionkey() {
10841          $keybytelen = ($this->encryptdata['Length'] / 8);
10842          if (!$this->encryptdata['pubkey']) { // standard mode
10843              if ($this->encryptdata['mode'] == 3) { // AES-256
10844                  // generate 256 bit random key
10845                  $this->encryptdata['key'] = substr(hash('sha256', TCPDF_STATIC::getRandomSeed(), true), 0, $keybytelen);
10846                  // truncate passwords
10847                  $this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
10848                  $this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
10849                  // Compute U value
10850                  $this->encryptdata['U'] = $this->_Uvalue();
10851                  // Compute UE value
10852                  $this->encryptdata['UE'] = $this->_UEvalue();
10853                  // Compute O value
10854                  $this->encryptdata['O'] = $this->_Ovalue();
10855                  // Compute OE value
10856                  $this->encryptdata['OE'] = $this->_OEvalue();
10857                  // Compute P value
10858                  $this->encryptdata['P'] = $this->encryptdata['protection'];
10859                  // Computing the encryption dictionary's Perms (permissions) value
10860                  $perms = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
10861                  $perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
10862                  if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
10863                      $perms .= 'F';
10864                  } else {
10865                      $perms .= 'T';
10866                  }
10867                  $perms .= 'adb'; // bytes 9-11
10868                  $perms .= 'nick'; // bytes 12-15
10869                  $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB));
10870                  $this->encryptdata['perms'] = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->encryptdata['key'], $perms, MCRYPT_MODE_ECB, $iv);
10871              } else { // RC4-40, RC4-128, AES-128
10872                  // Pad passwords
10873                  $this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10874                  $this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10875                  // Compute O value
10876                  $this->encryptdata['O'] = $this->_Ovalue();
10877                  // get default permissions (reverse byte order)
10878                  $permissions = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']);
10879                  // Compute encryption key
10880                  $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
10881                  if ($this->encryptdata['mode'] > 0) {
10882                      for ($i = 0; $i < 50; ++$i) {
10883                          $tmp = TCPDF_STATIC::_md5_16(substr($tmp, 0, $keybytelen));
10884                      }
10885                  }
10886                  $this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
10887                  // Compute U value
10888                  $this->encryptdata['U'] = $this->_Uvalue();
10889                  // Compute P value
10890                  $this->encryptdata['P'] = $this->encryptdata['protection'];
10891              }
10892          } else { // Public-Key mode
10893              // random 20-byte seed
10894              $seed = sha1(TCPDF_STATIC::getRandomSeed(), true);
10895              $recipient_bytes = '';
10896              foreach ($this->encryptdata['pubkeys'] as $pubkey) {
10897                  // for each public certificate
10898                  if (isset($pubkey['p'])) {
10899                      $pkprotection = TCPDF_STATIC::getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
10900                  } else {
10901                      $pkprotection = $this->encryptdata['protection'];
10902                  }
10903                  // get default permissions (reverse byte order)
10904                  $pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
10905                  // envelope data
10906                  $envelope = $seed.$pkpermissions;
10907                  // write the envelope data to a temporary file
10908                  $tempkeyfile = TCPDF_STATIC::getObjFilename('key');
10909                  $f = fopen($tempkeyfile, 'wb');
10910                  if (!$f) {
10911                      $this->Error('Unable to create temporary key file: '.$tempkeyfile);
10912                  }
10913                  $envelope_length = strlen($envelope);
10914                  fwrite($f, $envelope, $envelope_length);
10915                  fclose($f);
10916                  $tempencfile = TCPDF_STATIC::getObjFilename('enc');
10917                  if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
10918                      $this->Error('Unable to encrypt the file: '.$tempkeyfile);
10919                  }
10920                  unlink($tempkeyfile);
10921                  // read encryption signature
10922                  $signature = file_get_contents($tempencfile, false, null, $envelope_length);
10923                  unlink($tempencfile);
10924                  // extract signature
10925                  $signature = substr($signature, strpos($signature, 'Content-Disposition'));
10926                  $tmparr = explode("\n\n", $signature);
10927                  $signature = trim($tmparr[1]);
10928                  unset($tmparr);
10929                  // decode signature
10930                  $signature = base64_decode($signature);
10931                  // convert signature to hex
10932                  $hexsignature = current(unpack('H*', $signature));
10933                  // store signature on recipients array
10934                  $this->encryptdata['Recipients'][] = $hexsignature;
10935                  // The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
10936                  $recipient_bytes .= $signature;
10937              }
10938              // calculate encryption key
10939              if ($this->encryptdata['mode'] == 3) { // AES-256
10940                  $this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
10941              } else { // RC4-40, RC4-128, AES-128
10942                  $this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
10943              }
10944          }
10945      }
10946  
10947      /**
10948       * Set document protection
10949       * Remark: the protection against modification is for people who have the full Acrobat product.
10950       * 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.
10951       * 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.
10952       * @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>
10953       * @param $user_pass (String) user password. Empty by default.
10954       * @param $owner_pass (String) owner password. If not specified, a random value is used.
10955       * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
10956       * @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')))
10957       * @public
10958       * @since 2.0.000 (2008-01-02)
10959       * @author Nicola Asuni
10960       */
10961  	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) {
10962          if ($this->pdfa_mode) {
10963              // encryption is not allowed in PDF/A mode
10964              return;
10965          }
10966          $this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
10967          if (($pubkeys !== null) AND (is_array($pubkeys))) {
10968              // public-key mode
10969              $this->encryptdata['pubkeys'] = $pubkeys;
10970              if ($mode == 0) {
10971                  // public-Key Security requires at least 128 bit
10972                  $mode = 1;
10973              }
10974              if (!function_exists('openssl_pkcs7_encrypt')) {
10975                  $this->Error('Public-Key Security requires openssl library.');
10976              }
10977              // Set Public-Key filter (availabe are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
10978              $this->encryptdata['pubkey'] = true;
10979              $this->encryptdata['Filter'] = 'Adobe.PubSec';
10980              $this->encryptdata['StmF'] = 'DefaultCryptFilter';
10981              $this->encryptdata['StrF'] = 'DefaultCryptFilter';
10982          } else {
10983              // standard mode (password mode)
10984              $this->encryptdata['pubkey'] = false;
10985              $this->encryptdata['Filter'] = 'Standard';
10986              $this->encryptdata['StmF'] = 'StdCF';
10987              $this->encryptdata['StrF'] = 'StdCF';
10988          }
10989          if ($mode > 1) { // AES
10990              if (!extension_loaded('mcrypt')) {
10991                  $this->Error('AES encryption requires mcrypt library (http://www.php.net/manual/en/mcrypt.requirements.php).');
10992              }
10993              if (mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
10994                  $this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
10995              }
10996              if (($mode == 3) AND !function_exists('hash')) {
10997                  // the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
10998                  $this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
10999              }
11000          }
11001          if ($owner_pass === null) {
11002              $owner_pass = md5(TCPDF_STATIC::getRandomSeed());
11003          }
11004          $this->encryptdata['user_password'] = $user_pass;
11005          $this->encryptdata['owner_password'] = $owner_pass;
11006          $this->encryptdata['mode'] = $mode;
11007          switch ($mode) {
11008              case 0: { // RC4 40 bit
11009                  $this->encryptdata['V'] = 1;
11010                  $this->encryptdata['Length'] = 40;
11011                  $this->encryptdata['CF']['CFM'] = 'V2';
11012                  break;
11013              }
11014              case 1: { // RC4 128 bit
11015                  $this->encryptdata['V'] = 2;
11016                  $this->encryptdata['Length'] = 128;
11017                  $this->encryptdata['CF']['CFM'] = 'V2';
11018                  if ($this->encryptdata['pubkey']) {
11019                      $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
11020                      $this->encryptdata['Recipients'] = array();
11021                  }
11022                  break;
11023              }
11024              case 2: { // AES 128 bit
11025                  $this->encryptdata['V'] = 4;
11026                  $this->encryptdata['Length'] = 128;
11027                  $this->encryptdata['CF']['CFM'] = 'AESV2';
11028                  $this->encryptdata['CF']['Length'] = 128;
11029                  if ($this->encryptdata['pubkey']) {
11030                      $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
11031                      $this->encryptdata['Recipients'] = array();
11032                  }
11033                  break;
11034              }
11035              case 3: { // AES 256 bit
11036                  $this->encryptdata['V'] = 5;
11037                  $this->encryptdata['Length'] = 256;
11038                  $this->encryptdata['CF']['CFM'] = 'AESV3';
11039                  $this->encryptdata['CF']['Length'] = 256;
11040                  if ($this->encryptdata['pubkey']) {
11041                      $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
11042                      $this->encryptdata['Recipients'] = array();
11043                  }
11044                  break;
11045              }
11046          }
11047          $this->encrypted = true;
11048          $this->encryptdata['fileid'] = TCPDF_STATIC::convertHexStringToString($this->file_id);
11049          $this->_generateencryptionkey();
11050      }
11051  
11052      // END OF ENCRYPTION FUNCTIONS -------------------------
11053  
11054      // START TRANSFORMATIONS SECTION -----------------------
11055  
11056      /**
11057       * Starts a 2D tranformation saving current graphic state.
11058       * This function must be called before scaling, mirroring, translation, rotation and skewing.
11059       * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11060       * @public
11061       * @since 2.1.000 (2008-01-07)
11062       * @see StartTransform(), StopTransform()
11063       */
11064  	public function StartTransform() {
11065          if ($this->state != 2) {
11066              return;
11067          }
11068          $this->_outSaveGraphicsState();
11069          if ($this->inxobj) {
11070              // we are inside an XObject template
11071              $this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
11072          } else {
11073              $this->transfmrk[$this->page][] = $this->pagelen[$this->page];
11074          }
11075          ++$this->transfmatrix_key;
11076          $this->transfmatrix[$this->transfmatrix_key] = array();
11077      }
11078  
11079      /**
11080       * Stops a 2D tranformation restoring previous graphic state.
11081       * This function must be called after scaling, mirroring, translation, rotation and skewing.
11082       * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11083       * @public
11084       * @since 2.1.000 (2008-01-07)
11085       * @see StartTransform(), StopTransform()
11086       */
11087  	public function StopTransform() {
11088          if ($this->state != 2) {
11089              return;
11090          }
11091          $this->_outRestoreGraphicsState();
11092          if (isset($this->transfmatrix[$this->transfmatrix_key])) {
11093              array_pop($this->transfmatrix[$this->transfmatrix_key]);
11094              --$this->transfmatrix_key;
11095          }
11096          if ($this->inxobj) {
11097              // we are inside an XObject template
11098              array_pop($this->xobjects[$this->xobjid]['transfmrk']);
11099          } else {
11100              array_pop($this->transfmrk[$this->page]);
11101          }
11102      }
11103      /**
11104       * Horizontal Scaling.
11105       * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
11106       * @param $x (int) abscissa of the scaling center. Default is current x position
11107       * @param $y (int) ordinate of the scaling center. Default is current y position
11108       * @public
11109       * @since 2.1.000 (2008-01-07)
11110       * @see StartTransform(), StopTransform()
11111       */
11112  	public function ScaleX($s_x, $x='', $y='') {
11113          $this->Scale($s_x, 100, $x, $y);
11114      }
11115  
11116      /**
11117       * Vertical Scaling.
11118       * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
11119       * @param $x (int) abscissa of the scaling center. Default is current x position
11120       * @param $y (int) ordinate of the scaling center. Default is current y position
11121       * @public
11122       * @since 2.1.000 (2008-01-07)
11123       * @see StartTransform(), StopTransform()
11124       */
11125  	public function ScaleY($s_y, $x='', $y='') {
11126          $this->Scale(100, $s_y, $x, $y);
11127      }
11128  
11129      /**
11130       * Vertical and horizontal proportional Scaling.
11131       * @param $s (float) scaling factor for width and height as percent. 0 is not allowed.
11132       * @param $x (int) abscissa of the scaling center. Default is current x position
11133       * @param $y (int) ordinate of the scaling center. Default is current y position
11134       * @public
11135       * @since 2.1.000 (2008-01-07)
11136       * @see StartTransform(), StopTransform()
11137       */
11138  	public function ScaleXY($s, $x='', $y='') {
11139          $this->Scale($s, $s, $x, $y);
11140      }
11141  
11142      /**
11143       * Vertical and horizontal non-proportional Scaling.
11144       * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
11145       * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
11146       * @param $x (int) abscissa of the scaling center. Default is current x position
11147       * @param $y (int) ordinate of the scaling center. Default is current y position
11148       * @public
11149       * @since 2.1.000 (2008-01-07)
11150       * @see StartTransform(), StopTransform()
11151       */
11152  	public function Scale($s_x, $s_y, $x='', $y='') {
11153          if ($x === '') {
11154              $x = $this->x;
11155          }
11156          if ($y === '') {
11157              $y = $this->y;
11158          }
11159          if (($s_x == 0) OR ($s_y == 0)) {
11160              $this->Error('Please do not use values equal to zero for scaling');
11161          }
11162          $y = ($this->h - $y) * $this->k;
11163          $x *= $this->k;
11164          //calculate elements of transformation matrix
11165          $s_x /= 100;
11166          $s_y /= 100;
11167          $tm = array();
11168          $tm[0] = $s_x;
11169          $tm[1] = 0;
11170          $tm[2] = 0;
11171          $tm[3] = $s_y;
11172          $tm[4] = $x * (1 - $s_x);
11173          $tm[5] = $y * (1 - $s_y);
11174          //scale the coordinate system
11175          $this->Transform($tm);
11176      }
11177  
11178      /**
11179       * Horizontal Mirroring.
11180       * @param $x (int) abscissa of the point. Default is current x position
11181       * @public
11182       * @since 2.1.000 (2008-01-07)
11183       * @see StartTransform(), StopTransform()
11184       */
11185  	public function MirrorH($x='') {
11186          $this->Scale(-100, 100, $x);
11187      }
11188  
11189      /**
11190       * Verical Mirroring.
11191       * @param $y (int) ordinate of the point. Default is current y position
11192       * @public
11193       * @since 2.1.000 (2008-01-07)
11194       * @see StartTransform(), StopTransform()
11195       */
11196  	public function MirrorV($y='') {
11197          $this->Scale(100, -100, '', $y);
11198      }
11199  
11200      /**
11201       * Point reflection mirroring.
11202       * @param $x (int) abscissa of the point. Default is current x position
11203       * @param $y (int) ordinate of the point. Default is current y position
11204       * @public
11205       * @since 2.1.000 (2008-01-07)
11206       * @see StartTransform(), StopTransform()
11207       */
11208  	public function MirrorP($x='',$y='') {
11209          $this->Scale(-100, -100, $x, $y);
11210      }
11211  
11212      /**
11213       * Reflection against a straight line through point (x, y) with the gradient angle (angle).
11214       * @param $angle (float) gradient angle of the straight line. Default is 0 (horizontal line).
11215       * @param $x (int) abscissa of the point. Default is current x position
11216       * @param $y (int) ordinate of the point. Default is current y position
11217       * @public
11218       * @since 2.1.000 (2008-01-07)
11219       * @see StartTransform(), StopTransform()
11220       */
11221  	public function MirrorL($angle=0, $x='',$y='') {
11222          $this->Scale(-100, 100, $x, $y);
11223          $this->Rotate(-2*($angle-90), $x, $y);
11224      }
11225  
11226      /**
11227       * Translate graphic object horizontally.
11228       * @param $t_x (int) movement to the right (or left for RTL)
11229       * @public
11230       * @since 2.1.000 (2008-01-07)
11231       * @see StartTransform(), StopTransform()
11232       */
11233  	public function TranslateX($t_x) {
11234          $this->Translate($t_x, 0);
11235      }
11236  
11237      /**
11238       * Translate graphic object vertically.
11239       * @param $t_y (int) movement to the bottom
11240       * @public
11241       * @since 2.1.000 (2008-01-07)
11242       * @see StartTransform(), StopTransform()
11243       */
11244  	public function TranslateY($t_y) {
11245          $this->Translate(0, $t_y);
11246      }
11247  
11248      /**
11249       * Translate graphic object horizontally and vertically.
11250       * @param $t_x (int) movement to the right
11251       * @param $t_y (int) movement to the bottom
11252       * @public
11253       * @since 2.1.000 (2008-01-07)
11254       * @see StartTransform(), StopTransform()
11255       */
11256  	public function Translate($t_x, $t_y) {
11257          //calculate elements of transformation matrix
11258          $tm = array();
11259          $tm[0] = 1;
11260          $tm[1] = 0;
11261          $tm[2] = 0;
11262          $tm[3] = 1;
11263          $tm[4] = $t_x * $this->k;
11264          $tm[5] = -$t_y * $this->k;
11265          //translate the coordinate system
11266          $this->Transform($tm);
11267      }
11268  
11269      /**
11270       * Rotate object.
11271       * @param $angle (float) angle in degrees for counter-clockwise rotation
11272       * @param $x (int) abscissa of the rotation center. Default is current x position
11273       * @param $y (int) ordinate of the rotation center. Default is current y position
11274       * @public
11275       * @since 2.1.000 (2008-01-07)
11276       * @see StartTransform(), StopTransform()
11277       */
11278  	public function Rotate($angle, $x='', $y='') {
11279          if ($x === '') {
11280              $x = $this->x;
11281          }
11282          if ($y === '') {
11283              $y = $this->y;
11284          }
11285          $y = ($this->h - $y) * $this->k;
11286          $x *= $this->k;
11287          //calculate elements of transformation matrix
11288          $tm = array();
11289          $tm[0] = cos(deg2rad($angle));
11290          $tm[1] = sin(deg2rad($angle));
11291          $tm[2] = -$tm[1];
11292          $tm[3] = $tm[0];
11293          $tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
11294          $tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
11295          //rotate the coordinate system around ($x,$y)
11296          $this->Transform($tm);
11297      }
11298  
11299      /**
11300       * Skew horizontally.
11301       * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11302       * @param $x (int) abscissa of the skewing center. default is current x position
11303       * @param $y (int) ordinate of the skewing center. default is current y position
11304       * @public
11305       * @since 2.1.000 (2008-01-07)
11306       * @see StartTransform(), StopTransform()
11307       */
11308  	public function SkewX($angle_x, $x='', $y='') {
11309          $this->Skew($angle_x, 0, $x, $y);
11310      }
11311  
11312      /**
11313       * Skew vertically.
11314       * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11315       * @param $x (int) abscissa of the skewing center. default is current x position
11316       * @param $y (int) ordinate of the skewing center. default is current y position
11317       * @public
11318       * @since 2.1.000 (2008-01-07)
11319       * @see StartTransform(), StopTransform()
11320       */
11321  	public function SkewY($angle_y, $x='', $y='') {
11322          $this->Skew(0, $angle_y, $x, $y);
11323      }
11324  
11325      /**
11326       * Skew.
11327       * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11328       * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11329       * @param $x (int) abscissa of the skewing center. default is current x position
11330       * @param $y (int) ordinate of the skewing center. default is current y position
11331       * @public
11332       * @since 2.1.000 (2008-01-07)
11333       * @see StartTransform(), StopTransform()
11334       */
11335  	public function Skew($angle_x, $angle_y, $x='', $y='') {
11336          if ($x === '') {
11337              $x = $this->x;
11338          }
11339          if ($y === '') {
11340              $y = $this->y;
11341          }
11342          if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
11343              $this->Error('Please use values between -90 and +90 degrees for Skewing.');
11344          }
11345          $x *= $this->k;
11346          $y = ($this->h - $y) * $this->k;
11347          //calculate elements of transformation matrix
11348          $tm = array();
11349          $tm[0] = 1;
11350          $tm[1] = tan(deg2rad($angle_y));
11351          $tm[2] = tan(deg2rad($angle_x));
11352          $tm[3] = 1;
11353          $tm[4] = -$tm[2] * $y;
11354          $tm[5] = -$tm[1] * $x;
11355          //skew the coordinate system
11356          $this->Transform($tm);
11357      }
11358  
11359      /**
11360       * Apply graphic transformations.
11361       * @param $tm (array) transformation matrix
11362       * @protected
11363       * @since 2.1.000 (2008-01-07)
11364       * @see StartTransform(), StopTransform()
11365       */
11366  	protected function Transform($tm) {
11367          if ($this->state != 2) {
11368              return;
11369          }
11370          $this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
11371          // add tranformation matrix
11372          $this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
11373          // update transformation mark
11374          if ($this->inxobj) {
11375              // we are inside an XObject template
11376              if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
11377                  $key = key($this->xobjects[$this->xobjid]['transfmrk']);
11378                  $this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
11379              }
11380          } elseif (end($this->transfmrk[$this->page]) !== false) {
11381              $key = key($this->transfmrk[$this->page]);
11382              $this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
11383          }
11384      }
11385  
11386      // END TRANSFORMATIONS SECTION -------------------------
11387  
11388      // START GRAPHIC FUNCTIONS SECTION ---------------------
11389      // The following section is based on the code provided by David Hernandez Sanz
11390  
11391      /**
11392       * 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.
11393       * @param $width (float) The width.
11394       * @public
11395       * @since 1.0
11396       * @see Line(), Rect(), Cell(), MultiCell()
11397       */
11398  	public function SetLineWidth($width) {
11399          //Set line width
11400          $this->LineWidth = $width;
11401          $this->linestyleWidth = sprintf('%F w', ($width * $this->k));
11402          if ($this->state == 2) {
11403              $this->_out($this->linestyleWidth);
11404          }
11405      }
11406  
11407      /**
11408       * Returns the current the line width.
11409       * @return int Line width
11410       * @public
11411       * @since 2.1.000 (2008-01-07)
11412       * @see Line(), SetLineWidth()
11413       */
11414  	public function GetLineWidth() {
11415          return $this->LineWidth;
11416      }
11417  
11418      /**
11419       * Set line style.
11420       * @param $style (array) Line style. Array with keys among the following:
11421       * <ul>
11422       *     <li>width (float): Width of the line in user units.</li>
11423       *     <li>cap (string): Type of cap to put on the line. Possible values are:
11424       * butt, round, square. The difference between "square" and "butt" is that
11425       * "square" projects a flat end past the end of the line.</li>
11426       *     <li>join (string): Type of join. Possible values are: miter, round,
11427       * bevel.</li>
11428       *     <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
11429       * series of length values, which are the lengths of the on and off dashes.
11430       * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
11431       * 1 off, 2 on, 1 off, ...</li>
11432       *     <li>phase (integer): Modifier on the dash pattern which is used to shift
11433       * the point at which the pattern starts.</li>
11434       *     <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>
11435       * </ul>
11436       * @param $ret (boolean) if true do not send the command.
11437       * @return string the PDF command
11438       * @public
11439       * @since 2.1.000 (2008-01-08)
11440       */
11441  	public function SetLineStyle($style, $ret=false) {
11442          $s = ''; // string to be returned
11443          if (!is_array($style)) {
11444              return;
11445          }
11446          if (isset($style['width'])) {
11447              $this->LineWidth = $style['width'];
11448              $this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
11449              $s .= $this->linestyleWidth.' ';
11450          }
11451          if (isset($style['cap'])) {
11452              $ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
11453              if (isset($ca[$style['cap']])) {
11454                  $this->linestyleCap = $ca[$style['cap']].' J';
11455                  $s .= $this->linestyleCap.' ';
11456              }
11457          }
11458          if (isset($style['join'])) {
11459              $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
11460              if (isset($ja[$style['join']])) {
11461                  $this->linestyleJoin = $ja[$style['join']].' j';
11462                  $s .= $this->linestyleJoin.' ';
11463              }
11464          }
11465          if (isset($style['dash'])) {
11466              $dash_string = '';
11467              if ($style['dash']) {
11468                  if (preg_match('/^.+,/', $style['dash']) > 0) {
11469                      $tab = explode(',', $style['dash']);
11470                  } else {
11471                      $tab = array($style['dash']);
11472                  }
11473                  $dash_string = '';
11474                  foreach ($tab as $i => $v) {
11475                      if ($i) {
11476                          $dash_string .= ' ';
11477                      }
11478                      $dash_string .= sprintf('%F', $v);
11479                  }
11480              }
11481              if (!isset($style['phase']) OR !$style['dash']) {
11482                  $style['phase'] = 0;
11483              }
11484              $this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
11485              $s .= $this->linestyleDash.' ';
11486          }
11487          if (isset($style['color'])) {
11488              $s .= $this->SetDrawColorArray($style['color'], true).' ';
11489          }
11490          if (!$ret AND ($this->state == 2)) {
11491              $this->_out($s);
11492          }
11493          return $s;
11494      }
11495  
11496      /**
11497       * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
11498       * @param $x (float) Abscissa of point.
11499       * @param $y (float) Ordinate of point.
11500       * @protected
11501       * @since 2.1.000 (2008-01-08)
11502       */
11503  	protected function _outPoint($x, $y) {
11504          if ($this->state == 2) {
11505              $this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
11506          }
11507      }
11508  
11509      /**
11510       * Append a straight line segment from the current point to the point (x, y).
11511       * The new current point shall be (x, y).
11512       * @param $x (float) Abscissa of end point.
11513       * @param $y (float) Ordinate of end point.
11514       * @protected
11515       * @since 2.1.000 (2008-01-08)
11516       */
11517  	protected function _outLine($x, $y) {
11518          if ($this->state == 2) {
11519              $this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
11520          }
11521      }
11522  
11523      /**
11524       * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
11525       * @param $x (float) Abscissa of upper-left corner.
11526       * @param $y (float) Ordinate of upper-left corner.
11527       * @param $w (float) Width.
11528       * @param $h (float) Height.
11529       * @param $op (string) options
11530       * @protected
11531       * @since 2.1.000 (2008-01-08)
11532       */
11533  	protected function _outRect($x, $y, $w, $h, $op) {
11534          if ($this->state == 2) {
11535              $this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k), (($this->h - $y) * $this->k), ($w * $this->k), (-$h * $this->k), $op));
11536          }
11537      }
11538  
11539      /**
11540       * 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.
11541       * The new current point shall be (x3, y3).
11542       * @param $x1 (float) Abscissa of control point 1.
11543       * @param $y1 (float) Ordinate of control point 1.
11544       * @param $x2 (float) Abscissa of control point 2.
11545       * @param $y2 (float) Ordinate of control point 2.
11546       * @param $x3 (float) Abscissa of end point.
11547       * @param $y3 (float) Ordinate of end point.
11548       * @protected
11549       * @since 2.1.000 (2008-01-08)
11550       */
11551  	protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
11552          if ($this->state == 2) {
11553              $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)));
11554          }
11555      }
11556  
11557      /**
11558       * 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.
11559       * The new current point shall be (x3, y3).
11560       * @param $x2 (float) Abscissa of control point 2.
11561       * @param $y2 (float) Ordinate of control point 2.
11562       * @param $x3 (float) Abscissa of end point.
11563       * @param $y3 (float) Ordinate of end point.
11564       * @protected
11565       * @since 4.9.019 (2010-04-26)
11566       */
11567  	protected function _outCurveV($x2, $y2, $x3, $y3) {
11568          if ($this->state == 2) {
11569              $this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11570          }
11571      }
11572  
11573      /**
11574       * 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.
11575       * The new current point shall be (x3, y3).
11576       * @param $x1 (float) Abscissa of control point 1.
11577       * @param $y1 (float) Ordinate of control point 1.
11578       * @param $x3 (float) Abscissa of end point.
11579       * @param $y3 (float) Ordinate of end point.
11580       * @protected
11581       * @since 2.1.000 (2008-01-08)
11582       */
11583  	protected function _outCurveY($x1, $y1, $x3, $y3) {
11584          if ($this->state == 2) {
11585              $this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11586          }
11587      }
11588  
11589      /**
11590       * Draws a line between two points.
11591       * @param $x1 (float) Abscissa of first point.
11592       * @param $y1 (float) Ordinate of first point.
11593       * @param $x2 (float) Abscissa of second point.
11594       * @param $y2 (float) Ordinate of second point.
11595       * @param $style (array) Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
11596       * @public
11597       * @since 1.0
11598       * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
11599       */
11600  	public function Line($x1, $y1, $x2, $y2, $style=array()) {
11601          if ($this->state != 2) {
11602              return;
11603          }
11604          if (is_array($style)) {
11605              $this->SetLineStyle($style);
11606          }
11607          $this->_outPoint($x1, $y1);
11608          $this->_outLine($x2, $y2);
11609          $this->_out('S');
11610      }
11611  
11612      /**
11613       * Draws a rectangle.
11614       * @param $x (float) Abscissa of upper-left corner.
11615       * @param $y (float) Ordinate of upper-left corner.
11616       * @param $w (float) Width.
11617       * @param $h (float) Height.
11618       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11619       * @param $border_style (array) Border style of rectangle. Array with keys among the following:
11620       * <ul>
11621       *     <li>all: Line style of all borders. Array like for SetLineStyle().</li>
11622       *     <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
11623       * </ul>
11624       * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
11625       * @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).
11626       * @public
11627       * @since 1.0
11628       * @see SetLineStyle()
11629       */
11630  	public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
11631          if ($this->state != 2) {
11632              return;
11633          }
11634          if (empty($style)) {
11635              $style = 'S';
11636          }
11637          if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
11638              // set background color
11639              $this->SetFillColorArray($fill_color);
11640          }
11641          if (!empty($border_style)) {
11642              if (isset($border_style['all']) AND !empty($border_style['all'])) {
11643                  //set global style for border
11644                  $this->SetLineStyle($border_style['all']);
11645                  $border_style = array();
11646              } else {
11647                  // remove stroke operator from style
11648                  $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*' );
11649                  if (isset($opnostroke[$style])) {
11650                      $style = $opnostroke[$style];
11651                  }
11652              }
11653          }
11654          if (!empty($style)) {
11655              $op = TCPDF_STATIC::getPathPaintOperator($style);
11656              $this->_outRect($x, $y, $w, $h, $op);
11657          }
11658          if (!empty($border_style)) {
11659              $border_style2 = array();
11660              foreach ($border_style as $line => $value) {
11661                  $length = strlen($line);
11662                  for ($i = 0; $i < $length; ++$i) {
11663                      $border_style2[$line[$i]] = $value;
11664                  }
11665              }
11666              $border_style = $border_style2;
11667              if (isset($border_style['L']) AND $border_style['L']) {
11668                  $this->Line($x, $y, $x, $y + $h, $border_style['L']);
11669              }
11670              if (isset($border_style['T']) AND $border_style['T']) {
11671                  $this->Line($x, $y, $x + $w, $y, $border_style['T']);
11672              }
11673              if (isset($border_style['R']) AND $border_style['R']) {
11674                  $this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
11675              }
11676              if (isset($border_style['B']) AND $border_style['B']) {
11677                  $this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
11678              }
11679          }
11680      }
11681  
11682      /**
11683       * Draws a Bezier curve.
11684       * The Bezier curve is a tangent to the line between the control points at
11685       * either end of the curve.
11686       * @param $x0 (float) Abscissa of start point.
11687       * @param $y0 (float) Ordinate of start point.
11688       * @param $x1 (float) Abscissa of control point 1.
11689       * @param $y1 (float) Ordinate of control point 1.
11690       * @param $x2 (float) Abscissa of control point 2.
11691       * @param $y2 (float) Ordinate of control point 2.
11692       * @param $x3 (float) Abscissa of end point.
11693       * @param $y3 (float) Ordinate of end point.
11694       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11695       * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11696       * @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).
11697       * @public
11698       * @see SetLineStyle()
11699       * @since 2.1.000 (2008-01-08)
11700       */
11701  	public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
11702          if ($this->state != 2) {
11703              return;
11704          }
11705          if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11706              $this->SetFillColorArray($fill_color);
11707          }
11708          $op = TCPDF_STATIC::getPathPaintOperator($style);
11709          if ($line_style) {
11710              $this->SetLineStyle($line_style);
11711          }
11712          $this->_outPoint($x0, $y0);
11713          $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11714          $this->_out($op);
11715      }
11716  
11717      /**
11718       * Draws a poly-Bezier curve.
11719       * Each Bezier curve segment is a tangent to the line between the control points at
11720       * either end of the curve.
11721       * @param $x0 (float) Abscissa of start point.
11722       * @param $y0 (float) Ordinate of start point.
11723       * @param $segments (float) An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
11724       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11725       * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11726       * @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).
11727       * @public
11728       * @see SetLineStyle()
11729       * @since 3.0008 (2008-05-12)
11730       */
11731  	public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
11732          if ($this->state != 2) {
11733              return;
11734          }
11735          if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11736              $this->SetFillColorArray($fill_color);
11737          }
11738          $op = TCPDF_STATIC::getPathPaintOperator($style);
11739          if ($op == 'f') {
11740              $line_style = array();
11741          }
11742          if ($line_style) {
11743              $this->SetLineStyle($line_style);
11744          }
11745          $this->_outPoint($x0, $y0);
11746          foreach ($segments as $segment) {
11747              list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
11748              $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11749          }
11750          $this->_out($op);
11751      }
11752  
11753      /**
11754       * Draws an ellipse.
11755       * An ellipse is formed from n Bezier curves.
11756       * @param $x0 (float) Abscissa of center point.
11757       * @param $y0 (float) Ordinate of center point.
11758       * @param $rx (float) Horizontal radius.
11759       * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11760       * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
11761       * @param $astart: (float) Angle start of draw line. Default value: 0.
11762       * @param $afinish: (float) Angle finish of draw line. Default value: 360.
11763       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11764       * @param $line_style (array) Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
11765       * @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).
11766       * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11767       * @author Nicola Asuni
11768       * @public
11769       * @since 2.1.000 (2008-01-08)
11770       */
11771  	public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11772          if ($this->state != 2) {
11773              return;
11774          }
11775          if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
11776              $ry = $rx;
11777          }
11778          if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11779              $this->SetFillColorArray($fill_color);
11780          }
11781          $op = TCPDF_STATIC::getPathPaintOperator($style);
11782          if ($op == 'f') {
11783              $line_style = array();
11784          }
11785          if ($line_style) {
11786              $this->SetLineStyle($line_style);
11787          }
11788          $this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
11789          $this->_out($op);
11790      }
11791  
11792      /**
11793       * Append an elliptical arc to the current path.
11794       * An ellipse is formed from n Bezier curves.
11795       * @param $xc (float) Abscissa of center point.
11796       * @param $yc (float) Ordinate of center point.
11797       * @param $rx (float) Horizontal radius.
11798       * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11799       * @param $xang: (float) Angle between the X-axis and the major axis of the ellipse. Default value: 0.
11800       * @param $angs: (float) Angle start of draw line. Default value: 0.
11801       * @param $angf: (float) Angle finish of draw line. Default value: 360.
11802       * @param $pie (boolean) if true do not mark the border point (used to draw pie sectors).
11803       * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11804       * @param $startpoint (boolean) if true output a starting point.
11805       * @param $ccw (boolean) if true draws in counter-clockwise.
11806       * @param $svg (boolean) if true the angles are in svg mode (already calculated).
11807       * @return array bounding box coordinates (x min, y min, x max, y max)
11808       * @author Nicola Asuni
11809       * @protected
11810       * @since 4.9.019 (2010-04-26)
11811       */
11812  	protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
11813          if (($rx <= 0) OR ($ry < 0)) {
11814              return;
11815          }
11816          $k = $this->k;
11817          if ($nc < 2) {
11818              $nc = 2;
11819          }
11820          $xmin = 2147483647;
11821          $ymin = 2147483647;
11822          $xmax = 0;
11823          $ymax = 0;
11824          if ($pie) {
11825              // center of the arc
11826              $this->_outPoint($xc, $yc);
11827          }
11828          $xang = deg2rad((float) $xang);
11829          $angs = deg2rad((float) $angs);
11830          $angf = deg2rad((float) $angf);
11831          if ($svg) {
11832              $as = $angs;
11833              $af = $angf;
11834          } else {
11835              $as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
11836              $af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
11837          }
11838          if ($as < 0) {
11839              $as += (2 * M_PI);
11840          }
11841          if ($af < 0) {
11842              $af += (2 * M_PI);
11843          }
11844          if ($ccw AND ($as > $af)) {
11845              // reverse rotation
11846              $as -= (2 * M_PI);
11847          } elseif (!$ccw AND ($as < $af)) {
11848              // reverse rotation
11849              $af -= (2 * M_PI);
11850          }
11851          $total_angle = ($af - $as);
11852          if ($nc < 2) {
11853              $nc = 2;
11854          }
11855          // total arcs to draw
11856          $nc *= (2 * abs($total_angle) / M_PI);
11857          $nc = round($nc) + 1;
11858          // angle of each arc
11859          $arcang = ($total_angle / $nc);
11860          // center point in PDF coordinates
11861          $x0 = $xc;
11862          $y0 = ($this->h - $yc);
11863          // starting angle
11864          $ang = $as;
11865          $alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
11866          $cos_xang = cos($xang);
11867          $sin_xang = sin($xang);
11868          $cos_ang = cos($ang);
11869          $sin_ang = sin($ang);
11870          // first arc point
11871          $px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11872          $py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11873          // first Bezier control point
11874          $qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11875          $qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11876          if ($pie) {
11877              // line from center to arc starting point
11878              $this->_outLine($px1, $this->h - $py1);
11879          } elseif ($startpoint) {
11880              // arc starting point
11881              $this->_outPoint($px1, $this->h - $py1);
11882          }
11883          // draw arcs
11884          for ($i = 1; $i <= $nc; ++$i) {
11885              // starting angle
11886              $ang = $as + ($i * $arcang);
11887              if ($i == $nc) {
11888                  $ang = $af;
11889              }
11890              $cos_ang = cos($ang);
11891              $sin_ang = sin($ang);
11892              // second arc point
11893              $px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11894              $py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11895              // second Bezier control point
11896              $qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11897              $qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11898              // draw arc
11899              $cx1 = ($px1 + $qx1);
11900              $cy1 = ($this->h - ($py1 + $qy1));
11901              $cx2 = ($px2 - $qx2);
11902              $cy2 = ($this->h - ($py2 - $qy2));
11903              $cx3 = $px2;
11904              $cy3 = ($this->h - $py2);
11905              $this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
11906              // get bounding box coordinates
11907              $xmin = min($xmin, $cx1, $cx2, $cx3);
11908              $ymin = min($ymin, $cy1, $cy2, $cy3);
11909              $xmax = max($xmax, $cx1, $cx2, $cx3);
11910              $ymax = max($ymax, $cy1, $cy2, $cy3);
11911              // move to next point
11912              $px1 = $px2;
11913              $py1 = $py2;
11914              $qx1 = $qx2;
11915              $qy1 = $qy2;
11916          }
11917          if ($pie) {
11918              $this->_outLine($xc, $yc);
11919              // get bounding box coordinates
11920              $xmin = min($xmin, $xc);
11921              $ymin = min($ymin, $yc);
11922              $xmax = max($xmax, $xc);
11923              $ymax = max($ymax, $yc);
11924          }
11925          return array($xmin, $ymin, $xmax, $ymax);
11926      }
11927  
11928      /**
11929       * Draws a circle.
11930       * A circle is formed from n Bezier curves.
11931       * @param $x0 (float) Abscissa of center point.
11932       * @param $y0 (float) Ordinate of center point.
11933       * @param $r (float) Radius.
11934       * @param $angstr: (float) Angle start of draw line. Default value: 0.
11935       * @param $angend: (float) Angle finish of draw line. Default value: 360.
11936       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11937       * @param $line_style (array) Line style of circle. Array like for SetLineStyle(). Default value: 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 $nc (integer) Number of curves used to draw a 90 degrees portion of circle.
11940       * @public
11941       * @since 2.1.000 (2008-01-08)
11942       */
11943  	public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11944          $this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
11945      }
11946  
11947      /**
11948       * Draws a polygonal line
11949       * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11950       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11951       * @param $line_style (array) Line style of polygon. Array with keys among the following:
11952       * <ul>
11953       *     <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11954       *     <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11955       * </ul>
11956       * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11957       * @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).
11958       * @since 4.8.003 (2009-09-15)
11959       * @public
11960       */
11961  	public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
11962          $this->Polygon($p, $style, $line_style, $fill_color, false);
11963      }
11964  
11965      /**
11966       * Draws a polygon.
11967       * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11968       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11969       * @param $line_style (array) Line style of polygon. Array with keys among the following:
11970       * <ul>
11971       *     <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11972       *     <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11973       * </ul>
11974       * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11975       * @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).
11976       * @param $closed (boolean) if true the polygon is closes, otherwise will remain open
11977       * @public
11978       * @since 2.1.000 (2008-01-08)
11979       */
11980  	public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
11981          if ($this->state != 2) {
11982              return;
11983          }
11984          $nc = count($p); // number of coordinates
11985          $np = $nc / 2; // number of points
11986          if ($closed) {
11987              // close polygon by adding the first 2 points at the end (one line)
11988              for ($i = 0; $i < 4; ++$i) {
11989                  $p[$nc + $i] = $p[$i];
11990              }
11991              // copy style for the last added line
11992              if (isset($line_style[0])) {
11993                  $line_style[$np] = $line_style[0];
11994              }
11995              $nc += 4;
11996          }
11997          if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11998              $this->SetFillColorArray($fill_color);
11999          }
12000          $op = TCPDF_STATIC::getPathPaintOperator($style);
12001          if ($op == 'f') {
12002              $line_style = array();
12003          }
12004          $draw = true;
12005          if ($line_style) {
12006              if (isset($line_style['all'])) {
12007                  $this->SetLineStyle($line_style['all']);
12008              } else {
12009                  $draw = false;
12010                  if ($op == 'B') {
12011                      // draw fill
12012                      $op = 'f';
12013                      $this->_outPoint($p[0], $p[1]);
12014                      for ($i = 2; $i < $nc; $i = $i + 2) {
12015                          $this->_outLine($p[$i], $p[$i + 1]);
12016                      }
12017                      $this->_out($op);
12018                  }
12019                  // draw outline
12020                  $this->_outPoint($p[0], $p[1]);
12021                  for ($i = 2; $i < $nc; $i = $i + 2) {
12022                      $line_num = ($i / 2) - 1;
12023                      if (isset($line_style[$line_num])) {
12024                          if ($line_style[$line_num] != 0) {
12025                              if (is_array($line_style[$line_num])) {
12026                                  $this->_out('S');
12027                                  $this->SetLineStyle($line_style[$line_num]);
12028                                  $this->_outPoint($p[$i - 2], $p[$i - 1]);
12029                                  $this->_outLine($p[$i], $p[$i + 1]);
12030                                  $this->_out('S');
12031                                  $this->_outPoint($p[$i], $p[$i + 1]);
12032                              } else {
12033                                  $this->_outLine($p[$i], $p[$i + 1]);
12034                              }
12035                          }
12036                      } else {
12037                          $this->_outLine($p[$i], $p[$i + 1]);
12038                      }
12039                  }
12040                  $this->_out($op);
12041              }
12042          }
12043          if ($draw) {
12044              $this->_outPoint($p[0], $p[1]);
12045              for ($i = 2; $i < $nc; $i = $i + 2) {
12046                  $this->_outLine($p[$i], $p[$i + 1]);
12047              }
12048              $this->_out($op);
12049          }
12050      }
12051  
12052      /**
12053       * Draws a regular polygon.
12054       * @param $x0 (float) Abscissa of center point.
12055       * @param $y0 (float) Ordinate of center point.
12056       * @param $r: (float) Radius of inscribed circle.
12057       * @param $ns (integer) Number of sides.
12058       * @param $angle (float) Angle oriented (anti-clockwise). Default value: 0.
12059       * @param $draw_circle (boolean) Draw inscribed circle or not. Default value: false.
12060       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12061       * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
12062       * <ul>
12063       *     <li>all: Line style of all sides. Array like for SetLineStyle().</li>
12064       *     <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
12065       * </ul>
12066       * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12067       * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12068       * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
12069       * <ul>
12070       *     <li>D or empty string: Draw (default).</li>
12071       *     <li>F: Fill.</li>
12072       *     <li>DF or FD: Draw and fill.</li>
12073       *     <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12074       *     <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12075       * </ul>
12076       * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12077       * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12078       * @public
12079       * @since 2.1.000 (2008-01-08)
12080       */
12081  	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()) {
12082          if (3 > $ns) {
12083              $ns = 3;
12084          }
12085          if ($draw_circle) {
12086              $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12087          }
12088          $p = array();
12089          for ($i = 0; $i < $ns; ++$i) {
12090              $a = $angle + ($i * 360 / $ns);
12091              $a_rad = deg2rad((float) $a);
12092              $p[] = $x0 + ($r * sin($a_rad));
12093              $p[] = $y0 + ($r * cos($a_rad));
12094          }
12095          $this->Polygon($p, $style, $line_style, $fill_color);
12096      }
12097  
12098      /**
12099       * Draws a star polygon
12100       * @param $x0 (float) Abscissa of center point.
12101       * @param $y0 (float) Ordinate of center point.
12102       * @param $r (float) Radius of inscribed circle.
12103       * @param $nv (integer) Number of vertices.
12104       * @param $ng (integer) Number of gap (if ($ng % $nv = 1) then is a regular polygon).
12105       * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
12106       * @param $draw_circle: (boolean) Draw inscribed circle or not. Default value is false.
12107       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12108       * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
12109       * <ul>
12110       *     <li>all: Line style of all sides. Array like for
12111       * SetLineStyle().</li>
12112       *     <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
12113       * </ul>
12114       * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12115       * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12116       * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
12117       * <ul>
12118       *     <li>D or empty string: Draw (default).</li>
12119       *     <li>F: Fill.</li>
12120       *     <li>DF or FD: Draw and fill.</li>
12121       *     <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12122       *     <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12123       * </ul>
12124       * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12125       * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12126       * @public
12127       * @since 2.1.000 (2008-01-08)
12128       */
12129  	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()) {
12130          if ($nv < 2) {
12131              $nv = 2;
12132          }
12133          if ($draw_circle) {
12134              $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12135          }
12136          $p2 = array();
12137          $visited = array();
12138          for ($i = 0; $i < $nv; ++$i) {
12139              $a = $angle + ($i * 360 / $nv);
12140              $a_rad = deg2rad((float) $a);
12141              $p2[] = $x0 + ($r * sin($a_rad));
12142              $p2[] = $y0 + ($r * cos($a_rad));
12143              $visited[] = false;
12144          }
12145          $p = array();
12146          $i = 0;
12147          do {
12148              $p[] = $p2[$i * 2];
12149              $p[] = $p2[($i * 2) + 1];
12150              $visited[$i] = true;
12151              $i += $ng;
12152              $i %= $nv;
12153          } while (!$visited[$i]);
12154          $this->Polygon($p, $style, $line_style, $fill_color);
12155      }
12156  
12157      /**
12158       * Draws a rounded rectangle.
12159       * @param $x (float) Abscissa of upper-left corner.
12160       * @param $y (float) Ordinate of upper-left corner.
12161       * @param $w (float) Width.
12162       * @param $h (float) Height.
12163       * @param $r (float) the radius of the circle used to round off the corners of the rectangle.
12164       * @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").
12165       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12166       * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12167       * @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).
12168       * @public
12169       * @since 2.1.000 (2008-01-08)
12170       */
12171  	public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12172          $this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
12173      }
12174  
12175      /**
12176       * Draws a rounded rectangle.
12177       * @param $x (float) Abscissa of upper-left corner.
12178       * @param $y (float) Ordinate of upper-left corner.
12179       * @param $w (float) Width.
12180       * @param $h (float) Height.
12181       * @param $rx (float) the x-axis radius of the ellipse used to round off the corners of the rectangle.
12182       * @param $ry (float) the y-axis radius of the ellipse used to round off the corners of the rectangle.
12183       * @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").
12184       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12185       * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12186       * @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).
12187       * @public
12188       * @since 4.9.019 (2010-04-22)
12189       */
12190  	public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12191          if ($this->state != 2) {
12192              return;
12193          }
12194          if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
12195              // Not rounded
12196              $this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
12197              return;
12198          }
12199          // Rounded
12200          if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12201              $this->SetFillColorArray($fill_color);
12202          }
12203          $op = TCPDF_STATIC::getPathPaintOperator($style);
12204          if ($op == 'f') {
12205              $border_style = array();
12206          }
12207          if ($border_style) {
12208              $this->SetLineStyle($border_style);
12209          }
12210          $MyArc = 4 / 3 * (sqrt(2) - 1);
12211          $this->_outPoint($x + $rx, $y);
12212          $xc = $x + $w - $rx;
12213          $yc = $y + $ry;
12214          $this->_outLine($xc, $y);
12215          if ($round_corner[0]) {
12216              $this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
12217          } else {
12218              $this->_outLine($x + $w, $y);
12219          }
12220          $xc = $x + $w - $rx;
12221          $yc = $y + $h - $ry;
12222          $this->_outLine($x + $w, $yc);
12223          if ($round_corner[1]) {
12224              $this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
12225          } else {
12226              $this->_outLine($x + $w, $y + $h);
12227          }
12228          $xc = $x + $rx;
12229          $yc = $y + $h - $ry;
12230          $this->_outLine($xc, $y + $h);
12231          if ($round_corner[2]) {
12232              $this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
12233          } else {
12234              $this->_outLine($x, $y + $h);
12235          }
12236          $xc = $x + $rx;
12237          $yc = $y + $ry;
12238          $this->_outLine($x, $yc);
12239          if ($round_corner[3]) {
12240              $this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
12241          } else {
12242              $this->_outLine($x, $y);
12243              $this->_outLine($x + $rx, $y);
12244          }
12245          $this->_out($op);
12246      }
12247  
12248      /**
12249       * Draws a grahic arrow.
12250       * @param $x0 (float) Abscissa of first point.
12251       * @param $y0 (float) Ordinate of first point.
12252       * @param $x1 (float) Abscissa of second point.
12253       * @param $y1 (float) Ordinate of second point.
12254       * @param $head_style (int) (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
12255       * @param $arm_size (float) length of arrowhead arms
12256       * @param $arm_angle (int) angle between an arm and the shaft
12257       * @author Piotr Galecki, Nicola Asuni, Andy Meier
12258       * @since 4.6.018 (2009-07-10)
12259       */
12260  	public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
12261          // getting arrow direction angle
12262          // 0 deg angle is when both arms go along X axis. angle grows clockwise.
12263          $dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
12264          if ($dir_angle < 0) {
12265              $dir_angle += (2 * M_PI);
12266          }
12267          $arm_angle = deg2rad($arm_angle);
12268          $sx1 = $x1;
12269          $sy1 = $y1;
12270          if ($head_style > 0) {
12271              // calculate the stopping point for the arrow shaft
12272              $sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
12273              $sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
12274          }
12275          // main arrow line / shaft
12276          $this->Line($x0, $y0, $sx1, $sy1);
12277          // left arrowhead arm tip
12278          $x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
12279          $y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
12280          // right arrowhead arm tip
12281          $x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
12282          $y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
12283          $mode = 'D';
12284          $style = array();
12285          switch ($head_style) {
12286              case 0: {
12287                  // draw only arrowhead arms
12288                  $mode = 'D';
12289                  $style = array(1, 1, 0);
12290                  break;
12291              }
12292              case 1: {
12293                  // draw closed arrowhead, but no fill
12294                  $mode = 'D';
12295                  break;
12296              }
12297              case 2: {
12298                  // closed and filled arrowhead
12299                  $mode = 'DF';
12300                  break;
12301              }
12302              case 3: {
12303                  // filled arrowhead
12304                  $mode = 'F';
12305                  break;
12306              }
12307          }
12308          $this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
12309      }
12310  
12311      // END GRAPHIC FUNCTIONS SECTION -----------------------
12312  
12313      /**
12314       * Add a Named Destination.
12315       * NOTE: destination names are unique, so only last entry will be saved.
12316       * @param $name (string) Destination name.
12317       * @param $y (float) Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
12318       * @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.
12319       * @param $x (float) X position in user units of the destiantion on the selected page (default = -1 = current position;).
12320       * @return (string) Stripped named destination identifier or false in case of error.
12321       * @public
12322       * @author Christian Deligant, Nicola Asuni
12323       * @since 5.9.097 (2011-06-23)
12324       */
12325  	public function setDestination($name, $y=-1, $page='', $x=-1) {
12326          // remove unsupported characters
12327          $name = TCPDF_STATIC::encodeNameObject($name);
12328          if (TCPDF_STATIC::empty_string($name)) {
12329              return false;
12330          }
12331          if ($y == -1) {
12332              $y = $this->GetY();
12333          } elseif ($y < 0) {
12334              $y = 0;
12335          } elseif ($y > $this->h) {
12336              $y = $this->h;
12337          }
12338          if ($x == -1) {
12339              $x = $this->GetX();
12340          } elseif ($x < 0) {
12341              $x = 0;
12342          } elseif ($x > $this->w) {
12343              $x = $this->w;
12344          }
12345          $fixed = false;
12346          if (!empty($page) AND ($page[0] == '*')) {
12347              $page = intval(substr($page, 1));
12348              // this page number will not be changed when moving/add/deleting pages
12349              $fixed = true;
12350          }
12351          if (empty($page)) {
12352              $page = $this->PageNo();
12353              if (empty($page)) {
12354                  return;
12355              }
12356          }
12357          $this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
12358          return $name;
12359      }
12360  
12361      /**
12362       * Return the Named Destination array.
12363       * @return (array) Named Destination array.
12364       * @public
12365       * @author Nicola Asuni
12366       * @since 5.9.097 (2011-06-23)
12367       */
12368  	public function getDestination() {
12369          return $this->dests;
12370      }
12371  
12372      /**
12373       * Insert Named Destinations.
12374       * @protected
12375       * @author Johannes G\FCntert, Nicola Asuni
12376       * @since 5.9.098 (2011-06-23)
12377       */
12378  	protected function _putdests() {
12379          if (empty($this->dests)) {
12380              return;
12381          }
12382          $this->n_dests = $this->_newobj();
12383          $out = ' <<';
12384          foreach($this->dests as $name => $o) {
12385              $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)));
12386          }
12387          $out .= ' >>';
12388          $out .= "\n".'endobj';
12389          $this->_out($out);
12390      }
12391  
12392      /**
12393       * Adds a bookmark - alias for Bookmark().
12394       * @param $txt (string) Bookmark description.
12395       * @param $level (int) Bookmark level (minimum value is 0).
12396       * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12397       * @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.
12398       * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12399       * @param $color (array) RGB color array (values from 0 to 255).
12400       * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12401       * @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).
12402       * @public
12403       */
12404  	public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12405          $this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
12406      }
12407  
12408      /**
12409       * Adds a bookmark.
12410       * @param $txt (string) Bookmark description.
12411       * @param $level (int) Bookmark level (minimum value is 0).
12412       * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12413       * @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.
12414       * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12415       * @param $color (array) RGB color array (values from 0 to 255).
12416       * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12417       * @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).
12418       * @public
12419       * @since 2.1.002 (2008-02-12)
12420       */
12421  	public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12422          if ($level < 0) {
12423              $level = 0;
12424          }
12425          if (isset($this->outlines[0])) {
12426              $lastoutline = end($this->outlines);
12427              $maxlevel = $lastoutline['l'] + 1;
12428          } else {
12429              $maxlevel = 0;
12430          }
12431          if ($level > $maxlevel) {
12432              $level = $maxlevel;
12433          }
12434          if ($y == -1) {
12435              $y = $this->GetY();
12436          } elseif ($y < 0) {
12437              $y = 0;
12438          } elseif ($y > $this->h) {
12439              $y = $this->h;
12440          }
12441          if ($x == -1) {
12442              $x = $this->GetX();
12443          } elseif ($x < 0) {
12444              $x = 0;
12445          } elseif ($x > $this->w) {
12446              $x = $this->w;
12447          }
12448          $fixed = false;
12449          if (!empty($page) AND ($page[0] == '*')) {
12450              $page = intval(substr($page, 1));
12451              // this page number will not be changed when moving/add/deleting pages
12452              $fixed = true;
12453          }
12454          if (empty($page)) {
12455              $page = $this->PageNo();
12456              if (empty($page)) {
12457                  return;
12458              }
12459          }
12460          $this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
12461      }
12462  
12463      /**
12464       * Sort bookmarks for page and key.
12465       * @protected
12466       * @since 5.9.119 (2011-09-19)
12467       */
12468  	protected function sortBookmarks() {
12469          // get sorting columns
12470          $outline_p = array();
12471          $outline_y = array();
12472          foreach ($this->outlines as $key => $row) {
12473              $outline_p[$key] = $row['p'];
12474              $outline_k[$key] = $key;
12475          }
12476          // sort outlines by page and original position
12477          array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
12478      }
12479  
12480      /**
12481       * Create a bookmark PDF string.
12482       * @protected
12483       * @author Olivier Plathey, Nicola Asuni
12484       * @since 2.1.002 (2008-02-12)
12485       */
12486  	protected function _putbookmarks() {
12487          $nb = count($this->outlines);
12488          if ($nb == 0) {
12489              return;
12490          }
12491          // sort bookmarks
12492          $this->sortBookmarks();
12493          $lru = array();
12494          $level = 0;
12495          foreach ($this->outlines as $i => $o) {
12496              if ($o['l'] > 0) {
12497                  $parent = $lru[($o['l'] - 1)];
12498                  //Set parent and last pointers
12499                  $this->outlines[$i]['parent'] = $parent;
12500                  $this->outlines[$parent]['last'] = $i;
12501                  if ($o['l'] > $level) {
12502                      //Level increasing: set first pointer
12503                      $this->outlines[$parent]['first'] = $i;
12504                  }
12505              } else {
12506                  $this->outlines[$i]['parent'] = $nb;
12507              }
12508              if (($o['l'] <= $level) AND ($i > 0)) {
12509                  //Set prev and next pointers
12510                  $prev = $lru[$o['l']];
12511                  $this->outlines[$prev]['next'] = $i;
12512                  $this->outlines[$i]['prev'] = $prev;
12513              }
12514              $lru[$o['l']] = $i;
12515              $level = $o['l'];
12516          }
12517          //Outline items
12518          $n = $this->n + 1;
12519          $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';
12520          foreach ($this->outlines as $i => $o) {
12521              $oid = $this->_newobj();
12522              // covert HTML title to string
12523              $title = preg_replace($nltags, "\n", $o['t']);
12524              $title = preg_replace("/[\r]+/si", '', $title);
12525              $title = preg_replace("/[\n]+/si", "\n", $title);
12526              $title = strip_tags($title);
12527              $title = $this->stringTrim($title);
12528              $out = '<</Title '.$this->_textstring($title, $oid);
12529              $out .= ' /Parent '.($n + $o['parent']).' 0 R';
12530              if (isset($o['prev'])) {
12531                  $out .= ' /Prev '.($n + $o['prev']).' 0 R';
12532              }
12533              if (isset($o['next'])) {
12534                  $out .= ' /Next '.($n + $o['next']).' 0 R';
12535              }
12536              if (isset($o['first'])) {
12537                  $out .= ' /First '.($n + $o['first']).' 0 R';
12538              }
12539              if (isset($o['last'])) {
12540                  $out .= ' /Last '.($n + $o['last']).' 0 R';
12541              }
12542              if (isset($o['u']) AND !empty($o['u'])) {
12543                  // link
12544                  if (is_string($o['u'])) {
12545                      if ($o['u'][0] == '#') {
12546                          // internal destination
12547                          $out .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($o['u'], 1));
12548                      } elseif ($o['u'][0] == '%') {
12549                          // embedded PDF file
12550                          $filename = basename(substr($o['u'], 1));
12551                          $out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
12552                      } elseif ($o['u'][0] == '*') {
12553                          // embedded generic file
12554                          $filename = basename(substr($o['u'], 1));
12555                          $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});';
12556                          $out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
12557                      } else {
12558                          // external URI link
12559                          $out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
12560                      }
12561                  } elseif (isset($this->links[$o['u']])) {
12562                      // internal link ID
12563                      $l = $this->links[$o['u']];
12564                      if (isset($this->page_obj_id[($l['p'])])) {
12565                          $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)));
12566                      }
12567                  }
12568              } elseif (isset($this->page_obj_id[($o['p'])])) {
12569                  // link to a page
12570                  $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)));
12571              }
12572              // set font style
12573              $style = 0;
12574              if (!empty($o['s'])) {
12575                  // bold
12576                  if (strpos($o['s'], 'B') !== false) {
12577                      $style |= 2;
12578                  }
12579                  // oblique
12580                  if (strpos($o['s'], 'I') !== false) {
12581                      $style |= 1;
12582                  }
12583              }
12584              $out .= sprintf(' /F %d', $style);
12585              // set bookmark color
12586              if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
12587                  $color = array_values($o['c']);
12588                  $out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
12589              } else {
12590                  // black
12591                  $out .= ' /C [0.0 0.0 0.0]';
12592              }
12593              $out .= ' /Count 0'; // normally closed item
12594              $out .= ' >>';
12595              $out .= "\n".'endobj';
12596              $this->_out($out);
12597          }
12598          //Outline root
12599          $this->OutlineRoot = $this->_newobj();
12600          $this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
12601      }
12602  
12603      // --- JAVASCRIPT ------------------------------------------------------
12604  
12605      /**
12606       * Adds a javascript
12607       * @param $script (string) Javascript code
12608       * @public
12609       * @author Johannes G\FCntert, Nicola Asuni
12610       * @since 2.1.002 (2008-02-12)
12611       */
12612  	public function IncludeJS($script) {
12613          $this->javascript .= $script;
12614      }
12615  
12616      /**
12617       * Adds a javascript object and return object ID
12618       * @param $script (string) Javascript code
12619       * @param $onload (boolean) if true executes this object when opening the document
12620       * @return int internal object ID
12621       * @public
12622       * @author Nicola Asuni
12623       * @since 4.8.000 (2009-09-07)
12624       */
12625  	public function addJavascriptObject($script, $onload=false) {
12626          if ($this->pdfa_mode) {
12627              // javascript is not allowed in PDF/A mode
12628              return false;
12629          }
12630          ++$this->n;
12631          $this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
12632          return $this->n;
12633      }
12634  
12635      /**
12636       * Create a javascript PDF string.
12637       * @protected
12638       * @author Johannes G\FCntert, Nicola Asuni
12639       * @since 2.1.002 (2008-02-12)
12640       */
12641  	protected function _putjavascript() {
12642          if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
12643              return;
12644          }
12645          if (strpos($this->javascript, 'this.addField') > 0) {
12646              if (!$this->ur['enabled']) {
12647                  //$this->setUserRights();
12648              }
12649              // the following two lines are used to avoid form fields duplication after saving
12650              // The addField method only works when releasing user rights (UR3)
12651              $jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
12652              $jsb = "getField('tcpdfdocsaved').value='saved';";
12653              $this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
12654          }
12655          // name tree for javascript
12656          $this->n_js = '<< /Names [';
12657          if (!empty($this->javascript)) {
12658              $this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
12659          }
12660          if (!empty($this->js_objects)) {
12661              foreach ($this->js_objects as $key => $val) {
12662                  if ($val['onload']) {
12663                      $this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
12664                  }
12665              }
12666          }
12667          $this->n_js .= ' ] >>';
12668          // default Javascript object
12669          if (!empty($this->javascript)) {
12670              $obj_id = $this->_newobj();
12671              $out = '<< /S /JavaScript';
12672              $out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
12673              $out .= ' >>';
12674              $out .= "\n".'endobj';
12675              $this->_out($out);
12676          }
12677          // additional Javascript objects
12678          if (!empty($this->js_objects)) {
12679              foreach ($this->js_objects as $key => $val) {
12680                  $out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
12681                  $this->_out($out);
12682              }
12683          }
12684      }
12685  
12686      /**
12687       * Adds a javascript form field.
12688       * @param $type (string) field type
12689       * @param $name (string) field name
12690       * @param $x (int) horizontal position
12691       * @param $y (int) vertical position
12692       * @param $w (int) width
12693       * @param $h (int) height
12694       * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12695       * @protected
12696       * @author Denis Van Nuffelen, Nicola Asuni
12697       * @since 2.1.002 (2008-02-12)
12698       */
12699  	protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
12700          if ($this->rtl) {
12701              $x = $x - $w;
12702          }
12703          // the followind avoid fields duplication after saving the document
12704          $this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
12705          $k = $this->k;
12706          $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";
12707          $this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
12708          while (list($key, $val) = each($prop)) {
12709              if (strcmp(substr($key, -5), 'Color') == 0) {
12710                  $val = TCPDF_COLORS::_JScolor($val);
12711              } else {
12712                  $val = "'".$val."'";
12713              }
12714              $this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
12715          }
12716          if ($this->rtl) {
12717              $this->x -= $w;
12718          } else {
12719              $this->x += $w;
12720          }
12721          $this->javascript .= '}';
12722      }
12723  
12724      // --- FORM FIELDS -----------------------------------------------------
12725  
12726  
12727  
12728      /**
12729       * Set default properties for form fields.
12730       * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12731       * @public
12732       * @author Nicola Asuni
12733       * @since 4.8.000 (2009-09-06)
12734       */
12735  	public function setFormDefaultProp($prop=array()) {
12736          $this->default_form_prop = $prop;
12737      }
12738  
12739      /**
12740       * Return the default properties for form fields.
12741       * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12742       * @public
12743       * @author Nicola Asuni
12744       * @since 4.8.000 (2009-09-06)
12745       */
12746  	public function getFormDefaultProp() {
12747          return $this->default_form_prop;
12748      }
12749  
12750      /**
12751       * Creates a text field
12752       * @param $name (string) field name
12753       * @param $w (float) Width of the rectangle
12754       * @param $h (float) Height of the rectangle
12755       * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12756       * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12757       * @param $x (float) Abscissa of the upper-left corner of the rectangle
12758       * @param $y (float) Ordinate of the upper-left corner of the rectangle
12759       * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12760       * @public
12761       * @author Nicola Asuni
12762       * @since 4.8.000 (2009-09-07)
12763       */
12764  	public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12765          if ($x === '') {
12766              $x = $this->x;
12767          }
12768          if ($y === '') {
12769              $y = $this->y;
12770          }
12771          // check page for no-write regions and adapt page margins if necessary
12772          list($x, $y) = $this->checkPageRegions($h, $x, $y);
12773          if ($js) {
12774              $this->_addfield('text', $name, $x, $y, $w, $h, $prop);
12775              return;
12776          }
12777          // get default style
12778          $prop = array_merge($this->getFormDefaultProp(), $prop);
12779          // get annotation data
12780          $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12781          // set default appearance stream
12782          $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12783          $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12784          $popt['da'] = $fontstyle;
12785          // build appearance stream
12786          $popt['ap'] = array();
12787          $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12788          $text = '';
12789          if (isset($prop['value']) AND !empty($prop['value'])) {
12790              $text = $prop['value'];
12791          } elseif (isset($opt['v']) AND !empty($opt['v'])) {
12792              $text = $opt['v'];
12793          }
12794          $tmpid = $this->startTemplate($w, $h, false);
12795          $align = '';
12796          if (isset($popt['q'])) {
12797              switch ($popt['q']) {
12798                  case 0: {
12799                      $align = 'L';
12800                      break;
12801                  }
12802                  case 1: {
12803                      $align = 'C';
12804                      break;
12805                  }
12806                  case 2: {
12807                      $align = 'R';
12808                      break;
12809                  }
12810                  default: {
12811                      $align = '';
12812                      break;
12813                  }
12814              }
12815          }
12816          $this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12817          $this->endTemplate();
12818          --$this->n;
12819          $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12820          unset($this->xobjects[$tmpid]);
12821          $popt['ap']['n'] .= 'Q EMC';
12822          // merge options
12823          $opt = array_merge($popt, $opt);
12824          // remove some conflicting options
12825          unset($opt['bs']);
12826          // set remaining annotation data
12827          $opt['Subtype'] = 'Widget';
12828          $opt['ft'] = 'Tx';
12829          $opt['t'] = $name;
12830          // Additional annotation's parameters (check _putannotsobj() method):
12831          //$opt['f']
12832          //$opt['as']
12833          //$opt['bs']
12834          //$opt['be']
12835          //$opt['c']
12836          //$opt['border']
12837          //$opt['h']
12838          //$opt['mk'];
12839          //$opt['mk']['r']
12840          //$opt['mk']['bc'];
12841          //$opt['mk']['bg'];
12842          unset($opt['mk']['ca']);
12843          unset($opt['mk']['rc']);
12844          unset($opt['mk']['ac']);
12845          unset($opt['mk']['i']);
12846          unset($opt['mk']['ri']);
12847          unset($opt['mk']['ix']);
12848          unset($opt['mk']['if']);
12849          //$opt['mk']['if']['sw'];
12850          //$opt['mk']['if']['s'];
12851          //$opt['mk']['if']['a'];
12852          //$opt['mk']['if']['fb'];
12853          unset($opt['mk']['tp']);
12854          //$opt['tu']
12855          //$opt['tm']
12856          //$opt['ff']
12857          //$opt['v']
12858          //$opt['dv']
12859          //$opt['a']
12860          //$opt['aa']
12861          //$opt['q']
12862          $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12863          if ($this->rtl) {
12864              $this->x -= $w;
12865          } else {
12866              $this->x += $w;
12867          }
12868      }
12869  
12870      /**
12871       * Creates a RadioButton field.
12872       * @param $name (string) Field name.
12873       * @param $w (int) Width of the radio button.
12874       * @param $prop (array) Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12875       * @param $opt (array) Annotation parameters. Possible values are described on official PDF32000_2008 reference.
12876       * @param $onvalue (string) Value to be returned if selected.
12877       * @param $checked (boolean) Define the initial state.
12878       * @param $x (float) Abscissa of the upper-left corner of the rectangle
12879       * @param $y (float) Ordinate of the upper-left corner of the rectangle
12880       * @param $js (boolean) If true put the field using JavaScript (requires Acrobat Writer to be rendered).
12881       * @public
12882       * @author Nicola Asuni
12883       * @since 4.8.000 (2009-09-07)
12884       */
12885  	public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
12886          if ($x === '') {
12887              $x = $this->x;
12888          }
12889          if ($y === '') {
12890              $y = $this->y;
12891          }
12892          // check page for no-write regions and adapt page margins if necessary
12893          list($x, $y) = $this->checkPageRegions($w, $x, $y);
12894          if ($js) {
12895              $this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
12896              return;
12897          }
12898          if (TCPDF_STATIC::empty_string($onvalue)) {
12899              $onvalue = 'On';
12900          }
12901          if ($checked) {
12902              $defval = $onvalue;
12903          } else {
12904              $defval = 'Off';
12905          }
12906          // set font
12907          $font = 'zapfdingbats';
12908          if ($this->pdfa_mode) {
12909              // all fonts must be embedded
12910              $font = 'pdfa'.$font;
12911          }
12912          $this->AddFont($font);
12913          $tmpfont = $this->getFontBuffer($font);
12914          // set data for parent group
12915          if (!isset($this->radiobutton_groups[$this->page])) {
12916              $this->radiobutton_groups[$this->page] = array();
12917          }
12918          if (!isset($this->radiobutton_groups[$this->page][$name])) {
12919              $this->radiobutton_groups[$this->page][$name] = array();
12920              ++$this->n;
12921              $this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
12922              $this->radio_groups[] = $this->n;
12923          }
12924          $kid = ($this->n + 1);
12925          // save object ID to be added on Kids entry on parent object
12926          $this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
12927          // get default style
12928          $prop = array_merge($this->getFormDefaultProp(), $prop);
12929          $prop['NoToggleToOff'] = 'true';
12930          $prop['Radio'] = 'true';
12931          $prop['borderStyle'] = 'inset';
12932          // get annotation data
12933          $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12934          // set additional default options
12935          $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
12936          $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
12937          $popt['da'] = $fontstyle;
12938          // build appearance stream
12939          $popt['ap'] = array();
12940          $popt['ap']['n'] = array();
12941          $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
12942          $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
12943          $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);
12944          $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);
12945          if (!isset($popt['mk'])) {
12946              $popt['mk'] = array();
12947          }
12948          $popt['mk']['ca'] = '(l)';
12949          // merge options
12950          $opt = array_merge($popt, $opt);
12951          // set remaining annotation data
12952          $opt['Subtype'] = 'Widget';
12953          $opt['ft'] = 'Btn';
12954          if ($checked) {
12955              $opt['v'] = array('/'.$onvalue);
12956              $opt['as'] = $onvalue;
12957          } else {
12958              $opt['as'] = 'Off';
12959          }
12960          // store readonly flag
12961          if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
12962              $this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
12963          }
12964          $this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
12965          $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
12966          if ($this->rtl) {
12967              $this->x -= $w;
12968          } else {
12969              $this->x += $w;
12970          }
12971      }
12972  
12973      /**
12974       * Creates a List-box field
12975       * @param $name (string) field name
12976       * @param $w (int) width
12977       * @param $h (int) height
12978       * @param $values (array) array containing the list of values.
12979       * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12980       * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12981       * @param $x (float) Abscissa of the upper-left corner of the rectangle
12982       * @param $y (float) Ordinate of the upper-left corner of the rectangle
12983       * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12984       * @public
12985       * @author Nicola Asuni
12986       * @since 4.8.000 (2009-09-07)
12987       */
12988  	public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12989          if ($x === '') {
12990              $x = $this->x;
12991          }
12992          if ($y === '') {
12993              $y = $this->y;
12994          }
12995          // check page for no-write regions and adapt page margins if necessary
12996          list($x, $y) = $this->checkPageRegions($h, $x, $y);
12997          if ($js) {
12998              $this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
12999              $s = '';
13000              foreach ($values as $value) {
13001                  if (is_array($value)) {
13002                      $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13003                  } else {
13004                      $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13005                  }
13006              }
13007              $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13008              return;
13009          }
13010          // get default style
13011          $prop = array_merge($this->getFormDefaultProp(), $prop);
13012          // get annotation data
13013          $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13014          // set additional default values
13015          $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13016          $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13017          $popt['da'] = $fontstyle;
13018          // build appearance stream
13019          $popt['ap'] = array();
13020          $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13021          $text = '';
13022          foreach($values as $item) {
13023              if (is_array($item)) {
13024                  $text .= $item[1]."\n";
13025              } else {
13026                  $text .= $item."\n";
13027              }
13028          }
13029          $tmpid = $this->startTemplate($w, $h, false);
13030          $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13031          $this->endTemplate();
13032          --$this->n;
13033          $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13034          unset($this->xobjects[$tmpid]);
13035          $popt['ap']['n'] .= 'Q EMC';
13036          // merge options
13037          $opt = array_merge($popt, $opt);
13038          // set remaining annotation data
13039          $opt['Subtype'] = 'Widget';
13040          $opt['ft'] = 'Ch';
13041          $opt['t'] = $name;
13042          $opt['opt'] = $values;
13043          unset($opt['mk']['ca']);
13044          unset($opt['mk']['rc']);
13045          unset($opt['mk']['ac']);
13046          unset($opt['mk']['i']);
13047          unset($opt['mk']['ri']);
13048          unset($opt['mk']['ix']);
13049          unset($opt['mk']['if']);
13050          unset($opt['mk']['tp']);
13051          $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13052          if ($this->rtl) {
13053              $this->x -= $w;
13054          } else {
13055              $this->x += $w;
13056          }
13057      }
13058  
13059      /**
13060       * Creates a Combo-box field
13061       * @param $name (string) field name
13062       * @param $w (int) width
13063       * @param $h (int) height
13064       * @param $values (array) array containing the list of values.
13065       * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13066       * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13067       * @param $x (float) Abscissa of the upper-left corner of the rectangle
13068       * @param $y (float) Ordinate of the upper-left corner of the rectangle
13069       * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13070       * @public
13071       * @author Nicola Asuni
13072       * @since 4.8.000 (2009-09-07)
13073       */
13074  	public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
13075          if ($x === '') {
13076              $x = $this->x;
13077          }
13078          if ($y === '') {
13079              $y = $this->y;
13080          }
13081          // check page for no-write regions and adapt page margins if necessary
13082          list($x, $y) = $this->checkPageRegions($h, $x, $y);
13083          if ($js) {
13084              $this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
13085              $s = '';
13086              foreach ($values as $value) {
13087                  if (is_array($value)) {
13088                      $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13089                  } else {
13090                      $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13091                  }
13092              }
13093              $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13094              return;
13095          }
13096          // get default style
13097          $prop = array_merge($this->getFormDefaultProp(), $prop);
13098          $prop['Combo'] = true;
13099          // get annotation data
13100          $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13101          // set additional default options
13102          $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13103          $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13104          $popt['da'] = $fontstyle;
13105          // build appearance stream
13106          $popt['ap'] = array();
13107          $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13108          $text = '';
13109          foreach($values as $item) {
13110              if (is_array($item)) {
13111                  $text .= $item[1]."\n";
13112              } else {
13113                  $text .= $item."\n";
13114              }
13115          }
13116          $tmpid = $this->startTemplate($w, $h, false);
13117          $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13118          $this->endTemplate();
13119          --$this->n;
13120          $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13121          unset($this->xobjects[$tmpid]);
13122          $popt['ap']['n'] .= 'Q EMC';
13123          // merge options
13124          $opt = array_merge($popt, $opt);
13125          // set remaining annotation data
13126          $opt['Subtype'] = 'Widget';
13127          $opt['ft'] = 'Ch';
13128          $opt['t'] = $name;
13129          $opt['opt'] = $values;
13130          unset($opt['mk']['ca']);
13131          unset($opt['mk']['rc']);
13132          unset($opt['mk']['ac']);
13133          unset($opt['mk']['i']);
13134          unset($opt['mk']['ri']);
13135          unset($opt['mk']['ix']);
13136          unset($opt['mk']['if']);
13137          unset($opt['mk']['tp']);
13138          $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13139          if ($this->rtl) {
13140              $this->x -= $w;
13141          } else {
13142              $this->x += $w;
13143          }
13144      }
13145  
13146      /**
13147       * Creates a CheckBox field
13148       * @param $name (string) field name
13149       * @param $w (int) width
13150       * @param $checked (boolean) define the initial state.
13151       * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13152       * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13153       * @param $onvalue (string) value to be returned if selected.
13154       * @param $x (float) Abscissa of the upper-left corner of the rectangle
13155       * @param $y (float) Ordinate of the upper-left corner of the rectangle
13156       * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13157       * @public
13158       * @author Nicola Asuni
13159       * @since 4.8.000 (2009-09-07)
13160       */
13161  	public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
13162          if ($x === '') {
13163              $x = $this->x;
13164          }
13165          if ($y === '') {
13166              $y = $this->y;
13167          }
13168          // check page for no-write regions and adapt page margins if necessary
13169          list($x, $y) = $this->checkPageRegions($w, $x, $y);
13170          if ($js) {
13171              $this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
13172              return;
13173          }
13174          if (!isset($prop['value'])) {
13175              $prop['value'] = array('Yes');
13176          }
13177          // get default style
13178          $prop = array_merge($this->getFormDefaultProp(), $prop);
13179          $prop['borderStyle'] = 'inset';
13180          // get annotation data
13181          $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13182          // set additional default options
13183          $font = 'zapfdingbats';
13184          if ($this->pdfa_mode) {
13185              // all fonts must be embedded
13186              $font = 'pdfa'.$font;
13187          }
13188          $this->AddFont($font);
13189          $tmpfont = $this->getFontBuffer($font);
13190          $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
13191          $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
13192          $popt['da'] = $fontstyle;
13193          // build appearance stream
13194          $popt['ap'] = array();
13195          $popt['ap']['n'] = array();
13196          $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
13197          $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
13198          $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);
13199          $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);
13200          // merge options
13201          $opt = array_merge($popt, $opt);
13202          // set remaining annotation data
13203          $opt['Subtype'] = 'Widget';
13204          $opt['ft'] = 'Btn';
13205          $opt['t'] = $name;
13206          if (TCPDF_STATIC::empty_string($onvalue)) {
13207              $onvalue = 'Yes';
13208          }
13209          $opt['opt'] = array($onvalue);
13210          if ($checked) {
13211              $opt['v'] = array('/Yes');
13212              $opt['as'] = 'Yes';
13213          } else {
13214              $opt['v'] = array('/Off');
13215              $opt['as'] = 'Off';
13216          }
13217          $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13218          if ($this->rtl) {
13219              $this->x -= $w;
13220          } else {
13221              $this->x += $w;
13222          }
13223      }
13224  
13225      /**
13226       * Creates a button field
13227       * @param $name (string) field name
13228       * @param $w (int) width
13229       * @param $h (int) height
13230       * @param $caption (string) caption.
13231       * @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.
13232       * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13233       * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13234       * @param $x (float) Abscissa of the upper-left corner of the rectangle
13235       * @param $y (float) Ordinate of the upper-left corner of the rectangle
13236       * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13237       * @public
13238       * @author Nicola Asuni
13239       * @since 4.8.000 (2009-09-07)
13240       */
13241  	public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
13242          if ($x === '') {
13243              $x = $this->x;
13244          }
13245          if ($y === '') {
13246              $y = $this->y;
13247          }
13248          // check page for no-write regions and adapt page margins if necessary
13249          list($x, $y) = $this->checkPageRegions($h, $x, $y);
13250          if ($js) {
13251              $this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
13252              $this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
13253              $this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
13254              $this->javascript .= 'f'.$name.".highlight='push';\n";
13255              $this->javascript .= 'f'.$name.".print=false;\n";
13256              return;
13257          }
13258          // get default style
13259          $prop = array_merge($this->getFormDefaultProp(), $prop);
13260          $prop['Pushbutton'] = 'true';
13261          $prop['highlight'] = 'push';
13262          $prop['display'] = 'display.noPrint';
13263          // get annotation data
13264          $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13265          $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13266          $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13267          $popt['da'] = $fontstyle;
13268          // build appearance stream
13269          $popt['ap'] = array();
13270          $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13271          $tmpid = $this->startTemplate($w, $h, false);
13272          $bw = (2 / $this->k); // border width
13273          $border = array(
13274              'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13275              'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
13276              'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13277              'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
13278          $this->SetFillColor(204);
13279          $this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
13280          $this->endTemplate();
13281          --$this->n;
13282          $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13283          unset($this->xobjects[$tmpid]);
13284          $popt['ap']['n'] .= 'Q EMC';
13285          // set additional default options
13286          if (!isset($popt['mk'])) {
13287              $popt['mk'] = array();
13288          }
13289          $ann_obj_id = ($this->n + 1);
13290          if (!empty($action) AND !is_array($action)) {
13291              $ann_obj_id = ($this->n + 2);
13292          }
13293          $popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
13294          $popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
13295          $popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
13296          // merge options
13297          $opt = array_merge($popt, $opt);
13298          // set remaining annotation data
13299          $opt['Subtype'] = 'Widget';
13300          $opt['ft'] = 'Btn';
13301          $opt['t'] = $caption;
13302          $opt['v'] = $name;
13303          if (!empty($action)) {
13304              if (is_array($action)) {
13305                  // form action options as on section 12.7.5 of PDF32000_2008.
13306                  $opt['aa'] = '/D <<';
13307                  $bmode = array('SubmitForm', 'ResetForm', 'ImportData');
13308                  foreach ($action AS $key => $val) {
13309                      if (($key == 'S') AND in_array($val, $bmode)) {
13310                          $opt['aa'] .= ' /S /'.$val;
13311                      } elseif (($key == 'F') AND (!empty($val))) {
13312                          $opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
13313                      } elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
13314                          $opt['aa'] .= ' /Fields [';
13315                          foreach ($val AS $field) {
13316                              $opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
13317                          }
13318                          $opt['aa'] .= ']';
13319                      } elseif (($key == 'Flags')) {
13320                          $ff = 0;
13321                          if (is_array($val)) {
13322                              foreach ($val AS $flag) {
13323                                  switch ($flag) {
13324                                      case 'Include/Exclude': {
13325                                          $ff += 1 << 0;
13326                                          break;
13327                                      }
13328                                      case 'IncludeNoValueFields': {
13329                                          $ff += 1 << 1;
13330                                          break;
13331                                      }
13332                                      case 'ExportFormat': {
13333                                          $ff += 1 << 2;
13334                                          break;
13335                                      }
13336                                      case 'GetMethod': {
13337                                          $ff += 1 << 3;
13338                                          break;
13339                                      }
13340                                      case 'SubmitCoordinates': {
13341                                          $ff += 1 << 4;
13342                                          break;
13343                                      }
13344                                      case 'XFDF': {
13345                                          $ff += 1 << 5;
13346                                          break;
13347                                      }
13348                                      case 'IncludeAppendSaves': {
13349                                          $ff += 1 << 6;
13350                                          break;
13351                                      }
13352                                      case 'IncludeAnnotations': {
13353                                          $ff += 1 << 7;
13354                                          break;
13355                                      }
13356                                      case 'SubmitPDF': {
13357                                          $ff += 1 << 8;
13358                                          break;
13359                                      }
13360                                      case 'CanonicalFormat': {
13361                                          $ff += 1 << 9;
13362                                          break;
13363                                      }
13364                                      case 'ExclNonUserAnnots': {
13365                                          $ff += 1 << 10;
13366                                          break;
13367                                      }
13368                                      case 'ExclFKey': {
13369                                          $ff += 1 << 11;
13370                                          break;
13371                                      }
13372                                      case 'EmbedForm': {
13373                                          $ff += 1 << 13;
13374                                          break;
13375                                      }
13376                                  }
13377                              }
13378                          } else {
13379                              $ff = intval($val);
13380                          }
13381                          $opt['aa'] .= ' /Flags '.$ff;
13382                      }
13383                  }
13384                  $opt['aa'] .= ' >>';
13385              } else {
13386                  // Javascript action or raw action command
13387                  $js_obj_id = $this->addJavascriptObject($action);
13388                  $opt['aa'] = '/D '.$js_obj_id.' 0 R';
13389              }
13390          }
13391          $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13392          if ($this->rtl) {
13393              $this->x -= $w;
13394          } else {
13395              $this->x += $w;
13396          }
13397      }
13398  
13399      // --- END FORMS FIELDS ------------------------------------------------
13400  
13401      /**
13402       * Add certification signature (DocMDP or UR3)
13403       * You can set only one signature type
13404       * @protected
13405       * @author Nicola Asuni
13406       * @since 4.6.008 (2009-05-07)
13407       */
13408  	protected function _putsignature() {
13409          if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
13410              return;
13411          }
13412          $sigobjid = ($this->sig_obj_id + 1);
13413          $out = $this->_getobj($sigobjid)."\n";
13414          $out .= '<< /Type /Sig';
13415          $out .= ' /Filter /Adobe.PPKLite';
13416          $out .= ' /SubFilter /adbe.pkcs7.detached';
13417          $out .= ' '.TCPDF_STATIC::$byterange_string;
13418          $out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
13419          if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
13420              $out .= ' /Reference ['; // array of signature reference dictionaries
13421              $out .= ' << /Type /SigRef';
13422              if ($this->signature_data['cert_type'] > 0) {
13423                  $out .= ' /TransformMethod /DocMDP';
13424                  $out .= ' /TransformParams <<';
13425                  $out .= ' /Type /TransformParams';
13426                  $out .= ' /P '.$this->signature_data['cert_type'];
13427                  $out .= ' /V /1.2';
13428              } else {
13429                  $out .= ' /TransformMethod /UR3';
13430                  $out .= ' /TransformParams <<';
13431                  $out .= ' /Type /TransformParams';
13432                  $out .= ' /V /2.2';
13433                  if (!TCPDF_STATIC::empty_string($this->ur['document'])) {
13434                      $out .= ' /Document['.$this->ur['document'].']';
13435                  }
13436                  if (!TCPDF_STATIC::empty_string($this->ur['form'])) {
13437                      $out .= ' /Form['.$this->ur['form'].']';
13438                  }
13439                  if (!TCPDF_STATIC::empty_string($this->ur['signature'])) {
13440                      $out .= ' /Signature['.$this->ur['signature'].']';
13441                  }
13442                  if (!TCPDF_STATIC::empty_string($this->ur['annots'])) {
13443                      $out .= ' /Annots['.$this->ur['annots'].']';
13444                  }
13445                  if (!TCPDF_STATIC::empty_string($this->ur['ef'])) {
13446                      $out .= ' /EF['.$this->ur['ef'].']';
13447                  }
13448                  if (!TCPDF_STATIC::empty_string($this->ur['formex'])) {
13449                      $out .= ' /FormEX['.$this->ur['formex'].']';
13450                  }
13451              }
13452              $out .= ' >>'; // close TransformParams
13453              // optional digest data (values must be calculated and replaced later)
13454              //$out .= ' /Data ********** 0 R';
13455              //$out .= ' /DigestMethod/MD5';
13456              //$out .= ' /DigestLocation[********** 34]';
13457              //$out .= ' /DigestValue<********************************>';
13458              $out .= ' >>';
13459              $out .= ' ]'; // end of reference
13460          }
13461          if (isset($this->signature_data['info']['Name']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Name'])) {
13462              $out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
13463          }
13464          if (isset($this->signature_data['info']['Location']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Location'])) {
13465              $out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
13466          }
13467          if (isset($this->signature_data['info']['Reason']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Reason'])) {
13468              $out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
13469          }
13470          if (isset($this->signature_data['info']['ContactInfo']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['ContactInfo'])) {
13471              $out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
13472          }
13473          $out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
13474          $out .= ' >>';
13475          $out .= "\n".'endobj';
13476          $this->_out($out);
13477      }
13478  
13479      /**
13480       * Set User's Rights for PDF Reader
13481       * WARNING: This is experimental and currently do not work.
13482       * Check the PDF Reference 8.7.1 Transform Methods,
13483       * Table 8.105 Entries in the UR transform parameters dictionary
13484       * @param $enable (boolean) if true enable user's rights on PDF reader
13485       * @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.
13486       * @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.
13487       * @param $form (string) Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
13488       * @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.
13489       * @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
13490       Names specifying additional embedded-files-related usage rights for the document.
13491       * @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.
13492       * @public
13493       * @author Nicola Asuni
13494       * @since 2.9.000 (2008-03-26)
13495       */
13496  	public function setUserRights(
13497              $enable=true,
13498              $document='/FullSave',
13499              $annots='/Create/Delete/Modify/Copy/Import/Export',
13500              $form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
13501              $signature='/Modify',
13502              $ef='/Create/Delete/Modify/Import',
13503              $formex='') {
13504          $this->ur['enabled'] = $enable;
13505          $this->ur['document'] = $document;
13506          $this->ur['annots'] = $annots;
13507          $this->ur['form'] = $form;
13508          $this->ur['signature'] = $signature;
13509          $this->ur['ef'] = $ef;
13510          $this->ur['formex'] = $formex;
13511          if (!$this->sign) {
13512              $this->setSignature('', '', '', '', 0, array());
13513          }
13514      }
13515  
13516      /**
13517       * Enable document signature (requires the OpenSSL Library).
13518       * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
13519       * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13520       * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13521       * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
13522       * @param $signing_cert (mixed) signing certificate (string or filename prefixed with 'file://')
13523       * @param $private_key (mixed) private key (string or filename prefixed with 'file://')
13524       * @param $private_key_password (string) password
13525       * @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.
13526       * @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.
13527       * @param $info (array) array of option information: Name, Location, Reason, ContactInfo.
13528       * @param $approval (string) Enable approval signature eg. for PDF incremental update
13529       * @public
13530       * @author Nicola Asuni
13531       * @since 4.6.005 (2009-04-24)
13532       */
13533  	public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array(), $approval='') {
13534          // to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13535          // to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13536          // to convert pfx certificate to pem: openssl
13537          //     OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
13538          $this->sign = true;
13539          ++$this->n;
13540          $this->sig_obj_id = $this->n; // signature widget
13541          ++$this->n; // signature object ($this->sig_obj_id + 1)
13542          $this->signature_data = array();
13543          if (strlen($signing_cert) == 0) {
13544              $this->Error('Please provide a certificate file and password!');
13545          }
13546          if (strlen($private_key) == 0) {
13547              $private_key = $signing_cert;
13548          }
13549          $this->signature_data['signcert'] = $signing_cert;
13550          $this->signature_data['privkey'] = $private_key;
13551          $this->signature_data['password'] = $private_key_password;
13552          $this->signature_data['extracerts'] = $extracerts;
13553          $this->signature_data['cert_type'] = $cert_type;
13554          $this->signature_data['info'] = $info;
13555          $this->signature_data['approval'] = $approval;
13556      }
13557  
13558      /**
13559       * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
13560       * @param $x (float) Abscissa of the upper-left corner.
13561       * @param $y (float) Ordinate of the upper-left corner.
13562       * @param $w (float) Width of the signature area.
13563       * @param $h (float) Height of the signature area.
13564       * @param $page (int) option page number (if < 0 the current page is used).
13565       * @param $name (string) Name of the signature.
13566       * @public
13567       * @author Nicola Asuni
13568       * @since 5.3.011 (2010-06-17)
13569       */
13570  	public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13571          $this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13572      }
13573  
13574      /**
13575       * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
13576       * @param $x (float) Abscissa of the upper-left corner.
13577       * @param $y (float) Ordinate of the upper-left corner.
13578       * @param $w (float) Width of the signature area.
13579       * @param $h (float) Height of the signature area.
13580       * @param $page (int) option page number (if < 0 the current page is used).
13581       * @param $name (string) Name of the signature.
13582       * @public
13583       * @author Nicola Asuni
13584       * @since 5.9.101 (2011-07-06)
13585       */
13586  	public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13587          ++$this->n;
13588          $this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13589      }
13590  
13591      /**
13592       * Get the array that defines the signature appearance (page and rectangle coordinates).
13593       * @param $x (float) Abscissa of the upper-left corner.
13594       * @param $y (float) Ordinate of the upper-left corner.
13595       * @param $w (float) Width of the signature area.
13596       * @param $h (float) Height of the signature area.
13597       * @param $page (int) option page number (if < 0 the current page is used).
13598       * @param $name (string) Name of the signature.
13599       * @return (array) Array defining page and rectangle coordinates of signature appearance.
13600       * @protected
13601       * @author Nicola Asuni
13602       * @since 5.9.101 (2011-07-06)
13603       */
13604  	protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13605          $sigapp = array();
13606          if (($page < 1) OR ($page > $this->numpages)) {
13607              $sigapp['page'] = $this->page;
13608          } else {
13609              $sigapp['page'] = intval($page);
13610          }
13611          if (empty($name)) {
13612              $sigapp['name'] = 'Signature';
13613          } else {
13614              $sigapp['name'] = $name;
13615          }
13616          $a = $x * $this->k;
13617          $b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
13618          $c = $w * $this->k;
13619          $d = $h * $this->k;
13620          $sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
13621          return $sigapp;
13622      }
13623  
13624      /**
13625       * Enable document timestamping (requires the OpenSSL Library).
13626       * The trusted timestamping improve document security that means that no one should be able to change the document once it has been recorded.
13627       * Use with digital signature only!
13628       * @param $tsa_host (string) Time Stamping Authority (TSA) server (prefixed with 'https://')
13629       * @param $tsa_username (string) Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional)
13630       * @param $tsa_password (string) Specifies the password for TSA authorization (optional)
13631       * @param $tsa_cert (string) Specifies the location of TSA certificate for authorization (optional for cURL)
13632       * @public
13633       * @author Richard Stockinger
13634       * @since 6.0.090 (2014-06-16)
13635       */
13636  	public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') {
13637          $this->tsa_data = array();
13638          if (!function_exists('curl_init')) {
13639              $this->Error('Please enable cURL PHP extension!');
13640          }
13641          if (strlen($tsa_host) == 0) {
13642              $this->Error('Please specify the host of Time Stamping Authority (TSA)!');
13643          }
13644          $this->tsa_data['tsa_host'] = $tsa_host;
13645          if (is_file($tsa_username)) {
13646              $this->tsa_data['tsa_auth'] = $tsa_username;
13647          } else {
13648              $this->tsa_data['tsa_username'] = $tsa_username;
13649          }
13650          $this->tsa_data['tsa_password'] = $tsa_password;
13651          $this->tsa_data['tsa_cert'] = $tsa_cert;
13652          $this->tsa_timestamp = true;
13653      }
13654  
13655      /**
13656       * NOT YET IMPLEMENTED
13657       * Request TSA for a timestamp
13658       * @param $signature (string) Digital signature as binary string
13659       * @return (string) Timestamped digital signature
13660       * @protected
13661       * @author Richard Stockinger
13662       * @since 6.0.090 (2014-06-16)
13663       */
13664  	protected function applyTSA($signature) {
13665          if (!$this->tsa_timestamp) {
13666              return $signature;
13667          }
13668          //@TODO: implement this feature
13669          return $signature;
13670      }
13671  
13672      /**
13673       * Create a new page group.
13674       * NOTE: call this function before calling AddPage()
13675       * @param $page (int) starting group page (leave empty for next page).
13676       * @public
13677       * @since 3.0.000 (2008-03-27)
13678       */
13679  	public function startPageGroup($page='') {
13680          if (empty($page)) {
13681              $page = $this->page + 1;
13682          }
13683          $this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
13684      }
13685  
13686      /**
13687       * Set the starting page number.
13688       * @param $num (int) Starting page number.
13689       * @since 5.9.093 (2011-06-16)
13690       * @public
13691       */
13692  	public function setStartingPageNumber($num=1) {
13693          $this->starting_page_number = max(0, intval($num));
13694      }
13695  
13696      /**
13697       * Returns the string alias used right align page numbers.
13698       * If the current font is unicode type, the returned string wil contain an additional open curly brace.
13699       * @return string
13700       * @since 5.9.099 (2011-06-27)
13701       * @public
13702       */
13703  	public function getAliasRightShift() {
13704          // calculate aproximatively the ratio between widths of aliases and replacements.
13705          $ref = '{'.TCPDF_STATIC::$alias_right_shift.'}{'.TCPDF_STATIC::$alias_tot_pages.'}{'.TCPDF_STATIC::$alias_num_page.'}';
13706          $rep = str_repeat(' ', $this->GetNumChars($ref));
13707          $wrep = $this->GetStringWidth($rep);
13708          if ($wrep > 0) {
13709              $wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
13710          } else {
13711              $wdiff = 1;
13712          }
13713          $sdiff = sprintf('%F', $wdiff);
13714          $alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
13715          if ($this->isUnicodeFont()) {
13716              $alias = '{'.$alias;
13717          }
13718          return $alias;
13719      }
13720  
13721      /**
13722       * Returns the string alias used for the total number of pages.
13723       * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13724       * This alias will be replaced by the total number of pages in the document.
13725       * @return string
13726       * @since 4.0.018 (2008-08-08)
13727       * @public
13728       */
13729  	public function getAliasNbPages() {
13730          if ($this->isUnicodeFont()) {
13731              return '{'.TCPDF_STATIC::$alias_tot_pages.'}';
13732          }
13733          return TCPDF_STATIC::$alias_tot_pages;
13734      }
13735  
13736      /**
13737       * Returns the string alias used for the page number.
13738       * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13739       * This alias will be replaced by the page number.
13740       * @return string
13741       * @since 4.5.000 (2009-01-02)
13742       * @public
13743       */
13744  	public function getAliasNumPage() {
13745          if ($this->isUnicodeFont()) {
13746              return '{'.TCPDF_STATIC::$alias_num_page.'}';
13747          }
13748          return TCPDF_STATIC::$alias_num_page;
13749      }
13750  
13751      /**
13752       * Return the alias for the total number of pages in the current page group.
13753       * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13754       * This alias will be replaced by the total number of pages in this group.
13755       * @return alias of the current page group
13756       * @public
13757       * @since 3.0.000 (2008-03-27)
13758       */
13759  	public function getPageGroupAlias() {
13760          if ($this->isUnicodeFont()) {
13761              return '{'.TCPDF_STATIC::$alias_group_tot_pages.'}';
13762          }
13763          return TCPDF_STATIC::$alias_group_tot_pages;
13764      }
13765  
13766      /**
13767       * Return the alias for the page number on the current page group.
13768       * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13769       * This alias will be replaced by the page number (relative to the belonging group).
13770       * @return alias of the current page group
13771       * @public
13772       * @since 4.5.000 (2009-01-02)
13773       */
13774  	public function getPageNumGroupAlias() {
13775          if ($this->isUnicodeFont()) {
13776              return '{'.TCPDF_STATIC::$alias_group_num_page.'}';
13777          }
13778          return TCPDF_STATIC::$alias_group_num_page;
13779      }
13780  
13781      /**
13782       * Return the current page in the group.
13783       * @return current page in the group
13784       * @public
13785       * @since 3.0.000 (2008-03-27)
13786       */
13787  	public function getGroupPageNo() {
13788          return $this->pagegroups[$this->currpagegroup];
13789      }
13790  
13791      /**
13792       * Returns the current group page number formatted as a string.
13793       * @public
13794       * @since 4.3.003 (2008-11-18)
13795       * @see PaneNo(), formatPageNumber()
13796       */
13797  	public function getGroupPageNoFormatted() {
13798          return TCPDF_STATIC::formatPageNumber($this->getGroupPageNo());
13799      }
13800  
13801      /**
13802       * Returns the current page number formatted as a string.
13803       * @public
13804       * @since 4.2.005 (2008-11-06)
13805       * @see PaneNo(), formatPageNumber()
13806       */
13807  	public function PageNoFormatted() {
13808          return TCPDF_STATIC::formatPageNumber($this->PageNo());
13809      }
13810  
13811      /**
13812       * Put pdf layers.
13813       * @protected
13814       * @since 3.0.000 (2008-03-27)
13815       */
13816  	protected function _putocg() {
13817          if (empty($this->pdflayers)) {
13818              return;
13819          }
13820          foreach ($this->pdflayers as $key => $layer) {
13821               $this->pdflayers[$key]['objid'] = $this->_newobj();
13822               $out = '<< /Type /OCG';
13823               $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
13824               $out .= ' /Usage <<';
13825               if (isset($layer['print']) AND ($layer['print'] !== NULL)) {
13826                  $out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
13827               }
13828               $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
13829               $out .= ' >> >>';
13830               $out .= "\n".'endobj';
13831               $this->_out($out);
13832          }
13833      }
13834  
13835      /**
13836       * Start a new pdf layer.
13837       * @param $name (string) Layer name (only a-z letters and numbers). Leave empty for automatic name.
13838       * @param $print (boolean|null) Set to TRUE to print this layer, FALSE to not print and NULL to not set this option
13839       * @param $view (boolean) Set to true to view this layer.
13840       * @param $lock (boolean) If true lock the layer
13841       * @public
13842       * @since 5.9.102 (2011-07-13)
13843       */
13844  	public function startLayer($name='', $print=true, $view=true, $lock=true) {
13845          if ($this->state != 2) {
13846              return;
13847          }
13848          $layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
13849          if (empty($name)) {
13850              $name = $layer;
13851          } else {
13852              $name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
13853          }
13854          $this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view, 'lock' => $lock);
13855          $this->openMarkedContent = true;
13856          $this->_out('/OC /'.$layer.' BDC');
13857      }
13858  
13859      /**
13860       * End the current PDF layer.
13861       * @public
13862       * @since 5.9.102 (2011-07-13)
13863       */
13864  	public function endLayer() {
13865          if ($this->state != 2) {
13866              return;
13867          }
13868          if ($this->openMarkedContent) {
13869              // close existing open marked-content layer
13870              $this->_out('EMC');
13871              $this->openMarkedContent = false;
13872          }
13873      }
13874  
13875      /**
13876       * Set the visibility of the successive elements.
13877       * This can be useful, for instance, to put a background
13878       * image or color that will show on screen but won't print.
13879       * @param $v (string) visibility mode. Legal values are: all, print, screen or view.
13880       * @public
13881       * @since 3.0.000 (2008-03-27)
13882       */
13883  	public function setVisibility($v) {
13884          if ($this->state != 2) {
13885              return;
13886          }
13887          $this->endLayer();
13888          switch($v) {
13889              case 'print': {
13890                  $this->startLayer('Print', true, false);
13891                  break;
13892              }
13893              case 'view':
13894              case 'screen': {
13895                  $this->startLayer('View', false, true);
13896                  break;
13897              }
13898              case 'all': {
13899                  $this->_out('');
13900                  break;
13901              }
13902              default: {
13903                  $this->Error('Incorrect visibility: '.$v);
13904                  break;
13905              }
13906          }
13907      }
13908  
13909      /**
13910       * Add transparency parameters to the current extgstate
13911       * @param $parms (array) parameters
13912       * @return the number of extgstates
13913       * @protected
13914       * @since 3.0.000 (2008-03-27)
13915       */
13916  	protected function addExtGState($parms) {
13917          if ($this->pdfa_mode) {
13918              // transparencies are not allowed in PDF/A mode
13919              return;
13920          }
13921          // check if this ExtGState already exist
13922          foreach ($this->extgstates as $i => $ext) {
13923              if ($ext['parms'] == $parms) {
13924                  if ($this->inxobj) {
13925                      // we are inside an XObject template
13926                      $this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
13927                  }
13928                  // return reference to existing ExtGState
13929                  return $i;
13930              }
13931          }
13932          $n = (count($this->extgstates) + 1);
13933          $this->extgstates[$n] = array('parms' => $parms);
13934          if ($this->inxobj) {
13935              // we are inside an XObject template
13936              $this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
13937          }
13938          return $n;
13939      }
13940  
13941      /**
13942       * Add an extgstate
13943       * @param $gs (array) extgstate
13944       * @protected
13945       * @since 3.0.000 (2008-03-27)
13946       */
13947  	protected function setExtGState($gs) {
13948          if ($this->pdfa_mode OR ($this->state != 2)) {
13949              // transparency is not allowed in PDF/A mode
13950              return;
13951          }
13952          $this->_out(sprintf('/GS%d gs', $gs));
13953      }
13954  
13955      /**
13956       * Put extgstates for object transparency
13957       * @protected
13958       * @since 3.0.000 (2008-03-27)
13959       */
13960  	protected function _putextgstates() {
13961          foreach ($this->extgstates as $i => $ext) {
13962              $this->extgstates[$i]['n'] = $this->_newobj();
13963              $out = '<< /Type /ExtGState';
13964              foreach ($ext['parms'] as $k => $v) {
13965                  if (is_float($v)) {
13966                      $v = sprintf('%F', $v);
13967                  } elseif ($v === true) {
13968                      $v = 'true';
13969                  } elseif ($v === false) {
13970                      $v = 'false';
13971                  }
13972                  $out .= ' /'.$k.' '.$v;
13973              }
13974              $out .= ' >>';
13975              $out .= "\n".'endobj';
13976              $this->_out($out);
13977          }
13978      }
13979  
13980      /**
13981       * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
13982       * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13983       * @param $stroking (boolean) If true apply overprint for stroking operations.
13984       * @param $nonstroking (boolean) If true apply overprint for painting operations other than stroking.
13985       * @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).
13986       * @public
13987       * @since 5.9.152 (2012-03-23)
13988       */
13989  	public function setOverprint($stroking=true, $nonstroking='', $mode=0) {
13990          if ($this->state != 2) {
13991              return;
13992          }
13993          $stroking = $stroking ? true : false;
13994          if (TCPDF_STATIC::empty_string($nonstroking)) {
13995              // default value if not set
13996              $nonstroking = $stroking;
13997          } else {
13998              $nonstroking = $nonstroking ? true : false;
13999          }
14000          if (($mode != 0) AND ($mode != 1)) {
14001              $mode = 0;
14002          }
14003          $this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
14004          $gs = $this->addExtGState($this->overprint);
14005          $this->setExtGState($gs);
14006      }
14007  
14008      /**
14009       * Get the overprint mode array (OP, op, OPM).
14010       * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14011       * @return array.
14012       * @public
14013       * @since 5.9.152 (2012-03-23)
14014       */
14015  	public function getOverprint() {
14016          return $this->overprint;
14017      }
14018  
14019      /**
14020       * Set alpha for stroking (CA) and non-stroking (ca) operations.
14021       * @param $stroking (float) Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
14022       * @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
14023       * @param $nonstroking (float) Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
14024       * @param $ais (boolean)
14025       * @public
14026       * @since 3.0.000 (2008-03-27)
14027       */
14028  	public function setAlpha($stroking=1, $bm='Normal', $nonstroking='', $ais=false) {
14029          if ($this->pdfa_mode) {
14030              // transparency is not allowed in PDF/A mode
14031              return;
14032          }
14033          $stroking = floatval($stroking);
14034          if (TCPDF_STATIC::empty_string($nonstroking)) {
14035              // default value if not set
14036              $nonstroking = $stroking;
14037          } else {
14038              $nonstroking = floatval($nonstroking);
14039          }
14040          if ($bm[0] == '/') {
14041              // remove trailing slash
14042              $bm = substr($bm, 1);
14043          }
14044          if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
14045              $bm = 'Normal';
14046          }
14047          $ais = $ais ? true : false;
14048          $this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
14049          $gs = $this->addExtGState($this->alpha);
14050          $this->setExtGState($gs);
14051      }
14052  
14053      /**
14054       * Get the alpha mode array (CA, ca, BM, AIS).
14055       * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14056       * @return array.
14057       * @public
14058       * @since 5.9.152 (2012-03-23)
14059       */
14060  	public function getAlpha() {
14061          return $this->alpha;
14062      }
14063  
14064      /**
14065       * Set the default JPEG compression quality (1-100)
14066       * @param $quality (int) JPEG quality, integer between 1 and 100
14067       * @public
14068       * @since 3.0.000 (2008-03-27)
14069       */
14070  	public function setJPEGQuality($quality) {
14071          if (($quality < 1) OR ($quality > 100)) {
14072              $quality = 75;
14073          }
14074          $this->jpeg_quality = intval($quality);
14075      }
14076  
14077      /**
14078       * Set the default number of columns in a row for HTML tables.
14079       * @param $cols (int) number of columns
14080       * @public
14081       * @since 3.0.014 (2008-06-04)
14082       */
14083  	public function setDefaultTableColumns($cols=4) {
14084          $this->default_table_columns = intval($cols);
14085      }
14086  
14087      /**
14088       * Set the height of the cell (line height) respect the font height.
14089       * @param $h (int) cell proportion respect font height (typical value = 1.25).
14090       * @public
14091       * @since 3.0.014 (2008-06-04)
14092       */
14093  	public function setCellHeightRatio($h) {
14094          $this->cell_height_ratio = $h;
14095      }
14096  
14097      /**
14098       * return the height of cell repect font height.
14099       * @public
14100       * @since 4.0.012 (2008-07-24)
14101       */
14102  	public function getCellHeightRatio() {
14103          return $this->cell_height_ratio;
14104      }
14105  
14106      /**
14107       * Set the PDF version (check PDF reference for valid values).
14108       * @param $version (string) PDF document version.
14109       * @public
14110       * @since 3.1.000 (2008-06-09)
14111       */
14112  	public function setPDFVersion($version='1.7') {
14113          if ($this->pdfa_mode) {
14114              // PDF/A mode
14115              $this->PDFVersion = '1.4';
14116          } else {
14117              $this->PDFVersion = $version;
14118          }
14119      }
14120  
14121      /**
14122       * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
14123       * (see Section 8.1 of PDF reference, "Viewer Preferences").
14124       * <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>
14125       * @param $preferences (array) array of options.
14126       * @author Nicola Asuni
14127       * @public
14128       * @since 3.1.000 (2008-06-09)
14129       */
14130  	public function setViewerPreferences($preferences) {
14131          $this->viewer_preferences = $preferences;
14132      }
14133  
14134      /**
14135       * Paints color transition registration bars
14136       * @param $x (float) abscissa of the top left corner of the rectangle.
14137       * @param $y (float) ordinate of the top left corner of the rectangle.
14138       * @param $w (float) width of the rectangle.
14139       * @param $h (float) height of the rectangle.
14140       * @param $transition (boolean) if true prints tcolor transitions to white.
14141       * @param $vertical (boolean) if true prints bar vertically.
14142       * @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.
14143       * @author Nicola Asuni
14144       * @since 4.9.000 (2010-03-26)
14145       * @public
14146       */
14147  	public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
14148          if (strpos($colors, 'ALLSPOT') !== false) {
14149              // expand spot colors
14150              $spot_colors = '';
14151              foreach ($this->spot_colors as $spot_color_name => $v) {
14152                  $spot_colors .= ','.$spot_color_name;
14153              }
14154              if (!empty($spot_colors)) {
14155                  $spot_colors = substr($spot_colors, 1);
14156                  $colors = str_replace('ALLSPOT', $spot_colors, $colors);
14157              } else {
14158                  $colors = str_replace('ALLSPOT', 'NONE', $colors);
14159              }
14160          }
14161          $bars = explode(',', $colors);
14162          $numbars = count($bars); // number of bars to print
14163          if ($numbars <= 0) {
14164              return;
14165          }
14166          // set bar measures
14167          if ($vertical) {
14168              $coords = array(0, 0, 0, 1);
14169              $wb = $w / $numbars; // bar width
14170              $hb = $h; // bar height
14171              $xd = $wb; // delta x
14172              $yd = 0; // delta y
14173          } else {
14174              $coords = array(1, 0, 0, 0);
14175              $wb = $w; // bar width
14176              $hb = $h / $numbars; // bar height
14177              $xd = 0; // delta x
14178              $yd = $hb; // delta y
14179          }
14180          $xb = $x;
14181          $yb = $y;
14182          foreach ($bars as $col) {
14183              switch ($col) {
14184                  // set transition colors
14185                  case 'A': { // BLACK (GRAYSCALE)
14186                      $col_a = array(255);
14187                      $col_b = array(0);
14188                      break;
14189                  }
14190                  case 'W': { // WHITE (GRAYSCALE)
14191                      $col_a = array(0);
14192                      $col_b = array(255);
14193                      break;
14194                  }
14195                  case 'R': { // RED (RGB)
14196                      $col_a = array(255,255,255);
14197                      $col_b = array(255,0,0);
14198                      break;
14199                  }
14200                  case 'G': { // GREEN (RGB)
14201                      $col_a = array(255,255,255);
14202                      $col_b = array(0,255,0);
14203                      break;
14204                  }
14205                  case 'B': { // BLUE (RGB)
14206                      $col_a = array(255,255,255);
14207                      $col_b = array(0,0,255);
14208                      break;
14209                  }
14210                  case 'C': { // CYAN (CMYK)
14211                      $col_a = array(0,0,0,0);
14212                      $col_b = array(100,0,0,0);
14213                      break;
14214                  }
14215                  case 'M': { // MAGENTA (CMYK)
14216                      $col_a = array(0,0,0,0);
14217                      $col_b = array(0,100,0,0);
14218                      break;
14219                  }
14220                  case 'Y': { // YELLOW (CMYK)
14221                      $col_a = array(0,0,0,0);
14222                      $col_b = array(0,0,100,0);
14223                      break;
14224                  }
14225                  case 'K': { // KEY - BLACK (CMYK)
14226                      $col_a = array(0,0,0,0);
14227                      $col_b = array(0,0,0,100);
14228                      break;
14229                  }
14230                  case 'RGB': { // BLACK REGISTRATION (RGB)
14231                      $col_a = array(255,255,255);
14232                      $col_b = array(0,0,0);
14233                      break;
14234                  }
14235                  case 'CMYK': { // BLACK REGISTRATION (CMYK)
14236                      $col_a = array(0,0,0,0);
14237                      $col_b = array(100,100,100,100);
14238                      break;
14239                  }
14240                  case 'ALL': { // SPOT COLOR REGISTRATION
14241                      $col_a = array(0,0,0,0,'None');
14242                      $col_b = array(100,100,100,100,'All');
14243                      break;
14244                  }
14245                  case 'NONE': { // SKIP THIS COLOR
14246                      $col_a = array(0,0,0,0,'None');
14247                      $col_b = array(0,0,0,0,'None');
14248                      break;
14249                  }
14250                  default: { // SPECIFIC SPOT COLOR NAME
14251                      $col_a = array(0,0,0,0,'None');
14252                      $col_b = TCPDF_COLORS::getSpotColor($col, $this->spot_colors);
14253                      if ($col_b === false) {
14254                          // in case of error defaults to the registration color
14255                          $col_b = array(100,100,100,100,'All');
14256                      }
14257                      break;
14258                  }
14259              }
14260              if ($col != 'NONE') {
14261                  if ($transition) {
14262                      // color gradient
14263                      $this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
14264                  } else {
14265                      $this->SetFillColorArray($col_b);
14266                      // colored rectangle
14267                      $this->Rect($xb, $yb, $wb, $hb, 'F', array());
14268                  }
14269                  $xb += $xd;
14270                  $yb += $yd;
14271              }
14272          }
14273      }
14274  
14275      /**
14276       * Paints crop marks.
14277       * @param $x (float) abscissa of the crop mark center.
14278       * @param $y (float) ordinate of the crop mark center.
14279       * @param $w (float) width of the crop mark.
14280       * @param $h (float) height of the crop mark.
14281       * @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.
14282       * @param $color (array) crop mark color (default spot registration color).
14283       * @author Nicola Asuni
14284       * @since 4.9.000 (2010-03-26)
14285       * @public
14286       */
14287  	public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
14288          $this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
14289          $type = strtoupper($type);
14290          $type = preg_replace('/[^A-Z\-\,]*/', '', $type);
14291          // split type in single components
14292          $type = str_replace('-', ',', $type);
14293          $type = str_replace('TL', 'T,L', $type);
14294          $type = str_replace('TR', 'T,R', $type);
14295          $type = str_replace('BL', 'F,L', $type);
14296          $type = str_replace('BR', 'F,R', $type);
14297          $type = str_replace('A', 'T,L', $type);
14298          $type = str_replace('B', 'T,R', $type);
14299          $type = str_replace('T,RO', 'BO', $type);
14300          $type = str_replace('C', 'F,L', $type);
14301          $type = str_replace('D', 'F,R', $type);
14302          $crops = explode(',', strtoupper($type));
14303          // remove duplicates
14304          $crops = array_unique($crops);
14305          $dw = ($w / 4); // horizontal space to leave before the intersection point
14306          $dh = ($h / 4); // vertical space to leave before the intersection point
14307          foreach ($crops as $crop) {
14308              switch ($crop) {
14309                  case 'T':
14310                  case 'TOP': {
14311                      $x1 = $x;
14312                      $y1 = ($y - $h);
14313                      $x2 = $x;
14314                      $y2 = ($y - $dh);
14315                      break;
14316                  }
14317                  case 'F':
14318                  case 'BOTTOM': {
14319                      $x1 = $x;
14320                      $y1 = ($y + $dh);
14321                      $x2 = $x;
14322                      $y2 = ($y + $h);
14323                      break;
14324                  }
14325                  case 'L':
14326                  case 'LEFT': {
14327                      $x1 = ($x - $w);
14328                      $y1 = $y;
14329                      $x2 = ($x - $dw);
14330                      $y2 = $y;
14331                      break;
14332                  }
14333                  case 'R':
14334                  case 'RIGHT': {
14335                      $x1 = ($x + $dw);
14336                      $y1 = $y;
14337                      $x2 = ($x + $w);
14338                      $y2 = $y;
14339                      break;
14340                  }
14341              }
14342              $this->Line($x1, $y1, $x2, $y2);
14343          }
14344      }
14345  
14346      /**
14347       * Paints a registration mark
14348       * @param $x (float) abscissa of the registration mark center.
14349       * @param $y (float) ordinate of the registration mark center.
14350       * @param $r (float) radius of the crop mark.
14351       * @param $double (boolean) if true print two concentric crop marks.
14352       * @param $cola (array) crop mark color (default spot registration color 'All').
14353       * @param $colb (array) second crop mark color (default spot registration color 'None').
14354       * @author Nicola Asuni
14355       * @since 4.9.000 (2010-03-26)
14356       * @public
14357       */
14358  	public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
14359          $line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
14360          $this->SetFillColorArray($cola);
14361          $this->PieSector($x, $y, $r, 90, 180, 'F');
14362          $this->PieSector($x, $y, $r, 270, 360, 'F');
14363          $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14364          if ($double) {
14365              $ri = $r * 0.5;
14366              $this->SetFillColorArray($colb);
14367              $this->PieSector($x, $y, $ri, 90, 180, 'F');
14368              $this->PieSector($x, $y, $ri, 270, 360, 'F');
14369              $this->SetFillColorArray($cola);
14370              $this->PieSector($x, $y, $ri, 0, 90, 'F');
14371              $this->PieSector($x, $y, $ri, 180, 270, 'F');
14372              $this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
14373          }
14374      }
14375  
14376      /**
14377       * Paints a CMYK registration mark
14378       * @param $x (float) abscissa of the registration mark center.
14379       * @param $y (float) ordinate of the registration mark center.
14380       * @param $r (float) radius of the crop mark.
14381       * @author Nicola Asuni
14382       * @since 6.0.038 (2013-09-30)
14383       * @public
14384       */
14385  	public function registrationMarkCMYK($x, $y, $r) {
14386          // line width
14387          $lw = max((0.5 / $this->k),($r / 8));
14388          // internal radius
14389          $ri = ($r * 0.6);
14390          // external radius
14391          $re = ($r * 1.3);
14392          // Cyan
14393          $this->SetFillColorArray(array(100,0,0,0));
14394          $this->PieSector($x, $y, $ri, 270, 360, 'F');
14395          // Magenta
14396          $this->SetFillColorArray(array(0,100,0,0));
14397          $this->PieSector($x, $y, $ri, 0, 90, 'F');
14398          // Yellow
14399          $this->SetFillColorArray(array(0,0,100,0));
14400          $this->PieSector($x, $y, $ri, 90, 180, 'F');
14401          // Key - black
14402          $this->SetFillColorArray(array(0,0,0,100));
14403          $this->PieSector($x, $y, $ri, 180, 270, 'F');
14404          // registration color
14405          $line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
14406          $this->SetFillColorArray(array(100,100,100,100,'All'));
14407          // external circle
14408          $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14409          // cross lines
14410          $this->Line($x, ($y - $re), $x, ($y - $ri));
14411          $this->Line($x, ($y + $ri), $x, ($y + $re));
14412          $this->Line(($x - $re), $y, ($x - $ri), $y);
14413          $this->Line(($x + $ri), $y, ($x + $re), $y);
14414      }
14415  
14416      /**
14417       * Paints a linear colour gradient.
14418       * @param $x (float) abscissa of the top left corner of the rectangle.
14419       * @param $y (float) ordinate of the top left corner of the rectangle.
14420       * @param $w (float) width of the rectangle.
14421       * @param $h (float) height of the rectangle.
14422       * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14423       * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14424       * @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).
14425       * @author Andreas W\FCrmser, Nicola Asuni
14426       * @since 3.1.000 (2008-06-09)
14427       * @public
14428       */
14429  	public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
14430          $this->Clip($x, $y, $w, $h);
14431          $this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14432      }
14433  
14434      /**
14435       * Paints a radial colour gradient.
14436       * @param $x (float) abscissa of the top left corner of the rectangle.
14437       * @param $y (float) ordinate of the top left corner of the rectangle.
14438       * @param $w (float) width of the rectangle.
14439       * @param $h (float) height of the rectangle.
14440       * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14441       * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14442       * @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.
14443       * @author Andreas W\FCrmser, Nicola Asuni
14444       * @since 3.1.000 (2008-06-09)
14445       * @public
14446       */
14447  	public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
14448          $this->Clip($x, $y, $w, $h);
14449          $this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14450      }
14451  
14452      /**
14453       * Paints a coons patch mesh.
14454       * @param $x (float) abscissa of the top left corner of the rectangle.
14455       * @param $y (float) ordinate of the top left corner of the rectangle.
14456       * @param $w (float) width of the rectangle.
14457       * @param $h (float) height of the rectangle.
14458       * @param $col1 (array) first color (lower left corner) (RGB components).
14459       * @param $col2 (array) second color (lower right corner) (RGB components).
14460       * @param $col3 (array) third color (upper right corner) (RGB components).
14461       * @param $col4 (array) fourth color (upper left corner) (RGB components).
14462       * @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>
14463       * @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
14464       * @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
14465       * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14466       * @author Andreas W\FCrmser, Nicola Asuni
14467       * @since 3.1.000 (2008-06-09)
14468       * @public
14469       */
14470  	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) {
14471          if ($this->pdfa_mode OR ($this->state != 2)) {
14472              return;
14473          }
14474          $this->Clip($x, $y, $w, $h);
14475          $n = count($this->gradients) + 1;
14476          $this->gradients[$n] = array();
14477          $this->gradients[$n]['type'] = 6; //coons patch mesh
14478          $this->gradients[$n]['coords'] = array();
14479          $this->gradients[$n]['antialias'] = $antialias;
14480          $this->gradients[$n]['colors'] = array();
14481          $this->gradients[$n]['transparency'] = false;
14482          //check the coords array if it is the simple array or the multi patch array
14483          if (!isset($coords[0]['f'])) {
14484              //simple array -> convert to multi patch array
14485              if (!isset($col1[1])) {
14486                  $col1[1] = $col1[2] = $col1[0];
14487              }
14488              if (!isset($col2[1])) {
14489                  $col2[1] = $col2[2] = $col2[0];
14490              }
14491              if (!isset($col3[1])) {
14492                  $col3[1] = $col3[2] = $col3[0];
14493              }
14494              if (!isset($col4[1])) {
14495                  $col4[1] = $col4[2] = $col4[0];
14496              }
14497              $patch_array[0]['f'] = 0;
14498              $patch_array[0]['points'] = $coords;
14499              $patch_array[0]['colors'][0]['r'] = $col1[0];
14500              $patch_array[0]['colors'][0]['g'] = $col1[1];
14501              $patch_array[0]['colors'][0]['b'] = $col1[2];
14502              $patch_array[0]['colors'][1]['r'] = $col2[0];
14503              $patch_array[0]['colors'][1]['g'] = $col2[1];
14504              $patch_array[0]['colors'][1]['b'] = $col2[2];
14505              $patch_array[0]['colors'][2]['r'] = $col3[0];
14506              $patch_array[0]['colors'][2]['g'] = $col3[1];
14507              $patch_array[0]['colors'][2]['b'] = $col3[2];
14508              $patch_array[0]['colors'][3]['r'] = $col4[0];
14509              $patch_array[0]['colors'][3]['g'] = $col4[1];
14510              $patch_array[0]['colors'][3]['b'] = $col4[2];
14511          } else {
14512              //multi patch array
14513              $patch_array = $coords;
14514          }
14515          $bpcd = 65535; //16 bits per coordinate
14516          //build the data stream
14517          $this->gradients[$n]['stream'] = '';
14518          $count_patch = count($patch_array);
14519          for ($i=0; $i < $count_patch; ++$i) {
14520              $this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
14521              $count_points = count($patch_array[$i]['points']);
14522              for ($j=0; $j < $count_points; ++$j) {
14523                  //each point as 16 bit
14524                  $patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
14525                  if ($patch_array[$i]['points'][$j] < 0) {
14526                      $patch_array[$i]['points'][$j] = 0;
14527                  }
14528                  if ($patch_array[$i]['points'][$j] > $bpcd) {
14529                      $patch_array[$i]['points'][$j] = $bpcd;
14530                  }
14531                  $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
14532                  $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
14533              }
14534              $count_cols = count($patch_array[$i]['colors']);
14535              for ($j=0; $j < $count_cols; ++$j) {
14536                  //each color component as 8 bit
14537                  $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
14538                  $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
14539                  $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
14540              }
14541          }
14542          //paint the gradient
14543          $this->_out('/Sh'.$n.' sh');
14544          //restore previous Graphic State
14545          $this->_outRestoreGraphicsState();
14546          if ($this->inxobj) {
14547              // we are inside an XObject template
14548              $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14549          }
14550      }
14551  
14552      /**
14553       * Set a rectangular clipping area.
14554       * @param $x (float) abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
14555       * @param $y (float) ordinate of the top left corner of the rectangle.
14556       * @param $w (float) width of the rectangle.
14557       * @param $h (float) height of the rectangle.
14558       * @author Andreas W\FCrmser, Nicola Asuni
14559       * @since 3.1.000 (2008-06-09)
14560       * @protected
14561       */
14562  	protected function Clip($x, $y, $w, $h) {
14563          if ($this->state != 2) {
14564               return;
14565          }
14566          if ($this->rtl) {
14567              $x = $this->w - $x - $w;
14568          }
14569          //save current Graphic State
14570          $s = 'q';
14571          //set clipping area
14572          $s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
14573          //set up transformation matrix for gradient
14574          $s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
14575          $this->_out($s);
14576      }
14577  
14578      /**
14579       * Output gradient.
14580       * @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)
14581       * @param $coords (array) array of coordinates.
14582       * @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).
14583       * @param $background (array) An array of colour components appropriate to the colour space, specifying a single background colour value.
14584       * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14585       * @author Nicola Asuni
14586       * @since 3.1.000 (2008-06-09)
14587       * @public
14588       */
14589  	public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
14590          if ($this->pdfa_mode OR ($this->state != 2)) {
14591              return;
14592          }
14593          $n = count($this->gradients) + 1;
14594          $this->gradients[$n] = array();
14595          $this->gradients[$n]['type'] = $type;
14596          $this->gradients[$n]['coords'] = $coords;
14597          $this->gradients[$n]['antialias'] = $antialias;
14598          $this->gradients[$n]['colors'] = array();
14599          $this->gradients[$n]['transparency'] = false;
14600          // color space
14601          $numcolspace = count($stops[0]['color']);
14602          $bcolor = array_values($background);
14603          switch($numcolspace) {
14604              case 5:   // SPOT
14605              case 4: { // CMYK
14606                  $this->gradients[$n]['colspace'] = 'DeviceCMYK';
14607                  if (!empty($background)) {
14608                      $this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
14609                  }
14610                  break;
14611              }
14612              case 3: { // RGB
14613                  $this->gradients[$n]['colspace'] = 'DeviceRGB';
14614                  if (!empty($background)) {
14615                      $this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
14616                  }
14617                  break;
14618              }
14619              case 1: { // GRAY SCALE
14620                  $this->gradients[$n]['colspace'] = 'DeviceGray';
14621                  if (!empty($background)) {
14622                      $this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
14623                  }
14624                  break;
14625              }
14626          }
14627          $num_stops = count($stops);
14628          $last_stop_id = $num_stops - 1;
14629          foreach ($stops as $key => $stop) {
14630              $this->gradients[$n]['colors'][$key] = array();
14631              // offset represents a location along the gradient vector
14632              if (isset($stop['offset'])) {
14633                  $this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
14634              } else {
14635                  if ($key == 0) {
14636                      $this->gradients[$n]['colors'][$key]['offset'] = 0;
14637                  } elseif ($key == $last_stop_id) {
14638                      $this->gradients[$n]['colors'][$key]['offset'] = 1;
14639                  } else {
14640                      $offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
14641                      $this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
14642                  }
14643              }
14644              if (isset($stop['opacity'])) {
14645                  $this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
14646                  if ((!$this->pdfa_mode) AND ($stop['opacity'] < 1)) {
14647                      $this->gradients[$n]['transparency'] = true;
14648                  }
14649              } else {
14650                  $this->gradients[$n]['colors'][$key]['opacity'] = 1;
14651              }
14652              // exponent for the exponential interpolation function
14653              if (isset($stop['exponent'])) {
14654                  $this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
14655              } else {
14656                  $this->gradients[$n]['colors'][$key]['exponent'] = 1;
14657              }
14658              // set colors
14659              $color = array_values($stop['color']);
14660              switch($numcolspace) {
14661                  case 5:   // SPOT
14662                  case 4: { // CMYK
14663                      $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
14664                      break;
14665                  }
14666                  case 3: { // RGB
14667                      $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
14668                      break;
14669                  }
14670                  case 1: { // GRAY SCALE
14671                      $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
14672                      break;
14673                  }
14674              }
14675          }
14676          if ($this->gradients[$n]['transparency']) {
14677              // paint luminosity gradient
14678              $this->_out('/TGS'.$n.' gs');
14679          }
14680          //paint the gradient
14681          $this->_out('/Sh'.$n.' sh');
14682          //restore previous Graphic State
14683          $this->_outRestoreGraphicsState();
14684          if ($this->inxobj) {
14685              // we are inside an XObject template
14686              $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14687          }
14688      }
14689  
14690      /**
14691       * Output gradient shaders.
14692       * @author Nicola Asuni
14693       * @since 3.1.000 (2008-06-09)
14694       * @protected
14695       */
14696  	function _putshaders() {
14697          if ($this->pdfa_mode) {
14698              return;
14699          }
14700          $idt = count($this->gradients); //index for transparency gradients
14701          foreach ($this->gradients as $id => $grad) {
14702              if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
14703                  $fc = $this->_newobj();
14704                  $out = '<<';
14705                  $out .= ' /FunctionType 3';
14706                  $out .= ' /Domain [0 1]';
14707                  $functions = '';
14708                  $bounds = '';
14709                  $encode = '';
14710                  $i = 1;
14711                  $num_cols = count($grad['colors']);
14712                  $lastcols = $num_cols - 1;
14713                  for ($i = 1; $i < $num_cols; ++$i) {
14714                      $functions .= ($fc + $i).' 0 R ';
14715                      if ($i < $lastcols) {
14716                          $bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
14717                      }
14718                      $encode .= '0 1 ';
14719                  }
14720                  $out .= ' /Functions ['.trim($functions).']';
14721                  $out .= ' /Bounds ['.trim($bounds).']';
14722                  $out .= ' /Encode ['.trim($encode).']';
14723                  $out .= ' >>';
14724                  $out .= "\n".'endobj';
14725                  $this->_out($out);
14726                  for ($i = 1; $i < $num_cols; ++$i) {
14727                      $this->_newobj();
14728                      $out = '<<';
14729                      $out .= ' /FunctionType 2';
14730                      $out .= ' /Domain [0 1]';
14731                      $out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
14732                      $out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
14733                      $out .= ' /N '.$grad['colors'][$i]['exponent'];
14734                      $out .= ' >>';
14735                      $out .= "\n".'endobj';
14736                      $this->_out($out);
14737                  }
14738                  // set transparency fuctions
14739                  if ($grad['transparency']) {
14740                      $ft = $this->_newobj();
14741                      $out = '<<';
14742                      $out .= ' /FunctionType 3';
14743                      $out .= ' /Domain [0 1]';
14744                      $functions = '';
14745                      $i = 1;
14746                      $num_cols = count($grad['colors']);
14747                      for ($i = 1; $i < $num_cols; ++$i) {
14748                          $functions .= ($ft + $i).' 0 R ';
14749                      }
14750                      $out .= ' /Functions ['.trim($functions).']';
14751                      $out .= ' /Bounds ['.trim($bounds).']';
14752                      $out .= ' /Encode ['.trim($encode).']';
14753                      $out .= ' >>';
14754                      $out .= "\n".'endobj';
14755                      $this->_out($out);
14756                      for ($i = 1; $i < $num_cols; ++$i) {
14757                          $this->_newobj();
14758                          $out = '<<';
14759                          $out .= ' /FunctionType 2';
14760                          $out .= ' /Domain [0 1]';
14761                          $out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
14762                          $out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
14763                          $out .= ' /N '.$grad['colors'][$i]['exponent'];
14764                          $out .= ' >>';
14765                          $out .= "\n".'endobj';
14766                          $this->_out($out);
14767                      }
14768                  }
14769              }
14770              // set shading object
14771              $this->_newobj();
14772              $out = '<< /ShadingType '.$grad['type'];
14773              if (isset($grad['colspace'])) {
14774                  $out .= ' /ColorSpace /'.$grad['colspace'];
14775              } else {
14776                  $out .= ' /ColorSpace /DeviceRGB';
14777              }
14778              if (isset($grad['background']) AND !empty($grad['background'])) {
14779                  $out .= ' /Background ['.$grad['background'].']';
14780              }
14781              if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
14782                  $out .= ' /AntiAlias true';
14783              }
14784              if ($grad['type'] == 2) {
14785                  $out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
14786                  $out .= ' /Domain [0 1]';
14787                  $out .= ' /Function '.$fc.' 0 R';
14788                  $out .= ' /Extend [true true]';
14789                  $out .= ' >>';
14790              } elseif ($grad['type'] == 3) {
14791                  //x0, y0, r0, x1, y1, r1
14792                  //at this this time radius of inner circle is 0
14793                  $out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
14794                  $out .= ' /Domain [0 1]';
14795                  $out .= ' /Function '.$fc.' 0 R';
14796                  $out .= ' /Extend [true true]';
14797                  $out .= ' >>';
14798              } elseif ($grad['type'] == 6) {
14799                  $out .= ' /BitsPerCoordinate 16';
14800                  $out .= ' /BitsPerComponent 8';
14801                  $out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
14802                  $out .= ' /BitsPerFlag 8';
14803                  $stream = $this->_getrawstream($grad['stream']);
14804                  $out .= ' /Length '.strlen($stream);
14805                  $out .= ' >>';
14806                  $out .= ' stream'."\n".$stream."\n".'endstream';
14807              }
14808              $out .= "\n".'endobj';
14809              $this->_out($out);
14810              if ($grad['transparency']) {
14811                  $shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
14812                  $shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
14813              }
14814              $this->gradients[$id]['id'] = $this->n;
14815              // set pattern object
14816              $this->_newobj();
14817              $out = '<< /Type /Pattern /PatternType 2';
14818              $out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
14819              $out .= ' >>';
14820              $out .= "\n".'endobj';
14821              $this->_out($out);
14822              $this->gradients[$id]['pattern'] = $this->n;
14823              // set shading and pattern for transparency mask
14824              if ($grad['transparency']) {
14825                  // luminosity pattern
14826                  $idgs = $id + $idt;
14827                  $this->_newobj();
14828                  $this->_out($shading_transparency);
14829                  $this->gradients[$idgs]['id'] = $this->n;
14830                  $this->_newobj();
14831                  $out = '<< /Type /Pattern /PatternType 2';
14832                  $out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
14833                  $out .= ' >>';
14834                  $out .= "\n".'endobj';
14835                  $this->_out($out);
14836                  $this->gradients[$idgs]['pattern'] = $this->n;
14837                  // luminosity XObject
14838                  $oid = $this->_newobj();
14839                  $this->xobjects['LX'.$oid] = array('n' => $oid);
14840                  $filter = '';
14841                  $stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
14842                  if ($this->compress) {
14843                      $filter = ' /Filter /FlateDecode';
14844                      $stream = gzcompress($stream);
14845                  }
14846                  $stream = $this->_getrawstream($stream);
14847                  $out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
14848                  $out .= ' /Length '.strlen($stream);
14849                  $rect = sprintf('%F %F', $this->wPt, $this->hPt);
14850                  $out .= ' /BBox [0 0 '.$rect.']';
14851                  $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
14852                  $out .= ' /Resources <<';
14853                  $out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
14854                  $out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
14855                  $out .= ' >>';
14856                  $out .= ' >> ';
14857                  $out .= ' stream'."\n".$stream."\n".'endstream';
14858                  $out .= "\n".'endobj';
14859                  $this->_out($out);
14860                  // SMask
14861                  $this->_newobj();
14862                  $out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
14863                  $this->_out($out);
14864                  // ExtGState
14865                  $this->_newobj();
14866                  $out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
14867                  $this->_out($out);
14868                  $this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
14869              }
14870          }
14871      }
14872  
14873      /**
14874       * Draw the sector of a circle.
14875       * It can be used for instance to render pie charts.
14876       * @param $xc (float) abscissa of the center.
14877       * @param $yc (float) ordinate of the center.
14878       * @param $r (float) radius.
14879       * @param $a (float) start angle (in degrees).
14880       * @param $b (float) end angle (in degrees).
14881       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14882       * @param $cw: (float) indicates whether to go clockwise (default: true).
14883       * @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.
14884       * @author Maxime Delorme, Nicola Asuni
14885       * @since 3.1.000 (2008-06-09)
14886       * @public
14887       */
14888  	public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
14889          $this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
14890      }
14891  
14892      /**
14893       * Draw the sector of an ellipse.
14894       * It can be used for instance to render pie charts.
14895       * @param $xc (float) abscissa of the center.
14896       * @param $yc (float) ordinate of the center.
14897       * @param $rx (float) the x-axis radius.
14898       * @param $ry (float) the y-axis radius.
14899       * @param $a (float) start angle (in degrees).
14900       * @param $b (float) end angle (in degrees).
14901       * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14902       * @param $cw: (float) indicates whether to go clockwise.
14903       * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
14904       * @param $nc (integer) Number of curves used to draw a 90 degrees portion of arc.
14905       * @author Maxime Delorme, Nicola Asuni
14906       * @since 3.1.000 (2008-06-09)
14907       * @public
14908       */
14909  	public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
14910          if ($this->state != 2) {
14911               return;
14912          }
14913          if ($this->rtl) {
14914              $xc = ($this->w - $xc);
14915          }
14916          $op = TCPDF_STATIC::getPathPaintOperator($style);
14917          if ($op == 'f') {
14918              $line_style = array();
14919          }
14920          if ($cw) {
14921              $d = $b;
14922              $b = (360 - $a + $o);
14923              $a = (360 - $d + $o);
14924          } else {
14925              $b += $o;
14926              $a += $o;
14927          }
14928          $this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
14929          $this->_out($op);
14930      }
14931  
14932      /**
14933       * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
14934       * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
14935       * Only vector drawing is supported, not text or bitmap.
14936       * 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).
14937       * @param $file (string) Name of the file containing the image or a '@' character followed by the EPS/AI data string.
14938       * @param $x (float) Abscissa of the upper-left corner.
14939       * @param $y (float) Ordinate of the upper-left corner.
14940       * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
14941       * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
14942       * @param $link (mixed) URL or identifier returned by AddLink().
14943       * @param $useBoundingBox (boolean) specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
14944       * @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>
14945       * @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>
14946       * @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)))
14947       * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
14948       * @param $fixoutvals (boolean) if true remove values outside the bounding box.
14949       * @author Valentin Schmidt, Nicola Asuni
14950       * @since 3.1.000 (2008-06-09)
14951       * @public
14952       */
14953  	public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
14954          if ($this->state != 2) {
14955               return;
14956          }
14957          if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
14958              // convert EPS to raster image using GD or ImageMagick libraries
14959              return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14960          }
14961          if ($x === '') {
14962              $x = $this->x;
14963          }
14964          if ($y === '') {
14965              $y = $this->y;
14966          }
14967          // check page for no-write regions and adapt page margins if necessary
14968          list($x, $y) = $this->checkPageRegions($h, $x, $y);
14969          $k = $this->k;
14970          if ($file[0] === '@') { // image from string
14971              $data = substr($file, 1);
14972          } else { // EPS/AI file
14973              $data = TCPDF_STATIC::fileGetContents($file);
14974          }
14975          if ($data === FALSE) {
14976              $this->Error('EPS file not found: '.$file);
14977          }
14978          $regs = array();
14979          // EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
14980          preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
14981          if (count($regs) > 1) {
14982              $version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
14983              if (strpos($version_str, 'Adobe Illustrator') !== false) {
14984                  $versexp = explode(' ', $version_str);
14985                  $version = (float)array_pop($versexp);
14986                  if ($version >= 9) {
14987                      $this->Error('This version of Adobe Illustrator file is not supported: '.$file);
14988                  }
14989              }
14990          }
14991          // strip binary bytes in front of PS-header
14992          $start = strpos($data, '%!PS-Adobe');
14993          if ($start > 0) {
14994              $data = substr($data, $start);
14995          }
14996          // find BoundingBox params
14997          preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
14998          if (count($regs) > 1) {
14999              list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
15000          } else {
15001              $this->Error('No BoundingBox found in EPS/AI file: '.$file);
15002          }
15003          $start = strpos($data, '%%EndSetup');
15004          if ($start === false) {
15005              $start = strpos($data, '%%EndProlog');
15006          }
15007          if ($start === false) {
15008              $start = strpos($data, '%%BoundingBox');
15009          }
15010          $data = substr($data, $start);
15011          $end = strpos($data, '%%PageTrailer');
15012          if ($end===false) {
15013              $end = strpos($data, 'showpage');
15014          }
15015          if ($end) {
15016              $data = substr($data, 0, $end);
15017          }
15018          // calculate image width and height on document
15019          if (($w <= 0) AND ($h <= 0)) {
15020              $w = ($x2 - $x1) / $k;
15021              $h = ($y2 - $y1) / $k;
15022          } elseif ($w <= 0) {
15023              $w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
15024          } elseif ($h <= 0) {
15025              $h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
15026          }
15027          // fit the image on available space
15028          list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
15029          if ($this->rasterize_vector_images) {
15030              // convert EPS to raster image using GD or ImageMagick libraries
15031              return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
15032          }
15033          // set scaling factors
15034          $scale_x = $w / (($x2 - $x1) / $k);
15035          $scale_y = $h / (($y2 - $y1) / $k);
15036          // set alignment
15037          $this->img_rb_y = $y + $h;
15038          // set alignment
15039          if ($this->rtl) {
15040              if ($palign == 'L') {
15041                  $ximg = $this->lMargin;
15042              } elseif ($palign == 'C') {
15043                  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15044              } elseif ($palign == 'R') {
15045                  $ximg = $this->w - $this->rMargin - $w;
15046              } else {
15047                  $ximg = $x - $w;
15048              }
15049              $this->img_rb_x = $ximg;
15050          } else {
15051              if ($palign == 'L') {
15052                  $ximg = $this->lMargin;
15053              } elseif ($palign == 'C') {
15054                  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15055              } elseif ($palign == 'R') {
15056                  $ximg = $this->w - $this->rMargin - $w;
15057              } else {
15058                  $ximg = $x;
15059              }
15060              $this->img_rb_x = $ximg + $w;
15061          }
15062          if ($useBoundingBox) {
15063              $dx = $ximg * $k - $x1;
15064              $dy = $y * $k - $y1;
15065          } else {
15066              $dx = $ximg * $k;
15067              $dy = $y * $k;
15068          }
15069          // save the current graphic state
15070          $this->_out('q'.$this->epsmarker);
15071          // translate
15072          $this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
15073          // scale
15074          if (isset($scale_x)) {
15075              $this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
15076          }
15077          // handle pc/unix/mac line endings
15078          $lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
15079          $u=0;
15080          $cnt = count($lines);
15081          for ($i=0; $i < $cnt; ++$i) {
15082              $line = $lines[$i];
15083              if (($line == '') OR ($line[0] == '%')) {
15084                  continue;
15085              }
15086              $len = strlen($line);
15087              // check for spot color names
15088              $color_name = '';
15089              if (strcasecmp('x', substr(trim($line), -1)) == 0) {
15090                  if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
15091                      // extract spot color name
15092                      $color_name = $matches[0];
15093                      // remove color name from string
15094                      $line = str_replace(' '.$color_name, '', $line);
15095                      // remove pharentesis from color name
15096                      $color_name = substr($color_name, 1, -1);
15097                  }
15098              }
15099              $chunks = explode(' ', $line);
15100              $cmd = trim(array_pop($chunks));
15101              // RGB
15102              if (($cmd == 'Xa') OR ($cmd == 'XA')) {
15103                  $b = array_pop($chunks);
15104                  $g = array_pop($chunks);
15105                  $r = array_pop($chunks);
15106                  $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!
15107                  continue;
15108              }
15109              $skip = false;
15110              if ($fixoutvals) {
15111                  // check for values outside the bounding box
15112                  switch ($cmd) {
15113                      case 'm':
15114                      case 'l':
15115                      case 'L': {
15116                          // skip values outside bounding box
15117                          foreach ($chunks as $key => $val) {
15118                              if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
15119                                  $skip = true;
15120                              } elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
15121                                  $skip = true;
15122                              }
15123                          }
15124                      }
15125                  }
15126              }
15127              switch ($cmd) {
15128                  case 'm':
15129                  case 'l':
15130                  case 'v':
15131                  case 'y':
15132                  case 'c':
15133                  case 'k':
15134                  case 'K':
15135                  case 'g':
15136                  case 'G':
15137                  case 's':
15138                  case 'S':
15139                  case 'J':
15140                  case 'j':
15141                  case 'w':
15142                  case 'M':
15143                  case 'd':
15144                  case 'n': {
15145                      if ($skip) {
15146                          break;
15147                      }
15148                      $this->_out($line);
15149                      break;
15150                  }
15151                  case 'x': {// custom fill color
15152                      if (empty($color_name)) {
15153                          // CMYK color
15154                          list($col_c, $col_m, $col_y, $col_k) = $chunks;
15155                          $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
15156                      } else {
15157                          // Spot Color (CMYK + tint)
15158                          list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15159                          $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15160                          $color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15161                          $this->_out($color_cmd);
15162                      }
15163                      break;
15164                  }
15165                  case 'X': { // custom stroke color
15166                      if (empty($color_name)) {
15167                          // CMYK color
15168                          list($col_c, $col_m, $col_y, $col_k) = $chunks;
15169                          $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
15170                      } else {
15171                          // Spot Color (CMYK + tint)
15172                          list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15173                          $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15174                          $color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15175                          $this->_out($color_cmd);
15176                      }
15177                      break;
15178                  }
15179                  case 'Y':
15180                  case 'N':
15181                  case 'V':
15182                  case 'L':
15183                  case 'C': {
15184                      if ($skip) {
15185                          break;
15186                      }
15187                      $line[($len - 1)] = strtolower($cmd);
15188                      $this->_out($line);
15189                      break;
15190                  }
15191                  case 'b':
15192                  case 'B': {
15193                      $this->_out($cmd . '*');
15194                      break;
15195                  }
15196                  case 'f':
15197                  case 'F': {
15198                      if ($u > 0) {
15199                          $isU = false;
15200                          $max = min(($i + 5), $cnt);
15201                          for ($j = ($i + 1); $j < $max; ++$j) {
15202                              $isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
15203                          }
15204                          if ($isU) {
15205                              $this->_out('f*');
15206                          }
15207                      } else {
15208                          $this->_out('f*');
15209                      }
15210                      break;
15211                  }
15212                  case '*u': {
15213                      ++$u;
15214                      break;
15215                  }
15216                  case '*U': {
15217                      --$u;
15218                      break;
15219                  }
15220              }
15221          }
15222          // restore previous graphic state
15223          $this->_out($this->epsmarker.'Q');
15224          if (!empty($border)) {
15225              $bx = $this->x;
15226              $by = $this->y;
15227              $this->x = $ximg;
15228              if ($this->rtl) {
15229                  $this->x += $w;
15230              }
15231              $this->y = $y;
15232              $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
15233              $this->x = $bx;
15234              $this->y = $by;
15235          }
15236          if ($link) {
15237              $this->Link($ximg, $y, $w, $h, $link, 0);
15238          }
15239          // set pointer to align the next text/objects
15240          switch($align) {
15241              case 'T':{
15242                  $this->y = $y;
15243                  $this->x = $this->img_rb_x;
15244                  break;
15245              }
15246              case 'M':{
15247                  $this->y = $y + round($h/2);
15248                  $this->x = $this->img_rb_x;
15249                  break;
15250              }
15251              case 'B':{
15252                  $this->y = $this->img_rb_y;
15253                  $this->x = $this->img_rb_x;
15254                  break;
15255              }
15256              case 'N':{
15257                  $this->SetY($this->img_rb_y);
15258                  break;
15259              }
15260              default:{
15261                  break;
15262              }
15263          }
15264          $this->endlinex = $this->img_rb_x;
15265      }
15266  
15267      /**
15268       * Set document barcode.
15269       * @param $bc (string) barcode
15270       * @public
15271       */
15272  	public function setBarcode($bc='') {
15273          $this->barcode = $bc;
15274      }
15275  
15276      /**
15277       * Get current barcode.
15278       * @return string
15279       * @public
15280       * @since 4.0.012 (2008-07-24)
15281       */
15282  	public function getBarcode() {
15283          return $this->barcode;
15284      }
15285  
15286      /**
15287       * Print a Linear Barcode.
15288       * @param $code (string) code to print
15289       * @param $type (string) type of barcode (see tcpdf_barcodes_1d.php for supported formats).
15290       * @param $x (int) x position in user units (empty string = current x position)
15291       * @param $y (int) y position in user units (empty string = current y position)
15292       * @param $w (int) width in user units (empty string = remaining page width)
15293       * @param $h (int) height in user units (empty string = remaining page height)
15294       * @param $xres (float) width of the smallest bar in user units (empty string = default value = 0.4mm)
15295       * @param $style (array) array of options:<ul>
15296       * <li>boolean $style['border'] if true prints a border</li>
15297       * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
15298       * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
15299       * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
15300       * <li>array $style['fgcolor'] color array for bars and text</li>
15301       * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
15302       * <li>boolean $style['text'] if true prints text below the barcode</li>
15303       * <li>string $style['label'] override default label</li>
15304       * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
15305       * <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>
15306       * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
15307       * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
15308       * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
15309       * <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>
15310       * <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>
15311       * @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>
15312       * @author Nicola Asuni
15313       * @since 3.1.000 (2008-06-09)
15314       * @public
15315       */
15316  	public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style='', $align='') {
15317          if (TCPDF_STATIC::empty_string(trim($code))) {
15318              return;
15319          }
15320          require_once(dirname(__FILE__).'/tcpdf_barcodes_1d.php');
15321          // save current graphic settings
15322          $gvars = $this->getGraphicVars();
15323          // create new barcode object
15324          $barcodeobj = new TCPDFBarcode($code, $type);
15325          $arrcode = $barcodeobj->getBarcodeArray();
15326          if (($arrcode === false) OR empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
15327              $this->Error('Error in 1D barcode string');
15328          }
15329          if ($arrcode['maxh'] <= 0) {
15330              $arrcode['maxh'] = 1;
15331          }
15332          // set default values
15333          if (!isset($style['position'])) {
15334              $style['position'] = '';
15335          } elseif ($style['position'] == 'S') {
15336              // keep this for backward compatibility
15337              $style['position'] = '';
15338              $style['stretch'] = true;
15339          }
15340          if (!isset($style['fitwidth'])) {
15341              if (!isset($style['stretch'])) {
15342                  $style['fitwidth'] = true;
15343              } else {
15344                  $style['fitwidth'] = false;
15345              }
15346          }
15347          if ($style['fitwidth']) {
15348              // disable stretch
15349              $style['stretch'] = false;
15350          }
15351          if (!isset($style['stretch'])) {
15352              if (($w === '') OR ($w <= 0)) {
15353                  $style['stretch'] = false;
15354              } else {
15355                  $style['stretch'] = true;
15356              }
15357          }
15358          if (!isset($style['fgcolor'])) {
15359              $style['fgcolor'] = array(0,0,0); // default black
15360          }
15361          if (!isset($style['bgcolor'])) {
15362              $style['bgcolor'] = false; // default transparent
15363          }
15364          if (!isset($style['border'])) {
15365              $style['border'] = false;
15366          }
15367          $fontsize = 0;
15368          if (!isset($style['text'])) {
15369              $style['text'] = false;
15370          }
15371          if ($style['text'] AND isset($style['font'])) {
15372              if (isset($style['fontsize'])) {
15373                  $fontsize = $style['fontsize'];
15374              }
15375              $this->SetFont($style['font'], '', $fontsize);
15376          }
15377          if (!isset($style['stretchtext'])) {
15378              $style['stretchtext'] = 4;
15379          }
15380          if ($x === '') {
15381              $x = $this->x;
15382          }
15383          if ($y === '') {
15384              $y = $this->y;
15385          }
15386          // check page for no-write regions and adapt page margins if necessary
15387          list($x, $y) = $this->checkPageRegions($h, $x, $y);
15388          if (($w === '') OR ($w <= 0)) {
15389              if ($this->rtl) {
15390                  $w = $x - $this->lMargin;
15391              } else {
15392                  $w = $this->w - $this->rMargin - $x;
15393              }
15394          }
15395          // padding
15396          if (!isset($style['padding'])) {
15397              $padding = 0;
15398          } elseif ($style['padding'] === 'auto') {
15399              $padding = 10 * ($w / ($arrcode['maxw'] + 20));
15400          } else {
15401              $padding = floatval($style['padding']);
15402          }
15403          // horizontal padding
15404          if (!isset($style['hpadding'])) {
15405              $hpadding = $padding;
15406          } elseif ($style['hpadding'] === 'auto') {
15407              $hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
15408          } else {
15409              $hpadding = floatval($style['hpadding']);
15410          }
15411          // vertical padding
15412          if (!isset($style['vpadding'])) {
15413              $vpadding = $padding;
15414          } elseif ($style['vpadding'] === 'auto') {
15415              $vpadding = ($hpadding / 2);
15416          } else {
15417              $vpadding = floatval($style['vpadding']);
15418          }
15419          // calculate xres (single bar width)
15420          $max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
15421          if ($style['stretch']) {
15422              $xres = $max_xres;
15423          } else {
15424              if (TCPDF_STATIC::empty_string($xres)) {
15425                  $xres = (0.141 * $this->k); // default bar width = 0.4 mm
15426              }
15427              if ($xres > $max_xres) {
15428                  // correct xres to fit on $w
15429                  $xres = $max_xres;
15430              }
15431              if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
15432                  OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
15433                  $hpadding = 10 * $xres;
15434                  if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
15435                      $vpadding = ($hpadding / 2);
15436                  }
15437              }
15438          }
15439          if ($style['fitwidth']) {
15440              $wold = $w;
15441              $w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
15442              if (isset($style['cellfitalign'])) {
15443                  switch ($style['cellfitalign']) {
15444                      case 'L': {
15445                          if ($this->rtl) {
15446                              $x -= ($wold - $w);
15447                          }
15448                          break;
15449                      }
15450                      case 'R': {
15451                          if (!$this->rtl) {
15452                              $x += ($wold - $w);
15453                          }
15454                          break;
15455                      }
15456                      case 'C': {
15457                          if ($this->rtl) {
15458                              $x -= (($wold - $w) / 2);
15459                          } else {
15460                              $x += (($wold - $w) / 2);
15461                          }
15462                          break;
15463                      }
15464                      default : {
15465                          break;
15466                      }
15467                  }
15468              }
15469          }
15470          $text_height = $this->getCellHeight($fontsize / $this->k);
15471          // height
15472          if (($h === '') OR ($h <= 0)) {
15473              // set default height
15474              $h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
15475          }
15476          $barh = $h - $text_height - (2 * $vpadding);
15477          if ($barh <=0) {
15478              // try to reduce font or padding to fit barcode on available height
15479              if ($text_height > $h) {
15480                  $fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
15481                  $text_height = $this->getCellHeight($fontsize / $this->k);
15482                  $this->SetFont($style['font'], '', $fontsize);
15483              }
15484              if ($vpadding > 0) {
15485                  $vpadding = (($h - $text_height) / 4);
15486              }
15487              $barh = $h - $text_height - (2 * $vpadding);
15488          }
15489          // fit the barcode on available space
15490          list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15491          // set alignment
15492          $this->img_rb_y = $y + $h;
15493          // set alignment
15494          if ($this->rtl) {
15495              if ($style['position'] == 'L') {
15496                  $xpos = $this->lMargin;
15497              } elseif ($style['position'] == 'C') {
15498                  $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15499              } elseif ($style['position'] == 'R') {
15500                  $xpos = $this->w - $this->rMargin - $w;
15501              } else {
15502                  $xpos = $x - $w;
15503              }
15504              $this->img_rb_x = $xpos;
15505          } else {
15506              if ($style['position'] == 'L') {
15507                  $xpos = $this->lMargin;
15508              } elseif ($style['position'] == 'C') {
15509                  $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15510              } elseif ($style['position'] == 'R') {
15511                  $xpos = $this->w - $this->rMargin - $w;
15512              } else {
15513                  $xpos = $x;
15514              }
15515              $this->img_rb_x = $xpos + $w;
15516          }
15517          $xpos_rect = $xpos;
15518          if (!isset($style['align'])) {
15519              $style['align'] = 'C';
15520          }
15521          switch ($style['align']) {
15522              case 'L': {
15523                  $xpos = $xpos_rect + $hpadding;
15524                  break;
15525              }
15526              case 'R': {
15527                  $xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
15528                  break;
15529              }
15530              case 'C':
15531              default : {
15532                  $xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
15533                  break;
15534              }
15535          }
15536          $xpos_text = $xpos;
15537          // barcode is always printed in LTR direction
15538          $tempRTL = $this->rtl;
15539          $this->rtl = false;
15540          // print background color
15541          if ($style['bgcolor']) {
15542              $this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15543          } elseif ($style['border']) {
15544              $this->Rect($xpos_rect, $y, $w, $h, 'D');
15545          }
15546          // set foreground color
15547          $this->SetDrawColorArray($style['fgcolor']);
15548          $this->SetTextColorArray($style['fgcolor']);
15549          // print bars
15550          foreach ($arrcode['bcode'] as $k => $v) {
15551              $bw = ($v['w'] * $xres);
15552              if ($v['t']) {
15553                  // draw a vertical bar
15554                  $ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
15555                  $this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
15556              }
15557              $xpos += $bw;
15558          }
15559          // print text
15560          if ($style['text']) {
15561              if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
15562                  $label = $style['label'];
15563              } else {
15564                  $label = $code;
15565              }
15566              $txtwidth = ($arrcode['maxw'] * $xres);
15567              if ($this->GetStringWidth($label) > $txtwidth) {
15568                  $style['stretchtext'] = 2;
15569              }
15570              // print text
15571              $this->x = $xpos_text;
15572              $this->y = $y + $vpadding + $barh;
15573              $cellpadding = $this->cell_padding;
15574              $this->SetCellPadding(0);
15575              $this->Cell($txtwidth, '', $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
15576              $this->cell_padding = $cellpadding;
15577          }
15578          // restore original direction
15579          $this->rtl = $tempRTL;
15580          // restore previous settings
15581          $this->setGraphicVars($gvars);
15582          // set pointer to align the next text/objects
15583          switch($align) {
15584              case 'T':{
15585                  $this->y = $y;
15586                  $this->x = $this->img_rb_x;
15587                  break;
15588              }
15589              case 'M':{
15590                  $this->y = $y + round($h / 2);
15591                  $this->x = $this->img_rb_x;
15592                  break;
15593              }
15594              case 'B':{
15595                  $this->y = $this->img_rb_y;
15596                  $this->x = $this->img_rb_x;
15597                  break;
15598              }
15599              case 'N':{
15600                  $this->SetY($this->img_rb_y);
15601                  break;
15602              }
15603              default:{
15604                  break;
15605              }
15606          }
15607          $this->endlinex = $this->img_rb_x;
15608      }
15609  
15610      /**
15611       * Print 2D Barcode.
15612       * @param $code (string) code to print
15613       * @param $type (string) type of barcode (see tcpdf_barcodes_2d.php for supported formats).
15614       * @param $x (int) x position in user units
15615       * @param $y (int) y position in user units
15616       * @param $w (int) width in user units
15617       * @param $h (int) height in user units
15618       * @param $style (array) array of options:<ul>
15619       * <li>boolean $style['border'] if true prints a border around the barcode</li>
15620       * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
15621       * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
15622       * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
15623       * <li>int $style['module_width'] width of a single module in points</li>
15624       * <li>int $style['module_height'] height of a single module in points</li>
15625       * <li>array $style['fgcolor'] color array for bars and text</li>
15626       * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
15627       * <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>
15628       * <li>$style['module_height'] height of a single module in points</li></ul>
15629       * @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>
15630       * @param $distort (boolean) if true distort the barcode to fit width and height, otherwise preserve aspect ratio
15631       * @author Nicola Asuni
15632       * @since 4.5.037 (2009-04-07)
15633       * @public
15634       */
15635  	public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style='', $align='', $distort=false) {
15636          if (TCPDF_STATIC::empty_string(trim($code))) {
15637              return;
15638          }
15639          require_once(dirname(__FILE__).'/tcpdf_barcodes_2d.php');
15640          // save current graphic settings
15641          $gvars = $this->getGraphicVars();
15642          // create new barcode object
15643          $barcodeobj = new TCPDF2DBarcode($code, $type);
15644          $arrcode = $barcodeobj->getBarcodeArray();
15645          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)) {
15646              $this->Error('Error in 2D barcode string');
15647          }
15648          // set default values
15649          if (!isset($style['position'])) {
15650              $style['position'] = '';
15651          }
15652          if (!isset($style['fgcolor'])) {
15653              $style['fgcolor'] = array(0,0,0); // default black
15654          }
15655          if (!isset($style['bgcolor'])) {
15656              $style['bgcolor'] = false; // default transparent
15657          }
15658          if (!isset($style['border'])) {
15659              $style['border'] = false;
15660          }
15661          // padding
15662          if (!isset($style['padding'])) {
15663              $style['padding'] = 0;
15664          } elseif ($style['padding'] === 'auto') {
15665              $style['padding'] = 4;
15666          }
15667          if (!isset($style['hpadding'])) {
15668              $style['hpadding'] = $style['padding'];
15669          } elseif ($style['hpadding'] === 'auto') {
15670              $style['hpadding'] = 4;
15671          }
15672          if (!isset($style['vpadding'])) {
15673              $style['vpadding'] = $style['padding'];
15674          } elseif ($style['vpadding'] === 'auto') {
15675              $style['vpadding'] = 4;
15676          }
15677          $hpad = (2 * $style['hpadding']);
15678          $vpad = (2 * $style['vpadding']);
15679          // cell (module) dimension
15680          if (!isset($style['module_width'])) {
15681              $style['module_width'] = 1; // width of a single module in points
15682          }
15683          if (!isset($style['module_height'])) {
15684              $style['module_height'] = 1; // height of a single module in points
15685          }
15686          if ($x === '') {
15687              $x = $this->x;
15688          }
15689          if ($y === '') {
15690              $y = $this->y;
15691          }
15692          // check page for no-write regions and adapt page margins if necessary
15693          list($x, $y) = $this->checkPageRegions($h, $x, $y);
15694          // number of barcode columns and rows
15695          $rows = $arrcode['num_rows'];
15696          $cols = $arrcode['num_cols'];
15697          if (($rows <= 0) || ($cols <= 0)){
15698              $this->Error('Error in 2D barcode string');
15699          }
15700          // module width and height
15701          $mw = $style['module_width'];
15702          $mh = $style['module_height'];
15703          if (($mw <= 0) OR ($mh <= 0)) {
15704              $this->Error('Error in 2D barcode string');
15705          }
15706          // get max dimensions
15707          if ($this->rtl) {
15708              $maxw = $x - $this->lMargin;
15709          } else {
15710              $maxw = $this->w - $this->rMargin - $x;
15711          }
15712          $maxh = ($this->h - $this->tMargin - $this->bMargin);
15713          $ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
15714          $ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
15715          if (!$distort) {
15716              if (($maxw * $ratioHW) > $maxh) {
15717                  $maxw = $maxh * $ratioWH;
15718              }
15719              if (($maxh * $ratioWH) > $maxw) {
15720                  $maxh = $maxw * $ratioHW;
15721              }
15722          }
15723          // set maximum dimesions
15724          if ($w > $maxw) {
15725              $w = $maxw;
15726          }
15727          if ($h > $maxh) {
15728              $h = $maxh;
15729          }
15730          // set dimensions
15731          if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
15732              $w = ($cols + $hpad) * ($mw / $this->k);
15733              $h = ($rows + $vpad) * ($mh / $this->k);
15734          } elseif (($w === '') OR ($w <= 0)) {
15735              $w = $h * $ratioWH;
15736          } elseif (($h === '') OR ($h <= 0)) {
15737              $h = $w * $ratioHW;
15738          }
15739          // barcode size (excluding padding)
15740          $bw = ($w * $cols) / ($cols + $hpad);
15741          $bh = ($h * $rows) / ($rows + $vpad);
15742          // dimension of single barcode cell unit
15743          $cw = $bw / $cols;
15744          $ch = $bh / $rows;
15745          if (!$distort) {
15746              if (($cw / $ch) > ($mw / $mh)) {
15747                  // correct horizontal distortion
15748                  $cw = $ch * $mw / $mh;
15749                  $bw = $cw * $cols;
15750                  $style['hpadding'] = ($w - $bw) / (2 * $cw);
15751              } else {
15752                  // correct vertical distortion
15753                  $ch = $cw * $mh / $mw;
15754                  $bh = $ch * $rows;
15755                  $style['vpadding'] = ($h - $bh) / (2 * $ch);
15756              }
15757          }
15758          // fit the barcode on available space
15759          list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15760          // set alignment
15761          $this->img_rb_y = $y + $h;
15762          // set alignment
15763          if ($this->rtl) {
15764              if ($style['position'] == 'L') {
15765                  $xpos = $this->lMargin;
15766              } elseif ($style['position'] == 'C') {
15767                  $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15768              } elseif ($style['position'] == 'R') {
15769                  $xpos = $this->w - $this->rMargin - $w;
15770              } else {
15771                  $xpos = $x - $w;
15772              }
15773              $this->img_rb_x = $xpos;
15774          } else {
15775              if ($style['position'] == 'L') {
15776                  $xpos = $this->lMargin;
15777              } elseif ($style['position'] == 'C') {
15778                  $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15779              } elseif ($style['position'] == 'R') {
15780                  $xpos = $this->w - $this->rMargin - $w;
15781              } else {
15782                  $xpos = $x;
15783              }
15784              $this->img_rb_x = $xpos + $w;
15785          }
15786          $xstart = $xpos + ($style['hpadding'] * $cw);
15787          $ystart = $y + ($style['vpadding'] * $ch);
15788          // barcode is always printed in LTR direction
15789          $tempRTL = $this->rtl;
15790          $this->rtl = false;
15791          // print background color
15792          if ($style['bgcolor']) {
15793              $this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15794          } elseif ($style['border']) {
15795              $this->Rect($xpos, $y, $w, $h, 'D');
15796          }
15797          // set foreground color
15798          $this->SetDrawColorArray($style['fgcolor']);
15799          // print barcode cells
15800          // for each row
15801          for ($r = 0; $r < $rows; ++$r) {
15802              $xr = $xstart;
15803              // for each column
15804              for ($c = 0; $c < $cols; ++$c) {
15805                  if ($arrcode['bcode'][$r][$c] == 1) {
15806                      // draw a single barcode cell
15807                      $this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
15808                  }
15809                  $xr += $cw;
15810              }
15811              $ystart += $ch;
15812          }
15813          // restore original direction
15814          $this->rtl = $tempRTL;
15815          // restore previous settings
15816          $this->setGraphicVars($gvars);
15817          // set pointer to align the next text/objects
15818          switch($align) {
15819              case 'T':{
15820                  $this->y = $y;
15821                  $this->x = $this->img_rb_x;
15822                  break;
15823              }
15824              case 'M':{
15825                  $this->y = $y + round($h/2);
15826                  $this->x = $this->img_rb_x;
15827                  break;
15828              }
15829              case 'B':{
15830                  $this->y = $this->img_rb_y;
15831                  $this->x = $this->img_rb_x;
15832                  break;
15833              }
15834              case 'N':{
15835                  $this->SetY($this->img_rb_y);
15836                  break;
15837              }
15838              default:{
15839                  break;
15840              }
15841          }
15842          $this->endlinex = $this->img_rb_x;
15843      }
15844  
15845      /**
15846       * Returns an array containing current margins:
15847       * <ul>
15848              <li>$ret['left'] = left margin</li>
15849              <li>$ret['right'] = right margin</li>
15850              <li>$ret['top'] = top margin</li>
15851              <li>$ret['bottom'] = bottom margin</li>
15852              <li>$ret['header'] = header margin</li>
15853              <li>$ret['footer'] = footer margin</li>
15854              <li>$ret['cell'] = cell padding array</li>
15855              <li>$ret['padding_left'] = cell left padding</li>
15856              <li>$ret['padding_top'] = cell top padding</li>
15857              <li>$ret['padding_right'] = cell right padding</li>
15858              <li>$ret['padding_bottom'] = cell bottom padding</li>
15859       * </ul>
15860       * @return array containing all margins measures
15861       * @public
15862       * @since 3.2.000 (2008-06-23)
15863       */
15864  	public function getMargins() {
15865          $ret = array(
15866              'left' => $this->lMargin,
15867              'right' => $this->rMargin,
15868              'top' => $this->tMargin,
15869              'bottom' => $this->bMargin,
15870              'header' => $this->header_margin,
15871              'footer' => $this->footer_margin,
15872              'cell' => $this->cell_padding,
15873              'padding_left' => $this->cell_padding['L'],
15874              'padding_top' => $this->cell_padding['T'],
15875              'padding_right' => $this->cell_padding['R'],
15876              'padding_bottom' => $this->cell_padding['B']
15877          );
15878          return $ret;
15879      }
15880  
15881      /**
15882       * Returns an array containing original margins:
15883       * <ul>
15884              <li>$ret['left'] = left margin</li>
15885              <li>$ret['right'] = right margin</li>
15886       * </ul>
15887       * @return array containing all margins measures
15888       * @public
15889       * @since 4.0.012 (2008-07-24)
15890       */
15891  	public function getOriginalMargins() {
15892          $ret = array(
15893              'left' => $this->original_lMargin,
15894              'right' => $this->original_rMargin
15895          );
15896          return $ret;
15897      }
15898  
15899      /**
15900       * Returns the current font size.
15901       * @return current font size
15902       * @public
15903       * @since 3.2.000 (2008-06-23)
15904       */
15905  	public function getFontSize() {
15906          return $this->FontSize;
15907      }
15908  
15909      /**
15910       * Returns the current font size in points unit.
15911       * @return current font size in points unit
15912       * @public
15913       * @since 3.2.000 (2008-06-23)
15914       */
15915  	public function getFontSizePt() {
15916          return $this->FontSizePt;
15917      }
15918  
15919      /**
15920       * Returns the current font family name.
15921       * @return string current font family name
15922       * @public
15923       * @since 4.3.008 (2008-12-05)
15924       */
15925  	public function getFontFamily() {
15926          return $this->FontFamily;
15927      }
15928  
15929      /**
15930       * Returns the current font style.
15931       * @return string current font style
15932       * @public
15933       * @since 4.3.008 (2008-12-05)
15934       */
15935  	public function getFontStyle() {
15936          return $this->FontStyle;
15937      }
15938  
15939      /**
15940       * Cleanup HTML code (requires HTML Tidy library).
15941       * @param $html (string) htmlcode to fix
15942       * @param $default_css (string) CSS commands to add
15943       * @param $tagvs (array) parameters for setHtmlVSpace method
15944       * @param $tidy_options (array) options for tidy_parse_string function
15945       * @return string XHTML code cleaned up
15946       * @author Nicola Asuni
15947       * @public
15948       * @since 5.9.017 (2010-11-16)
15949       * @see setHtmlVSpace()
15950       */
15951  	public function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='') {
15952          return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces);
15953      }
15954  
15955      /**
15956       * Returns the border width from CSS property
15957       * @param $width (string) border width
15958       * @return int with in user units
15959       * @protected
15960       * @since 5.7.000 (2010-08-02)
15961       */
15962  	protected function getCSSBorderWidth($width) {
15963          if ($width == 'thin') {
15964              $width = (2 / $this->k);
15965          } elseif ($width == 'medium') {
15966              $width = (4 / $this->k);
15967          } elseif ($width == 'thick') {
15968              $width = (6 / $this->k);
15969          } else {
15970              $width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
15971          }
15972          return $width;
15973      }
15974  
15975      /**
15976       * Returns the border dash style from CSS property
15977       * @param $style (string) border style to convert
15978       * @return int sash style (return -1 in case of none or hidden border)
15979       * @protected
15980       * @since 5.7.000 (2010-08-02)
15981       */
15982  	protected function getCSSBorderDashStyle($style) {
15983          switch (strtolower($style)) {
15984              case 'none':
15985              case 'hidden': {
15986                  $dash = -1;
15987                  break;
15988              }
15989              case 'dotted': {
15990                  $dash = 1;
15991                  break;
15992              }
15993              case 'dashed': {
15994                  $dash = 3;
15995                  break;
15996              }
15997              case 'double':
15998              case 'groove':
15999              case 'ridge':
16000              case 'inset':
16001              case 'outset':
16002              case 'solid':
16003              default: {
16004                  $dash = 0;
16005                  break;
16006              }
16007          }
16008          return $dash;
16009      }
16010  
16011      /**
16012       * Returns the border style array from CSS border properties
16013       * @param $cssborder (string) border properties
16014       * @return array containing border properties
16015       * @protected
16016       * @since 5.7.000 (2010-08-02)
16017       */
16018  	protected function getCSSBorderStyle($cssborder) {
16019          $bprop = preg_split('/[\s]+/', trim($cssborder));
16020          $border = array(); // value to be returned
16021          switch (count($bprop)) {
16022              case 3: {
16023                  $width = $bprop[0];
16024                  $style = $bprop[1];
16025                  $color = $bprop[2];
16026                  break;
16027              }
16028              case 2: {
16029                  $width = 'medium';
16030                  $style = $bprop[0];
16031                  $color = $bprop[1];
16032                  break;
16033              }
16034              case 1: {
16035                  $width = 'medium';
16036                  $style = $bprop[0];
16037                  $color = 'black';
16038                  break;
16039              }
16040              default: {
16041                  $width = 'medium';
16042                  $style = 'solid';
16043                  $color = 'black';
16044                  break;
16045              }
16046          }
16047          if ($style == 'none') {
16048              return array();
16049          }
16050          $border['cap'] = 'square';
16051          $border['join'] = 'miter';
16052          $border['dash'] = $this->getCSSBorderDashStyle($style);
16053          if ($border['dash'] < 0) {
16054              return array();
16055          }
16056          $border['width'] = $this->getCSSBorderWidth($width);
16057          $border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
16058          return $border;
16059      }
16060  
16061      /**
16062       * Get the internal Cell padding from CSS attribute.
16063       * @param $csspadding (string) padding properties
16064       * @param $width (float) width of the containing element
16065       * @return array of cell paddings
16066       * @public
16067       * @since 5.9.000 (2010-10-04)
16068       */
16069  	public function getCSSPadding($csspadding, $width=0) {
16070          $padding = preg_split('/[\s]+/', trim($csspadding));
16071          $cell_padding = array(); // value to be returned
16072          switch (count($padding)) {
16073              case 4: {
16074                  $cell_padding['T'] = $padding[0];
16075                  $cell_padding['R'] = $padding[1];
16076                  $cell_padding['B'] = $padding[2];
16077                  $cell_padding['L'] = $padding[3];
16078                  break;
16079              }
16080              case 3: {
16081                  $cell_padding['T'] = $padding[0];
16082                  $cell_padding['R'] = $padding[1];
16083                  $cell_padding['B'] = $padding[2];
16084                  $cell_padding['L'] = $padding[1];
16085                  break;
16086              }
16087              case 2: {
16088                  $cell_padding['T'] = $padding[0];
16089                  $cell_padding['R'] = $padding[1];
16090                  $cell_padding['B'] = $padding[0];
16091                  $cell_padding['L'] = $padding[1];
16092                  break;
16093              }
16094              case 1: {
16095                  $cell_padding['T'] = $padding[0];
16096                  $cell_padding['R'] = $padding[0];
16097                  $cell_padding['B'] = $padding[0];
16098                  $cell_padding['L'] = $padding[0];
16099                  break;
16100              }
16101              default: {
16102                  return $this->cell_padding;
16103              }
16104          }
16105          if ($width == 0) {
16106              $width = $this->w - $this->lMargin - $this->rMargin;
16107          }
16108          $cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
16109          $cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
16110          $cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
16111          $cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
16112          return $cell_padding;
16113      }
16114  
16115      /**
16116       * Get the internal Cell margin from CSS attribute.
16117       * @param $cssmargin (string) margin properties
16118       * @param $width (float) width of the containing element
16119       * @return array of cell margins
16120       * @public
16121       * @since 5.9.000 (2010-10-04)
16122       */
16123  	public function getCSSMargin($cssmargin, $width=0) {
16124          $margin = preg_split('/[\s]+/', trim($cssmargin));
16125          $cell_margin = array(); // value to be returned
16126          switch (count($margin)) {
16127              case 4: {
16128                  $cell_margin['T'] = $margin[0];
16129                  $cell_margin['R'] = $margin[1];
16130                  $cell_margin['B'] = $margin[2];
16131                  $cell_margin['L'] = $margin[3];
16132                  break;
16133              }
16134              case 3: {
16135                  $cell_margin['T'] = $margin[0];
16136                  $cell_margin['R'] = $margin[1];
16137                  $cell_margin['B'] = $margin[2];
16138                  $cell_margin['L'] = $margin[1];
16139                  break;
16140              }
16141              case 2: {
16142                  $cell_margin['T'] = $margin[0];
16143                  $cell_margin['R'] = $margin[1];
16144                  $cell_margin['B'] = $margin[0];
16145                  $cell_margin['L'] = $margin[1];
16146                  break;
16147              }
16148              case 1: {
16149                  $cell_margin['T'] = $margin[0];
16150                  $cell_margin['R'] = $margin[0];
16151                  $cell_margin['B'] = $margin[0];
16152                  $cell_margin['L'] = $margin[0];
16153                  break;
16154              }
16155              default: {
16156                  return $this->cell_margin;
16157              }
16158          }
16159          if ($width == 0) {
16160              $width = $this->w - $this->lMargin - $this->rMargin;
16161          }
16162          $cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
16163          $cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
16164          $cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
16165          $cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
16166          return $cell_margin;
16167      }
16168  
16169      /**
16170       * Get the border-spacing from CSS attribute.
16171       * @param $cssbspace (string) border-spacing CSS properties
16172       * @param $width (float) width of the containing element
16173       * @return array of border spacings
16174       * @public
16175       * @since 5.9.010 (2010-10-27)
16176       */
16177  	public function getCSSBorderMargin($cssbspace, $width=0) {
16178          $space = preg_split('/[\s]+/', trim($cssbspace));
16179          $border_spacing = array(); // value to be returned
16180          switch (count($space)) {
16181              case 2: {
16182                  $border_spacing['H'] = $space[0];
16183                  $border_spacing['V'] = $space[1];
16184                  break;
16185              }
16186              case 1: {
16187                  $border_spacing['H'] = $space[0];
16188                  $border_spacing['V'] = $space[0];
16189                  break;
16190              }
16191              default: {
16192                  return array('H' => 0, 'V' => 0);
16193              }
16194          }
16195          if ($width == 0) {
16196              $width = $this->w - $this->lMargin - $this->rMargin;
16197          }
16198          $border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
16199          $border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
16200          return $border_spacing;
16201      }
16202  
16203      /**
16204       * Returns the letter-spacing value from CSS value
16205       * @param $spacing (string) letter-spacing value
16206       * @param $parent (float) font spacing (tracking) value of the parent element
16207       * @return float quantity to increases or decreases the space between characters in a text.
16208       * @protected
16209       * @since 5.9.000 (2010-10-02)
16210       */
16211  	protected function getCSSFontSpacing($spacing, $parent=0) {
16212          $val = 0; // value to be returned
16213          $spacing = trim($spacing);
16214          switch ($spacing) {
16215              case 'normal': {
16216                  $val = 0;
16217                  break;
16218              }
16219              case 'inherit': {
16220                  if ($parent == 'normal') {
16221                      $val = 0;
16222                  } else {
16223                      $val = $parent;
16224                  }
16225                  break;
16226              }
16227              default: {
16228                  $val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
16229              }
16230          }
16231          return $val;
16232      }
16233  
16234      /**
16235       * Returns the percentage of font stretching from CSS value
16236       * @param $stretch (string) stretch mode
16237       * @param $parent (float) stretch value of the parent element
16238       * @return float font stretching percentage
16239       * @protected
16240       * @since 5.9.000 (2010-10-02)
16241       */
16242  	protected function getCSSFontStretching($stretch, $parent=100) {
16243          $val = 100; // value to be returned
16244          $stretch = trim($stretch);
16245          switch ($stretch) {
16246              case 'ultra-condensed': {
16247                  $val = 40;
16248                  break;
16249              }
16250              case 'extra-condensed': {
16251                  $val = 55;
16252                  break;
16253              }
16254              case 'condensed': {
16255                  $val = 70;
16256                  break;
16257              }
16258              case 'semi-condensed': {
16259                  $val = 85;
16260                  break;
16261              }
16262              case 'normal': {
16263                  $val = 100;
16264                  break;
16265              }
16266              case 'semi-expanded': {
16267                  $val = 115;
16268                  break;
16269              }
16270              case 'expanded': {
16271                  $val = 130;
16272                  break;
16273              }
16274              case 'extra-expanded': {
16275                  $val = 145;
16276                  break;
16277              }
16278              case 'ultra-expanded': {
16279                  $val = 160;
16280                  break;
16281              }
16282              case 'wider': {
16283                  $val = ($parent + 10);
16284                  break;
16285              }
16286              case 'narrower': {
16287                  $val = ($parent - 10);
16288                  break;
16289              }
16290              case 'inherit': {
16291                  if ($parent == 'normal') {
16292                      $val = 100;
16293                  } else {
16294                      $val = $parent;
16295                  }
16296                  break;
16297              }
16298              default: {
16299                  $val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
16300              }
16301          }
16302          return $val;
16303      }
16304  
16305      /**
16306       * Convert HTML string containing font size value to points
16307       * @param $val (string) String containing font size value and unit.
16308       * @param $refsize (float) Reference font size in points.
16309       * @param $parent_size (float) Parent font size in points.
16310       * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
16311       * @return float value in points
16312       * @public
16313       */
16314  	public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
16315          $refsize = TCPDF_FONTS::getFontRefSize($refsize);
16316          $parent_size = TCPDF_FONTS::getFontRefSize($parent_size, $refsize);
16317          switch ($val) {
16318              case 'xx-small': {
16319                  $size = ($refsize - 4);
16320                  break;
16321              }
16322              case 'x-small': {
16323                  $size = ($refsize - 3);
16324                  break;
16325              }
16326              case 'small': {
16327                  $size = ($refsize - 2);
16328                  break;
16329              }
16330              case 'medium': {
16331                  $size = $refsize;
16332                  break;
16333              }
16334              case 'large': {
16335                  $size = ($refsize + 2);
16336                  break;
16337              }
16338              case 'x-large': {
16339                  $size = ($refsize + 4);
16340                  break;
16341              }
16342              case 'xx-large': {
16343                  $size = ($refsize + 6);
16344                  break;
16345              }
16346              case 'smaller': {
16347                  $size = ($parent_size - 3);
16348                  break;
16349              }
16350              case 'larger': {
16351                  $size = ($parent_size + 3);
16352                  break;
16353              }
16354              default: {
16355                  $size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
16356              }
16357          }
16358          return $size;
16359      }
16360  
16361      /**
16362       * Returns the HTML DOM array.
16363       * @param $html (string) html code
16364       * @return array
16365       * @protected
16366       * @since 3.2.000 (2008-06-20)
16367       */
16368  	protected function getHtmlDomArray($html) {
16369          // array of CSS styles ( selector => properties).
16370          $css = array();
16371          // get CSS array defined at previous call
16372          $matches = array();
16373          if (preg_match_all('/<cssarray>([^\<]*)<\/cssarray>/isU', $html, $matches) > 0) {
16374              if (isset($matches[1][0])) {
16375                  $css = array_merge($css, json_decode($this->unhtmlentities($matches[1][0]), true));
16376              }
16377              $html = preg_replace('/<cssarray>(.*?)<\/cssarray>/isU', '', $html);
16378          }
16379          // extract external CSS files
16380          $matches = array();
16381          if (preg_match_all('/<link([^\>]*)>/isU', $html, $matches) > 0) {
16382              foreach ($matches[1] as $key => $link) {
16383                  $type = array();
16384                  if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
16385                      $type = array();
16386                      preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
16387                      // get 'all' and 'print' media, other media types are discarded
16388                      // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16389                      if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16390                          $type = array();
16391                          if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
16392                              // read CSS data file
16393                              $cssdata = TCPDF_STATIC::fileGetContents(trim($type[1]));
16394                              if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
16395                                  $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16396                              }
16397                          }
16398                      }
16399                  }
16400              }
16401          }
16402          // extract style tags
16403          $matches = array();
16404          if (preg_match_all('/<style([^\>]*)>([^\<]*)<\/style>/isU', $html, $matches) > 0) {
16405              foreach ($matches[1] as $key => $media) {
16406                  $type = array();
16407                  preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
16408                  // get 'all' and 'print' media, other media types are discarded
16409                  // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16410                  if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16411                      $cssdata = $matches[2][$key];
16412                      $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16413                  }
16414              }
16415          }
16416          // create a special tag to contain the CSS array (used for table content)
16417          $csstagarray = '<cssarray>'.htmlentities(json_encode($css)).'</cssarray>';
16418          // remove head and style blocks
16419          $html = preg_replace('/<head([^\>]*)>(.*?)<\/head>/siU', '', $html);
16420          $html = preg_replace('/<style([^\>]*)>([^\<]*)<\/style>/isU', '', $html);
16421          // define block tags
16422          $blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
16423          // define self-closing tags
16424          $selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
16425          // remove all unsupported tags (the line below lists all supported tags)
16426          $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>');
16427          //replace some blank characters
16428          $html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
16429          $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);
16430          $html = preg_replace('@(\r\n|\r)@', "\n", $html);
16431          $repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
16432          $html = strtr($html, $repTable);
16433          $offset = 0;
16434          while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
16435              $html_a = substr($html, 0, $offset);
16436              $html_b = substr($html, $offset, ($pos - $offset + 6));
16437              while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
16438                  // preserve newlines on <pre> tag
16439                  $html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
16440              }
16441              while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
16442                  // preserve spaces on <pre> tag
16443                  $html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
16444              }
16445              $html = $html_a.$html_b.substr($html, $pos + 6);
16446              $offset = strlen($html_a.$html_b);
16447          }
16448          $offset = 0;
16449          while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
16450              $html_a = substr($html, 0, $offset);
16451              $html_b = substr($html, $offset, ($pos - $offset + 11));
16452              while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
16453                  // preserve newlines on <textarea> tag
16454                  $html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
16455                  $html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
16456              }
16457              $html = $html_a.$html_b.substr($html, $pos + 11);
16458              $offset = strlen($html_a.$html_b);
16459          }
16460          $html = preg_replace('/([\s]*)<option/si', '<option', $html);
16461          $html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
16462          $offset = 0;
16463          while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
16464              $html_a = substr($html, 0, $offset);
16465              $html_b = substr($html, $offset, ($pos - $offset + 9));
16466              while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
16467                  $html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
16468                  $html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
16469              }
16470              $html = $html_a.$html_b.substr($html, $pos + 9);
16471              $offset = strlen($html_a.$html_b);
16472          }
16473          if (preg_match("'</select'si", $html)) {
16474              $html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
16475              $html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
16476          }
16477          $html = str_replace("\n", ' ', $html);
16478          // restore textarea newlines
16479          $html = str_replace('<TBR>', "\n", $html);
16480          // remove extra spaces from code
16481          $html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
16482          $html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
16483          $html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
16484          $html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
16485          $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);
16486          $html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
16487          $html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
16488          $html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
16489          $html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
16490          $html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
16491          $html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
16492          $html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
16493          $html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
16494          $html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
16495          $html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
16496          $html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
16497          $html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
16498          $html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
16499          $html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
16500          // trim string
16501          $html = $this->stringTrim($html);
16502          // fix br tag after li
16503          $html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
16504          // fix first image tag alignment
16505          $html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
16506          // pattern for generic tag
16507          $tagpattern = '/(<[^>]+>)/';
16508          // explodes the string
16509          $a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
16510          // count elements
16511          $maxel = count($a);
16512          $elkey = 0;
16513          $key = 0;
16514          // create an array of elements
16515          $dom = array();
16516          $dom[$key] = array();
16517          // set inheritable properties fot the first void element
16518          // 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
16519          $dom[$key]['tag'] = false;
16520          $dom[$key]['block'] = false;
16521          $dom[$key]['value'] = '';
16522          $dom[$key]['parent'] = 0;
16523          $dom[$key]['hide'] = false;
16524          $dom[$key]['fontname'] = $this->FontFamily;
16525          $dom[$key]['fontstyle'] = $this->FontStyle;
16526          $dom[$key]['fontsize'] = $this->FontSizePt;
16527          $dom[$key]['font-stretch'] = $this->font_stretching;
16528          $dom[$key]['letter-spacing'] = $this->font_spacing;
16529          $dom[$key]['stroke'] = $this->textstrokewidth;
16530          $dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
16531          $dom[$key]['clip'] = ($this->textrendermode > 3);
16532          $dom[$key]['line-height'] = $this->cell_height_ratio;
16533          $dom[$key]['bgcolor'] = false;
16534          $dom[$key]['fgcolor'] = $this->fgcolor; // color
16535          $dom[$key]['strokecolor'] = $this->strokecolor;
16536          $dom[$key]['align'] = '';
16537          $dom[$key]['listtype'] = '';
16538          $dom[$key]['text-indent'] = 0;
16539          $dom[$key]['text-transform'] = '';
16540          $dom[$key]['border'] = array();
16541          $dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
16542          $thead = false; // true when we are inside the THEAD tag
16543          ++$key;
16544          $level = array();
16545          array_push($level, 0); // root
16546          while ($elkey < $maxel) {
16547              $dom[$key] = array();
16548              $element = $a[$elkey];
16549              $dom[$key]['elkey'] = $elkey;
16550              if (preg_match($tagpattern, $element)) {
16551                  // html tag
16552                  $element = substr($element, 1, -1);
16553                  // get tag name
16554                  preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
16555                  $tagname = strtolower($tag[1]);
16556                  // check if we are inside a table header
16557                  if ($tagname == 'thead') {
16558                      if ($element[0] == '/') {
16559                          $thead = false;
16560                      } else {
16561                          $thead = true;
16562                      }
16563                      ++$elkey;
16564                      continue;
16565                  }
16566                  $dom[$key]['tag'] = true;
16567                  $dom[$key]['value'] = $tagname;
16568                  if (in_array($dom[$key]['value'], $blocktags)) {
16569                      $dom[$key]['block'] = true;
16570                  } else {
16571                      $dom[$key]['block'] = false;
16572                  }
16573                  if ($element[0] == '/') {
16574                      // *** closing html tag
16575                      $dom[$key]['opening'] = false;
16576                      $dom[$key]['parent'] = end($level);
16577                      array_pop($level);
16578                      $dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
16579                      $dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
16580                      $dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
16581                      $dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
16582                      $dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
16583                      $dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
16584                      $dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
16585                      $dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
16586                      $dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
16587                      $dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
16588                      $dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
16589                      $dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
16590                      $dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
16591                      $dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
16592                      $dom[$key]['text-transform'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['text-transform'];
16593                      $dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
16594                      if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
16595                          $dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
16596                      }
16597                      // set the number of columns in table tag
16598                      if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
16599                          $dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
16600                      }
16601                      if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
16602                          $dom[($dom[$key]['parent'])]['content'] = $csstagarray;
16603                          for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
16604                              $dom[($dom[$key]['parent'])]['content'] .= $a[$dom[$i]['elkey']];
16605                          }
16606                          $key = $i;
16607                          // mark nested tables
16608                          $dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
16609                          // remove thead sections from nested tables
16610                          $dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
16611                          $dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
16612                      }
16613                      // store header rows on a new table
16614                      if (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['thead'] === true)) {
16615                          if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
16616                              $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
16617                          }
16618                          for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
16619                              $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
16620                          }
16621                          if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
16622                              $dom[($dom[$key]['parent'])]['attribute'] = array();
16623                          }
16624                          // header elements must be always contained in a single page
16625                          $dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
16626                      }
16627                      if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
16628                          // remove the nobr attributes from the table header
16629                          $dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
16630                          $dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
16631                      }
16632                  } else {
16633                      // *** opening or self-closing html tag
16634                      $dom[$key]['opening'] = true;
16635                      $dom[$key]['parent'] = end($level);
16636                      if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
16637                          // self-closing tag
16638                          $dom[$key]['self'] = true;
16639                      } else {
16640                          // opening tag
16641                          array_push($level, $key);
16642                          $dom[$key]['self'] = false;
16643                      }
16644                      // copy some values from parent
16645                      $parentkey = 0;
16646                      if ($key > 0) {
16647                          $parentkey = $dom[$key]['parent'];
16648                          $dom[$key]['hide'] = $dom[$parentkey]['hide'];
16649                          $dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
16650                          $dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
16651                          $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
16652                          $dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
16653                          $dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
16654                          $dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
16655                          $dom[$key]['fill'] = $dom[$parentkey]['fill'];
16656                          $dom[$key]['clip'] = $dom[$parentkey]['clip'];
16657                          $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16658                          $dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
16659                          $dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
16660                          $dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
16661                          $dom[$key]['align'] = $dom[$parentkey]['align'];
16662                          $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16663                          $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16664                          $dom[$key]['text-transform'] = $dom[$parentkey]['text-transform'];
16665                          $dom[$key]['border'] = array();
16666                          $dom[$key]['dir'] = $dom[$parentkey]['dir'];
16667                      }
16668                      // get attributes
16669                      preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
16670                      $dom[$key]['attribute'] = array(); // reset attribute array
16671                      while (list($id, $name) = each($attr_array[1])) {
16672                          $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
16673                      }
16674                      if (!empty($css)) {
16675                          // merge CSS style to current style
16676                          list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC::getCSSdataArray($dom, $key, $css);
16677                          $dom[$key]['attribute']['style'] = TCPDF_STATIC::getTagStyleFromCSSarray($dom[$key]['cssdata']);
16678                      }
16679                      // split style attributes
16680                      if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
16681                          // get style attributes
16682                          preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
16683                          $dom[$key]['style'] = array(); // reset style attribute array
16684                          while (list($id, $name) = each($style_array[1])) {
16685                              // in case of duplicate attribute the last replace the previous
16686                              $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
16687                          }
16688                          // --- get some style attributes ---
16689                          // text direction
16690                          if (isset($dom[$key]['style']['direction'])) {
16691                              $dom[$key]['dir'] = $dom[$key]['style']['direction'];
16692                          }
16693                          // display
16694                          if (isset($dom[$key]['style']['display'])) {
16695                              $dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
16696                          }
16697                          // font family
16698                          if (isset($dom[$key]['style']['font-family'])) {
16699                              $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
16700                          }
16701                          // list-style-type
16702                          if (isset($dom[$key]['style']['list-style-type'])) {
16703                              $dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
16704                              if ($dom[$key]['listtype'] == 'inherit') {
16705                                  $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16706                              }
16707                          }
16708                          // text-indent
16709                          if (isset($dom[$key]['style']['text-indent'])) {
16710                              $dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
16711                              if ($dom[$key]['text-indent'] == 'inherit') {
16712                                  $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16713                              }
16714                          }
16715                          // text-transform
16716                          if (isset($dom[$key]['style']['text-transform'])) {
16717                              $dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
16718                          }
16719                          // font size
16720                          if (isset($dom[$key]['style']['font-size'])) {
16721                              $fsize = trim($dom[$key]['style']['font-size']);
16722                              $dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
16723                          }
16724                          // font-stretch
16725                          if (isset($dom[$key]['style']['font-stretch'])) {
16726                              $dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
16727                          }
16728                          // letter-spacing
16729                          if (isset($dom[$key]['style']['letter-spacing'])) {
16730                              $dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
16731                          }
16732                          // line-height (internally is the cell height ratio)
16733                          if (isset($dom[$key]['style']['line-height'])) {
16734                              $lineheight = trim($dom[$key]['style']['line-height']);
16735                              switch ($lineheight) {
16736                                  // A normal line height. This is default
16737                                  case 'normal': {
16738                                      $dom[$key]['line-height'] = $dom[0]['line-height'];
16739                                      break;
16740                                  }
16741                                  case 'inherit': {
16742                                      $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16743                                  }
16744                                  default: {
16745                                      if (is_numeric($lineheight)) {
16746                                          // convert to percentage of font height
16747                                          $lineheight = ($lineheight * 100).'%';
16748                                      }
16749                                      $dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
16750                                      if (substr($lineheight, -1) !== '%') {
16751                                          if ($dom[$key]['fontsize'] <= 0) {
16752                                              $dom[$key]['line-height'] = 1;
16753                                          } else {
16754                                              $dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding['T'] - $this->cell_padding['B']) / $dom[$key]['fontsize']);
16755                                          }
16756                                      }
16757                                  }
16758                              }
16759                          }
16760                          // font style
16761                          if (isset($dom[$key]['style']['font-weight'])) {
16762                              if (strtolower($dom[$key]['style']['font-weight'][0]) == 'n') {
16763                                  if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
16764                                      $dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
16765                                  }
16766                              } elseif (strtolower($dom[$key]['style']['font-weight'][0]) == 'b') {
16767                                  $dom[$key]['fontstyle'] .= 'B';
16768                              }
16769                          }
16770                          if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style'][0]) == 'i')) {
16771                              $dom[$key]['fontstyle'] .= 'I';
16772                          }
16773                          // font color
16774                          if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['color']))) {
16775                              $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors);
16776                          } elseif ($dom[$key]['value'] == 'a') {
16777                              $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16778                          }
16779                          // background color
16780                          if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['background-color']))) {
16781                              $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors);
16782                          }
16783                          // text-decoration
16784                          if (isset($dom[$key]['style']['text-decoration'])) {
16785                              $decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
16786                              foreach ($decors as $dec) {
16787                                  $dec = trim($dec);
16788                                  if (!TCPDF_STATIC::empty_string($dec)) {
16789                                      if ($dec[0] == 'u') {
16790                                          // underline
16791                                          $dom[$key]['fontstyle'] .= 'U';
16792                                      } elseif ($dec[0] == 'l') {
16793                                          // line-through
16794                                          $dom[$key]['fontstyle'] .= 'D';
16795                                      } elseif ($dec[0] == 'o') {
16796                                          // overline
16797                                          $dom[$key]['fontstyle'] .= 'O';
16798                                      }
16799                                  }
16800                              }
16801                          } elseif ($dom[$key]['value'] == 'a') {
16802                              $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16803                          }
16804                          // check for width attribute
16805                          if (isset($dom[$key]['style']['width'])) {
16806                              $dom[$key]['width'] = $dom[$key]['style']['width'];
16807                          }
16808                          // check for height attribute
16809                          if (isset($dom[$key]['style']['height'])) {
16810                              $dom[$key]['height'] = $dom[$key]['style']['height'];
16811                          }
16812                          // check for text alignment
16813                          if (isset($dom[$key]['style']['text-align'])) {
16814                              $dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align'][0]);
16815                          }
16816                          // check for CSS border properties
16817                          if (isset($dom[$key]['style']['border'])) {
16818                              $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
16819                              if (!empty($borderstyle)) {
16820                                  $dom[$key]['border']['LTRB'] = $borderstyle;
16821                              }
16822                          }
16823                          if (isset($dom[$key]['style']['border-color'])) {
16824                              $brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
16825                              if (isset($brd_colors[3])) {
16826                                  $dom[$key]['border']['L']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[3], $this->spot_colors);
16827                              }
16828                              if (isset($brd_colors[1])) {
16829                                  $dom[$key]['border']['R']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[1], $this->spot_colors);
16830                              }
16831                              if (isset($brd_colors[0])) {
16832                                  $dom[$key]['border']['T']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[0], $this->spot_colors);
16833                              }
16834                              if (isset($brd_colors[2])) {
16835                                  $dom[$key]['border']['B']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[2], $this->spot_colors);
16836                              }
16837                          }
16838                          if (isset($dom[$key]['style']['border-width'])) {
16839                              $brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
16840                              if (isset($brd_widths[3])) {
16841                                  $dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
16842                              }
16843                              if (isset($brd_widths[1])) {
16844                                  $dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
16845                              }
16846                              if (isset($brd_widths[0])) {
16847                                  $dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
16848                              }
16849                              if (isset($brd_widths[2])) {
16850                                  $dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
16851                              }
16852                          }
16853                          if (isset($dom[$key]['style']['border-style'])) {
16854                              $brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
16855                              if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
16856                                  $dom[$key]['border']['L']['cap'] = 'square';
16857                                  $dom[$key]['border']['L']['join'] = 'miter';
16858                                  $dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
16859                                  if ($dom[$key]['border']['L']['dash'] < 0) {
16860                                      $dom[$key]['border']['L'] = array();
16861                                  }
16862                              }
16863                              if (isset($brd_styles[1])) {
16864                                  $dom[$key]['border']['R']['cap'] = 'square';
16865                                  $dom[$key]['border']['R']['join'] = 'miter';
16866                                  $dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
16867                                  if ($dom[$key]['border']['R']['dash'] < 0) {
16868                                      $dom[$key]['border']['R'] = array();
16869                                  }
16870                              }
16871                              if (isset($brd_styles[0])) {
16872                                  $dom[$key]['border']['T']['cap'] = 'square';
16873                                  $dom[$key]['border']['T']['join'] = 'miter';
16874                                  $dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
16875                                  if ($dom[$key]['border']['T']['dash'] < 0) {
16876                                      $dom[$key]['border']['T'] = array();
16877                                  }
16878                              }
16879                              if (isset($brd_styles[2])) {
16880                                  $dom[$key]['border']['B']['cap'] = 'square';
16881                                  $dom[$key]['border']['B']['join'] = 'miter';
16882                                  $dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
16883                                  if ($dom[$key]['border']['B']['dash'] < 0) {
16884                                      $dom[$key]['border']['B'] = array();
16885                                  }
16886                              }
16887                          }
16888                          $cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
16889                          foreach ($cellside as $bsk => $bsv) {
16890                              if (isset($dom[$key]['style']['border-'.$bsv])) {
16891                                  $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
16892                                  if (!empty($borderstyle)) {
16893                                      $dom[$key]['border'][$bsk] = $borderstyle;
16894                                  }
16895                              }
16896                              if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
16897                                  $dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors);
16898                              }
16899                              if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
16900                                  $dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
16901                              }
16902                              if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
16903                                  $dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
16904                                  if ($dom[$key]['border'][$bsk]['dash'] < 0) {
16905                                      $dom[$key]['border'][$bsk] = array();
16906                                  }
16907                              }
16908                          }
16909                          // check for CSS padding properties
16910                          if (isset($dom[$key]['style']['padding'])) {
16911                              $dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
16912                          } else {
16913                              $dom[$key]['padding'] = $this->cell_padding;
16914                          }
16915                          foreach ($cellside as $psk => $psv) {
16916                              if (isset($dom[$key]['style']['padding-'.$psv])) {
16917                                  $dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
16918                              }
16919                          }
16920                          // check for CSS margin properties
16921                          if (isset($dom[$key]['style']['margin'])) {
16922                              $dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
16923                          } else {
16924                              $dom[$key]['margin'] = $this->cell_margin;
16925                          }
16926                          foreach ($cellside as $psk => $psv) {
16927                              if (isset($dom[$key]['style']['margin-'.$psv])) {
16928                                  $dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
16929                              }
16930                          }
16931                          // check for CSS border-spacing properties
16932                          if (isset($dom[$key]['style']['border-spacing'])) {
16933                              $dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
16934                          }
16935                          // page-break-inside
16936                          if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
16937                              $dom[$key]['attribute']['nobr'] = 'true';
16938                          }
16939                          // page-break-before
16940                          if (isset($dom[$key]['style']['page-break-before'])) {
16941                              if ($dom[$key]['style']['page-break-before'] == 'always') {
16942                                  $dom[$key]['attribute']['pagebreak'] = 'true';
16943                              } elseif ($dom[$key]['style']['page-break-before'] == 'left') {
16944                                  $dom[$key]['attribute']['pagebreak'] = 'left';
16945                              } elseif ($dom[$key]['style']['page-break-before'] == 'right') {
16946                                  $dom[$key]['attribute']['pagebreak'] = 'right';
16947                              }
16948                          }
16949                          // page-break-after
16950                          if (isset($dom[$key]['style']['page-break-after'])) {
16951                              if ($dom[$key]['style']['page-break-after'] == 'always') {
16952                                  $dom[$key]['attribute']['pagebreakafter'] = 'true';
16953                              } elseif ($dom[$key]['style']['page-break-after'] == 'left') {
16954                                  $dom[$key]['attribute']['pagebreakafter'] = 'left';
16955                              } elseif ($dom[$key]['style']['page-break-after'] == 'right') {
16956                                  $dom[$key]['attribute']['pagebreakafter'] = 'right';
16957                              }
16958                          }
16959                      }
16960                      if (isset($dom[$key]['attribute']['display'])) {
16961                          $dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
16962                      }
16963                      if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
16964                          $borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
16965                          if (!empty($borderstyle)) {
16966                              $dom[$key]['border']['LTRB'] = $borderstyle;
16967                          }
16968                      }
16969                      // check for font tag
16970                      if ($dom[$key]['value'] == 'font') {
16971                          // font family
16972                          if (isset($dom[$key]['attribute']['face'])) {
16973                              $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
16974                          }
16975                          // font size
16976                          if (isset($dom[$key]['attribute']['size'])) {
16977                              if ($key > 0) {
16978                                  if ($dom[$key]['attribute']['size'][0] == '+') {
16979                                      $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
16980                                  } elseif ($dom[$key]['attribute']['size'][0] == '-') {
16981                                      $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
16982                                  } else {
16983                                      $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16984                                  }
16985                              } else {
16986                                  $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16987                              }
16988                          }
16989                      }
16990                      // force natural alignment for lists
16991                      if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
16992                          AND (!isset($dom[$key]['align']) OR TCPDF_STATIC::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
16993                          if ($this->rtl) {
16994                              $dom[$key]['align'] = 'R';
16995                          } else {
16996                              $dom[$key]['align'] = 'L';
16997                          }
16998                      }
16999                      if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
17000                          if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
17001                              $dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
17002                          }
17003                      }
17004                      if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
17005                          $dom[$key]['fontstyle'] .= 'B';
17006                      }
17007                      if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
17008                          $dom[$key]['fontstyle'] .= 'I';
17009                      }
17010                      if ($dom[$key]['value'] == 'u') {
17011                          $dom[$key]['fontstyle'] .= 'U';
17012                      }
17013                      if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
17014                          $dom[$key]['fontstyle'] .= 'D';
17015                      }
17016                      if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
17017                          $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
17018                      }
17019                      if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
17020                          $dom[$key]['fontname'] = $this->default_monospaced_font;
17021                      }
17022                      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)) {
17023                          // headings h1, h2, h3, h4, h5, h6
17024                          if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
17025                              $headsize = (4 - intval($dom[$key]['value']{1})) * 2;
17026                              $dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
17027                          }
17028                          if (!isset($dom[$key]['style']['font-weight'])) {
17029                              $dom[$key]['fontstyle'] .= 'B';
17030                          }
17031                      }
17032                      if (($dom[$key]['value'] == 'table')) {
17033                          $dom[$key]['rows'] = 0; // number of rows
17034                          $dom[$key]['trids'] = array(); // IDs of TR elements
17035                          $dom[$key]['thead'] = ''; // table header rows
17036                      }
17037                      if (($dom[$key]['value'] == 'tr')) {
17038                          $dom[$key]['cols'] = 0;
17039                          if ($thead) {
17040                              $dom[$key]['thead'] = true;
17041                              // rows on thead block are printed as a separate table
17042                          } else {
17043                              $dom[$key]['thead'] = false;
17044                              // store the number of rows on table element
17045                              ++$dom[($dom[$key]['parent'])]['rows'];
17046                              // store the TR elements IDs on table element
17047                              array_push($dom[($dom[$key]['parent'])]['trids'], $key);
17048                          }
17049                      }
17050                      if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
17051                          if (isset($dom[$key]['attribute']['colspan'])) {
17052                              $colspan = intval($dom[$key]['attribute']['colspan']);
17053                          } else {
17054                              $colspan = 1;
17055                          }
17056                          $dom[$key]['attribute']['colspan'] = $colspan;
17057                          $dom[($dom[$key]['parent'])]['cols'] += $colspan;
17058                      }
17059                      // text direction
17060                      if (isset($dom[$key]['attribute']['dir'])) {
17061                          $dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
17062                      }
17063                      // set foreground color attribute
17064                      if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['color']))) {
17065                          $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors);
17066                      } elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
17067                          $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
17068                      }
17069                      // set background color attribute
17070                      if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['bgcolor']))) {
17071                          $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors);
17072                      }
17073                      // set stroke color attribute
17074                      if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['strokecolor']))) {
17075                          $dom[$key]['strokecolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors);
17076                      }
17077                      // check for width attribute
17078                      if (isset($dom[$key]['attribute']['width'])) {
17079                          $dom[$key]['width'] = $dom[$key]['attribute']['width'];
17080                      }
17081                      // check for height attribute
17082                      if (isset($dom[$key]['attribute']['height'])) {
17083                          $dom[$key]['height'] = $dom[$key]['attribute']['height'];
17084                      }
17085                      // check for text alignment
17086                      if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
17087                          $dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align'][0]);
17088                      }
17089                      // check for text rendering mode (the following attributes do not exist in HTML)
17090                      if (isset($dom[$key]['attribute']['stroke'])) {
17091                          // font stroke width
17092                          $dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
17093                      }
17094                      if (isset($dom[$key]['attribute']['fill'])) {
17095                          // font fill
17096                          if ($dom[$key]['attribute']['fill'] == 'true') {
17097                              $dom[$key]['fill'] = true;
17098                          } else {
17099                              $dom[$key]['fill'] = false;
17100                          }
17101                      }
17102                      if (isset($dom[$key]['attribute']['clip'])) {
17103                          // clipping mode
17104                          if ($dom[$key]['attribute']['clip'] == 'true') {
17105                              $dom[$key]['clip'] = true;
17106                          } else {
17107                              $dom[$key]['clip'] = false;
17108                          }
17109                      }
17110                  } // end opening tag
17111              } else {
17112                  // text
17113                  $dom[$key]['tag'] = false;
17114                  $dom[$key]['block'] = false;
17115                  $dom[$key]['parent'] = end($level);
17116                  $dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
17117                  if (!empty($dom[$dom[$key]['parent']]['text-transform'])) {
17118                      // text-transform for unicode requires mb_convert_case (Multibyte String Functions)
17119                      if (function_exists('mb_convert_case')) {
17120                          $ttm = array('capitalize' => MB_CASE_TITLE, 'uppercase' => MB_CASE_UPPER, 'lowercase' => MB_CASE_LOWER);
17121                          if (isset($ttm[$dom[$dom[$key]['parent']]['text-transform']])) {
17122                              $element = mb_convert_case($element, $ttm[$dom[$dom[$key]['parent']]['text-transform']], $this->encoding);
17123                          }
17124                      } elseif (!$this->isunicode) {
17125                          switch ($dom[$dom[$key]['parent']]['text-transform']) {
17126                              case 'capitalize': {
17127                                  $element = ucwords(strtolower($element));
17128                                  break;
17129                              }
17130                              case 'uppercase': {
17131                                  $element = strtoupper($element);
17132                                  break;
17133                              }
17134                              case 'lowercase': {
17135                                  $element = strtolower($element);
17136                                  break;
17137                              }
17138                          }
17139                      }
17140                  }
17141                  $dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
17142              }
17143              ++$elkey;
17144              ++$key;
17145          }
17146          return $dom;
17147      }
17148  
17149      /**
17150       * Returns the string used to find spaces
17151       * @return string
17152       * @protected
17153       * @author Nicola Asuni
17154       * @since 4.8.024 (2010-01-15)
17155       */
17156  	protected function getSpaceString() {
17157          $spacestr = chr(32);
17158          if ($this->isUnicodeFont()) {
17159              $spacestr = chr(0).chr(32);
17160          }
17161          return $spacestr;
17162      }
17163  
17164      /**
17165       * Serialize an array of parameters to be used with TCPDF tag in HTML code.
17166       * @param $pararray (array) parameters array
17167       * @return sting containing serialized data
17168       * @since 4.9.006 (2010-04-02)
17169       * @public
17170       * @deprecated
17171       */
17172  	public function serializeTCPDFtagParameters($pararray) {
17173          return TCPDF_STATIC::serializeTCPDFtagParameters($pararray);
17174      }
17175  
17176      /**
17177       * Prints a cell (rectangular area) with optional borders, background color and html text string.
17178       * 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 />
17179       * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
17180       * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17181       * 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
17182       * NOTE: all the HTML attributes must be enclosed in double-quote.
17183       * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
17184       * @param $h (float) Cell minimum height. The cell extends automatically if needed.
17185       * @param $x (float) upper-left corner X coordinate
17186       * @param $y (float) upper-left corner Y coordinate
17187       * @param $html (string) html text to print. Default value: empty string.
17188       * @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)))
17189       * @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>
17190  Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
17191       * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
17192       * @param $reseth (boolean) if true reset the last cell height (default true).
17193       * @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>
17194       * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
17195       * @see Multicell(), writeHTML()
17196       * @public
17197       */
17198  	public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
17199          return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
17200      }
17201  
17202      /**
17203       * Allows to preserve some HTML formatting (limited support).<br />
17204       * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17205       * 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
17206       * NOTE: all the HTML attributes must be enclosed in double-quote.
17207       * @param $html (string) text to display
17208       * @param $ln (boolean) if true add a new line after text (default = true)
17209       * @param $fill (boolean) Indicates if the background must be painted (true) or transparent (false).
17210       * @param $reseth (boolean) if true reset the last cell height (default false).
17211       * @param $cell (boolean) if true add the current left (or right for RTL) padding to each Write (default false).
17212       * @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>
17213       * @public
17214       */
17215  	public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
17216          $gvars = $this->getGraphicVars();
17217          // store current values
17218          $prev_cell_margin = $this->cell_margin;
17219          $prev_cell_padding = $this->cell_padding;
17220          $prevPage = $this->page;
17221          $prevlMargin = $this->lMargin;
17222          $prevrMargin = $this->rMargin;
17223          $curfontname = $this->FontFamily;
17224          $curfontstyle = $this->FontStyle;
17225          $curfontsize = $this->FontSizePt;
17226          $curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
17227          $curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
17228          $curfontstretcing = $this->font_stretching;
17229          $curfonttracking = $this->font_spacing;
17230          $this->newline = true;
17231          $newline = true;
17232          $startlinepage = $this->page;
17233          $minstartliney = $this->y;
17234          $maxbottomliney = 0;
17235          $startlinex = $this->x;
17236          $startliney = $this->y;
17237          $yshift = 0;
17238          $loop = 0;
17239          $curpos = 0;
17240          $this_method_vars = array();
17241          $undo = false;
17242          $fontaligned = false;
17243          $reverse_dir = false; // true when the text direction is reversed
17244          $this->premode = false;
17245          if ($this->inxobj) {
17246              // we are inside an XObject template
17247              $pask = count($this->xobjects[$this->xobjid]['annotations']);
17248          } elseif (isset($this->PageAnnots[$this->page])) {
17249              $pask = count($this->PageAnnots[$this->page]);
17250          } else {
17251              $pask = 0;
17252          }
17253          if ($this->inxobj) {
17254              // we are inside an XObject template
17255              $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17256          } elseif (!$this->InFooter) {
17257              if (isset($this->footerlen[$this->page])) {
17258                  $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17259              } else {
17260                  $this->footerpos[$this->page] = $this->pagelen[$this->page];
17261              }
17262              $startlinepos = $this->footerpos[$this->page];
17263          } else {
17264              // we are inside the footer
17265              $startlinepos = $this->pagelen[$this->page];
17266          }
17267          $lalign = $align;
17268          $plalign = $align;
17269          if ($this->rtl) {
17270              $w = $this->x - $this->lMargin;
17271          } else {
17272              $w = $this->w - $this->rMargin - $this->x;
17273          }
17274          $w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
17275          if ($cell) {
17276              if ($this->rtl) {
17277                  $this->x -= $this->cell_padding['R'];
17278                  $this->lMargin += $this->cell_padding['R'];
17279              } else {
17280                  $this->x += $this->cell_padding['L'];
17281                  $this->rMargin += $this->cell_padding['L'];
17282              }
17283          }
17284          if ($this->customlistindent >= 0) {
17285              $this->listindent = $this->customlistindent;
17286          } else {
17287              $this->listindent = $this->GetStringWidth('000000');
17288          }
17289          $this->listindentlevel = 0;
17290          // save previous states
17291          $prev_cell_height_ratio = $this->cell_height_ratio;
17292          $prev_listnum = $this->listnum;
17293          $prev_listordered = $this->listordered;
17294          $prev_listcount = $this->listcount;
17295          $prev_lispacer = $this->lispacer;
17296          $this->listnum = 0;
17297          $this->listordered = array();
17298          $this->listcount = array();
17299          $this->lispacer = '';
17300          if ((TCPDF_STATIC::empty_string($this->lasth)) OR ($reseth)) {
17301              // reset row height
17302              $this->resetLastH();
17303          }
17304          $dom = $this->getHtmlDomArray($html);
17305          $maxel = count($dom);
17306          $key = 0;
17307          while ($key < $maxel) {
17308              if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND $dom[$key]['hide']) {
17309                  // store the node key
17310                  $hidden_node_key = $key;
17311                  if ($dom[$key]['self']) {
17312                      // skip just this self-closing tag
17313                      ++$key;
17314                  } else {
17315                      // skip this and all children tags
17316                      while (($key < $maxel) AND (!$dom[$key]['tag'] OR $dom[$key]['opening'] OR ($dom[$key]['parent'] != $hidden_node_key))) {
17317                          // skip hidden objects
17318                          ++$key;
17319                      }
17320                      ++$key;
17321                  }
17322              }
17323              if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
17324                  // check for pagebreak
17325                  if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
17326                      // add a page (or trig AcceptPageBreak() for multicolumn mode)
17327                      $this->checkPageBreak($this->PageBreakTrigger + 1);
17328                      $this->htmlvspace = ($this->PageBreakTrigger + 1);
17329                  }
17330                  if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
17331                      OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
17332                      // add a page (or trig AcceptPageBreak() for multicolumn mode)
17333                      $this->checkPageBreak($this->PageBreakTrigger + 1);
17334                      $this->htmlvspace = ($this->PageBreakTrigger + 1);
17335                  }
17336              }
17337              if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
17338                  if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
17339                      $dom[$key]['attribute']['nobr'] = false;
17340                  } else {
17341                      // store current object
17342                      $this->startTransaction();
17343                      // save this method vars
17344                      $this_method_vars['html'] = $html;
17345                      $this_method_vars['ln'] = $ln;
17346                      $this_method_vars['fill'] = $fill;
17347                      $this_method_vars['reseth'] = $reseth;
17348                      $this_method_vars['cell'] = $cell;
17349                      $this_method_vars['align'] = $align;
17350                      $this_method_vars['gvars'] = $gvars;
17351                      $this_method_vars['prevPage'] = $prevPage;
17352                      $this_method_vars['prev_cell_margin'] = $prev_cell_margin;
17353                      $this_method_vars['prev_cell_padding'] = $prev_cell_padding;
17354                      $this_method_vars['prevlMargin'] = $prevlMargin;
17355                      $this_method_vars['prevrMargin'] = $prevrMargin;
17356                      $this_method_vars['curfontname'] = $curfontname;
17357                      $this_method_vars['curfontstyle'] = $curfontstyle;
17358                      $this_method_vars['curfontsize'] = $curfontsize;
17359                      $this_method_vars['curfontascent'] = $curfontascent;
17360                      $this_method_vars['curfontdescent'] = $curfontdescent;
17361                      $this_method_vars['curfontstretcing'] = $curfontstretcing;
17362                      $this_method_vars['curfonttracking'] = $curfonttracking;
17363                      $this_method_vars['minstartliney'] = $minstartliney;
17364                      $this_method_vars['maxbottomliney'] = $maxbottomliney;
17365                      $this_method_vars['yshift'] = $yshift;
17366                      $this_method_vars['startlinepage'] = $startlinepage;
17367                      $this_method_vars['startlinepos'] = $startlinepos;
17368                      $this_method_vars['startlinex'] = $startlinex;
17369                      $this_method_vars['startliney'] = $startliney;
17370                      $this_method_vars['newline'] = $newline;
17371                      $this_method_vars['loop'] = $loop;
17372                      $this_method_vars['curpos'] = $curpos;
17373                      $this_method_vars['pask'] = $pask;
17374                      $this_method_vars['lalign'] = $lalign;
17375                      $this_method_vars['plalign'] = $plalign;
17376                      $this_method_vars['w'] = $w;
17377                      $this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
17378                      $this_method_vars['prev_listnum'] = $prev_listnum;
17379                      $this_method_vars['prev_listordered'] = $prev_listordered;
17380                      $this_method_vars['prev_listcount'] = $prev_listcount;
17381                      $this_method_vars['prev_lispacer'] = $prev_lispacer;
17382                      $this_method_vars['fontaligned'] = $fontaligned;
17383                      $this_method_vars['key'] = $key;
17384                      $this_method_vars['dom'] = $dom;
17385                  }
17386              }
17387              // print THEAD block
17388              if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
17389                  if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC::empty_string($dom[$dom[$key]['parent']]['thead'])) {
17390                      $this->inthead = true;
17391                      // print table header (thead)
17392                      $this->writeHTML($this->thead, false, false, false, false, '');
17393                      // check if we are on a new page or on a new column
17394                      if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
17395                          // we are on a new page or on a new column and the total object height is less than the available vertical space.
17396                          // restore previous object
17397                          $this->rollbackTransaction(true);
17398                          // restore previous values
17399                          foreach ($this_method_vars as $vkey => $vval) {
17400                              $$vkey = $vval;
17401                          }
17402                          // disable table header
17403                          $tmp_thead = $this->thead;
17404                          $this->thead = '';
17405                          // add a page (or trig AcceptPageBreak() for multicolumn mode)
17406                          $pre_y = $this->y;
17407                          if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
17408                              // fix for multicolumn mode
17409                              $startliney = $this->y;
17410                          }
17411                          $this->start_transaction_page = $this->page;
17412                          $this->start_transaction_y = $this->y;
17413                          // restore table header
17414                          $this->thead = $tmp_thead;
17415                          // fix table border properties
17416                          if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
17417                              $tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
17418                          } elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
17419                              $tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
17420                          } else {
17421                              $tmp_cellspacing = 0;
17422                          }
17423                          $dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
17424                          $dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
17425                          $dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
17426                          $xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
17427                          $dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
17428                          $dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
17429                          // print table header (thead)
17430                          $this->writeHTML($this->thead, false, false, false, false, '');
17431                      }
17432                  }
17433                  // move $key index forward to skip THEAD block
17434                  while ( ($key < $maxel) AND (!(
17435                      ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
17436                      OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
17437                      ++$key;
17438                  }
17439              }
17440              if ($dom[$key]['tag'] OR ($key == 0)) {
17441                  if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
17442                      $dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
17443                  }
17444                  // vertically align image in line
17445                  if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
17446                      // get image height
17447                      $imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], ($dom[$key]['fontsize'] / $this->k), 'px');
17448                      $autolinebreak = false;
17449                      if (!empty($dom[$key]['width'])) {
17450                          $imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], ($dom[$key]['fontsize'] / $this->k), 'px', false);
17451                          if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
17452                              AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
17453                              OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
17454                              // add automatic line break
17455                              $autolinebreak = true;
17456                              $this->Ln('', $cell);
17457                              if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
17458                                  // go back to evaluate this line break
17459                                  --$key;
17460                              }
17461                          }
17462                      }
17463                      if (!$autolinebreak) {
17464                          if ($this->inPageBody()) {
17465                              $pre_y = $this->y;
17466                              // check for page break
17467                              if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
17468                                  // fix for multicolumn mode
17469                                  $startliney = $this->y;
17470                              }
17471                          }
17472                          if ($this->page > $startlinepage) {
17473                              // fix line splitted over two pages
17474                              if (isset($this->footerlen[$startlinepage])) {
17475                                  $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17476                              }
17477                              // line to be moved one page forward
17478                              $pagebuff = $this->getPageBuffer($startlinepage);
17479                              $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17480                              $tstart = substr($pagebuff, 0, $startlinepos);
17481                              $tend = substr($this->getPageBuffer($startlinepage), $curpos);
17482                              // remove line from previous page
17483                              $this->setPageBuffer($startlinepage, $tstart.''.$tend);
17484                              $pagebuff = $this->getPageBuffer($this->page);
17485                              $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17486                              $tend = substr($pagebuff, $this->cntmrk[$this->page]);
17487                              // add line start to current page
17488                              $yshift = ($minstartliney - $this->y);
17489                              if ($fontaligned) {
17490                                  $yshift += ($curfontsize / $this->k);
17491                              }
17492                              $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17493                              $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17494                              // shift the annotations and links
17495                              if (isset($this->PageAnnots[$this->page])) {
17496                                  $next_pask = count($this->PageAnnots[$this->page]);
17497                              } else {
17498                                  $next_pask = 0;
17499                              }
17500                              if (isset($this->PageAnnots[$startlinepage])) {
17501                                  foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17502                                      if ($pak >= $pask) {
17503                                          $this->PageAnnots[$this->page][] = $pac;
17504                                          unset($this->PageAnnots[$startlinepage][$pak]);
17505                                          $npak = count($this->PageAnnots[$this->page]) - 1;
17506                                          $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17507                                      }
17508                                  }
17509                              }
17510                              $pask = $next_pask;
17511                              $startlinepos = $this->cntmrk[$this->page];
17512                              $startlinepage = $this->page;
17513                              $startliney = $this->y;
17514                              $this->newline = false;
17515                          }
17516                          $this->y += ($this->getCellHeight($curfontsize / $this->k) - ($curfontdescent * $this->cell_height_ratio) - $imgh);
17517                          $minstartliney = min($this->y, $minstartliney);
17518                          $maxbottomliney = ($startliney + $this->getCellHeight($curfontsize / $this->k));
17519                      }
17520                  } elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
17521                      // account for different font size
17522                      $pfontname = $curfontname;
17523                      $pfontstyle = $curfontstyle;
17524                      $pfontsize = $curfontsize;
17525                      $fontname = (isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname);
17526                      $fontstyle = (isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle);
17527                      $fontsize = (isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize);
17528                      $fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
17529                      $fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
17530                      if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
17531                          OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17532                          OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
17533                          if (($key < ($maxel - 1)) AND (
17534                                  ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
17535                                  OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17536                                  OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize)
17537                                  AND ($fontsize >= 0) AND ($curfontsize >= 0)
17538                                  AND (($fontsize != $curfontsize) OR ($fontstyle != $curfontstyle) OR ($fontname != $curfontname)))
17539                              )) {
17540                              if ($this->page > $startlinepage) {
17541                                  // fix lines splitted over two pages
17542                                  if (isset($this->footerlen[$startlinepage])) {
17543                                      $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17544                                  }
17545                                  // line to be moved one page forward
17546                                  $pagebuff = $this->getPageBuffer($startlinepage);
17547                                  $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17548                                  $tstart = substr($pagebuff, 0, $startlinepos);
17549                                  $tend = substr($this->getPageBuffer($startlinepage), $curpos);
17550                                  // remove line start from previous page
17551                                  $this->setPageBuffer($startlinepage, $tstart.''.$tend);
17552                                  $pagebuff = $this->getPageBuffer($this->page);
17553                                  $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17554                                  $tend = substr($pagebuff, $this->cntmrk[$this->page]);
17555                                  // add line start to current page
17556                                  $yshift = ($minstartliney - $this->y);
17557                                  $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17558                                  $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17559                                  // shift the annotations and links
17560                                  if (isset($this->PageAnnots[$this->page])) {
17561                                      $next_pask = count($this->PageAnnots[$this->page]);
17562                                  } else {
17563                                      $next_pask = 0;
17564                                  }
17565                                  if (isset($this->PageAnnots[$startlinepage])) {
17566                                      foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17567                                          if ($pak >= $pask) {
17568                                              $this->PageAnnots[$this->page][] = $pac;
17569                                              unset($this->PageAnnots[$startlinepage][$pak]);
17570                                              $npak = count($this->PageAnnots[$this->page]) - 1;
17571                                              $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17572                                          }
17573                                      }
17574                                  }
17575                                  $pask = $next_pask;
17576                                  $startlinepos = $this->cntmrk[$this->page];
17577                                  $startlinepage = $this->page;
17578                                  $startliney = $this->y;
17579                              }
17580                              if (!isset($dom[$key]['line-height'])) {
17581                                  $dom[$key]['line-height'] = $this->cell_height_ratio;
17582                              }
17583                              if (!$dom[$key]['block']) {
17584                                  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']))) {
17585                                      $this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
17586                                  }
17587                                  if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
17588                                      $current_line_align_data = array($key, $minstartliney, $maxbottomliney);
17589                                      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)))) {
17590                                          $minstartliney = min($this->y, $line_align_data[1]);
17591                                          $maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $line_align_data[2]);
17592                                      } else {
17593                                          $minstartliney = min($this->y, $minstartliney);
17594                                          $maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $maxbottomliney);
17595                                      }
17596                                      $line_align_data = $current_line_align_data;
17597                                  }
17598                              }
17599                              $this->cell_height_ratio = $dom[$key]['line-height'];
17600                              $fontaligned = true;
17601                          }
17602                          $this->SetFont($fontname, $fontstyle, $fontsize);
17603                          // reset row height
17604                          $this->resetLastH();
17605                          $curfontname = $fontname;
17606                          $curfontstyle = $fontstyle;
17607                          $curfontsize = $fontsize;
17608                          $curfontascent = $fontascent;
17609                          $curfontdescent = $fontdescent;
17610                      }
17611                  }
17612                  // set text rendering mode
17613                  $textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
17614                  $textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
17615                  $textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
17616                  $this->setTextRenderingMode($textstroke, $textfill, $textclip);
17617                  if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
17618                      $this->setFontStretching($dom[$key]['font-stretch']);
17619                  }
17620                  if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
17621                      $this->setFontSpacing($dom[$key]['letter-spacing']);
17622                  }
17623                  if (($plalign == 'J') AND $dom[$key]['block']) {
17624                      $plalign = '';
17625                  }
17626                  // get current position on page buffer
17627                  $curpos = $this->pagelen[$startlinepage];
17628                  if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
17629                      $this->SetFillColorArray($dom[$key]['bgcolor']);
17630                      $wfill = true;
17631                  } else {
17632                      $wfill = $fill | false;
17633                  }
17634                  if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
17635                      $this->SetTextColorArray($dom[$key]['fgcolor']);
17636                  }
17637                  if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
17638                      $this->SetDrawColorArray($dom[$key]['strokecolor']);
17639                  }
17640                  if (isset($dom[$key]['align'])) {
17641                      $lalign = $dom[$key]['align'];
17642                  }
17643                  if (TCPDF_STATIC::empty_string($lalign)) {
17644                      $lalign = $align;
17645                  }
17646              }
17647              // align lines
17648              if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
17649                  $newline = true;
17650                  $fontaligned = false;
17651                  // we are at the beginning of a new line
17652                  if (isset($startlinex)) {
17653                      $yshift = ($minstartliney - $startliney);
17654                      if (($yshift > 0) OR ($this->page > $startlinepage)) {
17655                          $yshift = 0;
17656                      }
17657                      $t_x = 0;
17658                      // the last line must be shifted to be aligned as requested
17659                      $linew = abs($this->endlinex - $startlinex);
17660                      if ($this->inxobj) {
17661                          // we are inside an XObject template
17662                          $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
17663                          if (isset($opentagpos)) {
17664                              $midpos = $opentagpos;
17665                          } else {
17666                              $midpos = 0;
17667                          }
17668                          if ($midpos > 0) {
17669                              $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
17670                              $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
17671                          } else {
17672                              $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
17673                              $pend = '';
17674                          }
17675                      } else {
17676                          $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
17677                          if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17678                              $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17679                              $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
17680                          } elseif (isset($opentagpos)) {
17681                              $midpos = $opentagpos;
17682                          } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17683                              $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17684                              $midpos = $this->footerpos[$startlinepage];
17685                          } else {
17686                              $midpos = 0;
17687                          }
17688                          if ($midpos > 0) {
17689                              $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
17690                              $pend = substr($this->getPageBuffer($startlinepage), $midpos);
17691                          } else {
17692                              $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
17693                              $pend = '';
17694                          }
17695                      }
17696                      if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
17697                          // calculate shifting amount
17698                          $tw = $w;
17699                          if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
17700                              $tw += $this->cell_padding['R'];
17701                          }
17702                          if ($this->lMargin != $prevlMargin) {
17703                              $tw += ($prevlMargin - $this->lMargin);
17704                          }
17705                          if ($this->rMargin != $prevrMargin) {
17706                              $tw += ($prevrMargin - $this->rMargin);
17707                          }
17708                          $one_space_width = $this->GetStringWidth(chr(32));
17709                          $no = 0; // number of spaces on a line contained on a single block
17710                          if ($this->isRTLTextDir()) { // RTL
17711                              // remove left space if exist
17712                              $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
17713                              if ($pos1 > 0) {
17714                                  $pos1 = intval($pos1);
17715                                  if ($this->isUnicodeFont()) {
17716                                      $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
17717                                      $spacelen = 2;
17718                                  } else {
17719                                      $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
17720                                      $spacelen = 1;
17721                                  }
17722                                  if ($pos1 == $pos2) {
17723                                      $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
17724                                      if (substr($pmid, $pos1, 4) == '[()]') {
17725                                          $linew -= $one_space_width;
17726                                      } elseif ($pos1 == strpos($pmid, '[(')) {
17727                                          $no = 1;
17728                                      }
17729                                  }
17730                              }
17731                          } else { // LTR
17732                              // remove right space if exist
17733                              $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
17734                              if ($pos1 > 0) {
17735                                  $pos1 = intval($pos1);
17736                                  if ($this->isUnicodeFont()) {
17737                                      $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
17738                                      $spacelen = 2;
17739                                  } else {
17740                                      $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
17741                                      $spacelen = 1;
17742                                  }
17743                                  if ($pos1 == $pos2) {
17744                                      $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
17745                                      $linew -= $one_space_width;
17746                                  }
17747                              }
17748                          }
17749                          $mdiff = ($tw - $linew);
17750                          if ($plalign == 'C') {
17751                              if ($this->rtl) {
17752                                  $t_x = -($mdiff / 2);
17753                              } else {
17754                                  $t_x = ($mdiff / 2);
17755                              }
17756                          } elseif ($plalign == 'R') {
17757                              // right alignment on LTR document
17758                              $t_x = $mdiff;
17759                          } elseif ($plalign == 'L') {
17760                              // left alignment on RTL document
17761                              $t_x = -$mdiff;
17762                          } elseif (($plalign == 'J') AND ($plalign == $lalign)) {
17763                              // Justification
17764                              if ($this->isRTLTextDir()) {
17765                                  // align text on the left
17766                                  $t_x = -$mdiff;
17767                              }
17768                              $ns = 0; // number of spaces
17769                              $pmidtemp = $pmid;
17770                              // escape special characters
17771                              $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
17772                              $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
17773                              // search spaces
17774                              if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
17775                                  $spacestr = $this->getSpaceString();
17776                                  $maxkk = count($lnstring[1]) - 1;
17777                                  for ($kk=0; $kk <= $maxkk; ++$kk) {
17778                                      // restore special characters
17779                                      $lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
17780                                      $lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
17781                                      // store number of spaces on the strings
17782                                      $lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
17783                                      // count total spaces on line
17784                                      $ns += $lnstring[2][$kk];
17785                                      $lnstring[3][$kk] = $ns;
17786                                  }
17787                                  if ($ns == 0) {
17788                                      $ns = 1;
17789                                  }
17790                                  // calculate additional space to add to each existing space
17791                                  $spacewidth = ($mdiff / ($ns - $no)) * $this->k;
17792                                  if ($this->FontSize <= 0) {
17793                                      $this->FontSize = 1;
17794                                  }
17795                                  $spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
17796                                  if ($this->font_spacing != 0) {
17797                                      // fixed spacing mode
17798                                      $osw = -1000 * $this->font_spacing / $this->FontSize;
17799                                      $spacewidthu += $osw;
17800                                  }
17801                                  $nsmax = $ns;
17802                                  $ns = 0;
17803                                  reset($lnstring);
17804                                  $offset = 0;
17805                                  $strcount = 0;
17806                                  $prev_epsposbeg = 0;
17807                                  $textpos = 0;
17808                                  if ($this->isRTLTextDir()) {
17809                                      $textpos = $this->wPt;
17810                                  }
17811                                  while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
17812                                      // check if we are inside a string section '[( ... )]'
17813                                      $stroffset = strpos($pmid, '[(', $offset);
17814                                      if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
17815                                          // set offset to the end of string section
17816                                          $offset = strpos($pmid, ')]', $stroffset);
17817                                          while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
17818                                              $offset = strpos($pmid, ')]', ($offset + 1));
17819                                          }
17820                                          if ($offset === false) {
17821                                              $this->Error('HTML Justification: malformed PDF code.');
17822                                          }
17823                                          continue;
17824                                      }
17825                                      if ($this->isRTLTextDir()) {
17826                                          $spacew = ($spacewidth * ($nsmax - $ns));
17827                                      } else {
17828                                          $spacew = ($spacewidth * $ns);
17829                                      }
17830                                      $offset = $strpiece[2][1] + strlen($strpiece[2][0]);
17831                                      $epsposend = strpos($pmid, $this->epsmarker.'Q', $offset);                
17832                                      if ($epsposend !== null) {
17833                                          $epsposend += strlen($this->epsmarker.'Q');
17834                                          $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
17835                                          if ($epsposbeg === null) {
17836                                              $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
17837                                              $prev_epsposbeg = $epsposbeg;
17838                                          }
17839                                          if (($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend)) {
17840                                              // shift EPS images
17841                                              $trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
17842                                              $pmid_b = substr($pmid, 0, $epsposbeg);
17843                                              $pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
17844                                              $pmid_e = substr($pmid, $epsposend);
17845                                              $pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
17846                                              $offset = $epsposend;
17847                                              continue;
17848                                          }
17849                                      }
17850                                      $currentxpos = 0;
17851                                      // shift blocks of code
17852                                      switch ($strpiece[2][0]) {
17853                                          case 'Td':
17854                                          case 'cm':
17855                                          case 'm':
17856                                          case 'l': {
17857                                              // get current X position
17858                                              preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
17859                                              if (!isset($xmatches[1])) {
17860                                                  break;
17861                                              }
17862                                              $currentxpos = $xmatches[1];
17863                                              $textpos = $currentxpos;
17864                                              if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
17865                                                  $ns = $lnstring[3][$strcount];
17866                                                  if ($this->isRTLTextDir()) {
17867                                                      $spacew = ($spacewidth * ($nsmax - $ns));
17868                                                  }
17869                                                  ++$strcount;
17870                                              }
17871                                              // justify block
17872                                              if (preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $pmatch) == 1) {
17873                                                  $newpmid = sprintf('%F',(floatval($pmatch[1]) + $spacew)).' '.$pmatch[2].' x*#!#*x'.$pmatch[3].$pmatch[4];
17874                                                  $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17875                                                  unset($pmatch, $newpmid);
17876                                              }
17877                                              break;
17878                                          }
17879                                          case 're': {
17880                                              // justify block
17881                                              if (!TCPDF_STATIC::empty_string($this->lispacer)) {
17882                                                  $this->lispacer = '';
17883                                                  continue;
17884                                              }
17885                                              preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
17886                                              if (!isset($xmatches[1])) {
17887                                                  break;
17888                                              }
17889                                              $currentxpos = $xmatches[1];
17890                                              $x_diff = 0;
17891                                              $w_diff = 0;
17892                                              if ($this->isRTLTextDir()) { // RTL
17893                                                  if ($currentxpos < $textpos) {
17894                                                      $x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
17895                                                      $w_diff = ($spacewidth * $lnstring[2][$strcount]);
17896                                                  } else {
17897                                                      if ($strcount > 0) {
17898                                                          $x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
17899                                                          $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17900                                                      }
17901                                                  }
17902                                              } else { // LTR
17903                                                  if ($currentxpos > $textpos) {
17904                                                      if ($strcount > 0) {
17905                                                          $x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
17906                                                      }
17907                                                      $w_diff = ($spacewidth * $lnstring[2][$strcount]);
17908                                                  } else {
17909                                                      if ($strcount > 1) {
17910                                                          $x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
17911                                                      }
17912                                                      if ($strcount > 0) {
17913                                                          $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17914                                                      }
17915                                                  }
17916                                              }
17917                                              if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $pmatch) == 1) {
17918                                                  $newx = sprintf('%F',(floatval($pmatch[1]) + $x_diff));
17919                                                  $neww = sprintf('%F',(floatval($pmatch[3]) + $w_diff));
17920                                                  $newpmid = $newx.' '.$pmatch[2].' '.$neww.' '.$pmatch[4].' x*#!#*x'.$pmatch[5].$pmatch[6];
17921                                                  $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17922                                                  unset($pmatch, $newpmid, $newx, $neww);
17923                                              }
17924                                              break;
17925                                          }
17926                                          case 'c': {
17927                                              // get current X position
17928                                              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);
17929                                              if (!isset($xmatches[1])) {
17930                                                  break;
17931                                              }
17932                                              $currentxpos = $xmatches[1];
17933                                              // justify block
17934                                              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) {
17935                                                  $newx1 = sprintf('%F',(floatval($pmatch[1]) + $spacew));
17936                                                  $newx2 = sprintf('%F',(floatval($pmatch[3]) + $spacew));
17937                                                  $newx3 = sprintf('%F',(floatval($pmatch[5]) + $spacew));
17938                                                  $newpmid = $newx1.' '.$pmatch[2].' '.$newx2.' '.$pmatch[4].' '.$newx3.' '.$pmatch[6].' x*#!#*x'.$pmatch[7].$pmatch[8];
17939                                                  $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17940                                                  unset($pmatch, $newpmid, $newx1, $newx2, $newx3);
17941                                              }
17942                                              break;
17943                                          }
17944                                      }
17945                                      // shift the annotations and links
17946                                      $cxpos = ($currentxpos / $this->k);
17947                                      $lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
17948                                      if ($this->inxobj) {
17949                                          // we are inside an XObject template
17950                                          foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
17951                                              if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17952                                                  if ($cxpos > $lmpos) {
17953                                                      $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
17954                                                      $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17955                                                  } else {
17956                                                      $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17957                                                  }
17958                                                  break;
17959                                              }
17960                                          }
17961                                      } elseif (isset($this->PageAnnots[$this->page])) {
17962                                          foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
17963                                              if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17964                                                  if ($cxpos > $lmpos) {
17965                                                      $this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
17966                                                      $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17967                                                  } else {
17968                                                      $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17969                                                  }
17970                                                  break;
17971                                              }
17972                                          }
17973                                      }
17974                                  } // end of while
17975                                  // remove markers
17976                                  $pmid = str_replace('x*#!#*x', '', $pmid);
17977                                  if ($this->isUnicodeFont()) {
17978                                      // multibyte characters
17979                                      $spacew = $spacewidthu;
17980                                      if ($this->font_stretching != 100) {
17981                                          // word spacing is affected by stretching
17982                                          $spacew /= ($this->font_stretching / 100);
17983                                      }
17984                                      // escape special characters
17985                                      $pos = 0;
17986                                      $pmid = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmid);
17987                                      $pmid = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmid);
17988                                      if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmid, $pamatch) > 0) {
17989                                          foreach($pamatch[0] as $pk => $pmatch) {
17990                                              $replace = $pamatch[1][$pk];
17991                                              $replace = str_replace('#!#OP#!#', '(', $replace);
17992                                              $replace = str_replace('#!#CP#!#', ')', $replace);
17993                                              $newpmid = '[('.str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacew).' (', $replace).')]';
17994                                              $pos = strpos($pmid, $pmatch, $pos);
17995                                              if ($pos !== FALSE) {
17996                                                  $pmid = substr_replace($pmid, $newpmid, $pos, strlen($pmatch));
17997                                              }
17998                                              ++$pos;
17999                                          }
18000                                          unset($pamatch);
18001                                      }
18002                                      if ($this->inxobj) {
18003                                          // we are inside an XObject template
18004                                          $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
18005                                      } else {
18006                                          $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
18007                                      }
18008                                      $endlinepos = strlen($pstart."\n".$pmid."\n");
18009                                  } else {
18010                                      // non-unicode (single-byte characters)
18011                                      if ($this->font_stretching != 100) {
18012                                          // word spacing (Tw) is affected by stretching
18013                                          $spacewidth /= ($this->font_stretching / 100);
18014                                      }
18015                                      $rs = sprintf('%F Tw', $spacewidth);
18016                                      $pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
18017                                      if ($this->inxobj) {
18018                                          // we are inside an XObject template
18019                                          $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
18020                                      } else {
18021                                          $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
18022                                      }
18023                                      $endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
18024                                  }
18025                              }
18026                          } // end of J
18027                      } // end if $startlinex
18028                      if (($t_x != 0) OR ($yshift < 0)) {
18029                          // shift the line
18030                          $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18031                          $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18032                          $endlinepos = strlen($pstart);
18033                          if ($this->inxobj) {
18034                              // we are inside an XObject template
18035                              $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18036                              foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18037                                  if ($pak >= $pask) {
18038                                      $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18039                                      $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18040                                  }
18041                              }
18042                          } else {
18043                              $this->setPageBuffer($startlinepage, $pstart.$pend);
18044                              // shift the annotations and links
18045                              if (isset($this->PageAnnots[$this->page])) {
18046                                  foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18047                                      if ($pak >= $pask) {
18048                                          $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18049                                          $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18050                                      }
18051                                  }
18052                              }
18053                          }
18054                          $this->y -= $yshift;
18055                      }
18056                  }
18057                  $pbrk = $this->checkPageBreak($this->lasth);
18058                  $this->newline = false;
18059                  $startlinex = $this->x;
18060                  $startliney = $this->y;
18061                  if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
18062                      $startliney -= ((0.3 * $this->FontSizePt) / $this->k);
18063                  } elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
18064                      $startliney -= (($this->FontSizePt / 0.7) / $this->k);
18065                  } else {
18066                      $minstartliney = $startliney;
18067                      $maxbottomliney = ($this->y + $this->getCellHeight($fontsize / $this->k));
18068                  }
18069                  $startlinepage = $this->page;
18070                  if (isset($endlinepos) AND (!$pbrk)) {
18071                      $startlinepos = $endlinepos;
18072                  } else {
18073                      if ($this->inxobj) {
18074                          // we are inside an XObject template
18075                          $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
18076                      } elseif (!$this->InFooter) {
18077                          if (isset($this->footerlen[$this->page])) {
18078                              $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18079                          } else {
18080                              $this->footerpos[$this->page] = $this->pagelen[$this->page];
18081                          }
18082                          $startlinepos = $this->footerpos[$this->page];
18083                      } else {
18084                          $startlinepos = $this->pagelen[$this->page];
18085                      }
18086                  }
18087                  unset($endlinepos);
18088                  $plalign = $lalign;
18089                  if (isset($this->PageAnnots[$this->page])) {
18090                      $pask = count($this->PageAnnots[$this->page]);
18091                  } else {
18092                      $pask = 0;
18093                  }
18094                  if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
18095                      AND (isset($this->emptypagemrk[$this->page]))
18096                      AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
18097                      $this->SetFont($fontname, $fontstyle, $fontsize);
18098                      if ($wfill) {
18099                          $this->SetFillColorArray($this->bgcolor);
18100                      }
18101                  }
18102              } // end newline
18103              if (isset($opentagpos)) {
18104                  unset($opentagpos);
18105              }
18106              if ($dom[$key]['tag']) {
18107                  if ($dom[$key]['opening']) {
18108                      // get text indentation (if any)
18109                      if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
18110                          $this->textindent = $dom[$key]['text-indent'];
18111                          $this->newline = true;
18112                      }
18113                      // table
18114                      if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
18115                          // available page width
18116                          if ($this->rtl) {
18117                              $wtmp = $this->x - $this->lMargin;
18118                          } else {
18119                              $wtmp = $this->w - $this->rMargin - $this->x;
18120                          }
18121                          // get cell spacing
18122                          if (isset($dom[$key]['attribute']['cellspacing'])) {
18123                              $clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
18124                              $cellspacing = array('H' => $clsp, 'V' => $clsp);
18125                          } elseif (isset($dom[$key]['border-spacing'])) {
18126                              $cellspacing = $dom[$key]['border-spacing'];
18127                          } else {
18128                              $cellspacing = array('H' => 0, 'V' => 0);
18129                          }
18130                          // table width
18131                          if (isset($dom[$key]['width'])) {
18132                              $table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
18133                          } else {
18134                              $table_width = $wtmp;
18135                          }
18136                          $table_width -= (2 * $cellspacing['H']);
18137                          if (!$this->inthead) {
18138                              $this->y += $cellspacing['V'];
18139                          }
18140                          if ($this->rtl) {
18141                              $cellspacingx = -$cellspacing['H'];
18142                          } else {
18143                              $cellspacingx = $cellspacing['H'];
18144                          }
18145                          // total table width without cellspaces
18146                          $table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
18147                          // minimum column width
18148                          $table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
18149                          // array of custom column widths
18150                          $table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
18151                      }
18152                      // table row
18153                      if ($dom[$key]['value'] == 'tr') {
18154                          // reset column counter
18155                          $colid = 0;
18156                      }
18157                      // table cell
18158                      if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18159                          $trid = $dom[$key]['parent'];
18160                          $table_el = $dom[$trid]['parent'];
18161                          if (!isset($dom[$table_el]['cols'])) {
18162                              $dom[$table_el]['cols'] = $dom[$trid]['cols'];
18163                          }
18164                          // store border info
18165                          $tdborder = 0;
18166                          if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
18167                              $tdborder = $dom[$key]['border'];
18168                          }
18169                          $colspan = intval($dom[$key]['attribute']['colspan']);
18170                          if ($colspan <= 0) {
18171                              $colspan = 1;
18172                          }
18173                          $old_cell_padding = $this->cell_padding;
18174                          if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
18175                              $crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
18176                              $current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
18177                          } elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
18178                              $current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
18179                          } else {
18180                              $current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
18181                          }
18182                          $this->cell_padding = $current_cell_padding;
18183                          if (isset($dom[$key]['height'])) {
18184                              // minimum cell height
18185                              $cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
18186                          } else {
18187                              $cellh = 0;
18188                          }
18189                          if (isset($dom[$key]['content'])) {
18190                              $cell_content = stripslashes($dom[$key]['content']);
18191                          } else {
18192                              $cell_content = '&nbsp;';
18193                          }
18194                          $tagtype = $dom[$key]['value'];
18195                          $parentid = $key;
18196                          while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
18197                              // move $key index forward
18198                              ++$key;
18199                          }
18200                          if (!isset($dom[$trid]['startpage'])) {
18201                              $dom[$trid]['startpage'] = $this->page;
18202                          } else {
18203                              $this->setPage($dom[$trid]['startpage']);
18204                          }
18205                          if (!isset($dom[$trid]['startcolumn'])) {
18206                              $dom[$trid]['startcolumn'] = $this->current_column;
18207                          } elseif ($this->current_column != $dom[$trid]['startcolumn']) {
18208                              $tmpx = $this->x;
18209                              $this->selectColumn($dom[$trid]['startcolumn']);
18210                              $this->x = $tmpx;
18211                          }
18212                          if (!isset($dom[$trid]['starty'])) {
18213                              $dom[$trid]['starty'] = $this->y;
18214                          } else {
18215                              $this->y = $dom[$trid]['starty'];
18216                          }
18217                          if (!isset($dom[$trid]['startx'])) {
18218                              $dom[$trid]['startx'] = $this->x;
18219                              $this->x += $cellspacingx;
18220                          } else {
18221                              $this->x += ($cellspacingx / 2);
18222                          }
18223                          if (isset($dom[$parentid]['attribute']['rowspan'])) {
18224                              $rowspan = intval($dom[$parentid]['attribute']['rowspan']);
18225                          } else {
18226                              $rowspan = 1;
18227                          }
18228                          // skip row-spanned cells started on the previous rows
18229                          if (isset($dom[$table_el]['rowspans'])) {
18230                              $rsk = 0;
18231                              $rskmax = count($dom[$table_el]['rowspans']);
18232                              while ($rsk < $rskmax) {
18233                                  $trwsp = $dom[$table_el]['rowspans'][$rsk];
18234                                  $rsstartx = $trwsp['startx'];
18235                                  $rsendx = $trwsp['endx'];
18236                                  // account for margin changes
18237                                  if ($trwsp['startpage'] < $this->page) {
18238                                      if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
18239                                          $dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
18240                                          $rsstartx -= $dl;
18241                                          $rsendx -= $dl;
18242                                      } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
18243                                          $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
18244                                          $rsstartx += $dl;
18245                                          $rsendx += $dl;
18246                                      }
18247                                  }
18248                                  if (($trwsp['rowspan'] > 0)
18249                                      AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
18250                                      AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
18251                                      AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
18252                                      // set the starting X position of the current cell
18253                                      $this->x = $rsendx + $cellspacingx;
18254                                      // increment column indicator
18255                                      $colid += $trwsp['colspan'];
18256                                      if (($trwsp['rowspan'] == 1)
18257                                          AND (isset($dom[$trid]['endy']))
18258                                          AND (isset($dom[$trid]['endpage']))
18259                                          AND (isset($dom[$trid]['endcolumn']))
18260                                          AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
18261                                          AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18262                                          // set ending Y position for row
18263                                          $dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18264                                          $dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
18265                                      }
18266                                      $rsk = 0;
18267                                  } else {
18268                                      ++$rsk;
18269                                  }
18270                              }
18271                          }
18272                          if (isset($dom[$parentid]['width'])) {
18273                              // user specified width
18274                              $cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
18275                              $tmpcw = ($cellw / $colspan);
18276                              for ($i = 0; $i < $colspan; ++$i) {
18277                                  $table_colwidths[($colid + $i)] = $tmpcw;
18278                              }
18279                          } else {
18280                              // inherit column width
18281                              $cellw = 0;
18282                              for ($i = 0; $i < $colspan; ++$i) {
18283                                  $cellw += $table_colwidths[($colid + $i)];
18284                              }
18285                          }
18286                          $cellw += (($colspan - 1) * $cellspacing['H']);
18287                          // increment column indicator
18288                          $colid += $colspan;
18289                          // add rowspan information to table element
18290                          if ($rowspan > 1) {
18291                              $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));
18292                          }
18293                          $cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
18294                          if ($rowspan > 1) {
18295                              $dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
18296                          }
18297                          // push background colors
18298                          if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
18299                              $dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
18300                          }
18301                          // store border info
18302                          if (isset($tdborder) AND !empty($tdborder)) {
18303                              $dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
18304                          }
18305                          $prevLastH = $this->lasth;
18306                          // store some info for multicolumn mode
18307                          if ($this->rtl) {
18308                              $this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
18309                          } else {
18310                              $this->colxshift['x'] = $this->x - $this->lMargin;
18311                          }
18312                          $this->colxshift['s'] = $cellspacing;
18313                          $this->colxshift['p'] = $current_cell_padding;
18314                          // ****** write the cell content ******
18315                          $this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
18316                          // restore some values
18317                          $this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
18318                          $this->lasth = $prevLastH;
18319                          $this->cell_padding = $old_cell_padding;
18320                          $dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
18321                          // update the end of row position
18322                          if ($rowspan <= 1) {
18323                              if (isset($dom[$trid]['endy'])) {
18324                                  if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
18325                                      $dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
18326                                  } elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
18327                                      $dom[$trid]['endy'] = $this->y;
18328                                  }
18329                              } else {
18330                                  $dom[$trid]['endy'] = $this->y;
18331                              }
18332                              if (isset($dom[$trid]['endpage'])) {
18333                                  $dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
18334                              } else {
18335                                  $dom[$trid]['endpage'] = $this->page;
18336                              }
18337                              if (isset($dom[$trid]['endcolumn'])) {
18338                                  $dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
18339                              } else {
18340                                  $dom[$trid]['endcolumn'] = $this->current_column;
18341                              }
18342                          } else {
18343                              // account for row-spanned cells
18344                              $dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
18345                              $dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
18346                              $dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
18347                              $dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
18348                          }
18349                          if (isset($dom[$table_el]['rowspans'])) {
18350                              // update endy and endpage on rowspanned cells
18351                              foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
18352                                  if ($trwsp['rowspan'] > 0) {
18353                                      if (isset($dom[$trid]['endpage'])) {
18354                                          if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18355                                              $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18356                                          } elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
18357                                              $dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
18358                                              $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
18359                                              $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
18360                                          } else {
18361                                              $dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
18362                                          }
18363                                      }
18364                                  }
18365                              }
18366                          }
18367                          $this->x += ($cellspacingx / 2);
18368                      } else {
18369                          // opening tag (or self-closing tag)
18370                          if (!isset($opentagpos)) {
18371                              if ($this->inxobj) {
18372                                  // we are inside an XObject template
18373                                  $opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
18374                              } elseif (!$this->InFooter) {
18375                                  if (isset($this->footerlen[$this->page])) {
18376                                      $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18377                                  } else {
18378                                      $this->footerpos[$this->page] = $this->pagelen[$this->page];
18379                                  }
18380                                  $opentagpos = $this->footerpos[$this->page];
18381                              }
18382                          }
18383                          $dom = $this->openHTMLTagHandler($dom, $key, $cell);
18384                      }
18385                  } else { // closing tag
18386                      $prev_numpages = $this->numpages;
18387                      $old_bordermrk = $this->bordermrk[$this->page];
18388                      $dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
18389                      if ($this->bordermrk[$this->page] > $old_bordermrk) {
18390                          $startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
18391                      }
18392                      if ($prev_numpages > $this->numpages) {
18393                          $startlinepage = $this->page;
18394                      }
18395                  }
18396              } elseif (strlen($dom[$key]['value']) > 0) {
18397                  // print list-item
18398                  if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) {
18399                      $this->SetFont($pfontname, $pfontstyle, $pfontsize);
18400                      $this->resetLastH();
18401                      $minstartliney = $this->y;
18402                      $maxbottomliney = ($startliney + $this->getCellHeight($this->FontSize));
18403                      if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
18404                          $this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
18405                      }
18406                      $this->SetFont($curfontname, $curfontstyle, $curfontsize);
18407                      $this->resetLastH();
18408                      if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
18409                          $pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
18410                          $pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
18411                          $this->y += ($this->getCellHeight(($pfontsize - $curfontsize) / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
18412                          $minstartliney = min($this->y, $minstartliney);
18413                          $maxbottomliney = max(($this->y + $this->getCellHeight($pfontsize / $this->k)), $maxbottomliney);
18414                      }
18415                  }
18416                  // text
18417                  $this->htmlvspace = 0;
18418                  if ((!$this->premode) AND $this->isRTLTextDir()) {
18419                      // reverse spaces order
18420                      $lsp = ''; // left spaces
18421                      $rsp = ''; // right spaces
18422                      if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18423                          $lsp = $matches[1];
18424                      }
18425                      if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18426                          $rsp = $matches[1];
18427                      }
18428                      $dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
18429                  }
18430                  if ($newline) {
18431                      if (!$this->premode) {
18432                          $prelen = strlen($dom[$key]['value']);
18433                          if ($this->isRTLTextDir()) {
18434                              // right trim except non-breaking space
18435                              $dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
18436                          } else {
18437                              // left trim except non-breaking space
18438                              $dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
18439                          }
18440                          $postlen = strlen($dom[$key]['value']);
18441                          if (($postlen == 0) AND ($prelen > 0)) {
18442                              $dom[$key]['trimmed_space'] = true;
18443                          }
18444                      }
18445                      $newline = false;
18446                      $firstblock = true;
18447                  } else {
18448                      $firstblock = false;
18449                      // replace empty multiple spaces string with a single space
18450                      $dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
18451                  }
18452                  $strrest = '';
18453                  if ($this->rtl) {
18454                      $this->x -= $this->textindent;
18455                  } else {
18456                      $this->x += $this->textindent;
18457                  }
18458                  if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
18459                      $strlinelen = $this->GetStringWidth($dom[$key]['value']);
18460                      if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
18461                          // HTML <a> Link
18462                          $hrefcolor = '';
18463                          if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
18464                              $hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
18465                          }
18466                          $hrefstyle = -1;
18467                          if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
18468                              $hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
18469                          }
18470                          $strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
18471                      } else {
18472                          $wadj = 0; // space to leave for block continuity
18473                          if ($this->rtl) {
18474                              $cwa = ($this->x - $this->lMargin);
18475                          } else {
18476                              $cwa = ($this->w - $this->rMargin - $this->x);
18477                          }
18478                          if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
18479                              // check the next text blocks for continuity
18480                              $nkey = ($key + 1);
18481                              $write_block = true;
18482                              $same_textdir = true;
18483                              $tmp_fontname = $this->FontFamily;
18484                              $tmp_fontstyle = $this->FontStyle;
18485                              $tmp_fontsize = $this->FontSizePt;
18486                              while ($write_block AND isset($dom[$nkey])) {
18487                                  if ($dom[$nkey]['tag']) {
18488                                      if ($dom[$nkey]['block']) {
18489                                          // end of block
18490                                          $write_block = false;
18491                                      }
18492                                      $tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
18493                                      $tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
18494                                      $tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
18495                                      $same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
18496                                  } else {
18497                                      $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'+/', $this->re_space['m'], $dom[$nkey]['value']);
18498                                      if (isset($nextstr[0]) AND $same_textdir) {
18499                                          $wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
18500                                          if (isset($nextstr[1])) {
18501                                              $write_block = false;
18502                                          }
18503                                      }
18504                                  }
18505                                  ++$nkey;
18506                              }
18507                          }
18508                          if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
18509                              $wadj = 0;
18510                              $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $dom[$key]['value']);
18511                              $numblks = count($nextstr);
18512                              if ($numblks > 1) {
18513                                  // try to split on blank spaces
18514                                  $wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
18515                              } else {
18516                                  // set the entire block on new line
18517                                  $wadj = $this->GetStringWidth($nextstr[0]);
18518                              }
18519                          }
18520                          // check for reversed text direction
18521                          if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
18522                              // LTR text on RTL direction or RTL text on LTR direction
18523                              $reverse_dir = true;
18524                              $this->rtl = !$this->rtl;
18525                              $revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
18526                              if ($this->rtl) {
18527                                  $this->x += $revshift;
18528                              } else {
18529                                  $this->x -= $revshift;
18530                              }
18531                              $xws = $this->x;
18532                          }
18533                          // ****** write only until the end of the line and get the rest ******
18534                          $strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
18535                          // restore default direction
18536                          if ($reverse_dir AND ($wadj == 0)) {
18537                              $this->x = $xws;
18538                              $this->rtl = !$this->rtl;
18539                              $reverse_dir = false;
18540                          }
18541                      }
18542                  }
18543                  $this->textindent = 0;
18544                  if (strlen($strrest) > 0) {
18545                      // store the remaining string on the previous $key position
18546                      $this->newline = true;
18547                      if ($strrest == $dom[$key]['value']) {
18548                          // used to avoid infinite loop
18549                          ++$loop;
18550                      } else {
18551                          $loop = 0;
18552                      }
18553                      $dom[$key]['value'] = $strrest;
18554                      if ($cell) {
18555                          if ($this->rtl) {
18556                              $this->x -= $this->cell_padding['R'];
18557                          } else {
18558                              $this->x += $this->cell_padding['L'];
18559                          }
18560                      }
18561                      if ($loop < 3) {
18562                          --$key;
18563                      }
18564                  } else {
18565                      $loop = 0;
18566                      // add the positive font spacing of the last character (if any)
18567                       if ($this->font_spacing > 0) {
18568                           if ($this->rtl) {
18569                              $this->x -= $this->font_spacing;
18570                          } else {
18571                              $this->x += $this->font_spacing;
18572                          }
18573                      }
18574                  }
18575              }
18576              ++$key;
18577              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')) {
18578                  // check if we are on a new page or on a new column
18579                  if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
18580                      // we are on a new page or on a new column and the total object height is less than the available vertical space.
18581                      // restore previous object
18582                      $this->rollbackTransaction(true);
18583                      // restore previous values
18584                      foreach ($this_method_vars as $vkey => $vval) {
18585                          $$vkey = $vval;
18586                      }
18587                      if (!empty($dom[$key]['thead'])) {
18588                          $this->inthead = true;
18589                      }
18590                      // add a page (or trig AcceptPageBreak() for multicolumn mode)
18591                      $pre_y = $this->y;
18592                      if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
18593                          $startliney = $this->y;
18594                      }
18595                      $undo = true; // avoid infinite loop
18596                  } else {
18597                      $undo = false;
18598                  }
18599              }
18600          } // end for each $key
18601          // align the last line
18602          if (isset($startlinex)) {
18603              $yshift = ($minstartliney - $startliney);
18604              if (($yshift > 0) OR ($this->page > $startlinepage)) {
18605                  $yshift = 0;
18606              }
18607              $t_x = 0;
18608              // the last line must be shifted to be aligned as requested
18609              $linew = abs($this->endlinex - $startlinex);
18610              if ($this->inxobj) {
18611                  // we are inside an XObject template
18612                  $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
18613                  if (isset($opentagpos)) {
18614                      $midpos = $opentagpos;
18615                  } else {
18616                      $midpos = 0;
18617                  }
18618                  if ($midpos > 0) {
18619                      $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
18620                      $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
18621                  } else {
18622                      $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
18623                      $pend = '';
18624                  }
18625              } else {
18626                  $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
18627                  if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18628                      $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18629                      $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
18630                  } elseif (isset($opentagpos)) {
18631                      $midpos = $opentagpos;
18632                  } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18633                      $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18634                      $midpos = $this->footerpos[$startlinepage];
18635                  } else {
18636                      $midpos = 0;
18637                  }
18638                  if ($midpos > 0) {
18639                      $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
18640                      $pend = substr($this->getPageBuffer($startlinepage), $midpos);
18641                  } else {
18642                      $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
18643                      $pend = '';
18644                  }
18645              }
18646              if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
18647                  // calculate shifting amount
18648                  $tw = $w;
18649                  if ($this->lMargin != $prevlMargin) {
18650                      $tw += ($prevlMargin - $this->lMargin);
18651                  }
18652                  if ($this->rMargin != $prevrMargin) {
18653                      $tw += ($prevrMargin - $this->rMargin);
18654                  }
18655                  $one_space_width = $this->GetStringWidth(chr(32));
18656                  $no = 0; // number of spaces on a line contained on a single block
18657                  if ($this->isRTLTextDir()) { // RTL
18658                      // remove left space if exist
18659                      $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
18660                      if ($pos1 > 0) {
18661                          $pos1 = intval($pos1);
18662                          if ($this->isUnicodeFont()) {
18663                              $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
18664                              $spacelen = 2;
18665                          } else {
18666                              $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
18667                              $spacelen = 1;
18668                          }
18669                          if ($pos1 == $pos2) {
18670                              $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
18671                              if (substr($pmid, $pos1, 4) == '[()]') {
18672                                  $linew -= $one_space_width;
18673                              } elseif ($pos1 == strpos($pmid, '[(')) {
18674                                  $no = 1;
18675                              }
18676                          }
18677                      }
18678                  } else { // LTR
18679                      // remove right space if exist
18680                      $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
18681                      if ($pos1 > 0) {
18682                          $pos1 = intval($pos1);
18683                          if ($this->isUnicodeFont()) {
18684                              $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
18685                              $spacelen = 2;
18686                          } else {
18687                              $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
18688                              $spacelen = 1;
18689                          }
18690                          if ($pos1 == $pos2) {
18691                              $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
18692                              $linew -= $one_space_width;
18693                          }
18694                      }
18695                  }
18696                  $mdiff = ($tw - $linew);
18697                  if ($plalign == 'C') {
18698                      if ($this->rtl) {
18699                          $t_x = -($mdiff / 2);
18700                      } else {
18701                          $t_x = ($mdiff / 2);
18702                      }
18703                  } elseif ($plalign == 'R') {
18704                      // right alignment on LTR document
18705                      $t_x = $mdiff;
18706                  } elseif ($plalign == 'L') {
18707                      // left alignment on RTL document
18708                      $t_x = -$mdiff;
18709                  }
18710              } // end if startlinex
18711              if (($t_x != 0) OR ($yshift < 0)) {
18712                  // shift the line
18713                  $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18714                  $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18715                  $endlinepos = strlen($pstart);
18716                  if ($this->inxobj) {
18717                      // we are inside an XObject template
18718                      $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18719                      foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18720                          if ($pak >= $pask) {
18721                              $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18722                              $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18723                          }
18724                      }
18725                  } else {
18726                      $this->setPageBuffer($startlinepage, $pstart.$pend);
18727                      // shift the annotations and links
18728                      if (isset($this->PageAnnots[$this->page])) {
18729                          foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18730                              if ($pak >= $pask) {
18731                                  $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18732                                  $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18733                              }
18734                          }
18735                      }
18736                  }
18737                  $this->y -= $yshift;
18738                  $yshift = 0;
18739              }
18740          }
18741          // restore previous values
18742          $this->setGraphicVars($gvars);
18743          if ($this->num_columns > 1) {
18744              $this->selectColumn();
18745          } elseif ($this->page > $prevPage) {
18746              $this->lMargin = $this->pagedim[$this->page]['olm'];
18747              $this->rMargin = $this->pagedim[$this->page]['orm'];
18748          }
18749          // restore previous list state
18750          $this->cell_height_ratio = $prev_cell_height_ratio;
18751          $this->listnum = $prev_listnum;
18752          $this->listordered = $prev_listordered;
18753          $this->listcount = $prev_listcount;
18754          $this->lispacer = $prev_lispacer;
18755          if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
18756              $this->Ln($this->lasth);
18757              if ($this->y < $maxbottomliney) {
18758                  $this->y = $maxbottomliney;
18759              }
18760          }
18761          unset($dom);
18762      }
18763  
18764      /**
18765       * Process opening tags.
18766       * @param $dom (array) html dom array
18767       * @param $key (int) current element id
18768       * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
18769       * @return $dom array
18770       * @protected
18771       */
18772  	protected function openHTMLTagHandler($dom, $key, $cell) {
18773          $tag = $dom[$key];
18774          $parent = $dom[($dom[$key]['parent'])];
18775          $firsttag = ($key == 1);
18776          // check for text direction attribute
18777          if (isset($tag['dir'])) {
18778              $this->setTempRTL($tag['dir']);
18779          } else {
18780              $this->tmprtl = false;
18781          }
18782          if ($tag['block']) {
18783              $hbz = 0; // distance from y to line bottom
18784              $hb = 0; // vertical space between block tags
18785              // calculate vertical space for block tags
18786              if (isset($this->tagvspaces[$tag['value']][0]['h']) AND ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
18787                  $cur_h = $this->tagvspaces[$tag['value']][0]['h'];
18788              } elseif (isset($tag['fontsize'])) {
18789                  $cur_h = $this->getCellHeight($tag['fontsize'] / $this->k);
18790              } else {
18791                  $cur_h = $this->getCellHeight($this->FontSize);
18792              }
18793              if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
18794                  $on = $this->tagvspaces[$tag['value']][0]['n'];
18795              } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18796                  $on = 0.6;
18797              } else {
18798                  $on = 1;
18799              }
18800              if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br', 'hr')))) {
18801                  $hb = 0;
18802              } else {
18803                  $hb = ($on * $cur_h);
18804              }
18805              if (($this->htmlvspace <= 0) AND ($on > 0)) {
18806                  if (isset($parent['fontsize'])) {
18807                      $hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
18808                  } else {
18809                      $hbz = $this->getCellHeight($this->FontSize);
18810                  }
18811              }
18812              if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
18813                  // fix vertical space after table
18814                  $hbz = 0;
18815              }
18816              // closing vertical space
18817              $hbc = 0;
18818              if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
18819                  $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
18820              } elseif (isset($parent['fontsize'])) {
18821                  $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
18822              } else {
18823                  $pre_h = $this->getCellHeight($this->FontSize);
18824              }
18825              if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
18826                  $cn = $this->tagvspaces[$tag['value']][1]['n'];
18827              } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18828                  $cn = 0.6;
18829              } else {
18830                  $cn = 1;
18831              }
18832              if (isset($this->tagvspaces[$tag['value']][1])) {
18833                  $hbc = ($cn * $pre_h);
18834              }
18835          }
18836          // Opening tag
18837          switch($tag['value']) {
18838              case 'table': {
18839                  $cp = 0;
18840                  $cs = 0;
18841                  $dom[$key]['rowspans'] = array();
18842                  if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
18843                      $this->htmlvspace = 0;
18844                      // set table header
18845                      if (!TCPDF_STATIC::empty_string($dom[$key]['thead'])) {
18846                          // set table header
18847                          $this->thead = $dom[$key]['thead'];
18848                          if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
18849                              $this->theadMargins = array();
18850                              $this->theadMargins['cell_padding'] = $this->cell_padding;
18851                              $this->theadMargins['lmargin'] = $this->lMargin;
18852                              $this->theadMargins['rmargin'] = $this->rMargin;
18853                              $this->theadMargins['page'] = $this->page;
18854                              $this->theadMargins['cell'] = $cell;
18855                              $this->theadMargins['gvars'] = $this->getGraphicVars();
18856                          }
18857                      }
18858                  }
18859                  // store current margins and page
18860                  $dom[$key]['old_cell_padding'] = $this->cell_padding;
18861                  if (isset($tag['attribute']['cellpadding'])) {
18862                      $pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
18863                      $this->SetCellPadding($pad);
18864                  } elseif (isset($tag['padding'])) {
18865                      $this->cell_padding = $tag['padding'];
18866                  }
18867                  if (isset($tag['attribute']['cellspacing'])) {
18868                      $cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
18869                  } elseif (isset($tag['border-spacing'])) {
18870                      $cs = $tag['border-spacing']['V'];
18871                  }
18872                  $prev_y = $this->y;
18873                  if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
18874                      $this->inthead = true;
18875                      // add a page (or trig AcceptPageBreak() for multicolumn mode)
18876                      $this->checkPageBreak($this->PageBreakTrigger + 1);
18877                  }
18878                  break;
18879              }
18880              case 'tr': {
18881                  // array of columns positions
18882                  $dom[$key]['cellpos'] = array();
18883                  break;
18884              }
18885              case 'hr': {
18886                  if ((isset($tag['height'])) AND ($tag['height'] != '')) {
18887                      $hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
18888                  } else {
18889                      $hrHeight = $this->GetLineWidth();
18890                  }
18891                  $this->addHTMLVertSpace($hbz, max($hb, ($hrHeight / 2)), $cell, $firsttag);
18892                  $x = $this->GetX();
18893                  $y = $this->GetY();
18894                  $wtmp = $this->w - $this->lMargin - $this->rMargin;
18895                  if ($cell) {
18896                      $wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
18897                  }
18898                  if ((isset($tag['width'])) AND ($tag['width'] != '')) {
18899                      $hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
18900                  } else {
18901                      $hrWidth = $wtmp;
18902                  }
18903                  $prevlinewidth = $this->GetLineWidth();
18904                  $this->SetLineWidth($hrHeight);
18905                  $this->Line($x, $y, $x + $hrWidth, $y);
18906                  $this->SetLineWidth($prevlinewidth);
18907                  $this->addHTMLVertSpace(max($hbc, ($hrHeight / 2)), 0, $cell, !isset($dom[($key + 1)]));
18908                  break;
18909              }
18910              case 'a': {
18911                  if (array_key_exists('href', $tag['attribute'])) {
18912                      $this->HREF['url'] = $tag['attribute']['href'];
18913                  }
18914                  break;
18915              }
18916              case 'img': {
18917                  if (!empty($tag['attribute']['src'])) {
18918                      if ($tag['attribute']['src'][0] === '@') {
18919                          // data stream
18920                          $tag['attribute']['src'] = '@'.base64_decode(substr($tag['attribute']['src'], 1));
18921                          $type = '';
18922                      } else {
18923                          // get image type
18924                          $type = TCPDF_IMAGES::getImageFileType($tag['attribute']['src']);
18925                      }
18926                      if (!isset($tag['width'])) {
18927                          $tag['width'] = 0;
18928                      }
18929                      if (!isset($tag['height'])) {
18930                          $tag['height'] = 0;
18931                      }
18932                      //if (!isset($tag['attribute']['align'])) {
18933                          // the only alignment supported is "bottom"
18934                          // further development is required for other modes.
18935                          $tag['attribute']['align'] = 'bottom';
18936                      //}
18937                      switch($tag['attribute']['align']) {
18938                          case 'top': {
18939                              $align = 'T';
18940                              break;
18941                          }
18942                          case 'middle': {
18943                              $align = 'M';
18944                              break;
18945                          }
18946                          case 'bottom': {
18947                              $align = 'B';
18948                              break;
18949                          }
18950                          default: {
18951                              $align = 'B';
18952                              break;
18953                          }
18954                      }
18955                      $prevy = $this->y;
18956                      $xpos = $this->x;
18957                      $imglink = '';
18958                      if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
18959                          $imglink = $this->HREF['url'];
18960                          if ($imglink[0] == '#') {
18961                              // convert url to internal link
18962                              $lnkdata = explode(',', $imglink);
18963                              if (isset($lnkdata[0])) {
18964                                  $page = intval(substr($lnkdata[0], 1));
18965                                  if (empty($page) OR ($page <= 0)) {
18966                                      $page = $this->page;
18967                                  }
18968                                  if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
18969                                      $lnky = floatval($lnkdata[1]);
18970                                  } else {
18971                                      $lnky = 0;
18972                                  }
18973                                  $imglink = $this->AddLink();
18974                                  $this->SetLink($imglink, $lnky, $page);
18975                              }
18976                          }
18977                      }
18978                      $border = 0;
18979                      if (isset($tag['border']) AND !empty($tag['border'])) {
18980                          // currently only support 1 (frame) or a combination of 'LTRB'
18981                          $border = $tag['border'];
18982                      }
18983                      $iw = '';
18984                      if (isset($tag['width'])) {
18985                          $iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k), 'px', false);
18986                      }
18987                      $ih = '';
18988                      if (isset($tag['height'])) {
18989                          $ih = $this->getHTMLUnitToUnits($tag['height'], ($tag['fontsize'] / $this->k), 'px', false);
18990                      }
18991                      if (($type == 'eps') OR ($type == 'ai')) {
18992                          $this->ImageEps($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
18993                      } elseif ($type == 'svg') {
18994                          $this->ImageSVG($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
18995                      } else {
18996                          $this->Image($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
18997                      }
18998                      switch($align) {
18999                          case 'T': {
19000                              $this->y = $prevy;
19001                              break;
19002                          }
19003                          case 'M': {
19004                              $this->y = (($this->img_rb_y + $prevy - ($this->getCellHeight($tag['fontsize'] / $this->k))) / 2);
19005                              break;
19006                          }
19007                          case 'B': {
19008                              $this->y = $this->img_rb_y - ($this->getCellHeight($tag['fontsize'] / $this->k) - ($this->getFontDescent($tag['fontname'], $tag['fontstyle'], $tag['fontsize']) * $this->cell_height_ratio));
19009                              break;
19010                          }
19011                      }
19012                  }
19013                  break;
19014              }
19015              case 'dl': {
19016                  ++$this->listnum;
19017                  if ($this->listnum == 1) {
19018                      $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19019                  } else {
19020                      $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19021                  }
19022                  break;
19023              }
19024              case 'dt': {
19025                  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19026                  break;
19027              }
19028              case 'dd': {
19029                  if ($this->rtl) {
19030                      $this->rMargin += $this->listindent;
19031                  } else {
19032                      $this->lMargin += $this->listindent;
19033                  }
19034                  ++$this->listindentlevel;
19035                  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19036                  break;
19037              }
19038              case 'ul':
19039              case 'ol': {
19040                  ++$this->listnum;
19041                  if ($tag['value'] == 'ol') {
19042                      $this->listordered[$this->listnum] = true;
19043                  } else {
19044                      $this->listordered[$this->listnum] = false;
19045                  }
19046                  if (isset($tag['attribute']['start'])) {
19047                      $this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
19048                  } else {
19049                      $this->listcount[$this->listnum] = 0;
19050                  }
19051                  if ($this->rtl) {
19052                      $this->rMargin += $this->listindent;
19053                      $this->x -= $this->listindent;
19054                  } else {
19055                      $this->lMargin += $this->listindent;
19056                      $this->x += $this->listindent;
19057                  }
19058                  ++$this->listindentlevel;
19059                  if ($this->listnum == 1) {
19060                      if ($key > 1) {
19061                          $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19062                      }
19063                  } else {
19064                      $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19065                  }
19066                  break;
19067              }
19068              case 'li': {
19069                  if ($key > 2) {
19070                      $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19071                  }
19072                  if ($this->listordered[$this->listnum]) {
19073                      // ordered item
19074                      if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19075                          $this->lispacer = $parent['attribute']['type'];
19076                      } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19077                          $this->lispacer = $parent['listtype'];
19078                      } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19079                          $this->lispacer = $this->lisymbol;
19080                      } else {
19081                          $this->lispacer = '#';
19082                      }
19083                      ++$this->listcount[$this->listnum];
19084                      if (isset($tag['attribute']['value'])) {
19085                          $this->listcount[$this->listnum] = intval($tag['attribute']['value']);
19086                      }
19087                  } else {
19088                      // unordered item
19089                      if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
19090                          $this->lispacer = $parent['attribute']['type'];
19091                      } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
19092                          $this->lispacer = $parent['listtype'];
19093                      } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
19094                          $this->lispacer = $this->lisymbol;
19095                      } else {
19096                          $this->lispacer = '!';
19097                      }
19098                  }
19099                  break;
19100              }
19101              case 'blockquote': {
19102                  if ($this->rtl) {
19103                      $this->rMargin += $this->listindent;
19104                  } else {
19105                      $this->lMargin += $this->listindent;
19106                  }
19107                  ++$this->listindentlevel;
19108                  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19109                  break;
19110              }
19111              case 'br': {
19112                  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19113                  break;
19114              }
19115              case 'div': {
19116                  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19117                  break;
19118              }
19119              case 'p': {
19120                  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19121                  break;
19122              }
19123              case 'pre': {
19124                  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19125                  $this->premode = true;
19126                  break;
19127              }
19128              case 'sup': {
19129                  $this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
19130                  break;
19131              }
19132              case 'sub': {
19133                  $this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
19134                  break;
19135              }
19136              case 'h1':
19137              case 'h2':
19138              case 'h3':
19139              case 'h4':
19140              case 'h5':
19141              case 'h6': {
19142                  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19143                  break;
19144              }
19145              // Form fields (since 4.8.000 - 2009-09-07)
19146              case 'form': {
19147                  if (isset($tag['attribute']['action'])) {
19148                      $this->form_action = $tag['attribute']['action'];
19149                  } else {
19150                      $this->Error('Please explicitly set action attribute path!');
19151                  }
19152                  if (isset($tag['attribute']['enctype'])) {
19153                      $this->form_enctype = $tag['attribute']['enctype'];
19154                  } else {
19155                      $this->form_enctype = 'application/x-www-form-urlencoded';
19156                  }
19157                  if (isset($tag['attribute']['method'])) {
19158                      $this->form_mode = $tag['attribute']['method'];
19159                  } else {
19160                      $this->form_mode = 'post';
19161                  }
19162                  break;
19163              }
19164              case 'input': {
19165                  if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19166                      $name = $tag['attribute']['name'];
19167                  } else {
19168                      break;
19169                  }
19170                  $prop = array();
19171                  $opt = array();
19172                  if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19173                      $prop['readonly'] = true;
19174                  }
19175                  if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19176                      $value = $tag['attribute']['value'];
19177                  }
19178                  if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC::empty_string($tag['attribute']['maxlength'])) {
19179                      $opt['maxlen'] = intval($tag['attribute']['maxlength']);
19180                  }
19181                  $h = $this->getCellHeight($this->FontSize);
19182                  if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19183                      $w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
19184                  } else {
19185                      $w = $h;
19186                  }
19187                  if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
19188                      $checked = true;
19189                  } else {
19190                      $checked = false;
19191                  }
19192                  if (isset($tag['align'])) {
19193                      switch ($tag['align']) {
19194                          case 'C': {
19195                              $opt['q'] = 1;
19196                              break;
19197                          }
19198                          case 'R': {
19199                              $opt['q'] = 2;
19200                              break;
19201                          }
19202                          case 'L':
19203                          default: {
19204                              break;
19205                          }
19206                      }
19207                  }
19208                  switch ($tag['attribute']['type']) {
19209                      case 'text': {
19210                          if (isset($value)) {
19211                              $opt['v'] = $value;
19212                          }
19213                          $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19214                          break;
19215                      }
19216                      case 'password': {
19217                          if (isset($value)) {
19218                              $opt['v'] = $value;
19219                          }
19220                          $prop['password'] = 'true';
19221                          $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19222                          break;
19223                      }
19224                      case 'checkbox': {
19225                          if (!isset($value)) {
19226                              break;
19227                          }
19228                          $this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
19229                          break;
19230                      }
19231                      case 'radio': {
19232                          if (!isset($value)) {
19233                              break;
19234                          }
19235                          $this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
19236                          break;
19237                      }
19238                      case 'submit': {
19239                          if (!isset($value)) {
19240                              $value = 'submit';
19241                          }
19242                          $w = $this->GetStringWidth($value) * 1.5;
19243                          $h *= 1.6;
19244                          $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19245                          $action = array();
19246                          $action['S'] = 'SubmitForm';
19247                          $action['F'] = $this->form_action;
19248                          if ($this->form_enctype != 'FDF') {
19249                              $action['Flags'] = array('ExportFormat');
19250                          }
19251                          if ($this->form_mode == 'get') {
19252                              $action['Flags'] = array('GetMethod');
19253                          }
19254                          $this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
19255                          break;
19256                      }
19257                      case 'reset': {
19258                          if (!isset($value)) {
19259                              $value = 'reset';
19260                          }
19261                          $w = $this->GetStringWidth($value) * 1.5;
19262                          $h *= 1.6;
19263                          $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19264                          $this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
19265                          break;
19266                      }
19267                      case 'file': {
19268                          $prop['fileSelect'] = 'true';
19269                          $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19270                          if (!isset($value)) {
19271                              $value = '*';
19272                          }
19273                          $w = $this->GetStringWidth($value) * 2;
19274                          $h *= 1.2;
19275                          $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19276                          $jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
19277                          $this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19278                          break;
19279                      }
19280                      case 'hidden': {
19281                          if (isset($value)) {
19282                              $opt['v'] = $value;
19283                          }
19284                          $opt['f'] = array('invisible', 'hidden');
19285                          $this->TextField($name, 0, 0, $prop, $opt, '', '', false);
19286                          break;
19287                      }
19288                      case 'image': {
19289                          // THIS TYPE MUST BE FIXED
19290                          if (isset($tag['attribute']['src']) AND !TCPDF_STATIC::empty_string($tag['attribute']['src'])) {
19291                              $img = $tag['attribute']['src'];
19292                          } else {
19293                              break;
19294                          }
19295                          $value = 'img';
19296                          //$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
19297                          if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19298                              $jsaction = $tag['attribute']['onclick'];
19299                          } else {
19300                              $jsaction = '';
19301                          }
19302                          $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19303                          break;
19304                      }
19305                      case 'button': {
19306                          if (!isset($value)) {
19307                              $value = ' ';
19308                          }
19309                          $w = $this->GetStringWidth($value) * 1.5;
19310                          $h *= 1.6;
19311                          $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19312                          if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19313                              $jsaction = $tag['attribute']['onclick'];
19314                          } else {
19315                              $jsaction = '';
19316                          }
19317                          $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19318                          break;
19319                      }
19320                  }
19321                  break;
19322              }
19323              case 'textarea': {
19324                  $prop = array();
19325                  $opt = array();
19326                  if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19327                      $prop['readonly'] = true;
19328                  }
19329                  if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19330                      $name = $tag['attribute']['name'];
19331                  } else {
19332                      break;
19333                  }
19334                  if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19335                      $opt['v'] = $tag['attribute']['value'];
19336                  }
19337                  if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC::empty_string($tag['attribute']['cols'])) {
19338                      $w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
19339                  } else {
19340                      $w = 40;
19341                  }
19342                  if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
19343                      $h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize);
19344                  } else {
19345                      $h = 10;
19346                  }
19347                  $prop['multiline'] = 'true';
19348                  $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19349                  break;
19350              }
19351              case 'select': {
19352                  $h = $this->getCellHeight($this->FontSize);
19353                  if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19354                      $h *= ($tag['attribute']['size'] + 1);
19355                  }
19356                  $prop = array();
19357                  $opt = array();
19358                  if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19359                      $name = $tag['attribute']['name'];
19360                  } else {
19361                      break;
19362                  }
19363                  $w = 0;
19364                  if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
19365                      $options = explode('#!NwL!#', $tag['attribute']['opt']);
19366                      $values = array();
19367                      foreach ($options as $val) {
19368                          if (strpos($val, '#!TaB!#') !== false) {
19369                              $opts = explode('#!TaB!#', $val);
19370                              $values[] = $opts;
19371                              $w = max($w, $this->GetStringWidth($opts[1]));
19372                          } else {
19373                              $values[] = $val;
19374                              $w = max($w, $this->GetStringWidth($val));
19375                          }
19376                      }
19377                  } else {
19378                      break;
19379                  }
19380                  $w *= 2;
19381                  if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
19382                      $prop['multipleSelection'] = 'true';
19383                      $this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19384                  } else {
19385                      $this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19386                  }
19387                  break;
19388              }
19389              case 'tcpdf': {
19390                  if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
19391                      // Special tag used to call TCPDF methods
19392                      if (isset($tag['attribute']['method'])) {
19393                          $tcpdf_method = $tag['attribute']['method'];
19394                          if (method_exists($this, $tcpdf_method)) {
19395                              if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
19396                                  $params = TCPDF_STATIC::unserializeTCPDFtagParameters($tag['attribute']['params']);
19397                                  call_user_func_array(array($this, $tcpdf_method), $params);
19398                              } else {
19399                                  $this->$tcpdf_method();
19400                              }
19401                              $this->newline = true;
19402                          }
19403                      }
19404                  }
19405                  break;
19406              }
19407              default: {
19408                  break;
19409              }
19410          }
19411          // define tags that support borders and background colors
19412          $bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
19413          if (in_array($tag['value'], $bordertags)) {
19414              // set border
19415              $dom[$key]['borderposition'] = $this->getBorderStartPosition();
19416          }
19417          if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
19418              $pba = $dom[$key]['attribute']['pagebreakafter'];
19419              // check for pagebreak
19420              if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19421                  // add a page (or trig AcceptPageBreak() for multicolumn mode)
19422                  $this->checkPageBreak($this->PageBreakTrigger + 1);
19423              }
19424              if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19425                  OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19426                  // add a page (or trig AcceptPageBreak() for multicolumn mode)
19427                  $this->checkPageBreak($this->PageBreakTrigger + 1);
19428              }
19429          }
19430          return $dom;
19431      }
19432  
19433      /**
19434       * Process closing tags.
19435       * @param $dom (array) html dom array
19436       * @param $key (int) current element id
19437       * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19438       * @param $maxbottomliney (int) maximum y value of current line
19439       * @return $dom array
19440       * @protected
19441       */
19442  	protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
19443          $tag = $dom[$key];
19444          $parent = $dom[($dom[$key]['parent'])];
19445          $lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
19446          $in_table_head = false;
19447          // maximum x position (used to draw borders)
19448          if ($this->rtl) {
19449              $xmax = $this->w;
19450          } else {
19451              $xmax = 0;
19452          }
19453          if ($tag['block']) {
19454              $hbz = 0; // distance from y to line bottom
19455              $hb = 0; // vertical space between block tags
19456              // calculate vertical space for block tags
19457              if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
19458                  $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
19459              } elseif (isset($parent['fontsize'])) {
19460                  $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
19461              } else {
19462                  $pre_h = $this->getCellHeight($this->FontSize);
19463              }
19464              if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
19465                  $cn = $this->tagvspaces[$tag['value']][1]['n'];
19466              } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19467                  $cn = 0.6;
19468              } else {
19469                  $cn = 1;
19470              }
19471              if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
19472                  $hb = 0;
19473              } else {
19474                  $hb = ($cn * $pre_h);
19475              }
19476              if ($maxbottomliney > $this->PageBreakTrigger) {
19477                  $hbz = $this->getCellHeight($this->FontSize);
19478              } elseif ($this->y < $maxbottomliney) {
19479                  $hbz = ($maxbottomliney - $this->y);
19480              }
19481          }
19482          // Closing tag
19483          switch($tag['value']) {
19484              case 'tr': {
19485                  $table_el = $dom[($dom[$key]['parent'])]['parent'];
19486                  if (!isset($parent['endy'])) {
19487                      $dom[($dom[$key]['parent'])]['endy'] = $this->y;
19488                      $parent['endy'] = $this->y;
19489                  }
19490                  if (!isset($parent['endpage'])) {
19491                      $dom[($dom[$key]['parent'])]['endpage'] = $this->page;
19492                      $parent['endpage'] = $this->page;
19493                  }
19494                  if (!isset($parent['endcolumn'])) {
19495                      $dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
19496                      $parent['endcolumn'] = $this->current_column;
19497                  }
19498                  // update row-spanned cells
19499                  if (isset($dom[$table_el]['rowspans'])) {
19500                      foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19501                          $dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
19502                          if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19503                              if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
19504                                  $dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
19505                              } elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
19506                                  $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19507                                  $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19508                                  $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19509                              }
19510                          }
19511                      }
19512                      // report new endy and endpage to the rowspanned cells
19513                      foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19514                          if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19515                              $dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
19516                              $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19517                              $dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
19518                              $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19519                              $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
19520                              $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19521                          }
19522                      }
19523                      // update remaining rowspanned cells
19524                      foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19525                          if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19526                              $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
19527                              $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
19528                              $dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
19529                          }
19530                      }
19531                  }
19532                  $prev_page = $this->page;
19533                  $this->setPage($dom[($dom[$key]['parent'])]['endpage']);
19534                  if ($this->num_columns > 1) {
19535                      if ((($this->current_column == 0) AND ($dom[($dom[$key]['parent'])]['endcolumn'] == ($this->num_columns - 1)))
19536                          OR (($this->current_column == $dom[($dom[$key]['parent'])]['endcolumn']) AND ($prev_page < $this->page))) {
19537                          // page jump
19538                          $this->selectColumn(0);
19539                          $dom[($dom[$key]['parent'])]['endcolumn'] = 0;
19540                          $dom[($dom[$key]['parent'])]['endy'] = $this->y;
19541                      } else {
19542                          $this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
19543                          $this->y = $dom[($dom[$key]['parent'])]['endy'];
19544                      }
19545                  } else {
19546                      $this->y = $dom[($dom[$key]['parent'])]['endy'];
19547                  }
19548                  if (isset($dom[$table_el]['attribute']['cellspacing'])) {
19549                      $this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
19550                  } elseif (isset($dom[$table_el]['border-spacing'])) {
19551                      $this->y += $dom[$table_el]['border-spacing']['V'];
19552                  }
19553                  $this->Ln(0, $cell);
19554                  if ($this->current_column == $parent['startcolumn']) {
19555                      $this->x = $parent['startx'];
19556                  }
19557                  // account for booklet mode
19558                  if ($this->page > $parent['startpage']) {
19559                      if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
19560                          $this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
19561                      } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
19562                          $this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
19563                      }
19564                  }
19565                  break;
19566              }
19567              case 'tablehead':
19568                  // closing tag used for the thead part
19569                  $in_table_head = true;
19570                  $this->inthead = false;
19571              case 'table': {
19572                  $table_el = $parent;
19573                  // set default border
19574                  if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
19575                      // set default border
19576                      $border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
19577                  } else {
19578                      $border = 0;
19579                  }
19580                  $default_border = $border;
19581                  // fix bottom line alignment of last line before page break
19582                  foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
19583                      // update row-spanned cells
19584                      if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19585                          foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19586                              if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
19587                                  $dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
19588                              }
19589                              if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
19590                                  $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
19591                              }
19592                          }
19593                      }
19594                      if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
19595                          $pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
19596                          $dom[$prevtrkey]['endy'] = $pgendy;
19597                          // update row-spanned cells
19598                          if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19599                              foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19600                                  if (($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
19601                                      $dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
19602                                      $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
19603                                  }
19604                              }
19605                          }
19606                      }
19607                      $prevtrkey = $trkey;
19608                      $table_el = $dom[($dom[$key]['parent'])];
19609                  }
19610                  // for each row
19611                  if (count($table_el['trids']) > 0) {
19612                      unset($xmax);
19613                  }
19614                  foreach ($table_el['trids'] as $j => $trkey) {
19615                      $parent = $dom[$trkey];
19616                      if (!isset($xmax)) {
19617                          $xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
19618                      }
19619                      // for each cell on the row
19620                      foreach ($parent['cellpos'] as $k => $cellpos) {
19621                          if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
19622                              $cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
19623                              $cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
19624                              $endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
19625                              $startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
19626                              $endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
19627                              $startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
19628                              $endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
19629                          } else {
19630                              $endy = $parent['endy'];
19631                              $startpage = $parent['startpage'];
19632                              $endpage = $parent['endpage'];
19633                              $startcolumn = $parent['startcolumn'];
19634                              $endcolumn = $parent['endcolumn'];
19635                          }
19636                          if ($this->num_columns == 0) {
19637                              $this->num_columns = 1;
19638                          }
19639                          if (isset($cellpos['border'])) {
19640                              $border = $cellpos['border'];
19641                          }
19642                          if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
19643                              $this->SetFillColorArray($cellpos['bgcolor']);
19644                              $fill = true;
19645                          } else {
19646                              $fill = false;
19647                          }
19648                          $x = $cellpos['startx'];
19649                          $y = $parent['starty'];
19650                          $starty = $y;
19651                          $w = abs($cellpos['endx'] - $cellpos['startx']);
19652                          // get border modes
19653                          $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
19654                          $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
19655                          $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19656                          // design borders around HTML cells.
19657                          for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
19658                              $ccode = '';
19659                              $this->setPage($page);
19660                              if ($this->num_columns < 2) {
19661                                  // single-column mode
19662                                  $this->x = $x;
19663                                  $this->y = $this->tMargin;
19664                              }
19665                              // account for margin changes
19666                              if ($page > $startpage) {
19667                                  if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
19668                                      $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
19669                                  } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
19670                                      $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
19671                                  }
19672                              }
19673                              if ($startpage == $endpage) { // single page
19674                                  $deltacol = 0;
19675                                  $deltath = 0;
19676                                  for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
19677                                      $this->selectColumn($column);
19678                                      if ($startcolumn == $endcolumn) { // single column
19679                                          $cborder = $border;
19680                                          $h = $endy - $parent['starty'];
19681                                          $this->y = $y;
19682                                          $this->x = $x;
19683                                      } elseif ($column == $startcolumn) { // first column
19684                                          $cborder = $border_start;
19685                                          $this->y = $starty;
19686                                          $this->x = $x;
19687                                          $h = $this->h - $this->y - $this->bMargin;
19688                                          if ($this->rtl) {
19689                                              $deltacol = $this->x + $this->rMargin - $this->w;
19690                                          } else {
19691                                              $deltacol = $this->x - $this->lMargin;
19692                                          }
19693                                      } elseif ($column == $endcolumn) { // end column
19694                                          $cborder = $border_end;
19695                                          if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19696                                              $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19697                                          }
19698                                          $this->x += $deltacol;
19699                                          $h = $endy - $this->y;
19700                                      } else { // middle column
19701                                          $cborder = $border_middle;
19702                                          if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19703                                              $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19704                                          }
19705                                          $this->x += $deltacol;
19706                                          $h = $this->h - $this->y - $this->bMargin;
19707                                      }
19708                                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19709                                  } // end for each column
19710                              } elseif ($page == $startpage) { // first page
19711                                  $deltacol = 0;
19712                                  $deltath = 0;
19713                                  for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
19714                                      $this->selectColumn($column);
19715                                      if ($column == $startcolumn) { // first column
19716                                          $cborder = $border_start;
19717                                          $this->y = $starty;
19718                                          $this->x = $x;
19719                                          $h = $this->h - $this->y - $this->bMargin;
19720                                          if ($this->rtl) {
19721                                              $deltacol = $this->x + $this->rMargin - $this->w;
19722                                          } else {
19723                                              $deltacol = $this->x - $this->lMargin;
19724                                          }
19725                                      } else { // middle column
19726                                          $cborder = $border_middle;
19727                                          if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19728                                              $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19729                                          }
19730                                          $this->x += $deltacol;
19731                                          $h = $this->h - $this->y - $this->bMargin;
19732                                      }
19733                                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19734                                  } // end for each column
19735                              } elseif ($page == $endpage) { // last page
19736                                  $deltacol = 0;
19737                                  $deltath = 0;
19738                                  for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
19739                                      $this->selectColumn($column);
19740                                      if ($column == $endcolumn) { // end column
19741                                          $cborder = $border_end;
19742                                          if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19743                                              $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19744                                          }
19745                                          $this->x += $deltacol;
19746                                          $h = $endy - $this->y;
19747                                      } else { // middle column
19748                                          $cborder = $border_middle;
19749                                          if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19750                                              $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19751                                          }
19752                                          $this->x += $deltacol;
19753                                          $h = $this->h - $this->y - $this->bMargin;
19754                                      }
19755                                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19756                                  } // end for each column
19757                              } else { // middle page
19758                                  $deltacol = 0;
19759                                  $deltath = 0;
19760                                  for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
19761                                      $this->selectColumn($column);
19762                                      $cborder = $border_middle;
19763                                      if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19764                                          $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19765                                      }
19766                                      $this->x += $deltacol;
19767                                      $h = $this->h - $this->y - $this->bMargin;
19768                                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19769                                  } // end for each column
19770                              }
19771                              if ($cborder OR $fill) {
19772                                  $offsetlen = strlen($ccode);
19773                                  // draw border and fill
19774                                  if ($this->inxobj) {
19775                                      // we are inside an XObject template
19776                                      if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
19777                                          $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
19778                                          $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
19779                                          $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
19780                                      } else {
19781                                          $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
19782                                          $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
19783                                      }
19784                                      $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
19785                                      $pstart = substr($pagebuff, 0, $pagemark);
19786                                      $pend = substr($pagebuff, $pagemark);
19787                                      $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
19788                                  } else {
19789                                      // draw border and fill
19790                                      if (end($this->transfmrk[$this->page]) !== false) {
19791                                          $pagemarkkey = key($this->transfmrk[$this->page]);
19792                                          $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
19793                                      } elseif ($this->InFooter) {
19794                                          $pagemark = $this->footerpos[$this->page];
19795                                      } else {
19796                                          $pagemark = $this->intmrk[$this->page];
19797                                      }
19798                                      $pagebuff = $this->getPageBuffer($this->page);
19799                                      $pstart = substr($pagebuff, 0, $pagemark);
19800                                      $pend = substr($pagebuff, $pagemark);
19801                                      $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
19802                                  }
19803                              }
19804                          } // end for each page
19805                          // restore default border
19806                          $border = $default_border;
19807                      } // end for each cell on the row
19808                      if (isset($table_el['attribute']['cellspacing'])) {
19809                          $this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
19810                      } elseif (isset($table_el['border-spacing'])) {
19811                          $this->y += $table_el['border-spacing']['V'];
19812                      }
19813                      $this->Ln(0, $cell);
19814                      $this->x = $parent['startx'];
19815                      if ($endpage > $startpage) {
19816                          if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
19817                              $this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
19818                          } elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
19819                              $this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
19820                          }
19821                      }
19822                  }
19823                  if (!$in_table_head) { // we are not inside a thead section
19824                      $this->cell_padding = $table_el['old_cell_padding'];
19825                      // reset row height
19826                      $this->resetLastH();
19827                      if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
19828                          $plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
19829                          if (($plendiff > 0) AND ($plendiff < 60)) {
19830                              $pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
19831                              if (substr($pagediff, 0, 5) == 'BT /F') {
19832                                  // the difference is only a font setting
19833                                  $plendiff = 0;
19834                              }
19835                          }
19836                          if ($plendiff == 0) {
19837                              // remove last blank page
19838                              $this->deletePage($this->numpages);
19839                          }
19840                      }
19841                      if (isset($this->theadMargins['top'])) {
19842                          // restore top margin
19843                          $this->tMargin = $this->theadMargins['top'];
19844                      }
19845                      if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
19846                          // reset main table header
19847                          $this->thead = '';
19848                          $this->theadMargins = array();
19849                          $this->pagedim[$this->page]['tm'] = $this->tMargin;
19850                      }
19851                  }
19852                  $parent = $table_el;
19853                  break;
19854              }
19855              case 'a': {
19856                  $this->HREF = '';
19857                  break;
19858              }
19859              case 'sup': {
19860                  $this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
19861                  break;
19862              }
19863              case 'sub': {
19864                  $this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
19865                  break;
19866              }
19867              case 'div': {
19868                  $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19869                  break;
19870              }
19871              case 'blockquote': {
19872                  if ($this->rtl) {
19873                      $this->rMargin -= $this->listindent;
19874                  } else {
19875                      $this->lMargin -= $this->listindent;
19876                  }
19877                  --$this->listindentlevel;
19878                  $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19879                  break;
19880              }
19881              case 'p': {
19882                  $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19883                  break;
19884              }
19885              case 'pre': {
19886                  $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19887                  $this->premode = false;
19888                  break;
19889              }
19890              case 'dl': {
19891                  --$this->listnum;
19892                  if ($this->listnum <= 0) {
19893                      $this->listnum = 0;
19894                      $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19895                  } else {
19896                      $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19897                  }
19898                  $this->resetLastH();
19899                  break;
19900              }
19901              case 'dt': {
19902                  $this->lispacer = '';
19903                  $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19904                  break;
19905              }
19906              case 'dd': {
19907                  $this->lispacer = '';
19908                  if ($this->rtl) {
19909                      $this->rMargin -= $this->listindent;
19910                  } else {
19911                      $this->lMargin -= $this->listindent;
19912                  }
19913                  --$this->listindentlevel;
19914                  $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19915                  break;
19916              }
19917              case 'ul':
19918              case 'ol': {
19919                  --$this->listnum;
19920                  $this->lispacer = '';
19921                  if ($this->rtl) {
19922                      $this->rMargin -= $this->listindent;
19923                  } else {
19924                      $this->lMargin -= $this->listindent;
19925                  }
19926                  --$this->listindentlevel;
19927                  if ($this->listnum <= 0) {
19928                      $this->listnum = 0;
19929                      $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19930                  } else {
19931                      $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19932                  }
19933                  $this->resetLastH();
19934                  break;
19935              }
19936              case 'li': {
19937                  $this->lispacer = '';
19938                  $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19939                  break;
19940              }
19941              case 'h1':
19942              case 'h2':
19943              case 'h3':
19944              case 'h4':
19945              case 'h5':
19946              case 'h6': {
19947                  $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19948                  break;
19949              }
19950              // Form fields (since 4.8.000 - 2009-09-07)
19951              case 'form': {
19952                  $this->form_action = '';
19953                  $this->form_enctype = 'application/x-www-form-urlencoded';
19954                  break;
19955              }
19956              default : {
19957                  break;
19958              }
19959          }
19960          // draw border and background (if any)
19961          $this->drawHTMLTagBorder($parent, $xmax);
19962          if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
19963              $pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
19964              // check for pagebreak
19965              if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19966                  // add a page (or trig AcceptPageBreak() for multicolumn mode)
19967                  $this->checkPageBreak($this->PageBreakTrigger + 1);
19968              }
19969              if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19970                  OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19971                  // add a page (or trig AcceptPageBreak() for multicolumn mode)
19972                  $this->checkPageBreak($this->PageBreakTrigger + 1);
19973              }
19974          }
19975          $this->tmprtl = false;
19976          return $dom;
19977      }
19978  
19979      /**
19980       * Add vertical spaces if needed.
19981       * @param $hbz (string) Distance between current y and line bottom.
19982       * @param $hb (string) The height of the break.
19983       * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19984       * @param $firsttag (boolean) set to true when the tag is the first.
19985       * @param $lasttag (boolean) set to true when the tag is the last.
19986       * @protected
19987       */
19988  	protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
19989          if ($firsttag) {
19990              $this->Ln(0, $cell);
19991              $this->htmlvspace = 0;
19992              return;
19993          }
19994          if ($lasttag) {
19995              $this->Ln($hbz, $cell);
19996              $this->htmlvspace = 0;
19997              return;
19998          }
19999          if ($hb < $this->htmlvspace) {
20000              $hd = 0;
20001          } else {
20002              $hd = $hb - $this->htmlvspace;
20003              $this->htmlvspace = $hb;
20004          }
20005          $this->Ln(($hbz + $hd), $cell);
20006      }
20007  
20008      /**
20009       * Return the starting coordinates to draw an html border
20010       * @return array containing top-left border coordinates
20011       * @protected
20012       * @since 5.7.000 (2010-08-03)
20013       */
20014  	protected function getBorderStartPosition() {
20015          if ($this->rtl) {
20016              $xmax = $this->lMargin;
20017          } else {
20018              $xmax = $this->w - $this->rMargin;
20019          }
20020          return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
20021      }
20022  
20023      /**
20024       * Draw an HTML block border and fill
20025       * @param $tag (array) array of tag properties.
20026       * @param $xmax (int) end X coordinate for border.
20027       * @protected
20028       * @since 5.7.000 (2010-08-03)
20029       */
20030  	protected function drawHTMLTagBorder($tag, $xmax) {
20031          if (!isset($tag['borderposition'])) {
20032              // nothing to draw
20033              return;
20034          }
20035          $prev_x = $this->x;
20036          $prev_y = $this->y;
20037          $prev_lasth = $this->lasth;
20038          $border = 0;
20039          $fill = false;
20040          $this->lasth = 0;
20041          if (isset($tag['border']) AND !empty($tag['border'])) {
20042              // get border style
20043              $border = $tag['border'];
20044              if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
20045                  // border for table header
20046                  $border = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20047              }
20048          }
20049          if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
20050              // get background color
20051              $old_bgcolor = $this->bgcolor;
20052              $this->SetFillColorArray($tag['bgcolor']);
20053              $fill = true;
20054          }
20055          if (!$border AND !$fill) {
20056              // nothing to draw
20057              return;
20058          }
20059          if (isset($tag['attribute']['cellspacing'])) {
20060              $clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
20061              $cellspacing = array('H' => $clsp, 'V' => $clsp);
20062          } elseif (isset($tag['border-spacing'])) {
20063              $cellspacing = $tag['border-spacing'];
20064          } else {
20065              $cellspacing = array('H' => 0, 'V' => 0);
20066          }
20067          if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
20068              // draw the border externally respect the sqare edge.
20069              $border['mode'] = 'ext';
20070          }
20071          if ($this->rtl) {
20072              if ($xmax >= $tag['borderposition']['x']) {
20073                  $xmax = $tag['borderposition']['xmax'];
20074              }
20075              $w = ($tag['borderposition']['x'] - $xmax);
20076          } else {
20077              if ($xmax <= $tag['borderposition']['x']) {
20078                  $xmax = $tag['borderposition']['xmax'];
20079              }
20080              $w = ($xmax - $tag['borderposition']['x']);
20081          }
20082          if ($w <= 0) {
20083              return;
20084          }
20085          $w += $cellspacing['H'];
20086          $startpage = $tag['borderposition']['page'];
20087          $startcolumn = $tag['borderposition']['column'];
20088          $x = $tag['borderposition']['x'];
20089          $y = $tag['borderposition']['y'];
20090          $endpage = $this->page;
20091          $starty = $tag['borderposition']['y'] - $cellspacing['V'];
20092          $currentY = $this->y;
20093          $this->x = $x;
20094          // get latest column
20095          $endcolumn = $this->current_column;
20096          if ($this->num_columns == 0) {
20097              $this->num_columns = 1;
20098          }
20099          // get border modes
20100          $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
20101          $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
20102          $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
20103          // temporary disable page regions
20104          $temp_page_regions = $this->page_regions;
20105          $this->page_regions = array();
20106          // design borders around HTML cells.
20107          for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
20108              $ccode = '';
20109              $this->setPage($page);
20110              if ($this->num_columns < 2) {
20111                  // single-column mode
20112                  $this->x = $x;
20113                  $this->y = $this->tMargin;
20114              }
20115              // account for margin changes
20116              if ($page > $startpage) {
20117                  if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
20118                      $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
20119                  } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
20120                      $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
20121                  }
20122              }
20123              if ($startpage == $endpage) {
20124                  // single page
20125                  for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
20126                      $this->selectColumn($column);
20127                      if ($startcolumn == $endcolumn) { // single column
20128                          $cborder = $border;
20129                          $h = ($currentY - $y) + $cellspacing['V'];
20130                          $this->y = $starty;
20131                      } elseif ($column == $startcolumn) { // first column
20132                          $cborder = $border_start;
20133                          $this->y = $starty;
20134                          $h = $this->h - $this->y - $this->bMargin;
20135                      } elseif ($column == $endcolumn) { // end column
20136                          $cborder = $border_end;
20137                          $h = $currentY - $this->y;
20138                      } else { // middle column
20139                          $cborder = $border_middle;
20140                          $h = $this->h - $this->y - $this->bMargin;
20141                      }
20142                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20143                  } // end for each column
20144              } elseif ($page == $startpage) { // first page
20145                  for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
20146                      $this->selectColumn($column);
20147                      if ($column == $startcolumn) { // first column
20148                          $cborder = $border_start;
20149                          $this->y = $starty;
20150                          $h = $this->h - $this->y - $this->bMargin;
20151                      } else { // middle column
20152                          $cborder = $border_middle;
20153                          $h = $this->h - $this->y - $this->bMargin;
20154                      }
20155                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20156                  } // end for each column
20157              } elseif ($page == $endpage) { // last page
20158                  for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
20159                      $this->selectColumn($column);
20160                      if ($column == $endcolumn) {
20161                          // end column
20162                          $cborder = $border_end;
20163                          $h = $currentY - $this->y;
20164                      } else {
20165                          // middle column
20166                          $cborder = $border_middle;
20167                          $h = $this->h - $this->y - $this->bMargin;
20168                      }
20169                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20170                  } // end for each column
20171              } else { // middle page
20172                  for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
20173                      $this->selectColumn($column);
20174                      $cborder = $border_middle;
20175                      $h = $this->h - $this->y - $this->bMargin;
20176                      $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20177                  } // end for each column
20178              }
20179              if ($cborder OR $fill) {
20180                  $offsetlen = strlen($ccode);
20181                  // draw border and fill
20182                  if ($this->inxobj) {
20183                      // we are inside an XObject template
20184                      if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
20185                          $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
20186                          $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
20187                          $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
20188                      } else {
20189                          $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
20190                          $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
20191                      }
20192                      $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
20193                      $pstart = substr($pagebuff, 0, $pagemark);
20194                      $pend = substr($pagebuff, $pagemark);
20195                      $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
20196                  } else {
20197                      if (end($this->transfmrk[$this->page]) !== false) {
20198                          $pagemarkkey = key($this->transfmrk[$this->page]);
20199                          $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
20200                      } elseif ($this->InFooter) {
20201                          $pagemark = $this->footerpos[$this->page];
20202                      } else {
20203                          $pagemark = $this->intmrk[$this->page];
20204                      }
20205                      $pagebuff = $this->getPageBuffer($this->page);
20206                      $pstart = substr($pagebuff, 0, $pagemark);
20207                      $pend = substr($pagebuff, $pagemark);
20208                      $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
20209                      $this->bordermrk[$this->page] += $offsetlen;
20210                      $this->cntmrk[$this->page] += $offsetlen;
20211                  }
20212              }
20213          } // end for each page
20214          // restore page regions
20215          $this->page_regions = $temp_page_regions;
20216          if (isset($old_bgcolor)) {
20217              // restore background color
20218              $this->SetFillColorArray($old_bgcolor);
20219          }
20220          // restore pointer position
20221          $this->x = $prev_x;
20222          $this->y = $prev_y;
20223          $this->lasth = $prev_lasth;
20224      }
20225  
20226      /**
20227       * Set the default bullet to be used as LI bullet symbol
20228       * @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')
20229       * @public
20230       * @since 4.0.028 (2008-09-26)
20231       */
20232  	public function setLIsymbol($symbol='!') {
20233          // check for custom image symbol
20234          if (substr($symbol, 0, 4) == 'img|') {
20235              $this->lisymbol = $symbol;
20236              return;
20237          }
20238          $symbol = strtolower($symbol);
20239          $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');
20240          if (in_array($symbol, $valid_symbols)) {
20241              $this->lisymbol = $symbol;
20242          } else {
20243              $this->lisymbol = '';
20244          }
20245      }
20246  
20247      /**
20248       * Set the booklet mode for double-sided pages.
20249       * @param $booklet (boolean) true set the booklet mode on, false otherwise.
20250       * @param $inner (float) Inner page margin.
20251       * @param $outer (float) Outer page margin.
20252       * @public
20253       * @since 4.2.000 (2008-10-29)
20254       */
20255  	public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
20256          $this->booklet = $booklet;
20257          if ($inner >= 0) {
20258              $this->lMargin = $inner;
20259          }
20260          if ($outer >= 0) {
20261              $this->rMargin = $outer;
20262          }
20263      }
20264  
20265      /**
20266       * Swap the left and right margins.
20267       * @param $reverse (boolean) if true swap left and right margins.
20268       * @protected
20269       * @since 4.2.000 (2008-10-29)
20270       */
20271  	protected function swapMargins($reverse=true) {
20272          if ($reverse) {
20273              // swap left and right margins
20274              $mtemp = $this->original_lMargin;
20275              $this->original_lMargin = $this->original_rMargin;
20276              $this->original_rMargin = $mtemp;
20277              $deltam = $this->original_lMargin - $this->original_rMargin;
20278              $this->lMargin += $deltam;
20279              $this->rMargin -= $deltam;
20280          }
20281      }
20282  
20283      /**
20284       * Set the vertical spaces for HTML tags.
20285       * The array must have the following structure (example):
20286       * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
20287       * The first array level contains the tag names,
20288       * the second level contains 0 for opening tags or 1 for closing tags,
20289       * the third level contains the vertical space unit (h) and the number spaces to add (n).
20290       * If the h parameter is not specified, default values are used.
20291       * @param $tagvs (array) array of tags and relative vertical spaces.
20292       * @public
20293       * @since 4.2.001 (2008-10-30)
20294       */
20295  	public function setHtmlVSpace($tagvs) {
20296          $this->tagvspaces = $tagvs;
20297      }
20298  
20299      /**
20300       * Set custom width for list indentation.
20301       * @param $width (float) width of the indentation. Use negative value to disable it.
20302       * @public
20303       * @since 4.2.007 (2008-11-12)
20304       */
20305  	public function setListIndentWidth($width) {
20306          return $this->customlistindent = floatval($width);
20307      }
20308  
20309      /**
20310       * Set the top/bottom cell sides to be open or closed when the cell cross the page.
20311       * @param $isopen (boolean) if true keeps the top/bottom border open for the cell sides that cross the page.
20312       * @public
20313       * @since 4.2.010 (2008-11-14)
20314       */
20315  	public function setOpenCell($isopen) {
20316          $this->opencell = $isopen;
20317      }
20318  
20319      /**
20320       * Set the color and font style for HTML links.
20321       * @param $color (array) RGB array of colors
20322       * @param $fontstyle (string) additional font styles to add
20323       * @public
20324       * @since 4.4.003 (2008-12-09)
20325       */
20326  	public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
20327          $this->htmlLinkColorArray = $color;
20328          $this->htmlLinkFontStyle = $fontstyle;
20329      }
20330  
20331      /**
20332       * Convert HTML string containing value and unit of measure to user's units or points.
20333       * @param $htmlval (string) String containing values and unit.
20334       * @param $refsize (string) Reference value in points.
20335       * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
20336       * @param $points (boolean) If true returns points, otherwise returns value in user's units.
20337       * @return float value in user's unit or point if $points=true
20338       * @public
20339       * @since 4.4.004 (2008-12-10)
20340       */
20341  	public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
20342          $supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
20343          $retval = 0;
20344          $value = 0;
20345          $unit = 'px';
20346          if ($points) {
20347              $k = 1;
20348          } else {
20349              $k = $this->k;
20350          }
20351          if (in_array($defaultunit, $supportedunits)) {
20352              $unit = $defaultunit;
20353          }
20354          if (is_numeric($htmlval)) {
20355              $value = floatval($htmlval);
20356          } elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
20357              $value = floatval($mnum[1]);
20358              if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
20359                  if (in_array($munit[1], $supportedunits)) {
20360                      $unit = $munit[1];
20361                  }
20362              }
20363          }
20364          switch ($unit) {
20365              // percentage
20366              case '%': {
20367                  $retval = (($value * $refsize) / 100);
20368                  break;
20369              }
20370              // relative-size
20371              case 'em': {
20372                  $retval = ($value * $refsize);
20373                  break;
20374              }
20375              // height of lower case 'x' (about half the font-size)
20376              case 'ex': {
20377                  $retval = ($value * ($refsize / 2));
20378                  break;
20379              }
20380              // absolute-size
20381              case 'in': {
20382                  $retval = (($value * $this->dpi) / $k);
20383                  break;
20384              }
20385              // centimeters
20386              case 'cm': {
20387                  $retval = (($value / 2.54 * $this->dpi) / $k);
20388                  break;
20389              }
20390              // millimeters
20391              case 'mm': {
20392                  $retval = (($value / 25.4 * $this->dpi) / $k);
20393                  break;
20394              }
20395              // one pica is 12 points
20396              case 'pc': {
20397                  $retval = (($value * 12) / $k);
20398                  break;
20399              }
20400              // points
20401              case 'pt': {
20402                  $retval = ($value / $k);
20403                  break;
20404              }
20405              // pixels
20406              case 'px': {
20407                  $retval = $this->pixelsToUnits($value);
20408                  if ($points) {
20409                      $retval *= $this->k;
20410                  }
20411                  break;
20412              }
20413          }
20414          return $retval;
20415      }
20416  
20417      /**
20418       * Output an HTML list bullet or ordered item symbol
20419       * @param $listdepth (int) list nesting level
20420       * @param $listtype (string) type of list
20421       * @param $size (float) current font size
20422       * @protected
20423       * @since 4.4.004 (2008-12-10)
20424       */
20425  	protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
20426          if ($this->state != 2) {
20427              return;
20428          }
20429          $size /= $this->k;
20430          $fill = '';
20431          $bgcolor = $this->bgcolor;
20432          $color = $this->fgcolor;
20433          $strokecolor = $this->strokecolor;
20434          $width = 0;
20435          $textitem = '';
20436          $tmpx = $this->x;
20437          $lspace = $this->GetStringWidth('  ');
20438          if ($listtype == '^') {
20439              // special symbol used for avoid justification of rect bullet
20440              $this->lispacer = '';
20441              return;
20442          } elseif ($listtype == '!') {
20443              // set default list type for unordered list
20444              $deftypes = array('disc', 'circle', 'square');
20445              $listtype = $deftypes[($listdepth - 1) % 3];
20446          } elseif ($listtype == '#') {
20447              // set default list type for ordered list
20448              $listtype = 'decimal';
20449          } elseif (substr($listtype, 0, 4) == 'img|') {
20450              // custom image type ('img|type|width|height|image.ext')
20451              $img = explode('|', $listtype);
20452              $listtype = 'img';
20453          }
20454          switch ($listtype) {
20455              // unordered types
20456              case 'none': {
20457                  break;
20458              }
20459              case 'disc': {
20460                  $r = $size / 6;
20461                  $lspace += (2 * $r);
20462                  if ($this->rtl) {
20463                      $this->x += $lspace;
20464                  } else {
20465                      $this->x -= $lspace;
20466                  }
20467                  $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
20468                  break;
20469              }
20470              case 'circle': {
20471                  $r = $size / 6;
20472                  $lspace += (2 * $r);
20473                  if ($this->rtl) {
20474                      $this->x += $lspace;
20475                  } else {
20476                      $this->x -= $lspace;
20477                  }
20478                  $prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
20479                  $new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
20480                  $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
20481                  $this->_out($prev_line_style); // restore line settings
20482                  break;
20483              }
20484              case 'square': {
20485                  $l = $size / 3;
20486                  $lspace += $l;
20487                  if ($this->rtl) {;
20488                      $this->x += $lspace;
20489                  } else {
20490                      $this->x -= $lspace;
20491                  }
20492                  $this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
20493                  break;
20494              }
20495              case 'img': {
20496                  // 1=>type, 2=>width, 3=>height, 4=>image.ext
20497                  $lspace += $img[2];
20498                  if ($this->rtl) {;
20499                      $this->x += $lspace;
20500                  } else {
20501                      $this->x -= $lspace;
20502                  }
20503                  $imgtype = strtolower($img[1]);
20504                  $prev_y = $this->y;
20505                  switch ($imgtype) {
20506                      case 'svg': {
20507                          $this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
20508                          break;
20509                      }
20510                      case 'ai':
20511                      case 'eps': {
20512                          $this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
20513                          break;
20514                      }
20515                      default: {
20516                          $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);
20517                          break;
20518                      }
20519                  }
20520                  $this->y = $prev_y;
20521                  break;
20522              }
20523              // ordered types
20524              // $this->listcount[$this->listnum];
20525              // $textitem
20526              case '1':
20527              case 'decimal': {
20528                  $textitem = $this->listcount[$this->listnum];
20529                  break;
20530              }
20531              case 'decimal-leading-zero': {
20532                  $textitem = sprintf('%02d', $this->listcount[$this->listnum]);
20533                  break;
20534              }
20535              case 'i':
20536              case 'lower-roman': {
20537                  $textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
20538                  break;
20539              }
20540              case 'I':
20541              case 'upper-roman': {
20542                  $textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
20543                  break;
20544              }
20545              case 'a':
20546              case 'lower-alpha':
20547              case 'lower-latin': {
20548                  $textitem = chr(97 + $this->listcount[$this->listnum] - 1);
20549                  break;
20550              }
20551              case 'A':
20552              case 'upper-alpha':
20553              case 'upper-latin': {
20554                  $textitem = chr(65 + $this->listcount[$this->listnum] - 1);
20555                  break;
20556              }
20557              case 'lower-greek': {
20558                  $textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
20559                  break;
20560              }
20561              /*
20562              // Types to be implemented (special handling)
20563              case 'hebrew': {
20564                  break;
20565              }
20566              case 'armenian': {
20567                  break;
20568              }
20569              case 'georgian': {
20570                  break;
20571              }
20572              case 'cjk-ideographic': {
20573                  break;
20574              }
20575              case 'hiragana': {
20576                  break;
20577              }
20578              case 'katakana': {
20579                  break;
20580              }
20581              case 'hiragana-iroha': {
20582                  break;
20583              }
20584              case 'katakana-iroha': {
20585                  break;
20586              }
20587              */
20588              default: {
20589                  $textitem = $this->listcount[$this->listnum];
20590              }
20591          }
20592          if (!TCPDF_STATIC::empty_string($textitem)) {
20593              // Check whether we need a new page or new column
20594              $prev_y = $this->y;
20595              $h = $this->getCellHeight($this->FontSize);
20596              if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
20597                  $tmpx = $this->x;
20598              }
20599              // print ordered item
20600              if ($this->rtl) {
20601                  $textitem = '.'.$textitem;
20602              } else {
20603                  $textitem = $textitem.'.';
20604              }
20605              $lspace += $this->GetStringWidth($textitem);
20606              if ($this->rtl) {
20607                  $this->x += $lspace;
20608              } else {
20609                  $this->x -= $lspace;
20610              }
20611              $this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
20612          }
20613          $this->x = $tmpx;
20614          $this->lispacer = '^';
20615          // restore colors
20616          $this->SetFillColorArray($bgcolor);
20617          $this->SetDrawColorArray($strokecolor);
20618          $this->SettextColorArray($color);
20619      }
20620  
20621      /**
20622       * Returns current graphic variables as array.
20623       * @return array of graphic variables
20624       * @protected
20625       * @since 4.2.010 (2008-11-14)
20626       */
20627  	protected function getGraphicVars() {
20628          $grapvars = array(
20629              'FontFamily' => $this->FontFamily,
20630              'FontStyle' => $this->FontStyle,
20631              'FontSizePt' => $this->FontSizePt,
20632              'rMargin' => $this->rMargin,
20633              'lMargin' => $this->lMargin,
20634              'cell_padding' => $this->cell_padding,
20635              'cell_margin' => $this->cell_margin,
20636              'LineWidth' => $this->LineWidth,
20637              'linestyleWidth' => $this->linestyleWidth,
20638              'linestyleCap' => $this->linestyleCap,
20639              'linestyleJoin' => $this->linestyleJoin,
20640              'linestyleDash' => $this->linestyleDash,
20641              'textrendermode' => $this->textrendermode,
20642              'textstrokewidth' => $this->textstrokewidth,
20643              'DrawColor' => $this->DrawColor,
20644              'FillColor' => $this->FillColor,
20645              'TextColor' => $this->TextColor,
20646              'ColorFlag' => $this->ColorFlag,
20647              'bgcolor' => $this->bgcolor,
20648              'fgcolor' => $this->fgcolor,
20649              'htmlvspace' => $this->htmlvspace,
20650              'listindent' => $this->listindent,
20651              'listindentlevel' => $this->listindentlevel,
20652              'listnum' => $this->listnum,
20653              'listordered' => $this->listordered,
20654              'listcount' => $this->listcount,
20655              'lispacer' => $this->lispacer,
20656              'cell_height_ratio' => $this->cell_height_ratio,
20657              'font_stretching' => $this->font_stretching,
20658              'font_spacing' => $this->font_spacing,
20659              'alpha' => $this->alpha,
20660              // extended
20661              'lasth' => $this->lasth,
20662              'tMargin' => $this->tMargin,
20663              'bMargin' => $this->bMargin,
20664              'AutoPageBreak' => $this->AutoPageBreak,
20665              'PageBreakTrigger' => $this->PageBreakTrigger,
20666              'x' => $this->x,
20667              'y' => $this->y,
20668              'w' => $this->w,
20669              'h' => $this->h,
20670              'wPt' => $this->wPt,
20671              'hPt' => $this->hPt,
20672              'fwPt' => $this->fwPt,
20673              'fhPt' => $this->fhPt,
20674              'page' => $this->page,
20675              'current_column' => $this->current_column,
20676              'num_columns' => $this->num_columns
20677              );
20678          return $grapvars;
20679      }
20680  
20681      /**
20682       * Set graphic variables.
20683       * @param $gvars (array) array of graphic variablesto restore
20684       * @param $extended (boolean) if true restore extended graphic variables
20685       * @protected
20686       * @since 4.2.010 (2008-11-14)
20687       */
20688  	protected function setGraphicVars($gvars, $extended=false) {
20689          if ($this->state != 2) {
20690               return;
20691          }
20692          $this->FontFamily = $gvars['FontFamily'];
20693          $this->FontStyle = $gvars['FontStyle'];
20694          $this->FontSizePt = $gvars['FontSizePt'];
20695          $this->rMargin = $gvars['rMargin'];
20696          $this->lMargin = $gvars['lMargin'];
20697          $this->cell_padding = $gvars['cell_padding'];
20698          $this->cell_margin = $gvars['cell_margin'];
20699          $this->LineWidth = $gvars['LineWidth'];
20700          $this->linestyleWidth = $gvars['linestyleWidth'];
20701          $this->linestyleCap = $gvars['linestyleCap'];
20702          $this->linestyleJoin = $gvars['linestyleJoin'];
20703          $this->linestyleDash = $gvars['linestyleDash'];
20704          $this->textrendermode = $gvars['textrendermode'];
20705          $this->textstrokewidth = $gvars['textstrokewidth'];
20706          $this->DrawColor = $gvars['DrawColor'];
20707          $this->FillColor = $gvars['FillColor'];
20708          $this->TextColor = $gvars['TextColor'];
20709          $this->ColorFlag = $gvars['ColorFlag'];
20710          $this->bgcolor = $gvars['bgcolor'];
20711          $this->fgcolor = $gvars['fgcolor'];
20712          $this->htmlvspace = $gvars['htmlvspace'];
20713          $this->listindent = $gvars['listindent'];
20714          $this->listindentlevel = $gvars['listindentlevel'];
20715          $this->listnum = $gvars['listnum'];
20716          $this->listordered = $gvars['listordered'];
20717          $this->listcount = $gvars['listcount'];
20718          $this->lispacer = $gvars['lispacer'];
20719          $this->cell_height_ratio = $gvars['cell_height_ratio'];
20720          $this->font_stretching = $gvars['font_stretching'];
20721          $this->font_spacing = $gvars['font_spacing'];
20722          $this->alpha = $gvars['alpha'];
20723          if ($extended) {
20724              // restore extended values
20725              $this->lasth = $gvars['lasth'];
20726              $this->tMargin = $gvars['tMargin'];
20727              $this->bMargin = $gvars['bMargin'];
20728              $this->AutoPageBreak = $gvars['AutoPageBreak'];
20729              $this->PageBreakTrigger = $gvars['PageBreakTrigger'];
20730              $this->x = $gvars['x'];
20731              $this->y = $gvars['y'];
20732              $this->w = $gvars['w'];
20733              $this->h = $gvars['h'];
20734              $this->wPt = $gvars['wPt'];
20735              $this->hPt = $gvars['hPt'];
20736              $this->fwPt = $gvars['fwPt'];
20737              $this->fhPt = $gvars['fhPt'];
20738              $this->page = $gvars['page'];
20739              $this->current_column = $gvars['current_column'];
20740              $this->num_columns = $gvars['num_columns'];
20741          }
20742          $this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
20743          if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
20744              $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
20745          }
20746      }
20747  
20748      /**
20749       * Outputs the "save graphics state" operator 'q'
20750       * @protected
20751       */
20752  	protected function _outSaveGraphicsState() {
20753          $this->_out('q');
20754      }
20755  
20756      /**
20757       * Outputs the "restore graphics state" operator 'Q'
20758       * @protected
20759       */
20760  	protected function _outRestoreGraphicsState() {
20761          $this->_out('Q');
20762      }
20763  
20764      /**
20765       * Writes data to a temporary file on filesystem.
20766       * @param $filename (string) file name
20767       * @param $data (mixed) data to write on file
20768       * @param $append (boolean) if true append data, false replace.
20769       * @param $serialize (boolean) if true serialize data.
20770       * @since 4.5.000 (2008-12-31)
20771       * @protected
20772       */
20773  	protected function writeDiskCache($filename, $data, $append=false, $serialize=false) {
20774          if ($append) {
20775              $fmode = 'ab+';
20776          } else {
20777              $fmode = 'wb+';
20778          }
20779          $f = @fopen($filename, $fmode);
20780          if (!$f) {
20781              $this->Error('Unable to write cache file: '.$filename);
20782          }
20783          if ($serialize) {
20784              $data = $this->file_id.serialize($data);
20785          }
20786          fwrite($f, $data);
20787          fclose($f);
20788          // update file length (needed for transactions)
20789          if (!isset($this->cache_file_length['_'.$filename])) {
20790              $this->cache_file_length['_'.$filename] = strlen($data);
20791          } else {
20792              $this->cache_file_length['_'.$filename] += strlen($data);
20793          }
20794      }
20795  
20796      /**
20797       * Read data from a temporary file on filesystem.
20798       * @param $filename (string) file name
20799       * @param $unserialize (boolean) if true unserialize data.
20800       * @return mixed retrieved data
20801       * @since 4.5.000 (2008-12-31)
20802       * @protected
20803       */
20804  	protected function readDiskCache($filename, $unserialize=false) {
20805          $data = file_get_contents($filename);
20806          if ($data === FALSE) {
20807              $this->Error('Unable to read the file: '.$filename);
20808          }
20809          if ($unserialize) {
20810              if (substr($data, 0, 32) != $this->file_id) {
20811                  $this->Error('Invalid cache file: '.$filename);
20812              }
20813              $data = unserialize(substr($data, 32));
20814          }
20815          return $data;
20816      }
20817  
20818      /**
20819       * Set buffer content (always append data).
20820       * @param $data (string) data
20821       * @protected
20822       * @since 4.5.000 (2009-01-02)
20823       */
20824  	protected function setBuffer($data) {
20825          $this->bufferlen += strlen($data);
20826          if ($this->diskcache) {
20827              if (!isset($this->buffer) OR TCPDF_STATIC::empty_string($this->buffer)) {
20828                  $this->buffer = TCPDF_STATIC::getObjFilename('buf');
20829              }
20830              $this->writeDiskCache($this->buffer, $data, true, false);
20831          } else {
20832              $this->buffer .= $data;
20833          }
20834      }
20835  
20836      /**
20837       * Replace the buffer content
20838       * @param $data (string) data
20839       * @protected
20840       * @since 5.5.000 (2010-06-22)
20841       */
20842  	protected function replaceBuffer($data) {
20843          $this->bufferlen = strlen($data);
20844          if ($this->diskcache) {
20845              if (!isset($this->buffer) OR TCPDF_STATIC::empty_string($this->buffer)) {
20846                  $this->buffer = TCPDF_STATIC::getObjFilename('buf');
20847              }
20848              $this->writeDiskCache($this->buffer, $data, false, false);
20849          } else {
20850              $this->buffer = $data;
20851          }
20852      }
20853  
20854      /**
20855       * Get buffer content.
20856       * @return string buffer content
20857       * @protected
20858       * @since 4.5.000 (2009-01-02)
20859       */
20860  	protected function getBuffer() {
20861          if ($this->diskcache) {
20862              return $this->readDiskCache($this->buffer, false);
20863          } else {
20864              return $this->buffer;
20865          }
20866      }
20867  
20868      /**
20869       * Set page buffer content.
20870       * @param $page (int) page number
20871       * @param $data (string) page data
20872       * @param $append (boolean) if true append data, false replace.
20873       * @protected
20874       * @since 4.5.000 (2008-12-31)
20875       */
20876  	protected function setPageBuffer($page, $data, $append=false) {
20877          if ($this->diskcache) {
20878              if (!isset($this->pages[$page])) {
20879                  $this->pages[$page] = TCPDF_STATIC::getObjFilename('page');
20880              }
20881              $this->writeDiskCache($this->pages[$page], $data, $append, false);
20882          } else {
20883              if ($append) {
20884                  $this->pages[$page] .= $data;
20885              } else {
20886                  $this->pages[$page] = $data;
20887              }
20888          }
20889          if ($append AND isset($this->pagelen[$page])) {
20890              $this->pagelen[$page] += strlen($data);
20891          } else {
20892              $this->pagelen[$page] = strlen($data);
20893          }
20894      }
20895  
20896      /**
20897       * Get page buffer content.
20898       * @param $page (int) page number
20899       * @return string page buffer content or false in case of error
20900       * @protected
20901       * @since 4.5.000 (2008-12-31)
20902       */
20903  	protected function getPageBuffer($page) {
20904          if ($this->diskcache) {
20905              return $this->readDiskCache($this->pages[$page], false);
20906          } elseif (isset($this->pages[$page])) {
20907              return $this->pages[$page];
20908          }
20909          return false;
20910      }
20911  
20912      /**
20913       * Set image buffer content.
20914       * @param $image (string) image key
20915       * @param $data (array) image data
20916       * @return int image index number
20917       * @protected
20918       * @since 4.5.000 (2008-12-31)
20919       */
20920  	protected function setImageBuffer($image, $data) {
20921          if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
20922              $this->imagekeys[$this->numimages] = $image;
20923              $data['i'] = $this->numimages;
20924              ++$this->numimages;
20925          }
20926          if ($this->diskcache) {
20927              if (!isset($this->images[$image])) {
20928                  $this->images[$image] = TCPDF_STATIC::getObjFilename('img');
20929              }
20930              $this->writeDiskCache($this->images[$image], $data, false, true);
20931          } else {
20932              $this->images[$image] = $data;
20933          }
20934          return $data['i'];
20935      }
20936  
20937      /**
20938       * Set image buffer content for a specified sub-key.
20939       * @param $image (string) image key
20940       * @param $key (string) image sub-key
20941       * @param $data (array) image data
20942       * @protected
20943       * @since 4.5.000 (2008-12-31)
20944       */
20945  	protected function setImageSubBuffer($image, $key, $data) {
20946          if (!isset($this->images[$image])) {
20947              $this->setImageBuffer($image, array());
20948          }
20949          if ($this->diskcache) {
20950              $tmpimg = $this->getImageBuffer($image);
20951              $tmpimg[$key] = $data;
20952              $this->writeDiskCache($this->images[$image], $tmpimg, false, true);
20953          } else {
20954              $this->images[$image][$key] = $data;
20955          }
20956      }
20957  
20958      /**
20959       * Get image buffer content.
20960       * @param $image (string) image key
20961       * @return string image buffer content or false in case of error
20962       * @protected
20963       * @since 4.5.000 (2008-12-31)
20964       */
20965  	protected function getImageBuffer($image) {
20966          if ($this->diskcache AND isset($this->images[$image])) {
20967              return $this->readDiskCache($this->images[$image], true);
20968          } elseif (isset($this->images[$image])) {
20969              return $this->images[$image];
20970          }
20971          return false;
20972      }
20973  
20974      /**
20975       * Set font buffer content.
20976       * @param $font (string) font key
20977       * @param $data (array) font data
20978       * @protected
20979       * @since 4.5.000 (2009-01-02)
20980       */
20981  	protected function setFontBuffer($font, $data) {
20982          if ($this->diskcache) {
20983              if (!isset($this->fonts[$font])) {
20984                  $this->fonts[$font] = TCPDF_STATIC::getObjFilename('font');
20985              }
20986              $this->writeDiskCache($this->fonts[$font], $data, false, true);
20987          } else {
20988              $this->fonts[$font] = $data;
20989          }
20990          if (!in_array($font, $this->fontkeys)) {
20991              $this->fontkeys[] = $font;
20992              // store object ID for current font
20993              ++$this->n;
20994              $this->font_obj_ids[$font] = $this->n;
20995              $this->setFontSubBuffer($font, 'n', $this->n);
20996          }
20997      }
20998  
20999      /**
21000       * Set font buffer content.
21001       * @param $font (string) font key
21002       * @param $key (string) font sub-key
21003       * @param $data (array) font data
21004       * @protected
21005       * @since 4.5.000 (2009-01-02)
21006       */
21007  	protected function setFontSubBuffer($font, $key, $data) {
21008          if (!isset($this->fonts[$font])) {
21009              $this->setFontBuffer($font, array());
21010          }
21011          if ($this->diskcache) {
21012              $tmpfont = $this->getFontBuffer($font);
21013              $tmpfont[$key] = $data;
21014              $this->writeDiskCache($this->fonts[$font], $tmpfont, false, true);
21015          } else {
21016              $this->fonts[$font][$key] = $data;
21017          }
21018      }
21019  
21020      /**
21021       * Get font buffer content.
21022       * @param $font (string) font key
21023       * @return string font buffer content or false in case of error
21024       * @protected
21025       * @since 4.5.000 (2009-01-02)
21026       */
21027  	protected function getFontBuffer($font) {
21028          if ($this->diskcache AND isset($this->fonts[$font])) {
21029              return $this->readDiskCache($this->fonts[$font], true);
21030          } elseif (isset($this->fonts[$font])) {
21031              return $this->fonts[$font];
21032          }
21033          return false;
21034      }
21035  
21036      /**
21037       * Move a page to a previous position.
21038       * @param $frompage (int) number of the source page
21039       * @param $topage (int) number of the destination page (must be less than $frompage)
21040       * @return true in case of success, false in case of error.
21041       * @public
21042       * @since 4.5.000 (2009-01-02)
21043       */
21044  	public function movePage($frompage, $topage) {
21045          if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
21046              return false;
21047          }
21048          if ($frompage == $this->page) {
21049              // close the page before moving it
21050              $this->endPage();
21051          }
21052          // move all page-related states
21053          $tmppage = $this->getPageBuffer($frompage);
21054          $tmppagedim = $this->pagedim[$frompage];
21055          $tmppagelen = $this->pagelen[$frompage];
21056          $tmpintmrk = $this->intmrk[$frompage];
21057          $tmpbordermrk = $this->bordermrk[$frompage];
21058          $tmpcntmrk = $this->cntmrk[$frompage];
21059          $tmppageobjects = $this->pageobjects[$frompage];
21060          if (isset($this->footerpos[$frompage])) {
21061              $tmpfooterpos = $this->footerpos[$frompage];
21062          }
21063          if (isset($this->footerlen[$frompage])) {
21064              $tmpfooterlen = $this->footerlen[$frompage];
21065          }
21066          if (isset($this->transfmrk[$frompage])) {
21067              $tmptransfmrk = $this->transfmrk[$frompage];
21068          }
21069          if (isset($this->PageAnnots[$frompage])) {
21070              $tmpannots = $this->PageAnnots[$frompage];
21071          }
21072          if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21073              for ($i = $frompage; $i > $topage; --$i) {
21074                  if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
21075                      --$this->pagegroups[$this->newpagegroup[$i]];
21076                      break;
21077                  }
21078              }
21079              for ($i = $topage; $i > 0; --$i) {
21080                  if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
21081                      ++$this->pagegroups[$this->newpagegroup[$i]];
21082                      break;
21083                  }
21084              }
21085          }
21086          for ($i = $frompage; $i > $topage; --$i) {
21087              $j = $i - 1;
21088              // shift pages down
21089              $this->setPageBuffer($i, $this->getPageBuffer($j));
21090              $this->pagedim[$i] = $this->pagedim[$j];
21091              $this->pagelen[$i] = $this->pagelen[$j];
21092              $this->intmrk[$i] = $this->intmrk[$j];
21093              $this->bordermrk[$i] = $this->bordermrk[$j];
21094              $this->cntmrk[$i] = $this->cntmrk[$j];
21095              $this->pageobjects[$i] = $this->pageobjects[$j];
21096              if (isset($this->footerpos[$j])) {
21097                  $this->footerpos[$i] = $this->footerpos[$j];
21098              } elseif (isset($this->footerpos[$i])) {
21099                  unset($this->footerpos[$i]);
21100              }
21101              if (isset($this->footerlen[$j])) {
21102                  $this->footerlen[$i] = $this->footerlen[$j];
21103              } elseif (isset($this->footerlen[$i])) {
21104                  unset($this->footerlen[$i]);
21105              }
21106              if (isset($this->transfmrk[$j])) {
21107                  $this->transfmrk[$i] = $this->transfmrk[$j];
21108              } elseif (isset($this->transfmrk[$i])) {
21109                  unset($this->transfmrk[$i]);
21110              }
21111              if (isset($this->PageAnnots[$j])) {
21112                  $this->PageAnnots[$i] = $this->PageAnnots[$j];
21113              } elseif (isset($this->PageAnnots[$i])) {
21114                  unset($this->PageAnnots[$i]);
21115              }
21116              if (isset($this->newpagegroup[$j])) {
21117                  $this->newpagegroup[$i] = $this->newpagegroup[$j];
21118                  unset($this->newpagegroup[$j]);
21119              }
21120              if ($this->currpagegroup == $j) {
21121                  $this->currpagegroup = $i;
21122              }
21123          }
21124          $this->setPageBuffer($topage, $tmppage);
21125          $this->pagedim[$topage] = $tmppagedim;
21126          $this->pagelen[$topage] = $tmppagelen;
21127          $this->intmrk[$topage] = $tmpintmrk;
21128          $this->bordermrk[$topage] = $tmpbordermrk;
21129          $this->cntmrk[$topage] = $tmpcntmrk;
21130          $this->pageobjects[$topage] = $tmppageobjects;
21131          if (isset($tmpfooterpos)) {
21132              $this->footerpos[$topage] = $tmpfooterpos;
21133          } elseif (isset($this->footerpos[$topage])) {
21134              unset($this->footerpos[$topage]);
21135          }
21136          if (isset($tmpfooterlen)) {
21137              $this->footerlen[$topage] = $tmpfooterlen;
21138          } elseif (isset($this->footerlen[$topage])) {
21139              unset($this->footerlen[$topage]);
21140          }
21141          if (isset($tmptransfmrk)) {
21142              $this->transfmrk[$topage] = $tmptransfmrk;
21143          } elseif (isset($this->transfmrk[$topage])) {
21144              unset($this->transfmrk[$topage]);
21145          }
21146          if (isset($tmpannots)) {
21147              $this->PageAnnots[$topage] = $tmpannots;
21148          } elseif (isset($this->PageAnnots[$topage])) {
21149              unset($this->PageAnnots[$topage]);
21150          }
21151          // adjust outlines
21152          $tmpoutlines = $this->outlines;
21153          foreach ($tmpoutlines as $key => $outline) {
21154              if (!$outline['f']) {
21155                  if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
21156                      $this->outlines[$key]['p'] = ($outline['p'] + 1);
21157                  } elseif ($outline['p'] == $frompage) {
21158                      $this->outlines[$key]['p'] = $topage;
21159                  }
21160              }
21161          }
21162          // adjust dests
21163          $tmpdests = $this->dests;
21164          foreach ($tmpdests as $key => $dest) {
21165              if (!$dest['f']) {
21166                  if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
21167                      $this->dests[$key]['p'] = ($dest['p'] + 1);
21168                  } elseif ($dest['p'] == $frompage) {
21169                      $this->dests[$key]['p'] = $topage;
21170                  }
21171              }
21172          }
21173          // adjust links
21174          $tmplinks = $this->links;
21175          foreach ($tmplinks as $key => $link) {
21176              if (!$link['f']) {
21177                  if (($link['p'] >= $topage) AND ($link['p'] < $frompage)) {
21178                      $this->links[$key]['p'] = ($link['p'] + 1);
21179                  } elseif ($link['p'] == $frompage) {
21180                      $this->links[$key]['p'] = $topage;
21181                  }
21182              }
21183          }
21184          // adjust javascript
21185          $jfrompage = $frompage;
21186          $jtopage = $topage;
21187          if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21188              foreach($pamatch[0] as $pk => $pmatch) {
21189                  $pagenum = intval($pamatch[3][$pk]) + 1;
21190                  if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
21191                      $newpage = ($pagenum + 1);
21192                  } elseif ($pagenum == $jfrompage) {
21193                      $newpage = $jtopage;
21194                  } else {
21195                      $newpage = $pagenum;
21196                  }
21197                  --$newpage;
21198                  $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21199                  $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21200              }
21201              unset($pamatch);
21202          }
21203          // return to last page
21204          $this->lastPage(true);
21205          return true;
21206      }
21207  
21208      /**
21209       * Remove the specified page.
21210       * @param $page (int) page to remove
21211       * @return true in case of success, false in case of error.
21212       * @public
21213       * @since 4.6.004 (2009-04-23)
21214       */
21215  	public function deletePage($page) {
21216          if (($page < 1) OR ($page > $this->numpages)) {
21217              return false;
21218          }
21219          // delete current page
21220          unset($this->pages[$page]);
21221          unset($this->pagedim[$page]);
21222          unset($this->pagelen[$page]);
21223          unset($this->intmrk[$page]);
21224          unset($this->bordermrk[$page]);
21225          unset($this->cntmrk[$page]);
21226          foreach ($this->pageobjects[$page] as $oid) {
21227              if (isset($this->offsets[$oid])){
21228                  unset($this->offsets[$oid]);
21229              }
21230          }
21231          unset($this->pageobjects[$page]);
21232          if (isset($this->footerpos[$page])) {
21233              unset($this->footerpos[$page]);
21234          }
21235          if (isset($this->footerlen[$page])) {
21236              unset($this->footerlen[$page]);
21237          }
21238          if (isset($this->transfmrk[$page])) {
21239              unset($this->transfmrk[$page]);
21240          }
21241          if (isset($this->PageAnnots[$page])) {
21242              unset($this->PageAnnots[$page]);
21243          }
21244          if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21245              for ($i = $page; $i > 0; --$i) {
21246                  if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
21247                      --$this->pagegroups[$this->newpagegroup[$i]];
21248                      break;
21249                  }
21250              }
21251          }
21252          if (isset($this->pageopen[$page])) {
21253              unset($this->pageopen[$page]);
21254          }
21255          if ($page < $this->numpages) {
21256              // update remaining pages
21257              for ($i = $page; $i < $this->numpages; ++$i) {
21258                  $j = $i + 1;
21259                  // shift pages
21260                  $this->setPageBuffer($i, $this->getPageBuffer($j));
21261                  $this->pagedim[$i] = $this->pagedim[$j];
21262                  $this->pagelen[$i] = $this->pagelen[$j];
21263                  $this->intmrk[$i] = $this->intmrk[$j];
21264                  $this->bordermrk[$i] = $this->bordermrk[$j];
21265                  $this->cntmrk[$i] = $this->cntmrk[$j];
21266                  $this->pageobjects[$i] = $this->pageobjects[$j];
21267                  if (isset($this->footerpos[$j])) {
21268                      $this->footerpos[$i] = $this->footerpos[$j];
21269                  } elseif (isset($this->footerpos[$i])) {
21270                      unset($this->footerpos[$i]);
21271                  }
21272                  if (isset($this->footerlen[$j])) {
21273                      $this->footerlen[$i] = $this->footerlen[$j];
21274                  } elseif (isset($this->footerlen[$i])) {
21275                      unset($this->footerlen[$i]);
21276                  }
21277                  if (isset($this->transfmrk[$j])) {
21278                      $this->transfmrk[$i] = $this->transfmrk[$j];
21279                  } elseif (isset($this->transfmrk[$i])) {
21280                      unset($this->transfmrk[$i]);
21281                  }
21282                  if (isset($this->PageAnnots[$j])) {
21283                      $this->PageAnnots[$i] = $this->PageAnnots[$j];
21284                  } elseif (isset($this->PageAnnots[$i])) {
21285                      unset($this->PageAnnots[$i]);
21286                  }
21287                  if (isset($this->newpagegroup[$j])) {
21288                      $this->newpagegroup[$i] = $this->newpagegroup[$j];
21289                      unset($this->newpagegroup[$j]);
21290                  }
21291                  if ($this->currpagegroup == $j) {
21292                      $this->currpagegroup = $i;
21293                  }
21294                  if (isset($this->pageopen[$j])) {
21295                      $this->pageopen[$i] = $this->pageopen[$j];
21296                  } elseif (isset($this->pageopen[$i])) {
21297                      unset($this->pageopen[$i]);
21298                  }
21299              }
21300              // remove last page
21301              unset($this->pages[$this->numpages]);
21302              unset($this->pagedim[$this->numpages]);
21303              unset($this->pagelen[$this->numpages]);
21304              unset($this->intmrk[$this->numpages]);
21305              unset($this->bordermrk[$this->numpages]);
21306              unset($this->cntmrk[$this->numpages]);
21307              foreach ($this->pageobjects[$this->numpages] as $oid) {
21308                  if (isset($this->offsets[$oid])){
21309                      unset($this->offsets[$oid]);
21310                  }
21311              }
21312              unset($this->pageobjects[$this->numpages]);
21313              if (isset($this->footerpos[$this->numpages])) {
21314                  unset($this->footerpos[$this->numpages]);
21315              }
21316              if (isset($this->footerlen[$this->numpages])) {
21317                  unset($this->footerlen[$this->numpages]);
21318              }
21319              if (isset($this->transfmrk[$this->numpages])) {
21320                  unset($this->transfmrk[$this->numpages]);
21321              }
21322              if (isset($this->PageAnnots[$this->numpages])) {
21323                  unset($this->PageAnnots[$this->numpages]);
21324              }
21325              if (isset($this->newpagegroup[$this->numpages])) {
21326                  unset($this->newpagegroup[$this->numpages]);
21327              }
21328              if ($this->currpagegroup == $this->numpages) {
21329                  $this->currpagegroup = ($this->numpages - 1);
21330              }
21331              if (isset($this->pagegroups[$this->numpages])) {
21332                  unset($this->pagegroups[$this->numpages]);
21333              }
21334              if (isset($this->pageopen[$this->numpages])) {
21335                  unset($this->pageopen[$this->numpages]);
21336              }
21337          }
21338          --$this->numpages;
21339          $this->page = $this->numpages;
21340          // adjust outlines
21341          $tmpoutlines = $this->outlines;
21342          foreach ($tmpoutlines as $key => $outline) {
21343              if (!$outline['f']) {
21344                  if ($outline['p'] > $page) {
21345                      $this->outlines[$key]['p'] = $outline['p'] - 1;
21346                  } elseif ($outline['p'] == $page) {
21347                      unset($this->outlines[$key]);
21348                  }
21349              }
21350          }
21351          // adjust dests
21352          $tmpdests = $this->dests;
21353          foreach ($tmpdests as $key => $dest) {
21354              if (!$dest['f']) {
21355                  if ($dest['p'] > $page) {
21356                      $this->dests[$key]['p'] = $dest['p'] - 1;
21357                  } elseif ($dest['p'] == $page) {
21358                      unset($this->dests[$key]);
21359                  }
21360              }
21361          }
21362          // adjust links
21363          $tmplinks = $this->links;
21364          foreach ($tmplinks as $key => $link) {
21365              if (!$link['f']) {
21366                  if ($link['p'] > $page) {
21367                      $this->links[$key]['p'] = $link['p'] - 1;
21368                  } elseif ($link['p'] == $page) {
21369                      unset($this->links[$key]);
21370                  }
21371              }
21372          }
21373          // adjust javascript
21374          $jpage = $page;
21375          if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21376              foreach($pamatch[0] as $pk => $pmatch) {
21377                  $pagenum = intval($pamatch[3][$pk]) + 1;
21378                  if ($pagenum >= $jpage) {
21379                      $newpage = ($pagenum - 1);
21380                  } elseif ($pagenum == $jpage) {
21381                      $newpage = 1;
21382                  } else {
21383                      $newpage = $pagenum;
21384                  }
21385                  --$newpage;
21386                  $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21387                  $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21388              }
21389              unset($pamatch);
21390          }
21391          // return to last page
21392          if ($this->numpages > 0) {
21393              $this->lastPage(true);
21394          }
21395          return true;
21396      }
21397  
21398      /**
21399       * Clone the specified page to a new page.
21400       * @param $page (int) number of page to copy (0 = current page)
21401       * @return true in case of success, false in case of error.
21402       * @public
21403       * @since 4.9.015 (2010-04-20)
21404       */
21405  	public function copyPage($page=0) {
21406          if ($page == 0) {
21407              // default value
21408              $page = $this->page;
21409          }
21410          if (($page < 1) OR ($page > $this->numpages)) {
21411              return false;
21412          }
21413          // close the last page
21414          $this->endPage();
21415          // copy all page-related states
21416          ++$this->numpages;
21417          $this->page = $this->numpages;
21418          $this->setPageBuffer($this->page, $this->getPageBuffer($page));
21419          $this->pagedim[$this->page] = $this->pagedim[$page];
21420          $this->pagelen[$this->page] = $this->pagelen[$page];
21421          $this->intmrk[$this->page] = $this->intmrk[$page];
21422          $this->bordermrk[$this->page] = $this->bordermrk[$page];
21423          $this->cntmrk[$this->page] = $this->cntmrk[$page];
21424          $this->pageobjects[$this->page] = $this->pageobjects[$page];
21425          $this->pageopen[$this->page] = false;
21426          if (isset($this->footerpos[$page])) {
21427              $this->footerpos[$this->page] = $this->footerpos[$page];
21428          }
21429          if (isset($this->footerlen[$page])) {
21430              $this->footerlen[$this->page] = $this->footerlen[$page];
21431          }
21432          if (isset($this->transfmrk[$page])) {
21433              $this->transfmrk[$this->page] = $this->transfmrk[$page];
21434          }
21435          if (isset($this->PageAnnots[$page])) {
21436              $this->PageAnnots[$this->page] = $this->PageAnnots[$page];
21437          }
21438          if (isset($this->newpagegroup[$page])) {
21439              // start a new group
21440              $this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
21441              $this->currpagegroup = $this->newpagegroup[$this->page];
21442              $this->pagegroups[$this->currpagegroup] = 1;
21443          } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
21444              ++$this->pagegroups[$this->currpagegroup];
21445          }
21446          // copy outlines
21447          $tmpoutlines = $this->outlines;
21448          foreach ($tmpoutlines as $key => $outline) {
21449              if ($outline['p'] == $page) {
21450                  $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']);
21451              }
21452          }
21453          // copy links
21454          $tmplinks = $this->links;
21455          foreach ($tmplinks as $key => $link) {
21456              if ($link['p'] == $page) {
21457                  $this->links[] = array('p' => $this->page, 'y' => $link['y'], 'f' => $link['f']);
21458              }
21459          }
21460          // return to last page
21461          $this->lastPage(true);
21462          return true;
21463      }
21464  
21465      /**
21466       * Output a Table of Content Index (TOC).
21467       * This method must be called after all Bookmarks were set.
21468       * Before calling this method you have to open the page using the addTOCPage() method.
21469       * After calling this method you have to call endTOCPage() to close the TOC page.
21470       * You can override this method to achieve different styles.
21471       * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21472       * @param $numbersfont (string) set the font for page numbers (please use monospaced font for better alignment).
21473       * @param $filler (string) string used to fill the space between text and page number.
21474       * @param $toc_name (string) name to use for TOC bookmark.
21475       * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21476       * @param $color (array) RGB color array for bookmark title (values from 0 to 255).
21477       * @public
21478       * @author Nicola Asuni
21479       * @since 4.5.000 (2009-01-02)
21480       * @see addTOCPage(), endTOCPage(), addHTMLTOC()
21481       */
21482  	public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
21483          $fontsize = $this->FontSizePt;
21484          $fontfamily = $this->FontFamily;
21485          $fontstyle = $this->FontStyle;
21486          $w = $this->w - $this->lMargin - $this->rMargin;
21487          $spacer = $this->GetStringWidth(chr(32)) * 4;
21488          $lmargin = $this->lMargin;
21489          $rmargin = $this->rMargin;
21490          $x_start = $this->GetX();
21491          $page_first = $this->page;
21492          $current_page = $this->page;
21493          $page_fill_start = false;
21494          $page_fill_end = false;
21495          $current_column = $this->current_column;
21496          if (TCPDF_STATIC::empty_string($numbersfont)) {
21497              $numbersfont = $this->default_monospaced_font;
21498          }
21499          if (TCPDF_STATIC::empty_string($filler)) {
21500              $filler = ' ';
21501          }
21502          if (TCPDF_STATIC::empty_string($page)) {
21503              $gap = ' ';
21504          } else {
21505              $gap = '';
21506              if ($page < 1) {
21507                  $page = 1;
21508              }
21509          }
21510          $this->SetFont($numbersfont, $fontstyle, $fontsize);
21511          $numwidth = $this->GetStringWidth('00000');
21512          $maxpage = 0; //used for pages on attached documents
21513          foreach ($this->outlines as $key => $outline) {
21514              // check for extra pages (used for attachments)
21515              if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
21516                  $outline['p'] += ($this->page - $page_first);
21517              }
21518              if ($this->rtl) {
21519                  $aligntext = 'R';
21520                  $alignnum = 'L';
21521              } else {
21522                  $aligntext = 'L';
21523                  $alignnum = 'R';
21524              }
21525              if ($outline['l'] == 0) {
21526                  $this->SetFont($fontfamily, $outline['s'].'B', $fontsize);
21527              } else {
21528                  $this->SetFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
21529              }
21530              $this->SetTextColorArray($outline['c']);
21531              // check for page break
21532              $this->checkPageBreak(2 * $this->getCellHeight($this->FontSize));
21533              // set margins and X position
21534              if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
21535                  $this->lMargin = $lmargin;
21536                  $this->rMargin = $rmargin;
21537              } else {
21538                  if ($this->current_column != $current_column) {
21539                      if ($this->rtl) {
21540                          $x_start = $this->w - $this->columns[$this->current_column]['x'];
21541                      } else {
21542                          $x_start = $this->columns[$this->current_column]['x'];
21543                      }
21544                  }
21545                  $lmargin = $this->lMargin;
21546                  $rmargin = $this->rMargin;
21547                  $current_page = $this->page;
21548                  $current_column = $this->current_column;
21549              }
21550              $this->SetX($x_start);
21551              $indent = ($spacer * $outline['l']);
21552              if ($this->rtl) {
21553                  $this->x -= $indent;
21554                  $this->rMargin = $this->w - $this->x;
21555              } else {
21556                  $this->x += $indent;
21557                  $this->lMargin = $this->x;
21558              }
21559              $link = $this->AddLink();
21560              $this->SetLink($link, $outline['y'], $outline['p']);
21561              // write the text
21562              if ($this->rtl) {
21563                  $txt = ' '.$outline['t'];
21564              } else {
21565                  $txt = $outline['t'].' ';
21566              }
21567              $this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
21568              if ($this->rtl) {
21569                  $tw = $this->x - $this->lMargin;
21570              } else {
21571                  $tw = $this->w - $this->rMargin - $this->x;
21572              }
21573              $this->SetFont($numbersfont, $fontstyle, $fontsize);
21574              if (TCPDF_STATIC::empty_string($page)) {
21575                  $pagenum = $outline['p'];
21576              } else {
21577                  // placemark to be replaced with the correct number
21578                  $pagenum = '{#'.($outline['p']).'}';
21579                  if ($this->isUnicodeFont()) {
21580                      $pagenum = '{'.$pagenum.'}';
21581                  }
21582                  $maxpage = max($maxpage, $outline['p']);
21583              }
21584              $fw = ($tw - $this->GetStringWidth($pagenum.$filler));
21585              $wfiller = $this->GetStringWidth($filler);
21586              if ($wfiller > 0) {
21587                  $numfills = floor($fw / $wfiller);
21588              } else {
21589                  $numfills = 0;
21590              }
21591              if ($numfills > 0) {
21592                  $rowfill = str_repeat($filler, $numfills);
21593              } else {
21594                  $rowfill = '';
21595              }
21596              if ($this->rtl) {
21597                  $pagenum = $pagenum.$gap.$rowfill;
21598              } else {
21599                  $pagenum = $rowfill.$gap.$pagenum;
21600              }
21601              // write the number
21602              $this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
21603          }
21604          $page_last = $this->getPage();
21605          $numpages = ($page_last - $page_first + 1);
21606          // account for booklet mode
21607          if ($this->booklet) {
21608              // check if a blank page is required before TOC
21609              $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21610              $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21611              if ($page_fill_start) {
21612                  // add a page at the end (to be moved before TOC)
21613                  $this->addPage();
21614                  ++$page_last;
21615                  ++$numpages;
21616              }
21617              if ($page_fill_end) {
21618                  // add a page at the end
21619                  $this->addPage();
21620                  ++$page_last;
21621                  ++$numpages;
21622              }
21623          }
21624          $maxpage = max($maxpage, $page_last);
21625          if (!TCPDF_STATIC::empty_string($page)) {
21626              for ($p = $page_first; $p <= $page_last; ++$p) {
21627                  // get page data
21628                  $temppage = $this->getPageBuffer($p);
21629                  for ($n = 1; $n <= $maxpage; ++$n) {
21630                      // update page numbers
21631                      $a = '{#'.$n.'}';
21632                      // get page number aliases
21633                      $pnalias = $this->getInternalPageNumberAliases($a);
21634                      // calculate replacement number
21635                      if (($n >= $page) AND ($n <= $this->numpages)) {
21636                          $np = $n + $numpages;
21637                      } else {
21638                          $np = $n;
21639                      }
21640                      $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21641                      $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21642                      // replace aliases with numbers
21643                      foreach ($pnalias['u'] as $u) {
21644                          $sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
21645                          if ($this->rtl) {
21646                              $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21647                          } else {
21648                              $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21649                          }
21650                          $temppage = str_replace($u, $nr, $temppage);
21651                      }
21652                      foreach ($pnalias['a'] as $a) {
21653                          $sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
21654                          if ($this->rtl) {
21655                              $nr = $na.' '.$sfill;
21656                          } else {
21657                              $nr = $sfill.' '.$na;
21658                          }
21659                          $temppage = str_replace($a, $nr, $temppage);
21660                      }
21661                  }
21662                  // save changes
21663                  $this->setPageBuffer($p, $temppage);
21664              }
21665              // move pages
21666              $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21667              if ($page_fill_start) {
21668                  $this->movePage($page_last, $page_first);
21669              }
21670              for ($i = 0; $i < $numpages; ++$i) {
21671                  $this->movePage($page_last, $page);
21672              }
21673          }
21674      }
21675  
21676      /**
21677       * Output a Table Of Content Index (TOC) using HTML templates.
21678       * This method must be called after all Bookmarks were set.
21679       * Before calling this method you have to open the page using the addTOCPage() method.
21680       * After calling this method you have to call endTOCPage() to close the TOC page.
21681       * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21682       * @param $toc_name (string) name to use for TOC bookmark.
21683       * @param $templates (array) array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
21684       * @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)
21685       * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21686       * @param $color (array) RGB color array for title (values from 0 to 255).
21687       * @public
21688       * @author Nicola Asuni
21689       * @since 5.0.001 (2010-05-06)
21690       * @see addTOCPage(), endTOCPage(), addTOC()
21691       */
21692  	public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
21693          $filler = ' ';
21694          $prev_htmlLinkColorArray = $this->htmlLinkColorArray;
21695          $prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
21696          // set new style for link
21697          $this->htmlLinkColorArray = array();
21698          $this->htmlLinkFontStyle = '';
21699          $page_first = $this->getPage();
21700          $page_fill_start = false;
21701          $page_fill_end = false;
21702          // get the font type used for numbers in each template
21703          $current_font = $this->FontFamily;
21704          foreach ($templates as $level => $html) {
21705              $dom = $this->getHtmlDomArray($html);
21706              foreach ($dom as $key => $value) {
21707                  if ($value['value'] == '#TOC_PAGE_NUMBER#') {
21708                      $this->SetFont($dom[($key - 1)]['fontname']);
21709                      $templates['F'.$level] = $this->isUnicodeFont();
21710                  }
21711              }
21712          }
21713          $this->SetFont($current_font);
21714          $maxpage = 0; //used for pages on attached documents
21715          foreach ($this->outlines as $key => $outline) {
21716              // get HTML template
21717              $row = $templates[$outline['l']];
21718              if (TCPDF_STATIC::empty_string($page)) {
21719                  $pagenum = $outline['p'];
21720              } else {
21721                  // placemark to be replaced with the correct number
21722                  $pagenum = '{#'.($outline['p']).'}';
21723                  if ($templates['F'.$outline['l']]) {
21724                      $pagenum = '{'.$pagenum.'}';
21725                  }
21726                  $maxpage = max($maxpage, $outline['p']);
21727              }
21728              // replace templates with current values
21729              $row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
21730              $row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
21731              // add link to page
21732              $row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
21733              // write bookmark entry
21734              $this->writeHTML($row, false, false, true, false, '');
21735          }
21736          // restore link styles
21737          $this->htmlLinkColorArray = $prev_htmlLinkColorArray;
21738          $this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
21739          // move TOC page and replace numbers
21740          $page_last = $this->getPage();
21741          $numpages = ($page_last - $page_first + 1);
21742          // account for booklet mode
21743          if ($this->booklet) {
21744              // check if a blank page is required before TOC
21745              $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21746              $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21747              if ($page_fill_start) {
21748                  // add a page at the end (to be moved before TOC)
21749                  $this->addPage();
21750                  ++$page_last;
21751                  ++$numpages;
21752              }
21753              if ($page_fill_end) {
21754                  // add a page at the end
21755                  $this->addPage();
21756                  ++$page_last;
21757                  ++$numpages;
21758              }
21759          }
21760          $maxpage = max($maxpage, $page_last);
21761          if (!TCPDF_STATIC::empty_string($page)) {
21762              for ($p = $page_first; $p <= $page_last; ++$p) {
21763                  // get page data
21764                  $temppage = $this->getPageBuffer($p);
21765                  for ($n = 1; $n <= $maxpage; ++$n) {
21766                      // update page numbers
21767                      $a = '{#'.$n.'}';
21768                      // get page number aliases
21769                      $pnalias = $this->getInternalPageNumberAliases($a);
21770                      // calculate replacement number
21771                      if ($n >= $page) {
21772                          $np = $n + $numpages;
21773                      } else {
21774                          $np = $n;
21775                      }
21776                      $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21777                      $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21778                      // replace aliases with numbers
21779                      foreach ($pnalias['u'] as $u) {
21780                          if ($correct_align) {
21781                              $sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
21782                              if ($this->rtl) {
21783                                  $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21784                              } else {
21785                                  $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21786                              }
21787                          } else {
21788                              $nr = $nu;
21789                          }
21790                          $temppage = str_replace($u, $nr, $temppage);
21791                      }
21792                      foreach ($pnalias['a'] as $a) {
21793                          if ($correct_align) {
21794                              $sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
21795                              if ($this->rtl) {
21796                                  $nr = $na.' '.$sfill;
21797                              } else {
21798                                  $nr = $sfill.' '.$na;
21799                              }
21800                          } else {
21801                              $nr = $na;
21802                          }
21803                          $temppage = str_replace($a, $nr, $temppage);
21804                      }
21805                  }
21806                  // save changes
21807                  $this->setPageBuffer($p, $temppage);
21808              }
21809              // move pages
21810              $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21811              if ($page_fill_start) {
21812                  $this->movePage($page_last, $page_first);
21813              }
21814              for ($i = 0; $i < $numpages; ++$i) {
21815                  $this->movePage($page_last, $page);
21816              }
21817          }
21818      }
21819  
21820      /**
21821       * Stores a copy of the current TCPDF object used for undo operation.
21822       * @public
21823       * @since 4.5.029 (2009-03-19)
21824       */
21825  	public function startTransaction() {
21826          if (isset($this->objcopy)) {
21827              // remove previous copy
21828              $this->commitTransaction();
21829          }
21830          // record current page number and Y position
21831          $this->start_transaction_page = $this->page;
21832          $this->start_transaction_y = $this->y;
21833          // clone current object
21834          $this->objcopy = TCPDF_STATIC::objclone($this);
21835      }
21836  
21837      /**
21838       * Delete the copy of the current TCPDF object used for undo operation.
21839       * @public
21840       * @since 4.5.029 (2009-03-19)
21841       */
21842  	public function commitTransaction() {
21843          if (isset($this->objcopy)) {
21844              $this->objcopy->_destroy(true, true);
21845              unset($this->objcopy);
21846          }
21847      }
21848  
21849      /**
21850       * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
21851       * @param $self (boolean) if true restores current class object to previous state without the need of reassignment via the returned value.
21852       * @return TCPDF object.
21853       * @public
21854       * @since 4.5.029 (2009-03-19)
21855       */
21856  	public function rollbackTransaction($self=false) {
21857          if (isset($this->objcopy)) {
21858              if (isset($this->objcopy->diskcache) AND $this->objcopy->diskcache) {
21859                  // truncate files to previous values
21860                  foreach ($this->objcopy->cache_file_length as $file => $length) {
21861                      $file = substr($file, 1);
21862                      $handle = fopen($file, 'r+');
21863                      ftruncate($handle, $length);
21864                  }
21865              }
21866              $this->_destroy(true, true);
21867              if ($self) {
21868                  $objvars = get_object_vars($this->objcopy);
21869                  foreach ($objvars as $key => $value) {
21870                      $this->$key = $value;
21871                  }
21872              }
21873              return $this->objcopy;
21874          }
21875          return $this;
21876      }
21877  
21878      // --- MULTI COLUMNS METHODS -----------------------
21879  
21880      /**
21881       * Set multiple columns of the same size
21882       * @param $numcols (int) number of columns (set to zero to disable columns mode)
21883       * @param $width (int) column width
21884       * @param $y (int) column starting Y position (leave empty for current Y position)
21885       * @public
21886       * @since 4.9.001 (2010-03-28)
21887       */
21888  	public function setEqualColumns($numcols=0, $width=0, $y='') {
21889          $this->columns = array();
21890          if ($numcols < 2) {
21891              $numcols = 0;
21892              $this->columns = array();
21893          } else {
21894              // maximum column width
21895              $maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
21896              if (($width == 0) OR ($width > $maxwidth)) {
21897                  $width = $maxwidth;
21898              }
21899              if (TCPDF_STATIC::empty_string($y)) {
21900                  $y = $this->y;
21901              }
21902              // space between columns
21903              $space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
21904              // fill the columns array (with, space, starting Y position)
21905              for ($i = 0; $i < $numcols; ++$i) {
21906                  $this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
21907              }
21908          }
21909          $this->num_columns = $numcols;
21910          $this->current_column = 0;
21911          $this->column_start_page = $this->page;
21912          $this->selectColumn(0);
21913      }
21914  
21915      /**
21916       * Remove columns and reset page margins.
21917       * @public
21918       * @since 5.9.072 (2011-04-26)
21919       */
21920  	public function resetColumns() {
21921          $this->lMargin = $this->original_lMargin;
21922          $this->rMargin = $this->original_rMargin;
21923          $this->setEqualColumns();
21924      }
21925  
21926      /**
21927       * Set columns array.
21928       * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
21929       * @param $columns (array)
21930       * @public
21931       * @since 4.9.001 (2010-03-28)
21932       */
21933  	public function setColumnsArray($columns) {
21934          $this->columns = $columns;
21935          $this->num_columns = count($columns);
21936          $this->current_column = 0;
21937          $this->column_start_page = $this->page;
21938          $this->selectColumn(0);
21939      }
21940  
21941      /**
21942       * Set position at a given column
21943       * @param $col (int) column number (from 0 to getNumberOfColumns()-1); empty string = current column.
21944       * @public
21945       * @since 4.9.001 (2010-03-28)
21946       */
21947  	public function selectColumn($col='') {
21948          if (is_string($col)) {
21949              $col = $this->current_column;
21950          } elseif ($col >= $this->num_columns) {
21951              $col = 0;
21952          }
21953          $xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
21954          $enable_thead = false;
21955          if ($this->num_columns > 1) {
21956              if ($col != $this->current_column) {
21957                  // move Y pointer at the top of the column
21958                  if ($this->column_start_page == $this->page) {
21959                      $this->y = $this->columns[$col]['y'];
21960                  } else {
21961                      $this->y = $this->tMargin;
21962                  }
21963                  // Avoid to write table headers more than once
21964                  if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
21965                      $enable_thead = true;
21966                      $this->maxselcol['page'] = $this->page;
21967                      $this->maxselcol['column'] = $col;
21968                  }
21969              }
21970              $xshift = $this->colxshift;
21971              // set X position of the current column by case
21972              $listindent = ($this->listindentlevel * $this->listindent);
21973              // calculate column X position
21974              $colpos = 0;
21975              for ($i = 0; $i < $col; ++$i) {
21976                  $colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
21977              }
21978              if ($this->rtl) {
21979                  $x = $this->w - $this->original_rMargin - $colpos;
21980                  $this->rMargin = ($this->w - $x + $listindent);
21981                  $this->lMargin = ($x - $this->columns[$col]['w']);
21982                  $this->x = $x - $listindent;
21983              } else {
21984                  $x = $this->original_lMargin + $colpos;
21985                  $this->lMargin = ($x + $listindent);
21986                  $this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
21987                  $this->x = $x + $listindent;
21988              }
21989              $this->columns[$col]['x'] = $x;
21990          }
21991          $this->current_column = $col;
21992          // fix for HTML mode
21993          $this->newline = true;
21994          // print HTML table header (if any)
21995          if ((!TCPDF_STATIC::empty_string($this->thead)) AND (!$this->inthead)) {
21996              if ($enable_thead) {
21997                  // print table header
21998                  $this->writeHTML($this->thead, false, false, false, false, '');
21999                  $this->y += $xshift['s']['V'];
22000                  // store end of header position
22001                  if (!isset($this->columns[$col]['th'])) {
22002                      $this->columns[$col]['th'] = array();
22003                  }
22004                  $this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
22005                  $this->lasth = 0;
22006              } elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
22007                  $this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
22008              }
22009          }
22010          // account for an html table cell over multiple columns
22011          if ($this->rtl) {
22012              $this->rMargin += $xshift['x'];
22013              $this->x -= ($xshift['x'] + $xshift['p']['R']);
22014          } else {
22015              $this->lMargin += $xshift['x'];
22016              $this->x += $xshift['x'] + $xshift['p']['L'];
22017          }
22018      }
22019  
22020      /**
22021       * Return the current column number
22022       * @return int current column number
22023       * @public
22024       * @since 5.5.011 (2010-07-08)
22025       */
22026  	public function getColumn() {
22027          return $this->current_column;
22028      }
22029  
22030      /**
22031       * Return the current number of columns.
22032       * @return int number of columns
22033       * @public
22034       * @since 5.8.018 (2010-08-25)
22035       */
22036  	public function getNumberOfColumns() {
22037          return $this->num_columns;
22038      }
22039  
22040      /**
22041       * Set Text rendering mode.
22042       * @param $stroke (int) outline size in user units (0 = disable).
22043       * @param $fill (boolean) if true fills the text (default).
22044       * @param $clip (boolean) if true activate clipping mode
22045       * @public
22046       * @since 4.9.008 (2009-04-02)
22047       */
22048  	public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
22049          // Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
22050          // convert text rendering parameters
22051          if ($stroke < 0) {
22052              $stroke = 0;
22053          }
22054          if ($fill === true) {
22055              if ($stroke > 0) {
22056                  if ($clip === true) {
22057                      // Fill, then stroke text and add to path for clipping
22058                      $textrendermode = 6;
22059                  } else {
22060                      // Fill, then stroke text
22061                      $textrendermode = 2;
22062                  }
22063                  $textstrokewidth = $stroke;
22064              } else {
22065                  if ($clip === true) {
22066                      // Fill text and add to path for clipping
22067                      $textrendermode = 4;
22068                  } else {
22069                      // Fill text
22070                      $textrendermode = 0;
22071                  }
22072              }
22073          } else {
22074              if ($stroke > 0) {
22075                  if ($clip === true) {
22076                      // Stroke text and add to path for clipping
22077                      $textrendermode = 5;
22078                  } else {
22079                      // Stroke text
22080                      $textrendermode = 1;
22081                  }
22082                  $textstrokewidth = $stroke;
22083              } else {
22084                  if ($clip === true) {
22085                      // Add text to path for clipping
22086                      $textrendermode = 7;
22087                  } else {
22088                      // Neither fill nor stroke text (invisible)
22089                      $textrendermode = 3;
22090                  }
22091              }
22092          }
22093          $this->textrendermode = $textrendermode;
22094          $this->textstrokewidth = $stroke;
22095      }
22096  
22097      /**
22098       * Set parameters for drop shadow effect for text.
22099       * @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.
22100       * @since 5.9.174 (2012-07-25)
22101       * @public
22102      */
22103  	public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
22104          if (isset($params['enabled'])) {
22105              $this->txtshadow['enabled'] = $params['enabled']?true:false;
22106          } else {
22107              $this->txtshadow['enabled'] = false;
22108          }
22109          if (isset($params['depth_w'])) {
22110              $this->txtshadow['depth_w'] = floatval($params['depth_w']);
22111          } else {
22112              $this->txtshadow['depth_w'] = 0;
22113          }
22114          if (isset($params['depth_h'])) {
22115              $this->txtshadow['depth_h'] = floatval($params['depth_h']);
22116          } else {
22117              $this->txtshadow['depth_h'] = 0;
22118          }
22119          if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
22120              $this->txtshadow['color'] = $params['color'];
22121          } else {
22122              $this->txtshadow['color'] = $this->strokecolor;
22123          }
22124          if (isset($params['opacity'])) {
22125              $this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
22126          } else {
22127              $this->txtshadow['opacity'] = 1;
22128          }
22129          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'))) {
22130              $this->txtshadow['blend_mode'] = $params['blend_mode'];
22131          } else {
22132              $this->txtshadow['blend_mode'] = 'Normal';
22133          }
22134          if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
22135              $this->txtshadow['enabled'] = false;
22136          }
22137      }
22138  
22139      /**
22140       * Return the text shadow parameters array.
22141       * @return Array of parameters.
22142       * @since 5.9.174 (2012-07-25)
22143       * @public
22144       */
22145  	public function getTextShadow() {
22146          return $this->txtshadow;
22147      }
22148  
22149      /**
22150       * Returns an array of chars containing soft hyphens.
22151       * @param $word (array) array of chars
22152       * @param $patterns (array) Array of hypenation patterns.
22153       * @param $dictionary (array) Array of words to be returned without applying the hyphenation algoritm.
22154       * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
22155       * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
22156       * @param $charmin (int) Minimum word length to apply the hyphenation algoritm.
22157       * @param $charmax (int) Maximum length of broken piece of word.
22158       * @return array text with soft hyphens
22159       * @author Nicola Asuni
22160       * @since 4.9.012 (2010-04-12)
22161       * @protected
22162       */
22163  	protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22164          $hyphenword = array(); // hyphens positions
22165          $numchars = count($word);
22166          if ($numchars <= $charmin) {
22167              return $word;
22168          }
22169          $word_string = TCPDF_FONTS::UTF8ArrSubString($word, '', '', $this->isunicode);
22170          // some words will be returned as-is
22171          $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})(\]?)$/';
22172          if (preg_match($pattern, $word_string) > 0) {
22173              // email
22174              return $word;
22175          }
22176          $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})(\]?)$/';
22177          if (preg_match($pattern, $word_string) > 0) {
22178              // URL
22179              return $word;
22180          }
22181          if (isset($dictionary[$word_string])) {
22182              return TCPDF_FONTS::UTF8StringToArray($dictionary[$word_string], $this->isunicode, $this->CurrentFont);
22183          }
22184          // surround word with '_' characters
22185          $tmpword = array_merge(array(46), $word, array(46));
22186          $tmpnumchars = $numchars + 2;
22187          $maxpos = $tmpnumchars - $charmin;
22188          for ($pos = 0; $pos < $maxpos; ++$pos) {
22189              $imax = min(($tmpnumchars - $pos), $charmax);
22190              for ($i = $charmin; $i <= $imax; ++$i) {
22191                  $subword = strtolower(TCPDF_FONTS::UTF8ArrSubString($tmpword, $pos, ($pos + $i), $this->isunicode));
22192                  if (isset($patterns[$subword])) {
22193                      $pattern = TCPDF_FONTS::UTF8StringToArray($patterns[$subword], $this->isunicode, $this->CurrentFont);
22194                      $pattern_length = count($pattern);
22195                      $digits = 1;
22196                      for ($j = 0; $j < $pattern_length; ++$j) {
22197                          // check if $pattern[$j] is a number = hyphenation level (only numbers from 1 to 5 are valid)
22198                          if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
22199                              if ($j == 0) {
22200                                  $zero = $pos - 1;
22201                              } else {
22202                                  $zero = $pos + $j - $digits;
22203                              }
22204                              // get hyphenation level
22205                              $level = ($pattern[$j] - 48);
22206                              // if two levels from two different patterns match at the same point, the higher one is selected.
22207                              if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] < $level)) {
22208                                  $hyphenword[$zero] = $level;
22209                              }
22210                              ++$digits;
22211                          }
22212                      }
22213                  }
22214              }
22215          }
22216          $inserted = 0;
22217          $maxpos = $numchars - $rightmin;
22218          for ($i = $leftmin; $i <= $maxpos; ++$i) {
22219              // only odd levels indicate allowed hyphenation points
22220              if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
22221                  // 173 = soft hyphen character
22222                  array_splice($word, $i + $inserted, 0, 173);
22223                  ++$inserted;
22224              }
22225          }
22226          return $word;
22227      }
22228  
22229      /**
22230       * Returns text with soft hyphens.
22231       * @param $text (string) text to process
22232       * @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/
22233       * @param $dictionary (array) Array of words to be returned without applying the hyphenation algoritm.
22234       * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
22235       * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
22236       * @param $charmin (int) Minimum word length to apply the hyphenation algoritm.
22237       * @param $charmax (int) Maximum length of broken piece of word.
22238       * @return array text with soft hyphens
22239       * @author Nicola Asuni
22240       * @since 4.9.012 (2010-04-12)
22241       * @public
22242       */
22243  	public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22244          $text = $this->unhtmlentities($text);
22245          $word = array(); // last word
22246          $txtarr = array(); // text to be returned
22247          $intag = false; // true if we are inside an HTML tag
22248          $skip = false; // true to skip hyphenation
22249          if (!is_array($patterns)) {
22250              $patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patterns);
22251          }
22252          // get array of characters
22253          $unichars = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
22254          // for each char
22255          foreach ($unichars as $char) {
22256              if ((!$intag) AND (!$skip) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
22257                  // letter character
22258                  $word[] = $char;
22259              } else {
22260                  // other type of character
22261                  if (!TCPDF_STATIC::empty_string($word)) {
22262                      // hypenate the word
22263                      $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22264                      $word = array();
22265                  }
22266                  $txtarr[] = $char;
22267                  if (chr($char) == '<') {
22268                      // we are inside an HTML tag
22269                      $intag = true;
22270                  } elseif ($intag AND (chr($char) == '>')) {
22271                      // end of HTML tag
22272                      $intag = false;
22273                      // check for style tag
22274                      $expected = array(115, 116, 121, 108, 101); // = 'style'
22275                      $current = array_slice($txtarr, -6, 5); // last 5 chars
22276                      $compare = array_diff($expected, $current);
22277                      if (empty($compare)) {
22278                          // check if it is a closing tag
22279                          $expected = array(47); // = '/'
22280                          $current = array_slice($txtarr, -7, 1);
22281                          $compare = array_diff($expected, $current);
22282                          if (empty($compare)) {
22283                              // closing style tag
22284                              $skip = false;
22285                          } else {
22286                              // opening style tag
22287                              $skip = true;
22288                          }
22289                      }
22290                  }
22291              }
22292          }
22293          if (!TCPDF_STATIC::empty_string($word)) {
22294              // hypenate the word
22295              $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22296          }
22297          // convert char array to string and return
22298          return TCPDF_FONTS::UTF8ArrSubString($txtarr, '', '', $this->isunicode);
22299      }
22300  
22301      /**
22302       * Enable/disable rasterization of vector images using ImageMagick library.
22303       * @param $mode (boolean) if true enable rasterization, false otherwise.
22304       * @public
22305       * @since 5.0.000 (2010-04-27)
22306       */
22307  	public function setRasterizeVectorImages($mode) {
22308          $this->rasterize_vector_images = $mode;
22309      }
22310  
22311      /**
22312       * Enable or disable default option for font subsetting.
22313       * @param $enable (boolean) if true enable font subsetting by default.
22314       * @author Nicola Asuni
22315       * @public
22316       * @since 5.3.002 (2010-06-07)
22317       */
22318  	public function setFontSubsetting($enable=true) {
22319          if ($this->pdfa_mode) {
22320              $this->font_subsetting = false;
22321          } else {
22322              $this->font_subsetting = $enable ? true : false;
22323          }
22324      }
22325  
22326      /**
22327       * Return the default option for font subsetting.
22328       * @return boolean default font subsetting state.
22329       * @author Nicola Asuni
22330       * @public
22331       * @since 5.3.002 (2010-06-07)
22332       */
22333  	public function getFontSubsetting() {
22334          return $this->font_subsetting;
22335      }
22336  
22337      /**
22338       * Left trim the input string
22339       * @param $str (string) string to trim
22340       * @param $replace (string) string that replace spaces.
22341       * @return left trimmed string
22342       * @author Nicola Asuni
22343       * @public
22344       * @since 5.8.000 (2010-08-11)
22345       */
22346  	public function stringLeftTrim($str, $replace='') {
22347          return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
22348      }
22349  
22350      /**
22351       * Right trim the input string
22352       * @param $str (string) string to trim
22353       * @param $replace (string) string that replace spaces.
22354       * @return right trimmed string
22355       * @author Nicola Asuni
22356       * @public
22357       * @since 5.8.000 (2010-08-11)
22358       */
22359  	public function stringRightTrim($str, $replace='') {
22360          return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
22361      }
22362  
22363      /**
22364       * Trim the input string
22365       * @param $str (string) string to trim
22366       * @param $replace (string) string that replace spaces.
22367       * @return trimmed string
22368       * @author Nicola Asuni
22369       * @public
22370       * @since 5.8.000 (2010-08-11)
22371       */
22372  	public function stringTrim($str, $replace='') {
22373          $str = $this->stringLeftTrim($str, $replace);
22374          $str = $this->stringRightTrim($str, $replace);
22375          return $str;
22376      }
22377  
22378      /**
22379       * Return true if the current font is unicode type.
22380       * @return true for unicode font, false otherwise.
22381       * @author Nicola Asuni
22382       * @public
22383       * @since 5.8.002 (2010-08-14)
22384       */
22385  	public function isUnicodeFont() {
22386          return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
22387      }
22388  
22389      /**
22390       * Return normalized font name
22391       * @param $fontfamily (string) property string containing font family names
22392       * @return string normalized font name
22393       * @author Nicola Asuni
22394       * @public
22395       * @since 5.8.004 (2010-08-17)
22396       */
22397  	public function getFontFamilyName($fontfamily) {
22398          // remove spaces and symbols
22399          $fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
22400          // extract all font names
22401          $fontslist = preg_split('/[,]/', $fontfamily);
22402          // find first valid font name
22403          foreach ($fontslist as $font) {
22404              // replace font variations
22405              $font = preg_replace('/regular$/', '', $font);
22406              $font = preg_replace('/italic$/', 'I', $font);
22407              $font = preg_replace('/oblique$/', 'I', $font);
22408              $font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
22409              // replace common family names and core fonts
22410              $pattern = array();
22411              $replacement = array();
22412              $pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
22413              $replacement[] = 'times';
22414              $pattern[] = '/^sansserif/';
22415              $replacement[] = 'helvetica';
22416              $pattern[] = '/^monospace/';
22417              $replacement[] = 'courier';
22418              $font = preg_replace($pattern, $replacement, $font);
22419              if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
22420                  return $font;
22421              }
22422          }
22423          // return current font as default
22424          return $this->CurrentFont['fontkey'];
22425      }
22426  
22427      /**
22428       * Start a new XObject Template.
22429       * 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).
22430       * 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.
22431       * Note: X,Y coordinates will be reset to 0,0.
22432       * @param $w (int) Template width in user units (empty string or zero = page width less margins).
22433       * @param $h (int) Template height in user units (empty string or zero = page height less margins).
22434       * @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).
22435       * @return int the XObject Template ID in case of success or false in case of error.
22436       * @author Nicola Asuni
22437       * @public
22438       * @since 5.8.017 (2010-08-24)
22439       * @see endTemplate(), printTemplate()
22440       */
22441  	public function startTemplate($w=0, $h=0, $group=false) {
22442          if ($this->inxobj) {
22443              // we are already inside an XObject template
22444              return false;
22445          }
22446          $this->inxobj = true;
22447          ++$this->n;
22448          // XObject ID
22449          $this->xobjid = 'XT'.$this->n;
22450          // object ID
22451          $this->xobjects[$this->xobjid] = array('n' => $this->n);
22452          // store current graphic state
22453          $this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
22454          // initialize data
22455          $this->xobjects[$this->xobjid]['intmrk'] = 0;
22456          $this->xobjects[$this->xobjid]['transfmrk'] = array();
22457          $this->xobjects[$this->xobjid]['outdata'] = '';
22458          $this->xobjects[$this->xobjid]['xobjects'] = array();
22459          $this->xobjects[$this->xobjid]['images'] = array();
22460          $this->xobjects[$this->xobjid]['fonts'] = array();
22461          $this->xobjects[$this->xobjid]['annotations'] = array();
22462          $this->xobjects[$this->xobjid]['extgstates'] = array();
22463          $this->xobjects[$this->xobjid]['gradients'] = array();
22464          $this->xobjects[$this->xobjid]['spot_colors'] = array();
22465          // set new environment
22466          $this->num_columns = 1;
22467          $this->current_column = 0;
22468          $this->SetAutoPageBreak(false);
22469          if (($w === '') OR ($w <= 0)) {
22470              $w = $this->w - $this->lMargin - $this->rMargin;
22471          }
22472          if (($h === '') OR ($h <= 0)) {
22473              $h = $this->h - $this->tMargin - $this->bMargin;
22474          }
22475          $this->xobjects[$this->xobjid]['x'] = 0;
22476          $this->xobjects[$this->xobjid]['y'] = 0;
22477          $this->xobjects[$this->xobjid]['w'] = $w;
22478          $this->xobjects[$this->xobjid]['h'] = $h;
22479          $this->w = $w;
22480          $this->h = $h;
22481          $this->wPt = $this->w * $this->k;
22482          $this->hPt = $this->h * $this->k;
22483          $this->fwPt = $this->wPt;
22484          $this->fhPt = $this->hPt;
22485          $this->x = 0;
22486          $this->y = 0;
22487          $this->lMargin = 0;
22488          $this->rMargin = 0;
22489          $this->tMargin = 0;
22490          $this->bMargin = 0;
22491          // set group mode
22492          $this->xobjects[$this->xobjid]['group'] = $group;
22493          return $this->xobjid;
22494      }
22495  
22496      /**
22497       * End the current XObject Template started with startTemplate() and restore the previous graphic state.
22498       * 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).
22499       * 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.
22500       * @return int the XObject Template ID in case of success or false in case of error.
22501       * @author Nicola Asuni
22502       * @public
22503       * @since 5.8.017 (2010-08-24)
22504       * @see startTemplate(), printTemplate()
22505       */
22506  	public function endTemplate() {
22507          if (!$this->inxobj) {
22508              // we are not inside a template
22509              return false;
22510          }
22511          $this->inxobj = false;
22512          // restore previous graphic state
22513          $this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
22514          return $this->xobjid;
22515      }
22516  
22517      /**
22518       * Print an XObject Template.
22519       * You can print an XObject Template inside the currently opened Template.
22520       * 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).
22521       * 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.
22522       * @param $id (string) The ID of XObject Template to print.
22523       * @param $x (int) X position in user units (empty string = current x position)
22524       * @param $y (int) Y position in user units (empty string = current y position)
22525       * @param $w (int) Width in user units (zero = remaining page width)
22526       * @param $h (int) Height in user units (zero = remaining page height)
22527       * @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>
22528       * @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>
22529       * @param $fitonpage (boolean) If true the template is resized to not exceed page dimensions.
22530       * @author Nicola Asuni
22531       * @public
22532       * @since 5.8.017 (2010-08-24)
22533       * @see startTemplate(), endTemplate()
22534       */
22535  	public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
22536          if ($this->state != 2) {
22537               return;
22538          }
22539          if (!isset($this->xobjects[$id])) {
22540              $this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
22541          }
22542          if ($this->inxobj) {
22543              if ($id == $this->xobjid) {
22544                  // close current template
22545                  $this->endTemplate();
22546              } else {
22547                  // use the template as resource for the template currently opened
22548                  $this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
22549              }
22550          }
22551          // set default values
22552          if ($x === '') {
22553              $x = $this->x;
22554          }
22555          if ($y === '') {
22556              $y = $this->y;
22557          }
22558          // check page for no-write regions and adapt page margins if necessary
22559          list($x, $y) = $this->checkPageRegions($h, $x, $y);
22560          $ow = $this->xobjects[$id]['w'];
22561          if ($ow <= 0) {
22562              $ow = 1;
22563          }
22564          $oh = $this->xobjects[$id]['h'];
22565          if ($oh <= 0) {
22566              $oh = 1;
22567          }
22568          // calculate template width and height on document
22569          if (($w <= 0) AND ($h <= 0)) {
22570              $w = $ow;
22571              $h = $oh;
22572          } elseif ($w <= 0) {
22573              $w = $h * $ow / $oh;
22574          } elseif ($h <= 0) {
22575              $h = $w * $oh / $ow;
22576          }
22577          // fit the template on available space
22578          list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22579          // set page alignment
22580          $rb_y = $y + $h;
22581          // set alignment
22582          if ($this->rtl) {
22583              if ($palign == 'L') {
22584                  $xt = $this->lMargin;
22585              } elseif ($palign == 'C') {
22586                  $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22587              } elseif ($palign == 'R') {
22588                  $xt = $this->w - $this->rMargin - $w;
22589              } else {
22590                  $xt = $x - $w;
22591              }
22592              $rb_x = $xt;
22593          } else {
22594              if ($palign == 'L') {
22595                  $xt = $this->lMargin;
22596              } elseif ($palign == 'C') {
22597                  $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22598              } elseif ($palign == 'R') {
22599                  $xt = $this->w - $this->rMargin - $w;
22600              } else {
22601                  $xt = $x;
22602              }
22603              $rb_x = $xt + $w;
22604          }
22605          // print XObject Template + Transformation matrix
22606          $this->StartTransform();
22607          // translate and scale
22608          $sx = ($w / $ow);
22609          $sy = ($h / $oh);
22610          $tm = array();
22611          $tm[0] = $sx;
22612          $tm[1] = 0;
22613          $tm[2] = 0;
22614          $tm[3] = $sy;
22615          $tm[4] = $xt * $this->k;
22616          $tm[5] = ($this->h - $h - $y) * $this->k;
22617          $this->Transform($tm);
22618          // set object
22619          $this->_out('/'.$id.' Do');
22620          $this->StopTransform();
22621          // add annotations
22622          if (!empty($this->xobjects[$id]['annotations'])) {
22623              foreach ($this->xobjects[$id]['annotations'] as $annot) {
22624                  // transform original coordinates
22625                  $coordlt = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
22626                  $ax = ($coordlt[4] / $this->k);
22627                  $ay = ($this->h - $h - ($coordlt[5] / $this->k));
22628                  $coordrb = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
22629                  $aw = ($coordrb[4] / $this->k) - $ax;
22630                  $ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
22631                  $this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
22632              }
22633          }
22634          // set pointer to align the next text/objects
22635          switch($align) {
22636              case 'T': {
22637                  $this->y = $y;
22638                  $this->x = $rb_x;
22639                  break;
22640              }
22641              case 'M': {
22642                  $this->y = $y + round($h/2);
22643                  $this->x = $rb_x;
22644                  break;
22645              }
22646              case 'B': {
22647                  $this->y = $rb_y;
22648                  $this->x = $rb_x;
22649                  break;
22650              }
22651              case 'N': {
22652                  $this->SetY($rb_y);
22653                  break;
22654              }
22655              default:{
22656                  break;
22657              }
22658          }
22659      }
22660  
22661      /**
22662       * Set the percentage of character stretching.
22663       * @param $perc (int) percentage of stretching (100 = no stretching)
22664       * @author Nicola Asuni
22665       * @public
22666       * @since 5.9.000 (2010-09-29)
22667       */
22668  	public function setFontStretching($perc=100) {
22669          $this->font_stretching = $perc;
22670      }
22671  
22672      /**
22673       * Get the percentage of character stretching.
22674       * @return float stretching value
22675       * @author Nicola Asuni
22676       * @public
22677       * @since 5.9.000 (2010-09-29)
22678       */
22679  	public function getFontStretching() {
22680          return $this->font_stretching;
22681      }
22682  
22683      /**
22684       * Set the amount to increase or decrease the space between characters in a text.
22685       * @param $spacing (float) amount to increase or decrease the space between characters in a text (0 = default spacing)
22686       * @author Nicola Asuni
22687       * @public
22688       * @since 5.9.000 (2010-09-29)
22689       */
22690  	public function setFontSpacing($spacing=0) {
22691          $this->font_spacing = $spacing;
22692      }
22693  
22694      /**
22695       * Get the amount to increase or decrease the space between characters in a text.
22696       * @return int font spacing (tracking) value
22697       * @author Nicola Asuni
22698       * @public
22699       * @since 5.9.000 (2010-09-29)
22700       */
22701  	public function getFontSpacing() {
22702          return $this->font_spacing;
22703      }
22704  
22705      /**
22706       * Return an array of no-write page regions
22707       * @return array of no-write page regions
22708       * @author Nicola Asuni
22709       * @public
22710       * @since 5.9.003 (2010-10-13)
22711       * @see setPageRegions(), addPageRegion()
22712       */
22713  	public function getPageRegions() {
22714          return $this->page_regions;
22715      }
22716  
22717      /**
22718       * Set no-write regions on page.
22719       * 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.
22720       * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22721       * You can set multiple regions for the same page.
22722       * @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.
22723       * @author Nicola Asuni
22724       * @public
22725       * @since 5.9.003 (2010-10-13)
22726       * @see addPageRegion(), getPageRegions()
22727       */
22728  	public function setPageRegions($regions=array()) {
22729          // empty current regions array
22730          $this->page_regions = array();
22731          // add regions
22732          foreach ($regions as $data) {
22733              $this->addPageRegion($data);
22734          }
22735      }
22736  
22737      /**
22738       * Add a single no-write region on selected page.
22739       * 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.
22740       * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22741       * You can set multiple regions for the same page.
22742       * @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).
22743       * @author Nicola Asuni
22744       * @public
22745       * @since 5.9.003 (2010-10-13)
22746       * @see setPageRegions(), getPageRegions()
22747       */
22748  	public function addPageRegion($region) {
22749          if (!isset($region['page']) OR empty($region['page'])) {
22750              $region['page'] = $this->page;
22751          }
22752          if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
22753              AND isset($region['yt'])  AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
22754              AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
22755              $this->page_regions[] = $region;
22756          }
22757      }
22758  
22759      /**
22760       * Remove a single no-write region.
22761       * @param $key (int) region key
22762       * @author Nicola Asuni
22763       * @public
22764       * @since 5.9.003 (2010-10-13)
22765       * @see setPageRegions(), getPageRegions()
22766       */
22767  	public function removePageRegion($key) {
22768          if (isset($this->page_regions[$key])) {
22769              unset($this->page_regions[$key]);
22770          }
22771      }
22772  
22773      /**
22774       * Check page for no-write regions and adapt current coordinates and page margins if necessary.
22775       * 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.
22776       * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22777       * @param $h (float) height of the text/image/object to print in user units
22778       * @param $x (float) current X coordinate in user units
22779       * @param $y (float) current Y coordinate in user units
22780       * @return array($x, $y)
22781       * @author Nicola Asuni
22782       * @protected
22783       * @since 5.9.003 (2010-10-13)
22784       */
22785  	protected function checkPageRegions($h, $x, $y) {
22786          // set default values
22787          if ($x === '') {
22788              $x = $this->x;
22789          }
22790          if ($y === '') {
22791              $y = $this->y;
22792          }
22793          if (!$this->check_page_regions OR empty($this->page_regions)) {
22794              // no page regions defined
22795              return array($x, $y);
22796          }
22797          if (empty($h)) {
22798              $h = $this->getCellHeight($this->FontSize);
22799          }
22800          // check for page break
22801          if ($this->checkPageBreak($h, $y)) {
22802              // the content will be printed on a new page
22803              $x = $this->x;
22804              $y = $this->y;
22805          }
22806          if ($this->num_columns > 1) {
22807              if ($this->rtl) {
22808                  $this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22809              } else {
22810                  $this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22811              }
22812          } else {
22813              if ($this->rtl) {
22814                  $this->lMargin = max($this->clMargin, $this->original_lMargin);
22815              } else {
22816                  $this->rMargin = max($this->crMargin, $this->original_rMargin);
22817              }
22818          }
22819          // adjust coordinates and page margins
22820          foreach ($this->page_regions as $regid => $regdata) {
22821              if ($regdata['page'] == $this->page) {
22822                  // check region boundaries
22823                  if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
22824                      // Y is inside the region
22825                      $minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
22826                      $yt = max($y, $regdata['yt']);
22827                      $yb = min(($yt + $h), $regdata['yb']);
22828                      $xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
22829                      $xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
22830                      if ($regdata['side'] == 'L') { // left side
22831                          $new_margin = max($xt, $xb);
22832                          if ($this->lMargin < $new_margin) {
22833                              if ($this->rtl) {
22834                                  // adjust left page margin
22835                                  $this->lMargin = max(0, $new_margin);
22836                              }
22837                              if ($x < $new_margin) {
22838                                  // adjust x position
22839                                  $x = $new_margin;
22840                                  if ($new_margin > ($this->w - $this->rMargin)) {
22841                                      // adjust y position
22842                                      $y = $regdata['yb'] - $h;
22843                                  }
22844                              }
22845                          }
22846                      } elseif ($regdata['side'] == 'R') { // right side
22847                          $new_margin = min($xt, $xb);
22848                          if (($this->w - $this->rMargin) > $new_margin) {
22849                              if (!$this->rtl) {
22850                                  // adjust right page margin
22851                                  $this->rMargin = max(0, ($this->w - $new_margin));
22852                              }
22853                              if ($x > $new_margin) {
22854                                  // adjust x position
22855                                  $x = $new_margin;
22856                                  if ($new_margin > $this->lMargin) {
22857                                      // adjust y position
22858                                      $y = $regdata['yb'] - $h;
22859                                  }
22860                              }
22861                          }
22862                      }
22863                  }
22864              }
22865          }
22866          return array($x, $y);
22867      }
22868  
22869      // --- SVG METHODS ---------------------------------------------------------
22870  
22871      /**
22872       * Embedd a Scalable Vector Graphics (SVG) image.
22873       * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
22874       * @param $file (string) Name of the SVG file or a '@' character followed by the SVG data string.
22875       * @param $x (float) Abscissa of the upper-left corner.
22876       * @param $y (float) Ordinate of the upper-left corner.
22877       * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
22878       * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
22879       * @param $link (mixed) URL or identifier returned by AddLink().
22880       * @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.
22881       * @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>
22882       * @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)))
22883       * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
22884       * @author Nicola Asuni
22885       * @since 5.0.000 (2010-05-02)
22886       * @public
22887       */
22888  	public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
22889          if ($this->state != 2) {
22890               return;
22891          }
22892          // reseet SVG vars
22893          $this->svggradients = array();
22894          $this->svggradientid = 0;
22895          $this->svgdefsmode = false;
22896          $this->svgdefs = array();
22897          $this->svgclipmode = false;
22898          $this->svgclippaths = array();
22899          $this->svgcliptm = array();
22900          $this->svgclipid = 0;
22901          $this->svgtext = '';
22902          $this->svgtextmode = array();
22903          if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
22904              // convert SVG to raster image using GD or ImageMagick libraries
22905              return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22906          }
22907          if ($file[0] === '@') { // image from string
22908              $this->svgdir = '';
22909              $svgdata = substr($file, 1);
22910          } else { // SVG file
22911              $this->svgdir = dirname($file);
22912              $svgdata = TCPDF_STATIC::fileGetContents($file);
22913          }
22914          if ($svgdata === FALSE) {
22915              $this->Error('SVG file not found: '.$file);
22916          }
22917          if ($x === '') {
22918              $x = $this->x;
22919          }
22920          if ($y === '') {
22921              $y = $this->y;
22922          }
22923          // check page for no-write regions and adapt page margins if necessary
22924          list($x, $y) = $this->checkPageRegions($h, $x, $y);
22925          $k = $this->k;
22926          $ox = 0;
22927          $oy = 0;
22928          $ow = $w;
22929          $oh = $h;
22930          $aspect_ratio_align = 'xMidYMid';
22931          $aspect_ratio_ms = 'meet';
22932          $regs = array();
22933          // get original image width and height
22934          preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
22935          if (isset($regs[1]) AND !empty($regs[1])) {
22936              $tmp = array();
22937              if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22938                  $ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22939              }
22940              $tmp = array();
22941              if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22942                  $oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22943              }
22944              $tmp = array();
22945              if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22946                  $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22947              }
22948              $tmp = array();
22949              if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22950                  $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22951              }
22952              $tmp = array();
22953              $view_box = array();
22954              if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
22955                  if (count($tmp) == 5) {
22956                      array_shift($tmp);
22957                      foreach ($tmp as $key => $val) {
22958                          $view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
22959                      }
22960                      $ox = $view_box[0];
22961                      $oy = $view_box[1];
22962                  }
22963                  // get aspect ratio
22964                  $tmp = array();
22965                  if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22966                      $aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
22967                      switch (count($aspect_ratio)) {
22968                          case 3: {
22969                              $aspect_ratio_align = $aspect_ratio[1];
22970                              $aspect_ratio_ms = $aspect_ratio[2];
22971                              break;
22972                          }
22973                          case 2: {
22974                              $aspect_ratio_align = $aspect_ratio[0];
22975                              $aspect_ratio_ms = $aspect_ratio[1];
22976                              break;
22977                          }
22978                          case 1: {
22979                              $aspect_ratio_align = $aspect_ratio[0];
22980                              $aspect_ratio_ms = 'meet';
22981                              break;
22982                          }
22983                      }
22984                  }
22985              }
22986          }
22987          if ($ow <= 0) {
22988              $ow = 1;
22989          }
22990          if ($oh <= 0) {
22991              $oh = 1;
22992          }
22993          // calculate image width and height on document
22994          if (($w <= 0) AND ($h <= 0)) {
22995              // convert image size to document unit
22996              $w = $ow;
22997              $h = $oh;
22998          } elseif ($w <= 0) {
22999              $w = $h * $ow / $oh;
23000          } elseif ($h <= 0) {
23001              $h = $w * $oh / $ow;
23002          }
23003          // fit the image on available space
23004          list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
23005          if ($this->rasterize_vector_images) {
23006              // convert SVG to raster image using GD or ImageMagick libraries
23007              return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
23008          }
23009          // set alignment
23010          $this->img_rb_y = $y + $h;
23011          // set alignment
23012          if ($this->rtl) {
23013              if ($palign == 'L') {
23014                  $ximg = $this->lMargin;
23015              } elseif ($palign == 'C') {
23016                  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
23017              } elseif ($palign == 'R') {
23018                  $ximg = $this->w - $this->rMargin - $w;
23019              } else {
23020                  $ximg = $x - $w;
23021              }
23022              $this->img_rb_x = $ximg;
23023          } else {
23024              if ($palign == 'L') {
23025                  $ximg = $this->lMargin;
23026              } elseif ($palign == 'C') {
23027                  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
23028              } elseif ($palign == 'R') {
23029                  $ximg = $this->w - $this->rMargin - $w;
23030              } else {
23031                  $ximg = $x;
23032              }
23033              $this->img_rb_x = $ximg + $w;
23034          }
23035          // store current graphic vars
23036          $gvars = $this->getGraphicVars();
23037          // store SVG position and scale factors
23038          $svgoffset_x = ($ximg - $ox) * $this->k;
23039          $svgoffset_y = -($y - $oy) * $this->k;
23040          if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
23041              $ow = $view_box[2];
23042              $oh = $view_box[3];
23043          } else {
23044              if ($ow <= 0) {
23045                  $ow = $w;
23046              }
23047              if ($oh <= 0) {
23048                  $oh = $h;
23049              }
23050          }
23051          $svgscale_x = $w / $ow;
23052          $svgscale_y = $h / $oh;
23053          // scaling and alignment
23054          if ($aspect_ratio_align != 'none') {
23055              // store current scaling values
23056              $svgscale_old_x = $svgscale_x;
23057              $svgscale_old_y = $svgscale_y;
23058              // force uniform scaling
23059              if ($aspect_ratio_ms == 'slice') {
23060                  // the entire viewport is covered by the viewBox
23061                  if ($svgscale_x > $svgscale_y) {
23062                      $svgscale_y = $svgscale_x;
23063                  } elseif ($svgscale_x < $svgscale_y) {
23064                      $svgscale_x = $svgscale_y;
23065                  }
23066              } else { // meet
23067                  // the entire viewBox is visible within the viewport
23068                  if ($svgscale_x < $svgscale_y) {
23069                      $svgscale_y = $svgscale_x;
23070                  } elseif ($svgscale_x > $svgscale_y) {
23071                      $svgscale_x = $svgscale_y;
23072                  }
23073              }
23074              // correct X alignment
23075              switch (substr($aspect_ratio_align, 1, 3)) {
23076                  case 'Min': {
23077                      // do nothing
23078                      break;
23079                  }
23080                  case 'Max': {
23081                      $svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
23082                      break;
23083                  }
23084                  default:
23085                  case 'Mid': {
23086                      $svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
23087                      break;
23088                  }
23089              }
23090              // correct Y alignment
23091              switch (substr($aspect_ratio_align, 5)) {
23092                  case 'Min': {
23093                      // do nothing
23094                      break;
23095                  }
23096                  case 'Max': {
23097                      $svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
23098                      break;
23099                  }
23100                  default:
23101                  case 'Mid': {
23102                      $svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
23103                      break;
23104                  }
23105              }
23106          }
23107          // store current page break mode
23108          $page_break_mode = $this->AutoPageBreak;
23109          $page_break_margin = $this->getBreakMargin();
23110          $cell_padding = $this->cell_padding;
23111          $this->SetCellPadding(0);
23112          $this->SetAutoPageBreak(false);
23113          // save the current graphic state
23114          $this->_out('q'.$this->epsmarker);
23115          // set initial clipping mask
23116          $this->Rect($ximg, $y, $w, $h, 'CNZ', array(), array());
23117          // scale and translate
23118          $e = $ox * $this->k * (1 - $svgscale_x);
23119          $f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
23120          $this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
23121          // creates a new XML parser to be used by the other XML functions
23122          $this->parser = xml_parser_create('UTF-8');
23123          // the following function allows to use parser inside object
23124          xml_set_object($this->parser, $this);
23125          // disable case-folding for this XML parser
23126          xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
23127          // sets the element handler functions for the XML parser
23128          xml_set_element_handler($this->parser, 'startSVGElementHandler', 'endSVGElementHandler');
23129          // sets the character data handler function for the XML parser
23130          xml_set_character_data_handler($this->parser, 'segSVGContentHandler');
23131          // start parsing an XML document
23132          if (!xml_parse($this->parser, $svgdata)) {
23133              $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));
23134              $this->Error($error_message);
23135          }
23136          // free this XML parser
23137          xml_parser_free($this->parser);
23138          // restore previous graphic state
23139          $this->_out($this->epsmarker.'Q');
23140          // restore graphic vars
23141          $this->setGraphicVars($gvars);
23142          $this->lasth = $gvars['lasth'];
23143          if (!empty($border)) {
23144              $bx = $this->x;
23145              $by = $this->y;
23146              $this->x = $ximg;
23147              if ($this->rtl) {
23148                  $this->x += $w;
23149              }
23150              $this->y = $y;
23151              $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
23152              $this->x = $bx;
23153              $this->y = $by;
23154          }
23155          if ($link) {
23156              $this->Link($ximg, $y, $w, $h, $link, 0);
23157          }
23158          // set pointer to align the next text/objects
23159          switch($align) {
23160              case 'T':{
23161                  $this->y = $y;
23162                  $this->x = $this->img_rb_x;
23163                  break;
23164              }
23165              case 'M':{
23166                  $this->y = $y + round($h/2);
23167                  $this->x = $this->img_rb_x;
23168                  break;
23169              }
23170              case 'B':{
23171                  $this->y = $this->img_rb_y;
23172                  $this->x = $this->img_rb_x;
23173                  break;
23174              }
23175              case 'N':{
23176                  $this->SetY($this->img_rb_y);
23177                  break;
23178              }
23179              default:{
23180                  // restore pointer to starting position
23181                  $this->x = $gvars['x'];
23182                  $this->y = $gvars['y'];
23183                  $this->page = $gvars['page'];
23184                  $this->current_column = $gvars['current_column'];
23185                  $this->tMargin = $gvars['tMargin'];
23186                  $this->bMargin = $gvars['bMargin'];
23187                  $this->w = $gvars['w'];
23188                  $this->h = $gvars['h'];
23189                  $this->wPt = $gvars['wPt'];
23190                  $this->hPt = $gvars['hPt'];
23191                  $this->fwPt = $gvars['fwPt'];
23192                  $this->fhPt = $gvars['fhPt'];
23193                  break;
23194              }
23195          }
23196          $this->endlinex = $this->img_rb_x;
23197          // restore page break
23198          $this->SetAutoPageBreak($page_break_mode, $page_break_margin);
23199          $this->cell_padding = $cell_padding;
23200      }
23201  
23202      /**
23203       * Convert SVG transformation matrix to PDF.
23204       * @param $tm (array) original SVG transformation matrix
23205       * @return array transformation matrix
23206       * @protected
23207       * @since 5.0.000 (2010-05-02)
23208       */
23209  	protected function convertSVGtMatrix($tm) {
23210          $a = $tm[0];
23211          $b = -$tm[1];
23212          $c = -$tm[2];
23213          $d = $tm[3];
23214          $e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
23215          $f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
23216          $x = 0;
23217          $y = $this->h * $this->k;
23218          $e = ($x * (1 - $a)) - ($y * $c) + $e;
23219          $f = ($y * (1 - $d)) - ($x * $b) + $f;
23220          return array($a, $b, $c, $d, $e, $f);
23221      }
23222  
23223      /**
23224       * Apply SVG graphic transformation matrix.
23225       * @param $tm (array) original SVG transformation matrix
23226       * @protected
23227       * @since 5.0.000 (2010-05-02)
23228       */
23229  	protected function SVGTransform($tm) {
23230          $this->Transform($this->convertSVGtMatrix($tm));
23231      }
23232  
23233      /**
23234       * Apply the requested SVG styles (*** TO BE COMPLETED ***)
23235       * @param $svgstyle (array) array of SVG styles to apply
23236       * @param $prevsvgstyle (array) array of previous SVG style
23237       * @param $x (int) X origin of the bounding box
23238       * @param $y (int) Y origin of the bounding box
23239       * @param $w (int) width of the bounding box
23240       * @param $h (int) height of the bounding box
23241       * @param $clip_function (string) clip function
23242       * @param $clip_params (array) array of parameters for clipping function
23243       * @return object style
23244       * @author Nicola Asuni
23245       * @since 5.0.000 (2010-05-02)
23246       * @protected
23247       */
23248  	protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
23249          if ($this->state != 2) {
23250               return;
23251          }
23252          $objstyle = '';
23253          $minlen = (0.01 / $this->k); // minimum acceptable length
23254          if (!isset($svgstyle['opacity'])) {
23255              return $objstyle;
23256          }
23257          // clip-path
23258          $regs = array();
23259          if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
23260              $clip_path = $this->svgclippaths[$regs[1]];
23261              foreach ($clip_path as $cp) {
23262                  $this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
23263              }
23264          }
23265          // opacity
23266          if ($svgstyle['opacity'] != 1) {
23267              $this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
23268          }
23269          // color
23270          $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
23271          $this->SetFillColorArray($fill_color);
23272          // text color
23273          $text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
23274          $this->SetTextColorArray($text_color);
23275          // clip
23276          if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
23277              $top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
23278              $right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
23279              $bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
23280              $left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
23281              $cx = $x + $left;
23282              $cy = $y + $top;
23283              $cw = $w - $left - $right;
23284              $ch = $h - $top - $bottom;
23285              if ($svgstyle['clip-rule'] == 'evenodd') {
23286                  $clip_rule = 'CNZ';
23287              } else {
23288                  $clip_rule = 'CEO';
23289              }
23290              $this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
23291          }
23292          // fill
23293          $regs = array();
23294          if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
23295              // gradient
23296              $gradient = $this->svggradients[$regs[1]];
23297              if (isset($gradient['xref'])) {
23298                  // reference to another gradient definition
23299                  $newgradient = $this->svggradients[$gradient['xref']];
23300                  $newgradient['coords'] = $gradient['coords'];
23301                  $newgradient['mode'] = $gradient['mode'];
23302                  $newgradient['type'] = $gradient['type'];
23303                  $newgradient['gradientUnits'] = $gradient['gradientUnits'];
23304                  if (isset($gradient['gradientTransform'])) {
23305                      $newgradient['gradientTransform'] = $gradient['gradientTransform'];
23306                  }
23307                  $gradient = $newgradient;
23308              }
23309              //save current Graphic State
23310              $this->_outSaveGraphicsState();
23311              //set clipping area
23312              if (!empty($clip_function) AND method_exists($this, $clip_function)) {
23313                  $bbox = call_user_func_array(array($this, $clip_function), $clip_params);
23314                  if ((!isset($gradient['type']) OR ($gradient['type'] != 3)) AND is_array($bbox) AND (count($bbox) == 4)) {
23315                      list($x, $y, $w, $h) = $bbox;
23316                  }
23317              }
23318              if ($gradient['mode'] == 'measure') {
23319                  if (!isset($gradient['coords'][4])) {
23320                      $gradient['coords'][4] = 0.5;
23321                  }
23322                  if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
23323                      $gtm = $gradient['gradientTransform'];
23324                      // apply transformation matrix
23325                      $xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
23326                      $ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
23327                      $xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
23328                      $yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
23329                      $r = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
23330                      $gradient['coords'][0] = $xa;
23331                      $gradient['coords'][1] = $ya;
23332                      $gradient['coords'][2] = $xb;
23333                      $gradient['coords'][3] = $yb;
23334                      $gradient['coords'][4] = $r;
23335                  }
23336                  // convert SVG coordinates to user units
23337                  $gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
23338                  $gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
23339                  $gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
23340                  $gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
23341                  $gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
23342                  if ($w <= $minlen) {
23343                      $w = $minlen;
23344                  }
23345                  if ($h <= $minlen) {
23346                      $h = $minlen;
23347                  }
23348                  // shift units
23349                  if ($gradient['gradientUnits'] == 'objectBoundingBox') {
23350                      // convert to SVG coordinate system
23351                      $gradient['coords'][0] += $x;
23352                      $gradient['coords'][1] += $y;
23353                      $gradient['coords'][2] += $x;
23354                      $gradient['coords'][3] += $y;
23355                  }
23356                  // calculate percentages
23357                  $gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
23358                  $gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
23359                  $gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
23360                  $gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
23361                  $gradient['coords'][4] /= $w;
23362              } elseif ($gradient['mode'] == 'percentage') {
23363                  foreach($gradient['coords'] as $key => $val) {
23364                      $gradient['coords'][$key] = (intval($val) / 100);
23365                      if ($val < 0) {
23366                          $gradient['coords'][$key] = 0;
23367                      } elseif ($val > 1) {
23368                          $gradient['coords'][$key] = 1;
23369                      }
23370                  }
23371              }
23372              if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
23373                  // single color (no shading)
23374                  $gradient['coords'][0] = 1;
23375                  $gradient['coords'][1] = 0;
23376                  $gradient['coords'][2] = 0.999;
23377                  $gradient['coords'][3] = 0;
23378              }
23379              // swap Y coordinates
23380              $tmp = $gradient['coords'][1];
23381              $gradient['coords'][1] = $gradient['coords'][3];
23382              $gradient['coords'][3] = $tmp;
23383              // set transformation map for gradient
23384              $cy = ($this->h - $y);
23385              if ($gradient['type'] == 3) {
23386                  // circular gradient
23387                  $cy -= ($gradient['coords'][1] * ($w + $h));
23388              } else {
23389                  $cy -= $h;
23390              }
23391              $this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), ($cy * $this->k)));
23392              if (count($gradient['stops']) > 1) {
23393                  $this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
23394              }
23395          } elseif ($svgstyle['fill'] != 'none') {
23396              $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
23397              if ($svgstyle['fill-opacity'] != 1) {
23398                  $this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
23399              }
23400              $this->SetFillColorArray($fill_color);
23401              if ($svgstyle['fill-rule'] == 'evenodd') {
23402                  $objstyle .= 'F*';
23403              } else {
23404                  $objstyle .= 'F';
23405              }
23406          }
23407          // stroke
23408          if ($svgstyle['stroke'] != 'none') {
23409              if ($svgstyle['stroke-opacity'] != 1) {
23410                  $this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
23411              } elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['stroke'], $rgba_matches)) {
23412                  $this->setAlpha($rgba_matches[1], 'Normal', $this->alpha['ca'], false);
23413              }
23414              $stroke_style = array(
23415                  'color' => TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors),
23416                  'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
23417                  'cap' => $svgstyle['stroke-linecap'],
23418                  'join' => $svgstyle['stroke-linejoin']
23419                  );
23420              if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
23421                  $stroke_style['dash'] = $svgstyle['stroke-dasharray'];
23422              }
23423              $this->SetLineStyle($stroke_style);
23424              $objstyle .= 'D';
23425          }
23426          // font
23427          $regs = array();
23428          if (!empty($svgstyle['font'])) {
23429              if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
23430                  $font_family = $this->getFontFamilyName($regs[1]);
23431              } else {
23432                  $font_family = $svgstyle['font-family'];
23433              }
23434              if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23435                  $font_size = trim($regs[1]);
23436              } else {
23437                  $font_size = $svgstyle['font-size'];
23438              }
23439              if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23440                  $font_style = trim($regs[1]);
23441              } else {
23442                  $font_style = $svgstyle['font-style'];
23443              }
23444              if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23445                  $font_weight = trim($regs[1]);
23446              } else {
23447                  $font_weight = $svgstyle['font-weight'];
23448              }
23449              if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23450                  $font_stretch = trim($regs[1]);
23451              } else {
23452                  $font_stretch = $svgstyle['font-stretch'];
23453              }
23454              if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23455                  $font_spacing = trim($regs[1]);
23456              } else {
23457                  $font_spacing = $svgstyle['letter-spacing'];
23458              }
23459          } else {
23460              $font_family = $this->getFontFamilyName($svgstyle['font-family']);
23461              $font_size = $svgstyle['font-size'];
23462              $font_style = $svgstyle['font-style'];
23463              $font_weight = $svgstyle['font-weight'];
23464              $font_stretch = $svgstyle['font-stretch'];
23465              $font_spacing = $svgstyle['letter-spacing'];
23466          }
23467          $font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit);
23468          $font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
23469          $font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
23470          switch ($font_style) {
23471              case 'italic': {
23472                  $font_style = 'I';
23473                  break;
23474              }
23475              case 'oblique': {
23476                  $font_style = 'I';
23477                  break;
23478              }
23479              default:
23480              case 'normal': {
23481                  $font_style = '';
23482                  break;
23483              }
23484          }
23485          switch ($font_weight) {
23486              case 'bold':
23487              case 'bolder': {
23488                  $font_style .= 'B';
23489                  break;
23490              }
23491          }
23492          switch ($svgstyle['text-decoration']) {
23493              case 'underline': {
23494                  $font_style .= 'U';
23495                  break;
23496              }
23497              case 'overline': {
23498                  $font_style .= 'O';
23499                  break;
23500              }
23501              case 'line-through': {
23502                  $font_style .= 'D';
23503                  break;
23504              }
23505              default:
23506              case 'none': {
23507                  break;
23508              }
23509          }
23510          $this->SetFont($font_family, $font_style, $font_size);
23511          $this->setFontStretching($font_stretch);
23512          $this->setFontSpacing($font_spacing);
23513          return $objstyle;
23514      }
23515  
23516      /**
23517       * Draws an SVG path
23518       * @param $d (string) attribute d of the path SVG element
23519       * @param $style (string) Style of rendering. Possible values are:
23520       * <ul>
23521       *     <li>D or empty string: Draw (default).</li>
23522       *     <li>F: Fill.</li>
23523       *     <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23524       *     <li>DF or FD: Draw and fill.</li>
23525       *     <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23526       *     <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
23527       *     <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
23528       * </ul>
23529       * @return array of container box measures (x, y, w, h)
23530       * @author Nicola Asuni
23531       * @since 5.0.000 (2010-05-02)
23532       * @protected
23533       */
23534  	protected function SVGPath($d, $style='') {
23535          if ($this->state != 2) {
23536               return;
23537          }
23538          // set fill/stroke style
23539          $op = TCPDF_STATIC::getPathPaintOperator($style, '');
23540          if (empty($op)) {
23541              return;
23542          }
23543          $paths = array();
23544          $d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
23545          preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
23546          $x = 0;
23547          $y = 0;
23548          $x1 = 0;
23549          $y1 = 0;
23550          $x2 = 0;
23551          $y2 = 0;
23552          $xmin = 2147483647;
23553          $xmax = 0;
23554          $ymin = 2147483647;
23555          $ymax = 0;
23556          $relcoord = false;
23557          $minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
23558          $firstcmd = true; // used to print first point
23559          // draw curve pieces
23560          foreach ($paths as $key => $val) {
23561              // get curve type
23562              $cmd = trim($val[1]);
23563              if (strtolower($cmd) == $cmd) {
23564                  // use relative coordinated instead of absolute
23565                  $relcoord = true;
23566                  $xoffset = $x;
23567                  $yoffset = $y;
23568              } else {
23569                  $relcoord = false;
23570                  $xoffset = 0;
23571                  $yoffset = 0;
23572              }
23573              $params = array();
23574              if (isset($val[2])) {
23575                  // get curve parameters
23576                  $rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
23577                  $params = array();
23578                  foreach ($rawparams as $ck => $cp) {
23579                      $params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
23580                      if (abs($params[$ck]) < $minlen) {
23581                          // aproximate little values to zero
23582                          $params[$ck] = 0;
23583                      }
23584                  }
23585              }
23586              // store current origin point
23587              $x0 = $x;
23588              $y0 = $y;
23589              switch (strtoupper($cmd)) {
23590                  case 'M': { // moveto
23591                      foreach ($params as $ck => $cp) {
23592                          if (($ck % 2) == 0) {
23593                              $x = $cp + $xoffset;
23594                          } else {
23595                              $y = $cp + $yoffset;
23596                              if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23597                                  if ($ck == 1) {
23598                                      $this->_outPoint($x, $y);
23599                                      $firstcmd = false;
23600                                  } else {
23601                                      $this->_outLine($x, $y);
23602                                  }
23603                                  $x0 = $x;
23604                                  $y0 = $y;
23605                              }
23606                              $xmin = min($xmin, $x);
23607                              $ymin = min($ymin, $y);
23608                              $xmax = max($xmax, $x);
23609                              $ymax = max($ymax, $y);
23610                              if ($relcoord) {
23611                                  $xoffset = $x;
23612                                  $yoffset = $y;
23613                              }
23614                          }
23615                      }
23616                      break;
23617                  }
23618                  case 'L': { // lineto
23619                      foreach ($params as $ck => $cp) {
23620                          if (($ck % 2) == 0) {
23621                              $x = $cp + $xoffset;
23622                          } else {
23623                              $y = $cp + $yoffset;
23624                              if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23625                                  $this->_outLine($x, $y);
23626                                  $x0 = $x;
23627                                  $y0 = $y;
23628                              }
23629                              $xmin = min($xmin, $x);
23630                              $ymin = min($ymin, $y);
23631                              $xmax = max($xmax, $x);
23632                              $ymax = max($ymax, $y);
23633                              if ($relcoord) {
23634                                  $xoffset = $x;
23635                                  $yoffset = $y;
23636                              }
23637                          }
23638                      }
23639                      break;
23640                  }
23641                  case 'H': { // horizontal lineto
23642                      foreach ($params as $ck => $cp) {
23643                          $x = $cp + $xoffset;
23644                          if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23645                              $this->_outLine($x, $y);
23646                              $x0 = $x;
23647                              $y0 = $y;
23648                          }
23649                          $xmin = min($xmin, $x);
23650                          $xmax = max($xmax, $x);
23651                          if ($relcoord) {
23652                              $xoffset = $x;
23653                          }
23654                      }
23655                      break;
23656                  }
23657                  case 'V': { // vertical lineto
23658                      foreach ($params as $ck => $cp) {
23659                          $y = $cp + $yoffset;
23660                          if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23661                              $this->_outLine($x, $y);
23662                              $x0 = $x;
23663                              $y0 = $y;
23664                          }
23665                          $ymin = min($ymin, $y);
23666                          $ymax = max($ymax, $y);
23667                          if ($relcoord) {
23668                              $yoffset = $y;
23669                          }
23670                      }
23671                      break;
23672                  }
23673                  case 'C': { // curveto
23674                      foreach ($params as $ck => $cp) {
23675                          $params[$ck] = $cp;
23676                          if ((($ck + 1) % 6) == 0) {
23677                              $x1 = $params[($ck - 5)] + $xoffset;
23678                              $y1 = $params[($ck - 4)] + $yoffset;
23679                              $x2 = $params[($ck - 3)] + $xoffset;
23680                              $y2 = $params[($ck - 2)] + $yoffset;
23681                              $x = $params[($ck - 1)] + $xoffset;
23682                              $y = $params[($ck)] + $yoffset;
23683                              $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23684                              $xmin = min($xmin, $x, $x1, $x2);
23685                              $ymin = min($ymin, $y, $y1, $y2);
23686                              $xmax = max($xmax, $x, $x1, $x2);
23687                              $ymax = max($ymax, $y, $y1, $y2);
23688                              if ($relcoord) {
23689                                  $xoffset = $x;
23690                                  $yoffset = $y;
23691                              }
23692                          }
23693                      }
23694                      break;
23695                  }
23696                  case 'S': { // shorthand/smooth curveto
23697                      foreach ($params as $ck => $cp) {
23698                          $params[$ck] = $cp;
23699                          if ((($ck + 1) % 4) == 0) {
23700                              if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
23701                                  $x1 = (2 * $x) - $x2;
23702                                  $y1 = (2 * $y) - $y2;
23703                              } else {
23704                                  $x1 = $x;
23705                                  $y1 = $y;
23706                              }
23707                              $x2 = $params[($ck - 3)] + $xoffset;
23708                              $y2 = $params[($ck - 2)] + $yoffset;
23709                              $x = $params[($ck - 1)] + $xoffset;
23710                              $y = $params[($ck)] + $yoffset;
23711                              $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23712                              $xmin = min($xmin, $x, $x1, $x2);
23713                              $ymin = min($ymin, $y, $y1, $y2);
23714                              $xmax = max($xmax, $x, $x1, $x2);
23715                              $ymax = max($ymax, $y, $y1, $y2);
23716                              if ($relcoord) {
23717                                  $xoffset = $x;
23718                                  $yoffset = $y;
23719                              }
23720                          }
23721                      }
23722                      break;
23723                  }
23724                  case 'Q': { // quadratic Bezier curveto
23725                      foreach ($params as $ck => $cp) {
23726                          $params[$ck] = $cp;
23727                          if ((($ck + 1) % 4) == 0) {
23728                              // convert quadratic points to cubic points
23729                              $x1 = $params[($ck - 3)] + $xoffset;
23730                              $y1 = $params[($ck - 2)] + $yoffset;
23731                              $xa = ($x + (2 * $x1)) / 3;
23732                              $ya = ($y + (2 * $y1)) / 3;
23733                              $x = $params[($ck - 1)] + $xoffset;
23734                              $y = $params[($ck)] + $yoffset;
23735                              $xb = ($x + (2 * $x1)) / 3;
23736                              $yb = ($y + (2 * $y1)) / 3;
23737                              $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23738                              $xmin = min($xmin, $x, $xa, $xb);
23739                              $ymin = min($ymin, $y, $ya, $yb);
23740                              $xmax = max($xmax, $x, $xa, $xb);
23741                              $ymax = max($ymax, $y, $ya, $yb);
23742                              if ($relcoord) {
23743                                  $xoffset = $x;
23744                                  $yoffset = $y;
23745                              }
23746                          }
23747                      }
23748                      break;
23749                  }
23750                  case 'T': { // shorthand/smooth quadratic Bezier curveto
23751                      foreach ($params as $ck => $cp) {
23752                          $params[$ck] = $cp;
23753                          if (($ck % 2) != 0) {
23754                              if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
23755                                  $x1 = (2 * $x) - $x1;
23756                                  $y1 = (2 * $y) - $y1;
23757                              } else {
23758                                  $x1 = $x;
23759                                  $y1 = $y;
23760                              }
23761                              // convert quadratic points to cubic points
23762                              $xa = ($x + (2 * $x1)) / 3;
23763                              $ya = ($y + (2 * $y1)) / 3;
23764                              $x = $params[($ck - 1)] + $xoffset;
23765                              $y = $params[($ck)] + $yoffset;
23766                              $xb = ($x + (2 * $x1)) / 3;
23767                              $yb = ($y + (2 * $y1)) / 3;
23768                              $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23769                              $xmin = min($xmin, $x, $xa, $xb);
23770                              $ymin = min($ymin, $y, $ya, $yb);
23771                              $xmax = max($xmax, $x, $xa, $xb);
23772                              $ymax = max($ymax, $y, $ya, $yb);
23773                              if ($relcoord) {
23774                                  $xoffset = $x;
23775                                  $yoffset = $y;
23776                              }
23777                          }
23778                      }
23779                      break;
23780                  }
23781                  case 'A': { // elliptical arc
23782                      foreach ($params as $ck => $cp) {
23783                          $params[$ck] = $cp;
23784                          if ((($ck + 1) % 7) == 0) {
23785                              $x0 = $x;
23786                              $y0 = $y;
23787                              $rx = abs($params[($ck - 6)]);
23788                              $ry = abs($params[($ck - 5)]);
23789                              $ang = -$rawparams[($ck - 4)];
23790                              $angle = deg2rad($ang);
23791                              $fa = $rawparams[($ck - 3)]; // large-arc-flag
23792                              $fs = $rawparams[($ck - 2)]; // sweep-flag
23793                              $x = $params[($ck - 1)] + $xoffset;
23794                              $y = $params[$ck] + $yoffset;
23795                              if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
23796                                  // endpoints are almost identical
23797                                  $xmin = min($xmin, $x);
23798                                  $ymin = min($ymin, $y);
23799                                  $xmax = max($xmax, $x);
23800                                  $ymax = max($ymax, $y);
23801                              } else {
23802                                  $cos_ang = cos($angle);
23803                                  $sin_ang = sin($angle);
23804                                  $a = (($x0 - $x) / 2);
23805                                  $b = (($y0 - $y) / 2);
23806                                  $xa = ($a * $cos_ang) - ($b * $sin_ang);
23807                                  $ya = ($a * $sin_ang) + ($b * $cos_ang);
23808                                  $rx2 = $rx * $rx;
23809                                  $ry2 = $ry * $ry;
23810                                  $xa2 = $xa * $xa;
23811                                  $ya2 = $ya * $ya;
23812                                  $delta = ($xa2 / $rx2) + ($ya2 / $ry2);
23813                                  if ($delta > 1) {
23814                                      $rx *= sqrt($delta);
23815                                      $ry *= sqrt($delta);
23816                                      $rx2 = $rx * $rx;
23817                                      $ry2 = $ry * $ry;
23818                                  }
23819                                  $numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
23820                                  if ($numerator < 0) {
23821                                      $root = 0;
23822                                  } else {
23823                                      $root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
23824                                  }
23825                                  if ($fa == $fs){
23826                                      $root *= -1;
23827                                  }
23828                                  $cax = $root * (($rx * $ya) / $ry);
23829                                  $cay = -$root * (($ry * $xa) / $rx);
23830                                  // coordinates of ellipse center
23831                                  $cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
23832                                  $cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
23833                                  // get angles
23834                                  $angs = TCPDF_STATIC::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
23835                                  $dang = TCPDF_STATIC::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
23836                                  if (($fs == 0) AND ($dang > 0)) {
23837                                      $dang -= (2 * M_PI);
23838                                  } elseif (($fs == 1) AND ($dang < 0)) {
23839                                      $dang += (2 * M_PI);
23840                                  }
23841                                  $angf = $angs - $dang;
23842                                  if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
23843                                      // reverse angles
23844                                      $tmp = $angs;
23845                                      $angs = $angf;
23846                                      $angf = $tmp;
23847                                  }
23848                                  $angs = round(rad2deg($angs), 6);
23849                                  $angf = round(rad2deg($angf), 6);
23850                                  // covent angles to positive values
23851                                  if (($angs < 0) AND ($angf < 0)) {
23852                                      $angs += 360;
23853                                      $angf += 360;
23854                                  }
23855                                  $pie = false;
23856                                  if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
23857                                      $pie = true;
23858                                  }
23859                                  list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
23860                                  $xmin = min($xmin, $x, $axmin);
23861                                  $ymin = min($ymin, $y, $aymin);
23862                                  $xmax = max($xmax, $x, $axmax);
23863                                  $ymax = max($ymax, $y, $aymax);
23864                              }
23865                              if ($relcoord) {
23866                                  $xoffset = $x;
23867                                  $yoffset = $y;
23868                              }
23869                          }
23870                      }
23871                      break;
23872                  }
23873                  case 'Z': {
23874                      $this->_out('h');
23875                      break;
23876                  }
23877              }
23878              $firstcmd = false;
23879          } // end foreach
23880          if (!empty($op)) {
23881              $this->_out($op);
23882          }
23883          return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
23884      }
23885  
23886      /**
23887       * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
23888       * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
23889       * @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.
23890       * @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.
23891       * @param $ctm (array) tranformation matrix for clipping mode (starting transformation matrix).
23892       * @author Nicola Asuni
23893       * @since 5.0.000 (2010-05-02)
23894       * @protected
23895       */
23896  	protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
23897          // check if we are in clip mode
23898          if ($this->svgclipmode) {
23899              $this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
23900              return;
23901          }
23902          if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
23903              if (isset($attribs['id'])) {
23904                  $attribs['child_elements'] = array();
23905                  $this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23906                  return;
23907              }
23908              if (end($this->svgdefs) !== FALSE) {
23909                  $last_svgdefs_id = key($this->svgdefs);
23910                  if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
23911                      $attribs['id'] = 'DF_'.(count($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements']) + 1);
23912                      $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23913                      return;
23914                  }
23915              }
23916              return;
23917          }
23918          $clipping = false;
23919          if ($parser == 'clip-path') {
23920              // set clipping mode
23921              $clipping = true;
23922          }
23923          // get styling properties
23924          $prev_svgstyle = $this->svgstyles[max(0,(count($this->svgstyles) - 1))]; // previous style
23925          $svgstyle = $this->svgstyles[0]; // set default style
23926          if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
23927              // default fill attribute for clipping
23928              $attribs['fill'] = 'none';
23929          }
23930          if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
23931              // fix style for regular expression
23932              $attribs['style'] = ';'.$attribs['style'];
23933          }
23934          foreach ($prev_svgstyle as $key => $val) {
23935              if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
23936                  // inherit previous value
23937                  $svgstyle[$key] = $val;
23938              }
23939              if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
23940                  // specific attribute settings
23941                  if ($attribs[$key] == 'inherit') {
23942                      $svgstyle[$key] = $val;
23943                  } else {
23944                      $svgstyle[$key] = $attribs[$key];
23945                  }
23946              } elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
23947                  // CSS style syntax
23948                  $attrval = array();
23949                  if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
23950                      if ($attrval[1] == 'inherit') {
23951                          $svgstyle[$key] = $val;
23952                      } else {
23953                          $svgstyle[$key] = $attrval[1];
23954                      }
23955                  }
23956              }
23957          }
23958          // transformation matrix
23959          if (!empty($ctm)) {
23960              $tm = $ctm;
23961          } else {
23962              $tm = array(1,0,0,1,0,0);
23963          }
23964          if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
23965              $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
23966          }
23967          $svgstyle['transfmatrix'] = $tm;
23968          $invisible = false;
23969          if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
23970              // the current graphics element is invisible (nothing is painted)
23971              $invisible = true;
23972          }
23973          // process tag
23974          switch($name) {
23975              case 'defs': {
23976                  $this->svgdefsmode = true;
23977                  break;
23978              }
23979              // clipPath
23980              case 'clipPath': {
23981                  if ($invisible) {
23982                      break;
23983                  }
23984                  $this->svgclipmode = true;
23985                  if (!isset($attribs['id'])) {
23986                      $attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
23987                  }
23988                  $this->svgclipid = $attribs['id'];
23989                  $this->svgclippaths[$this->svgclipid] = array();
23990                  $this->svgcliptm[$this->svgclipid] = $tm;
23991                  break;
23992              }
23993              case 'svg': {
23994                  // start of SVG object
23995                  break;
23996              }
23997              case 'g': {
23998                  // group together related graphics elements
23999                  array_push($this->svgstyles, $svgstyle);
24000                  $this->StartTransform();
24001                  $x = (isset($attribs['x'])?$attribs['x']:0);
24002                  $y = (isset($attribs['y'])?$attribs['y']:0);
24003                  $w = 1;//(isset($attribs['width'])?$attribs['width']:1);
24004                  $h = 1;//(isset($attribs['height'])?$attribs['height']:1);
24005                  $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24006                  $this->SVGTransform($tm);
24007                  $this->setSVGStyles($svgstyle, $prev_svgstyle);
24008                  break;
24009              }
24010              case 'linearGradient': {
24011                  if ($this->pdfa_mode) {
24012                      break;
24013                  }
24014                  if (!isset($attribs['id'])) {
24015                      $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24016                  }
24017                  $this->svggradientid = $attribs['id'];
24018                  $this->svggradients[$this->svggradientid] = array();
24019                  $this->svggradients[$this->svggradientid]['type'] = 2;
24020                  $this->svggradients[$this->svggradientid]['stops'] = array();
24021                  if (isset($attribs['gradientUnits'])) {
24022                      $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24023                  } else {
24024                      $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24025                  }
24026                  //$attribs['spreadMethod']
24027                  if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
24028                      OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
24029                          OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
24030                          OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
24031                          OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
24032                      $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24033                  } else {
24034                      $this->svggradients[$this->svggradientid]['mode'] = 'measure';
24035                  }
24036                  $x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
24037                  $y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
24038                  $x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
24039                  $y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
24040                  if (isset($attribs['gradientTransform'])) {
24041                      $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24042                  }
24043                  $this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
24044                  if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24045                      // gradient is defined on another place
24046                      $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24047                  }
24048                  break;
24049              }
24050              case 'radialGradient': {
24051                  if ($this->pdfa_mode) {
24052                      break;
24053                  }
24054                  if (!isset($attribs['id'])) {
24055                      $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
24056                  }
24057                  $this->svggradientid = $attribs['id'];
24058                  $this->svggradients[$this->svggradientid] = array();
24059                  $this->svggradients[$this->svggradientid]['type'] = 3;
24060                  $this->svggradients[$this->svggradientid]['stops'] = array();
24061                  if (isset($attribs['gradientUnits'])) {
24062                      $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
24063                  } else {
24064                      $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
24065                  }
24066                  //$attribs['spreadMethod']
24067                  if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
24068                      OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
24069                      OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')))) {
24070                      $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
24071                  } elseif (isset($attribs['r']) AND is_numeric($attribs['r']) AND ($attribs['r']) <= 1) {
24072                      $this->svggradients[$this->svggradientid]['mode'] = 'ratio';
24073                  } else {
24074                      $this->svggradients[$this->svggradientid]['mode'] = 'measure';
24075                  }
24076                  $cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
24077                  $cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
24078                  $fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
24079                  $fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
24080                  $r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
24081                  if (isset($attribs['gradientTransform'])) {
24082                      $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
24083                  }
24084                  $this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
24085                  if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24086                      // gradient is defined on another place
24087                      $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
24088                  }
24089                  break;
24090              }
24091              case 'stop': {
24092                  // gradient stops
24093                  if (substr($attribs['offset'], -1) == '%') {
24094                      $offset = floatval(substr($attribs['offset'], -1)) / 100;
24095                  } else {
24096                      $offset = floatval($attribs['offset']);
24097                      if ($offset > 1) {
24098                          $offset /= 100;
24099                      }
24100                  }
24101                  $stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
24102                  $opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
24103                  $this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
24104                  break;
24105              }
24106              // paths
24107              case 'path': {
24108                  if ($invisible) {
24109                      break;
24110                  }
24111                  if (isset($attribs['d'])) {
24112                      $d = trim($attribs['d']);
24113                      if (!empty($d)) {
24114                          $x = (isset($attribs['x'])?$attribs['x']:0);
24115                          $y = (isset($attribs['y'])?$attribs['y']:0);
24116                          $w = (isset($attribs['width'])?$attribs['width']:1);
24117                          $h = (isset($attribs['height'])?$attribs['height']:1);
24118                          $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24119                          if ($clipping) {
24120                              $this->SVGTransform($tm);
24121                              $this->SVGPath($d, 'CNZ');
24122                          } else {
24123                              $this->StartTransform();
24124                              $this->SVGTransform($tm);
24125                              $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
24126                              if (!empty($obstyle)) {
24127                                  $this->SVGPath($d, $obstyle);
24128                              }
24129                              $this->StopTransform();
24130                          }
24131                      }
24132                  }
24133                  break;
24134              }
24135              // shapes
24136              case 'rect': {
24137                  if ($invisible) {
24138                      break;
24139                  }
24140                  $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24141                  $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24142                  $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24143                  $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24144                  $rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
24145                  $ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
24146                  if ($clipping) {
24147                      $this->SVGTransform($tm);
24148                      $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
24149                  } else {
24150                      $this->StartTransform();
24151                      $this->SVGTransform($tm);
24152                      $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
24153                      if (!empty($obstyle)) {
24154                          $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
24155                      }
24156                      $this->StopTransform();
24157                  }
24158                  break;
24159              }
24160              case 'circle': {
24161                  if ($invisible) {
24162                      break;
24163                  }
24164                  $r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
24165                  $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24166                  $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24167                  $x = ($cx - $r);
24168                  $y = ($cy - $r);
24169                  $w = (2 * $r);
24170                  $h = $w;
24171                  if ($clipping) {
24172                      $this->SVGTransform($tm);
24173                      $this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
24174                  } else {
24175                      $this->StartTransform();
24176                      $this->SVGTransform($tm);
24177                      $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
24178                      if (!empty($obstyle)) {
24179                          $this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
24180                      }
24181                      $this->StopTransform();
24182                  }
24183                  break;
24184              }
24185              case 'ellipse': {
24186                  if ($invisible) {
24187                      break;
24188                  }
24189                  $rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
24190                  $ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
24191                  $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
24192                  $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
24193                  $x = ($cx - $rx);
24194                  $y = ($cy - $ry);
24195                  $w = (2 * $rx);
24196                  $h = (2 * $ry);
24197                  if ($clipping) {
24198                      $this->SVGTransform($tm);
24199                      $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
24200                  } else {
24201                      $this->StartTransform();
24202                      $this->SVGTransform($tm);
24203                      $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
24204                      if (!empty($obstyle)) {
24205                          $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
24206                      }
24207                      $this->StopTransform();
24208                  }
24209                  break;
24210              }
24211              case 'line': {
24212                  if ($invisible) {
24213                      break;
24214                  }
24215                  $x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
24216                  $y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
24217                  $x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
24218                  $y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
24219                  $x = $x1;
24220                  $y = $y1;
24221                  $w = abs($x2 - $x1);
24222                  $h = abs($y2 - $y1);
24223                  if (!$clipping) {
24224                      $this->StartTransform();
24225                      $this->SVGTransform($tm);
24226                      $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
24227                      $this->Line($x1, $y1, $x2, $y2);
24228                      $this->StopTransform();
24229                  }
24230                  break;
24231              }
24232              case 'polyline':
24233              case 'polygon': {
24234                  if ($invisible) {
24235                      break;
24236                  }
24237                  $points = (isset($attribs['points'])?$attribs['points']:'0 0');
24238                  $points = trim($points);
24239                  // note that point may use a complex syntax not covered here
24240                  $points = preg_split('/[\,\s]+/si', $points);
24241                  if (count($points) < 4) {
24242                      break;
24243                  }
24244                  $p = array();
24245                  $xmin = 2147483647;
24246                  $xmax = 0;
24247                  $ymin = 2147483647;
24248                  $ymax = 0;
24249                  foreach ($points as $key => $val) {
24250                      $p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24251                      if (($key % 2) == 0) {
24252                          // X coordinate
24253                          $xmin = min($xmin, $p[$key]);
24254                          $xmax = max($xmax, $p[$key]);
24255                      } else {
24256                          // Y coordinate
24257                          $ymin = min($ymin, $p[$key]);
24258                          $ymax = max($ymax, $p[$key]);
24259                      }
24260                  }
24261                  $x = $xmin;
24262                  $y = $ymin;
24263                  $w = ($xmax - $xmin);
24264                  $h = ($ymax - $ymin);
24265                  if ($name == 'polyline') {
24266                      $this->StartTransform();
24267                      $this->SVGTransform($tm);
24268                      $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
24269                      if (!empty($obstyle)) {
24270                          $this->PolyLine($p, $obstyle, array(), array());
24271                      }
24272                      $this->StopTransform();
24273                  } else { // polygon
24274                      if ($clipping) {
24275                          $this->SVGTransform($tm);
24276                          $this->Polygon($p, 'CNZ', array(), array(), true);
24277                      } else {
24278                          $this->StartTransform();
24279                          $this->SVGTransform($tm);
24280                          $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
24281                          if (!empty($obstyle)) {
24282                              $this->Polygon($p, $obstyle, array(), array(), true);
24283                          }
24284                          $this->StopTransform();
24285                      }
24286                  }
24287                  break;
24288              }
24289              // image
24290              case 'image': {
24291                  if ($invisible) {
24292                      break;
24293                  }
24294                  if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
24295                      break;
24296                  }
24297                  $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24298                  $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24299                  $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24300                  $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24301                  $img = $attribs['xlink:href'];
24302                  if (!$clipping) {
24303                      $this->StartTransform();
24304                      $this->SVGTransform($tm);
24305                      $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
24306                      if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
24307                          // embedded image encoded as base64
24308                          $img = '@'.base64_decode(substr($img, strlen($m[0])));
24309                      } else {
24310                          // fix image path
24311                          if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
24312                              // replace relative path with full server path
24313                              $img = $this->svgdir.'/'.$img;
24314                          }
24315                          if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
24316                              $findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
24317                              if (($findroot === false) OR ($findroot > 1)) {
24318                                  if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
24319                                      $img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
24320                                  } else {
24321                                      $img = $_SERVER['DOCUMENT_ROOT'].$img;
24322                                  }
24323                              }
24324                          }
24325                          $img = urldecode($img);
24326                          $testscrtype = @parse_url($img);
24327                          if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
24328                              // convert URL to server path
24329                              $img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
24330                          }
24331                      }
24332                      // get image type
24333                      $imgtype = TCPDF_IMAGES::getImageFileType($img);
24334                      if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
24335                          $this->ImageEps($img, $x, $y, $w, $h);
24336                      } elseif ($imgtype == 'svg') {
24337                          $this->ImageSVG($img, $x, $y, $w, $h);
24338                      } else {
24339                          $this->Image($img, $x, $y, $w, $h);
24340                      }
24341                      $this->StopTransform();
24342                  }
24343                  break;
24344              }
24345              // text
24346              case 'text':
24347              case 'tspan': {
24348                  // only basic support - advanced features must be implemented
24349                  $this->svgtextmode['invisible'] = $invisible;
24350                  if ($invisible) {
24351                      break;
24352                  }
24353                  array_push($this->svgstyles, $svgstyle);
24354                  if (isset($attribs['x'])) {
24355                      $x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
24356                  } elseif ($name == 'tspan') {
24357                      $x = $this->x;
24358                  } else {
24359                      $x = 0;
24360                  }
24361                  if (isset($attribs['dx'])) {
24362                      $x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
24363                  }
24364                  if (isset($attribs['y'])) {
24365                      $y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
24366                  } elseif ($name == 'tspan') {
24367                      $y = $this->y;
24368                  } else {
24369                      $y = 0;
24370                  }
24371                  if (isset($attribs['dy'])) {
24372                      $y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
24373                  }
24374                  $svgstyle['text-color'] = $svgstyle['fill'];
24375                  $this->svgtext = '';
24376                  if (isset($svgstyle['text-anchor'])) {
24377                      $this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
24378                  } else {
24379                      $this->svgtextmode['text-anchor'] = 'start';
24380                  }
24381                  if (isset($svgstyle['direction'])) {
24382                      if ($svgstyle['direction'] == 'rtl') {
24383                          $this->svgtextmode['rtl'] = true;
24384                      } else {
24385                          $this->svgtextmode['rtl'] = false;
24386                      }
24387                  } else {
24388                      $this->svgtextmode['rtl'] = false;
24389                  }
24390                  if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
24391                      $this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
24392                  } else {
24393                      $this->svgtextmode['stroke'] = false;
24394                  }
24395                  $this->StartTransform();
24396                  $this->SVGTransform($tm);
24397                  $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
24398                  $this->x = $x;
24399                  $this->y = $y;
24400                  break;
24401              }
24402              // use
24403              case 'use': {
24404                  if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24405                      $svgdefid = substr($attribs['xlink:href'], 1);
24406                      if (isset($this->svgdefs[$svgdefid])) {
24407                          $use = $this->svgdefs[$svgdefid];
24408                          if (isset($attribs['xlink:href'])) {
24409                              unset($attribs['xlink:href']);
24410                          }
24411                          if (isset($attribs['id'])) {
24412                              unset($attribs['id']);
24413                          }
24414                          if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
24415                              $attribs['x'] += $use['attribs']['x'];
24416                          }
24417                          if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
24418                              $attribs['y'] += $use['attribs']['y'];
24419                          }
24420                          if (empty($attribs['style'])) {
24421                              $attribs['style'] = '';
24422                          }
24423                          if (!empty($use['attribs']['style'])) {
24424                              // merge styles
24425                              $attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
24426                          }
24427                          $attribs = array_merge($use['attribs'], $attribs);
24428                          $this->startSVGElementHandler($parser, $use['name'], $attribs);
24429                          return;
24430                      }
24431                  }
24432                  break;
24433              }
24434              default: {
24435                  break;
24436              }
24437          } // end of switch
24438          // process child elements
24439          if (!empty($attribs['child_elements'])) {
24440              $child_elements = $attribs['child_elements'];
24441              unset($attribs['child_elements']);
24442              foreach($child_elements as $child_element) {
24443                  if (empty($child_element['attribs']['closing_tag'])) {
24444                      $this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
24445                  } else {
24446                      if (isset($child_element['attribs']['content'])) {
24447                          $this->svgtext = $child_element['attribs']['content'];
24448                      }
24449                      $this->endSVGElementHandler('child-tag', $child_element['name']);
24450                  }
24451              }
24452          }
24453      }
24454  
24455      /**
24456       * Sets the closing SVG element handler function for the XML parser.
24457       * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24458       * @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.
24459       * @author Nicola Asuni
24460       * @since 5.0.000 (2010-05-02)
24461       * @protected
24462       */
24463  	protected function endSVGElementHandler($parser, $name) {
24464          if ($this->svgdefsmode AND !in_array($name, array('defs', 'clipPath', 'linearGradient', 'radialGradient', 'stop'))) {;
24465              if (end($this->svgdefs) !== FALSE) {
24466                  $last_svgdefs_id = key($this->svgdefs);
24467                  if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
24468                      foreach($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'] as $child_element) {
24469                          if (isset($child_element['attribs']['id']) AND ($child_element['name'] == $name)) {
24470                              $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$child_element['attribs']['id'].'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24471                              return;
24472                          }
24473                      }
24474                      if ($this->svgdefs[$last_svgdefs_id]['name'] == $name) {
24475                          $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$last_svgdefs_id.'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24476                          return;
24477                      }
24478                  }
24479              }
24480              return;
24481          }
24482          switch($name) {
24483              case 'defs': {
24484                  $this->svgdefsmode = false;
24485                  break;
24486              }
24487              // clipPath
24488              case 'clipPath': {
24489                  $this->svgclipmode = false;
24490                  break;
24491              }
24492              case 'g': {
24493                  // ungroup: remove last style from array
24494                  array_pop($this->svgstyles);
24495                  $this->StopTransform();
24496                  break;
24497              }
24498              case 'text':
24499              case 'tspan': {
24500                  if ($this->svgtextmode['invisible']) {
24501                      // This implementation must be fixed to following the rule:
24502                      // 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.
24503                      break;
24504                  }
24505                  // print text
24506                  $text = $this->svgtext;
24507                  //$text = $this->stringTrim($text);
24508                  $textlen = $this->GetStringWidth($text);
24509                  if ($this->svgtextmode['text-anchor'] != 'start') {
24510                      // check if string is RTL text
24511                      if ($this->svgtextmode['text-anchor'] == 'end') {
24512                          if ($this->svgtextmode['rtl']) {
24513                              $this->x += $textlen;
24514                          } else {
24515                              $this->x -= $textlen;
24516                          }
24517                      } elseif ($this->svgtextmode['text-anchor'] == 'middle') {
24518                          if ($this->svgtextmode['rtl']) {
24519                              $this->x += ($textlen / 2);
24520                          } else {
24521                              $this->x -= ($textlen / 2);
24522                          }
24523                      }
24524                  }
24525                  $textrendermode = $this->textrendermode;
24526                  $textstrokewidth = $this->textstrokewidth;
24527                  $this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
24528                  if ($name == 'text') {
24529                      // store current coordinates
24530                      $tmpx = $this->x;
24531                      $tmpy = $this->y;
24532                  }
24533                  $this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
24534                  if ($name == 'text') {
24535                      // restore coordinates
24536                      $this->x = $tmpx;
24537                      $this->y = $tmpy;
24538                  }
24539                  // restore previous rendering mode
24540                  $this->textrendermode = $textrendermode;
24541                  $this->textstrokewidth = $textstrokewidth;
24542                  $this->svgtext = '';
24543                  $this->StopTransform();
24544                  if (!$this->svgdefsmode) {
24545                      array_pop($this->svgstyles);
24546                  }
24547                  break;
24548              }
24549              default: {
24550                  break;
24551              }
24552          }
24553      }
24554  
24555      /**
24556       * Sets the character data handler function for the XML parser.
24557       * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24558       * @param $data (string) The second parameter, data, contains the character data as a string.
24559       * @author Nicola Asuni
24560       * @since 5.0.000 (2010-05-02)
24561       * @protected
24562       */
24563  	protected function segSVGContentHandler($parser, $data) {
24564          $this->svgtext .= $data;
24565      }
24566  
24567      // --- END SVG METHODS -----------------------------------------------------
24568  
24569  } // END OF TCPDF CLASS
24570  
24571  //============================================================+
24572  // END OF FILE
24573  //============================================================+


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1