ILIAS  eassessment Revision 61809
 All Data Structures Namespaces Files Functions Variables Groups Pages
tcpdf.php
Go to the documentation of this file.
1 <?php
2 //============================================================+
3 // File name : tcpdf.php
4 // Version : 5.9.009
5 // Begin : 2002-08-03
6 // Last Update : 2010-10-21
7 // Author : Nicola Asuni - Tecnick.com S.r.l - Via Della Pace, 11 - 09044 - Quartucciu (CA) - ITALY - www.tecnick.com - info@tecnick.com
8 // License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
9 // -------------------------------------------------------------------
10 // Copyright (C) 2002-2010 Nicola Asuni - Tecnick.com S.r.l.
11 //
12 // This file is part of TCPDF software library.
13 //
14 // TCPDF is free software: you can redistribute it and/or modify it
15 // under the terms of the GNU Lesser General Public License as
16 // published by the Free Software Foundation, either version 3 of the
17 // License, or (at your option) any later version.
18 //
19 // TCPDF is distributed in the hope that it will be useful, but
20 // WITHOUT ANY WARRANTY; without even the implied warranty of
21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22 // See the GNU Lesser General Public License for more details.
23 //
24 // You should have received a copy of the GNU Lesser General Public License
25 // along with TCPDF. If not, see <http://www.gnu.org/licenses/>.
26 //
27 // See LICENSE.TXT file for more information.
28 // -------------------------------------------------------------------
29 //
30 // Description : This is a PHP class for generating PDF documents without
31 // requiring external extensions.
32 //
33 // NOTE:
34 // This class was originally derived in 2002 from the Public
35 // Domain FPDF class by Olivier Plathey (http://www.fpdf.org),
36 // but now is almost entirely rewritten and contains thousands of
37 // new lines of code and hundreds new features.
38 //
39 // Main features:
40 // * no external libraries are required for the basic functions;
41 // * all standard page formats, custom page formats, custom margins and units of measure;
42 // * UTF-8 Unicode and Right-To-Left languages;
43 // * TrueTypeUnicode, OpenTypeUnicode, TrueType, OpenType, Type1 and CID-0 fonts;
44 // * font subsetting;
45 // * methods to publish some XHTML + CSS code, Javascript and Forms;
46 // * images, graphic (geometric figures) and transformation methods;
47 // * 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)
48 // * 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, QR-Code, PDF417;
49 // * Grayscale, RGB, CMYK, Spot Colors and Transparencies;
50 // * automatic page header and footer management;
51 // * document encryption up to 256 bit and digital signature certifications;
52 // * transactions to UNDO commands;
53 // * PDF annotations, including links, text and file attachments;
54 // * text rendering modes (fill, stroke and clipping);
55 // * multiple columns mode;
56 // * no-write page regions;
57 // * bookmarks and table of content;
58 // * text hyphenation;
59 // * text stretching and spacing (tracking/kerning);
60 // * automatic page break, line break and text alignments including justification;
61 // * automatic page numbering and page groups;
62 // * move and delete pages;
63 // * page compression (requires php-zlib extension);
64 // * XOBject Templates;
65 //
66 // -----------------------------------------------------------
67 // THANKS TO:
68 //
69 // Olivier Plathey (http://www.fpdf.org) for original FPDF.
70 // Efthimios Mavrogeorgiadis (emavro@yahoo.com) for suggestions on RTL language support.
71 // Klemen Vodopivec (http://www.fpdf.de/downloads/addons/37/) for Encryption algorithm.
72 // Warren Sherliker (wsherliker@gmail.com) for better image handling.
73 // dullus for text Justification.
74 // Bob Vincent (pillarsdotnet@users.sourceforge.net) for <li> value attribute.
75 // Patrick Benny for text stretch suggestion on Cell().
76 // Johannes Güntert for JavaScript support.
77 // Denis Van Nuffelen for Dynamic Form.
78 // Jacek Czekaj for multibyte justification
79 // Anthony Ferrara for the reintroduction of legacy image methods.
80 // Sourceforge user 1707880 (hucste) for line-trough mode.
81 // Larry Stanbery for page groups.
82 // Martin Hall-May for transparency.
83 // Aaron C. Spike for Polycurve method.
84 // Mohamad Ali Golkar, Saleh AlMatrafe, Charles Abbott for Arabic and Persian support.
85 // Moritz Wagner and Andreas Wurmser for graphic functions.
86 // Andrew Whitehead for core fonts support.
87 // Esteban Joël Marín for OpenType font conversion.
88 // Teus Hagen for several suggestions and fixes.
89 // Yukihiro Nakadaira for CID-0 CJK fonts fixes.
90 // Kosmas Papachristos for some CSS improvements.
91 // Marcel Partap for some fixes.
92 // Won Kyu Park for several suggestions, fixes and patches.
93 // Dominik Dzienia for QR-code support.
94 // Laurent Minguet for some suggestions.
95 // Christian Deligant for some suggestions and fixes.
96 // Anyone that has reported a bug or sent a suggestion.
97 //============================================================+
98 
144 require_once(dirname(__FILE__).'/config/tcpdf_config.php');
145 
149 define('PDF_PRODUCER', 'TCPDF 5.9.009 (http://www.tcpdf.org)');
150 
161 class TCPDF {
162 
163  // Protected properties
164 
169  protected $page;
170 
175  protected $n;
176 
181  protected $offsets;
182 
187  protected $buffer;
188 
193  protected $pages = array();
194 
199  protected $state;
200 
205  protected $compress;
206 
211  protected $CurOrientation;
212 
217  protected $pagedim = array();
218 
223  protected $k;
224 
229  protected $fwPt;
230 
235  protected $fhPt;
236 
241  protected $wPt;
242 
247  protected $hPt;
248 
253  protected $w;
254 
259  protected $h;
260 
265  protected $lMargin;
266 
271  protected $tMargin;
272 
277  protected $rMargin;
278 
283  protected $bMargin;
284 
290  protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
291 
297  protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
298 
303  protected $x;
304 
309  protected $y;
310 
315  protected $lasth;
316 
321  protected $LineWidth;
322 
327  protected $CoreFonts;
328 
333  protected $fonts = array();
334 
339  protected $FontFiles = array();
340 
345  protected $diffs = array();
346 
351  protected $images = array();
352 
357  protected $PageAnnots = array();
358 
363  protected $links = array();
364 
369  protected $FontFamily;
370 
375  protected $FontStyle;
376 
382  protected $FontAscent;
383 
389  protected $FontDescent;
390 
395  protected $underline;
396 
401  protected $overline;
402 
407  protected $CurrentFont;
408 
413  protected $FontSizePt;
414 
419  protected $FontSize;
420 
425  protected $DrawColor;
426 
431  protected $FillColor;
432 
437  protected $TextColor;
438 
443  protected $ColorFlag;
444 
449  protected $AutoPageBreak;
450 
455  protected $PageBreakTrigger;
456 
461  protected $InFooter = false;
462 
467  protected $ZoomMode;
468 
473  protected $LayoutMode;
474 
479  protected $title = '';
480 
485  protected $subject = '';
486 
491  protected $author = '';
492 
497  protected $keywords = '';
498 
503  protected $creator = '';
504 
509  protected $AliasNbPages = '{nb}';
510 
515  protected $AliasNumPage = '{pnb}';
516 
523  protected $img_rb_x;
524 
531  protected $img_rb_y;
532 
539  protected $imgscale = 1;
540 
547  protected $isunicode = false;
548 
555  protected $unicode;
556 
562  protected $PDFVersion = '1.7';
563 
568  protected $header_margin;
569 
574  protected $footer_margin;
575 
581  protected $original_lMargin;
582 
588  protected $original_rMargin;
589 
594  protected $header_font;
595 
600  protected $footer_font;
601 
606  protected $l;
607 
612  protected $barcode = false;
613 
618  protected $print_header = true;
619 
624  protected $print_footer = true;
625 
630  protected $header_logo = '';
631 
636  protected $header_logo_width = 30;
637 
642  protected $header_title = '';
643 
648  protected $header_string = '';
649 
654  protected $default_table_columns = 4;
655 
656  // variables for html parser
657 
662  protected $HREF = array();
663 
668  protected $fontlist = array();
669 
674  protected $fgcolor;
675 
680  protected $listordered = array();
681 
686  protected $listcount = array();
687 
692  protected $listnum = 0;
693 
698  protected $listindent = 0;
699 
704  protected $listindentlevel = 0;
705 
710  protected $bgcolor;
711 
716  protected $tempfontsize = 10;
717 
722  protected $lispacer = '';
723 
729  protected $encoding = 'UTF-8';
730 
737 
743  protected $rtl = false;
744 
750  protected $tmprtl = false;
751 
752  // --- Variables used for document encryption:
753 
759  protected $encrypted;
760 
766  protected $encryptdata = array();
767 
773  protected $last_enc_key;
774 
780  protected $last_enc_key_c;
781 
786  protected $enc_padding = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A";
787 
793  protected $file_id;
794 
795  // --- bookmark ---
796 
802  protected $outlines = array();
803 
809  protected $OutlineRoot;
810 
811  // --- javascript and form ---
812 
818  protected $javascript = '';
819 
825  protected $n_js;
826 
832  protected $linethrough;
833 
839  protected $ur = array();
840 
846  protected $dpi = 72;
847 
853  protected $newpagegroup = array();
854 
860  protected $pagegroups;
861 
867  protected $currpagegroup;
868 
874  protected $visibility = 'all';
875 
881  protected $n_ocg_print;
882 
888  protected $n_ocg_view;
889 
895  protected $extgstates;
896 
902  protected $jpeg_quality;
903 
910 
917 
923  protected $PageMode;
924 
930  protected $gradients = array();
931 
938  protected $intmrk = array();
939 
946  protected $bordermrk = array();
947 
954  protected $emptypagemrk = array();
955 
962  protected $cntmrk = array();
963 
969  protected $footerpos = array();
970 
976  protected $footerlen = array();
977 
983  protected $newline = true;
984 
990  protected $endlinex = 0;
991 
997  protected $linestyleWidth = '';
998 
1004  protected $linestyleCap = '0 J';
1005 
1011  protected $linestyleJoin = '0 j';
1012 
1018  protected $linestyleDash = '[] 0 d';
1019 
1025  protected $openMarkedContent = false;
1026 
1032  protected $htmlvspace = 0;
1033 
1039  protected $spot_colors = array();
1040 
1046  protected $lisymbol = '';
1047 
1053  protected $epsmarker = 'x#!#EPS#!#x';
1054 
1060  protected $transfmatrix = array();
1061 
1067  protected $transfmatrix_key = 0;
1068 
1074  protected $booklet = false;
1075 
1081  protected $feps = 0.005;
1082 
1088  protected $tagvspaces = array();
1089 
1096  protected $customlistindent = -1;
1097 
1103  protected $opencell = true;
1104 
1110  protected $embeddedfiles = array();
1111 
1117  protected $premode = false;
1118 
1125  protected $transfmrk = array();
1126 
1132  protected $htmlLinkColorArray = array(0, 0, 255);
1133 
1139  protected $htmlLinkFontStyle = 'U';
1140 
1146  protected $numpages = 0;
1147 
1153  protected $pagelen = array();
1154 
1160  protected $numimages = 0;
1161 
1167  protected $imagekeys = array();
1168 
1174  protected $bufferlen = 0;
1175 
1181  protected $diskcache = false;
1182 
1188  protected $numfonts = 0;
1189 
1195  protected $fontkeys = array();
1196 
1202  protected $font_obj_ids = array();
1203 
1209  protected $pageopen = array();
1210 
1216  protected $default_monospaced_font = 'courier';
1217 
1223  protected $objcopy;
1224 
1230  protected $cache_file_length = array();
1231 
1237  protected $thead = '';
1238 
1244  protected $theadMargins = array();
1245 
1251  protected $cache_UTF8StringToArray = array();
1252 
1259 
1266 
1272  protected $sign = false;
1273 
1279  protected $signature_data = array();
1280 
1286  protected $signature_max_length = 11742;
1287 
1293  protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1294 
1300  protected $re_spaces = '/[^\S\xa0]/';
1301 
1307  protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1308 
1314  protected $sig_obj_id = 0;
1315 
1321  protected $byterange_string = '/ByteRange[0 ********** ********** **********]';
1322 
1328  protected $sig_annot_ref = '***SIGANNREF*** 0 R';
1329 
1335  protected $page_obj_id = array();
1336 
1342  protected $form_obj_id = array();
1343 
1349  protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1350 
1356  protected $js_objects = array();
1357 
1363  protected $form_action = '';
1364 
1370  protected $form_enctype = 'application/x-www-form-urlencoded';
1371 
1377  protected $form_mode = 'post';
1378 
1384  protected $annotation_fonts = array();
1385 
1391  protected $radiobutton_groups = array();
1392 
1398  protected $radio_groups = array();
1399 
1405  protected $textindent = 0;
1406 
1413 
1419  protected $start_transaction_y = 0;
1420 
1426  protected $inthead = false;
1427 
1433  protected $columns = array();
1434 
1440  protected $num_columns = 1;
1441 
1447  protected $current_column = 0;
1448 
1454  protected $column_start_page = 0;
1455 
1461  protected $maxselcol = array('page' => 0, 'column' => 0);
1462 
1468  protected $colxshift = array('x' => 0, 's' => 0, 'p' => 0);
1469 
1475  protected $textrendermode = 0;
1476 
1482  protected $textstrokewidth = 0;
1483 
1489  protected $strokecolor;
1490 
1496  protected $pdfunit = 'mm';
1497 
1502  protected $tocpage = false;
1503 
1509  protected $rasterize_vector_images = false;
1510 
1516  protected $font_subsetting = true;
1517 
1523  protected $default_graphic_vars = array();
1524 
1530  protected $xobjects = array();
1531 
1537  protected $inxobj = false;
1538 
1544  protected $xobjid = '';
1545 
1551  protected $font_stretching = 100;
1552 
1558  protected $font_spacing = 0;
1559 
1566  protected $page_regions = array();
1567 
1573  protected $webcolor = array();
1574 
1580  protected $svgdir = '';
1581 
1587  protected $svgunit = 'px';
1588 
1594  protected $svggradients = array();
1595 
1601  protected $svggradientid = 0;
1602 
1608  protected $svgdefsmode = false;
1609 
1615  protected $svgdefs = array();
1616 
1622  protected $svgclipmode = false;
1623 
1629  protected $svgclippaths = array();
1630 
1636  protected $svgcliptm = array();
1637 
1643  protected $svgclipid = 0;
1644 
1650  protected $svgtext = '';
1651 
1657  protected $svgtextmode = array();
1658 
1664  protected $svginheritprop = array('clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'fill', 'fill-opacity', 'fill-rule', 'font', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'image-rendering', 'kerning', 'letter-spacing', 'marker', 'marker-end', 'marker-mid', 'marker-start', 'pointer-events', 'shape-rendering', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-rendering', 'visibility', 'word-spacing', 'writing-mode');
1665 
1671  protected $svgstyles = array(array(
1672  'alignment-baseline' => 'auto',
1673  'baseline-shift' => 'baseline',
1674  'clip' => 'auto',
1675  'clip-path' => 'none',
1676  'clip-rule' => 'nonzero',
1677  'color' => 'black',
1678  'color-interpolation' => 'sRGB',
1679  'color-interpolation-filters' => 'linearRGB',
1680  'color-profile' => 'auto',
1681  'color-rendering' => 'auto',
1682  'cursor' => 'auto',
1683  'direction' => 'ltr',
1684  'display' => 'inline',
1685  'dominant-baseline' => 'auto',
1686  'enable-background' => 'accumulate',
1687  'fill' => 'black',
1688  'fill-opacity' => 1,
1689  'fill-rule' => 'nonzero',
1690  'filter' => 'none',
1691  'flood-color' => 'black',
1692  'flood-opacity' => 1,
1693  'font' => '',
1694  'font-family' => 'helvetica',
1695  'font-size' => 'medium',
1696  'font-size-adjust' => 'none',
1697  'font-stretch' => 'normal',
1698  'font-style' => 'normal',
1699  'font-variant' => 'normal',
1700  'font-weight' => 'normal',
1701  'glyph-orientation-horizontal' => '0deg',
1702  'glyph-orientation-vertical' => 'auto',
1703  'image-rendering' => 'auto',
1704  'kerning' => 'auto',
1705  'letter-spacing' => 'normal',
1706  'lighting-color' => 'white',
1707  'marker' => '',
1708  'marker-end' => 'none',
1709  'marker-mid' => 'none',
1710  'marker-start' => 'none',
1711  'mask' => 'none',
1712  'opacity' => 1,
1713  'overflow' => 'auto',
1714  'pointer-events' => 'visiblePainted',
1715  'shape-rendering' => 'auto',
1716  'stop-color' => 'black',
1717  'stop-opacity' => 1,
1718  'stroke' => 'none',
1719  'stroke-dasharray' => 'none',
1720  'stroke-dashoffset' => 0,
1721  'stroke-linecap' => 'butt',
1722  'stroke-linejoin' => 'miter',
1723  'stroke-miterlimit' => 4,
1724  'stroke-opacity' => 1,
1725  'stroke-width' => 1,
1726  'text-anchor' => 'start',
1727  'text-decoration' => 'none',
1728  'text-rendering' => 'auto',
1729  'unicode-bidi' => 'normal',
1730  'visibility' => 'visible',
1731  'word-spacing' => 'normal',
1732  'writing-mode' => 'lr-tb',
1733  'text-color' => 'black',
1734  'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1735  ));
1736 
1737  //------------------------------------------------------------
1738  // METHODS
1739  //------------------------------------------------------------
1740 
1753  public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false) {
1754  /* Set internal character encoding to ASCII */
1755  if (function_exists('mb_internal_encoding') AND mb_internal_encoding()) {
1756  $this->internal_encoding = mb_internal_encoding();
1757  mb_internal_encoding('ASCII');
1758  }
1759  require(dirname(__FILE__).'/htmlcolors.php');
1760  $this->webcolor = $webcolor;
1761  require_once(dirname(__FILE__).'/unicode_data.php');
1762  $this->unicode = new TCPDF_UNICODE_DATA();
1763  $this->font_obj_ids = array();
1764  $this->page_obj_id = array();
1765  $this->form_obj_id = array();
1766  // set disk caching
1767  $this->diskcache = $diskcache ? true : false;
1768  // set language direction
1769  $this->rtl = false;
1770  $this->tmprtl = false;
1771  // some checks
1772  $this->_dochecks();
1773  // initialization of properties
1774  $this->isunicode = $unicode;
1775  $this->page = 0;
1776  $this->transfmrk[0] = array();
1777  $this->pagedim = array();
1778  $this->n = 2;
1779  $this->buffer = '';
1780  $this->pages = array();
1781  $this->state = 0;
1782  $this->fonts = array();
1783  $this->FontFiles = array();
1784  $this->diffs = array();
1785  $this->images = array();
1786  $this->links = array();
1787  $this->gradients = array();
1788  $this->InFooter = false;
1789  $this->lasth = 0;
1790  $this->FontFamily = 'helvetica';
1791  $this->FontStyle = '';
1792  $this->FontSizePt = 12;
1793  $this->underline = false;
1794  $this->overline = false;
1795  $this->linethrough = false;
1796  $this->DrawColor = '0 G';
1797  $this->FillColor = '0 g';
1798  $this->TextColor = '0 g';
1799  $this->ColorFlag = false;
1800  // encryption values
1801  $this->encrypted = false;
1802  $this->last_enc_key = '';
1803  // standard Unicode fonts
1804  $this->CoreFonts = array(
1805  'courier'=>'Courier',
1806  'courierB'=>'Courier-Bold',
1807  'courierI'=>'Courier-Oblique',
1808  'courierBI'=>'Courier-BoldOblique',
1809  'helvetica'=>'Helvetica',
1810  'helveticaB'=>'Helvetica-Bold',
1811  'helveticaI'=>'Helvetica-Oblique',
1812  'helveticaBI'=>'Helvetica-BoldOblique',
1813  'times'=>'Times-Roman',
1814  'timesB'=>'Times-Bold',
1815  'timesI'=>'Times-Italic',
1816  'timesBI'=>'Times-BoldItalic',
1817  'symbol'=>'Symbol',
1818  'zapfdingbats'=>'ZapfDingbats'
1819  );
1820  // set scale factor
1821  $this->setPageUnit($unit);
1822  // set page format and orientation
1823  $this->setPageFormat($format, $orientation);
1824  // page margins (1 cm)
1825  $margin = 28.35 / $this->k;
1826  $this->SetMargins($margin, $margin);
1827  // internal cell padding
1828  $cpadding = $margin / 10;
1829  $this->setCellPaddings($cpadding, 0, $cpadding, 0);
1830  // cell margins
1831  $this->setCellMargins(0, 0, 0, 0);
1832  // line width (0.2 mm)
1833  $this->LineWidth = 0.57 / $this->k;
1834  $this->linestyleWidth = sprintf('%.2F w', ($this->LineWidth * $this->k));
1835  $this->linestyleCap = '0 J';
1836  $this->linestyleJoin = '0 j';
1837  $this->linestyleDash = '[] 0 d';
1838  // automatic page break
1839  $this->SetAutoPageBreak(true, (2 * $margin));
1840  // full width display mode
1841  $this->SetDisplayMode('fullwidth');
1842  // compression
1843  $this->SetCompression(true);
1844  // set default PDF version number
1845  $this->PDFVersion = '1.7';
1846  $this->encoding = $encoding;
1847  $this->HREF = array();
1848  $this->getFontsList();
1849  $this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
1850  $this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
1851  $this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
1852  $this->extgstates = array();
1853  // user's rights
1854  $this->sign = false;
1855  $this->ur['enabled'] = false;
1856  $this->ur['document'] = '/FullSave';
1857  $this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
1858  $this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
1859  $this->ur['signature'] = '/Modify';
1860  $this->ur['ef'] = '/Create/Delete/Modify/Import';
1861  $this->ur['formex'] = '';
1862  $this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1863  // set default JPEG quality
1864  $this->jpeg_quality = 75;
1865  // initialize some settings
1866  $this->utf8Bidi(array(''), '');
1867  // set default font
1868  $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
1869  // check if PCRE Unicode support is enabled
1870  if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
1871  // PCRE unicode support is turned ON
1872  // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
1873  // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
1874  // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
1875  //$this->setSpacesRE('/[^\S\P{Z}\P{Lo}\xa0]/u');
1876  $this->setSpacesRE('/[^\S\P{Z}\xa0]/u');
1877  } else {
1878  // PCRE unicode support is turned OFF
1879  $this->setSpacesRE('/[^\S\xa0]/');
1880  }
1881  $this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1882  // set file ID for trailer
1883  $this->file_id = md5($this->getRandomSeed('TCPDF'.$orientation.$unit.$format.$encoding));
1884  // get default graphic vars
1885  $this->default_graphic_vars = $this->getGraphicVars();
1886  }
1887 
1893  public function __destruct() {
1894  // restore internal encoding
1895  if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) {
1896  mb_internal_encoding($this->internal_encoding);
1897  }
1898  // unset all class variables
1899  $this->_destroy(true);
1900  }
1901 
1908  public function setPageUnit($unit) {
1909  $unit = strtolower($unit);
1910  //Set scale factor
1911  switch ($unit) {
1912  // points
1913  case 'px':
1914  case 'pt': {
1915  $this->k = 1;
1916  break;
1917  }
1918  // millimeters
1919  case 'mm': {
1920  $this->k = $this->dpi / 25.4;
1921  break;
1922  }
1923  // centimeters
1924  case 'cm': {
1925  $this->k = $this->dpi / 2.54;
1926  break;
1927  }
1928  // inches
1929  case 'in': {
1930  $this->k = $this->dpi;
1931  break;
1932  }
1933  // unsupported unit
1934  default : {
1935  $this->Error('Incorrect unit: '.$unit);
1936  break;
1937  }
1938  }
1939  $this->pdfunit = $unit;
1940  if (isset($this->CurOrientation)) {
1941  $this->setPageOrientation($this->CurOrientation);
1942  }
1943  }
1944 
2259  public function getPageSizeFromFormat($format) {
2260  // Paper cordinates are calculated in this way: (inches * 72) where (1 inch = 25.4 mm)
2261  switch (strtoupper($format)) {
2262  // ISO 216 A Series + 2 SIS 014711 extensions
2263  case 'A0' : {$pf = array( 2383.937, 3370.394); break;}
2264  case 'A1' : {$pf = array( 1683.780, 2383.937); break;}
2265  case 'A2' : {$pf = array( 1190.551, 1683.780); break;}
2266  case 'A3' : {$pf = array( 841.890, 1190.551); break;}
2267  case 'A4' : {$pf = array( 595.276, 841.890); break;}
2268  case 'A5' : {$pf = array( 419.528, 595.276); break;}
2269  case 'A6' : {$pf = array( 297.638, 419.528); break;}
2270  case 'A7' : {$pf = array( 209.764, 297.638); break;}
2271  case 'A8' : {$pf = array( 147.402, 209.764); break;}
2272  case 'A9' : {$pf = array( 104.882, 147.402); break;}
2273  case 'A10': {$pf = array( 73.701, 104.882); break;}
2274  case 'A11': {$pf = array( 51.024, 73.701); break;}
2275  case 'A12': {$pf = array( 36.850, 51.024); break;}
2276  // ISO 216 B Series + 2 SIS 014711 extensions
2277  case 'B0' : {$pf = array( 2834.646, 4008.189); break;}
2278  case 'B1' : {$pf = array( 2004.094, 2834.646); break;}
2279  case 'B2' : {$pf = array( 1417.323, 2004.094); break;}
2280  case 'B3' : {$pf = array( 1000.630, 1417.323); break;}
2281  case 'B4' : {$pf = array( 708.661, 1000.630); break;}
2282  case 'B5' : {$pf = array( 498.898, 708.661); break;}
2283  case 'B6' : {$pf = array( 354.331, 498.898); break;}
2284  case 'B7' : {$pf = array( 249.449, 354.331); break;}
2285  case 'B8' : {$pf = array( 175.748, 249.449); break;}
2286  case 'B9' : {$pf = array( 124.724, 175.748); break;}
2287  case 'B10': {$pf = array( 87.874, 124.724); break;}
2288  case 'B11': {$pf = array( 62.362, 87.874); break;}
2289  case 'B12': {$pf = array( 42.520, 62.362); break;}
2290  // ISO 216 C Series + 2 SIS 014711 extensions + 2 EXTENSION
2291  case 'C0' : {$pf = array( 2599.370, 3676.535); break;}
2292  case 'C1' : {$pf = array( 1836.850, 2599.370); break;}
2293  case 'C2' : {$pf = array( 1298.268, 1836.850); break;}
2294  case 'C3' : {$pf = array( 918.425, 1298.268); break;}
2295  case 'C4' : {$pf = array( 649.134, 918.425); break;}
2296  case 'C5' : {$pf = array( 459.213, 649.134); break;}
2297  case 'C6' : {$pf = array( 323.150, 459.213); break;}
2298  case 'C7' : {$pf = array( 229.606, 323.150); break;}
2299  case 'C8' : {$pf = array( 161.575, 229.606); break;}
2300  case 'C9' : {$pf = array( 113.386, 161.575); break;}
2301  case 'C10': {$pf = array( 79.370, 113.386); break;}
2302  case 'C11': {$pf = array( 56.693, 79.370); break;}
2303  case 'C12': {$pf = array( 39.685, 56.693); break;}
2304  case 'C76': {$pf = array( 229.606, 459.213); break;}
2305  case 'DL' : {$pf = array( 311.811, 623.622); break;}
2306  // SIS 014711 E Series
2307  case 'E0' : {$pf = array( 2491.654, 3517.795); break;}
2308  case 'E1' : {$pf = array( 1757.480, 2491.654); break;}
2309  case 'E2' : {$pf = array( 1247.244, 1757.480); break;}
2310  case 'E3' : {$pf = array( 878.740, 1247.244); break;}
2311  case 'E4' : {$pf = array( 623.622, 878.740); break;}
2312  case 'E5' : {$pf = array( 439.370, 623.622); break;}
2313  case 'E6' : {$pf = array( 311.811, 439.370); break;}
2314  case 'E7' : {$pf = array( 221.102, 311.811); break;}
2315  case 'E8' : {$pf = array( 155.906, 221.102); break;}
2316  case 'E9' : {$pf = array( 110.551, 155.906); break;}
2317  case 'E10': {$pf = array( 76.535, 110.551); break;}
2318  case 'E11': {$pf = array( 53.858, 76.535); break;}
2319  case 'E12': {$pf = array( 36.850, 53.858); break;}
2320  // SIS 014711 G Series
2321  case 'G0' : {$pf = array( 2715.591, 3838.110); break;}
2322  case 'G1' : {$pf = array( 1919.055, 2715.591); break;}
2323  case 'G2' : {$pf = array( 1357.795, 1919.055); break;}
2324  case 'G3' : {$pf = array( 958.110, 1357.795); break;}
2325  case 'G4' : {$pf = array( 677.480, 958.110); break;}
2326  case 'G5' : {$pf = array( 479.055, 677.480); break;}
2327  case 'G6' : {$pf = array( 337.323, 479.055); break;}
2328  case 'G7' : {$pf = array( 238.110, 337.323); break;}
2329  case 'G8' : {$pf = array( 167.244, 238.110); break;}
2330  case 'G9' : {$pf = array( 119.055, 167.244); break;}
2331  case 'G10': {$pf = array( 82.205, 119.055); break;}
2332  case 'G11': {$pf = array( 59.528, 82.205); break;}
2333  case 'G12': {$pf = array( 39.685, 59.528); break;}
2334  // ISO Press
2335  case 'RA0': {$pf = array( 2437.795, 3458.268); break;}
2336  case 'RA1': {$pf = array( 1729.134, 2437.795); break;}
2337  case 'RA2': {$pf = array( 1218.898, 1729.134); break;}
2338  case 'RA3': {$pf = array( 864.567, 1218.898); break;}
2339  case 'RA4': {$pf = array( 609.449, 864.567); break;}
2340  case 'SRA0': {$pf = array( 2551.181, 3628.346); break;}
2341  case 'SRA1': {$pf = array( 1814.173, 2551.181); break;}
2342  case 'SRA2': {$pf = array( 1275.591, 1814.173); break;}
2343  case 'SRA3': {$pf = array( 907.087, 1275.591); break;}
2344  case 'SRA4': {$pf = array( 637.795, 907.087); break;}
2345  // German DIN 476
2346  case '4A0': {$pf = array( 4767.874, 6740.787); break;}
2347  case '2A0': {$pf = array( 3370.394, 4767.874); break;}
2348  // Variations on the ISO Standard
2349  case 'A2_EXTRA' : {$pf = array( 1261.417, 1754.646); break;}
2350  case 'A3+' : {$pf = array( 932.598, 1369.134); break;}
2351  case 'A3_EXTRA' : {$pf = array( 912.756, 1261.417); break;}
2352  case 'A3_SUPER' : {$pf = array( 864.567, 1440.000); break;}
2353  case 'SUPER_A3' : {$pf = array( 864.567, 1380.472); break;}
2354  case 'A4_EXTRA' : {$pf = array( 666.142, 912.756); break;}
2355  case 'A4_SUPER' : {$pf = array( 649.134, 912.756); break;}
2356  case 'SUPER_A4' : {$pf = array( 643.465, 1009.134); break;}
2357  case 'A4_LONG' : {$pf = array( 595.276, 986.457); break;}
2358  case 'F4' : {$pf = array( 595.276, 935.433); break;}
2359  case 'SO_B5_EXTRA': {$pf = array( 572.598, 782.362); break;}
2360  case 'A5_EXTRA' : {$pf = array( 490.394, 666.142); break;}
2361  // ANSI Series
2362  case 'ANSI_E': {$pf = array( 2448.000, 3168.000); break;}
2363  case 'ANSI_D': {$pf = array( 1584.000, 2448.000); break;}
2364  case 'ANSI_C': {$pf = array( 1224.000, 1584.000); break;}
2365  case 'ANSI_B': {$pf = array( 792.000, 1224.000); break;}
2366  case 'ANSI_A': {$pf = array( 612.000, 792.000); break;}
2367  // Traditional 'Loose' North American Paper Sizes
2368  case 'USLEDGER':
2369  case 'LEDGER' : {$pf = array( 1224.000, 792.000); break;}
2370  case 'ORGANIZERK':
2371  case 'BIBLE':
2372  case 'USTABLOID':
2373  case 'TABLOID': {$pf = array( 792.000, 1224.000); break;}
2374  case 'ORGANIZERM':
2375  case 'USLETTER':
2376  case 'LETTER' : {$pf = array( 612.000, 792.000); break;}
2377  case 'USLEGAL':
2378  case 'LEGAL' : {$pf = array( 612.000, 1008.000); break;}
2379  case 'GOVERNMENTLETTER':
2380  case 'GLETTER': {$pf = array( 576.000, 756.000); break;}
2381  case 'JUNIORLEGAL':
2382  case 'JLEGAL' : {$pf = array( 576.000, 360.000); break;}
2383  // Other North American Paper Sizes
2384  case 'QUADDEMY': {$pf = array( 2520.000, 3240.000); break;}
2385  case 'SUPER_B': {$pf = array( 936.000, 1368.000); break;}
2386  case 'QUARTO': {$pf = array( 648.000, 792.000); break;}
2387  case 'GOVERNMENTLEGAL':
2388  case 'FOLIO': {$pf = array( 612.000, 936.000); break;}
2389  case 'MONARCH':
2390  case 'EXECUTIVE': {$pf = array( 522.000, 756.000); break;}
2391  case 'ORGANIZERL':
2392  case 'STATEMENT':
2393  case 'MEMO': {$pf = array( 396.000, 612.000); break;}
2394  case 'FOOLSCAP': {$pf = array( 595.440, 936.000); break;}
2395  case 'COMPACT': {$pf = array( 306.000, 486.000); break;}
2396  case 'ORGANIZERJ': {$pf = array( 198.000, 360.000); break;}
2397  // Canadian standard CAN 2-9.60M
2398  case 'P1': {$pf = array( 1587.402, 2437.795); break;}
2399  case 'P2': {$pf = array( 1218.898, 1587.402); break;}
2400  case 'P3': {$pf = array( 793.701, 1218.898); break;}
2401  case 'P4': {$pf = array( 609.449, 793.701); break;}
2402  case 'P5': {$pf = array( 396.850, 609.449); break;}
2403  case 'P6': {$pf = array( 303.307, 396.850); break;}
2404  // North American Architectural Sizes
2405  case 'ARCH_E' : {$pf = array( 2592.000, 3456.000); break;}
2406  case 'ARCH_E1': {$pf = array( 2160.000, 3024.000); break;}
2407  case 'ARCH_D' : {$pf = array( 1728.000, 2592.000); break;}
2408  case 'BROADSHEET':
2409  case 'ARCH_C' : {$pf = array( 1296.000, 1728.000); break;}
2410  case 'ARCH_B' : {$pf = array( 864.000, 1296.000); break;}
2411  case 'ARCH_A' : {$pf = array( 648.000, 864.000); break;}
2412  // --- North American Envelope Sizes ---
2413  // - Announcement Envelopes
2414  case 'ANNENV_A2' : {$pf = array( 314.640, 414.000); break;}
2415  case 'ANNENV_A6' : {$pf = array( 342.000, 468.000); break;}
2416  case 'ANNENV_A7' : {$pf = array( 378.000, 522.000); break;}
2417  case 'ANNENV_A8' : {$pf = array( 396.000, 584.640); break;}
2418  case 'ANNENV_A10' : {$pf = array( 450.000, 692.640); break;}
2419  case 'ANNENV_SLIM': {$pf = array( 278.640, 638.640); break;}
2420  // - Commercial Envelopes
2421  case 'COMMENV_N6_1/4': {$pf = array( 252.000, 432.000); break;}
2422  case 'COMMENV_N6_3/4': {$pf = array( 260.640, 468.000); break;}
2423  case 'COMMENV_N8' : {$pf = array( 278.640, 540.000); break;}
2424  case 'COMMENV_N9' : {$pf = array( 278.640, 638.640); break;}
2425  case 'COMMENV_N10' : {$pf = array( 296.640, 684.000); break;}
2426  case 'COMMENV_N11' : {$pf = array( 324.000, 746.640); break;}
2427  case 'COMMENV_N12' : {$pf = array( 342.000, 792.000); break;}
2428  case 'COMMENV_N14' : {$pf = array( 360.000, 828.000); break;}
2429  // - Catalogue Envelopes
2430  case 'CATENV_N1' : {$pf = array( 432.000, 648.000); break;}
2431  case 'CATENV_N1_3/4' : {$pf = array( 468.000, 684.000); break;}
2432  case 'CATENV_N2' : {$pf = array( 468.000, 720.000); break;}
2433  case 'CATENV_N3' : {$pf = array( 504.000, 720.000); break;}
2434  case 'CATENV_N6' : {$pf = array( 540.000, 756.000); break;}
2435  case 'CATENV_N7' : {$pf = array( 576.000, 792.000); break;}
2436  case 'CATENV_N8' : {$pf = array( 594.000, 810.000); break;}
2437  case 'CATENV_N9_1/2' : {$pf = array( 612.000, 756.000); break;}
2438  case 'CATENV_N9_3/4' : {$pf = array( 630.000, 810.000); break;}
2439  case 'CATENV_N10_1/2': {$pf = array( 648.000, 864.000); break;}
2440  case 'CATENV_N12_1/2': {$pf = array( 684.000, 900.000); break;}
2441  case 'CATENV_N13_1/2': {$pf = array( 720.000, 936.000); break;}
2442  case 'CATENV_N14_1/4': {$pf = array( 810.000, 882.000); break;}
2443  case 'CATENV_N14_1/2': {$pf = array( 828.000, 1044.000); break;}
2444  // Japanese (JIS P 0138-61) Standard B-Series
2445  case 'JIS_B0' : {$pf = array( 2919.685, 4127.244); break;}
2446  case 'JIS_B1' : {$pf = array( 2063.622, 2919.685); break;}
2447  case 'JIS_B2' : {$pf = array( 1459.843, 2063.622); break;}
2448  case 'JIS_B3' : {$pf = array( 1031.811, 1459.843); break;}
2449  case 'JIS_B4' : {$pf = array( 728.504, 1031.811); break;}
2450  case 'JIS_B5' : {$pf = array( 515.906, 728.504); break;}
2451  case 'JIS_B6' : {$pf = array( 362.835, 515.906); break;}
2452  case 'JIS_B7' : {$pf = array( 257.953, 362.835); break;}
2453  case 'JIS_B8' : {$pf = array( 181.417, 257.953); break;}
2454  case 'JIS_B9' : {$pf = array( 127.559, 181.417); break;}
2455  case 'JIS_B10': {$pf = array( 90.709, 127.559); break;}
2456  case 'JIS_B11': {$pf = array( 62.362, 90.709); break;}
2457  case 'JIS_B12': {$pf = array( 45.354, 62.362); break;}
2458  // PA Series
2459  case 'PA0' : {$pf = array( 2381.102, 3174.803,); break;}
2460  case 'PA1' : {$pf = array( 1587.402, 2381.102); break;}
2461  case 'PA2' : {$pf = array( 1190.551, 1587.402); break;}
2462  case 'PA3' : {$pf = array( 793.701, 1190.551); break;}
2463  case 'PA4' : {$pf = array( 595.276, 793.701); break;}
2464  case 'PA5' : {$pf = array( 396.850, 595.276); break;}
2465  case 'PA6' : {$pf = array( 297.638, 396.850); break;}
2466  case 'PA7' : {$pf = array( 198.425, 297.638); break;}
2467  case 'PA8' : {$pf = array( 147.402, 198.425); break;}
2468  case 'PA9' : {$pf = array( 99.213, 147.402); break;}
2469  case 'PA10': {$pf = array( 73.701, 99.213); break;}
2470  // Standard Photographic Print Sizes
2471  case 'PASSPORT_PHOTO': {$pf = array( 99.213, 127.559); break;}
2472  case 'E' : {$pf = array( 233.858, 340.157); break;}
2473  case 'L':
2474  case '3R' : {$pf = array( 252.283, 360.000); break;}
2475  case 'KG':
2476  case '4R' : {$pf = array( 289.134, 430.866); break;}
2477  case '4D' : {$pf = array( 340.157, 430.866); break;}
2478  case '2L':
2479  case '5R' : {$pf = array( 360.000, 504.567); break;}
2480  case '8P':
2481  case '6R' : {$pf = array( 430.866, 575.433); break;}
2482  case '6P':
2483  case '8R' : {$pf = array( 575.433, 720.000); break;}
2484  case '6PW':
2485  case 'S8R' : {$pf = array( 575.433, 864.567); break;}
2486  case '4P':
2487  case '10R' : {$pf = array( 720.000, 864.567); break;}
2488  case '4PW':
2489  case 'S10R': {$pf = array( 720.000, 1080.000); break;}
2490  case '11R' : {$pf = array( 790.866, 1009.134); break;}
2491  case 'S11R': {$pf = array( 790.866, 1224.567); break;}
2492  case '12R' : {$pf = array( 864.567, 1080.000); break;}
2493  case 'S12R': {$pf = array( 864.567, 1292.598); break;}
2494  // Common Newspaper Sizes
2495  case 'NEWSPAPER_BROADSHEET': {$pf = array( 2125.984, 1700.787); break;}
2496  case 'NEWSPAPER_BERLINER' : {$pf = array( 1332.283, 892.913); break;}
2497  case 'NEWSPAPER_TABLOID':
2498  case 'NEWSPAPER_COMPACT' : {$pf = array( 1218.898, 793.701); break;}
2499  // Business Cards
2500  case 'CREDIT_CARD':
2501  case 'BUSINESS_CARD':
2502  case 'BUSINESS_CARD_ISO7810': {$pf = array( 153.014, 242.646); break;}
2503  case 'BUSINESS_CARD_ISO216' : {$pf = array( 147.402, 209.764); break;}
2504  case 'BUSINESS_CARD_IT':
2505  case 'BUSINESS_CARD_UK':
2506  case 'BUSINESS_CARD_FR':
2507  case 'BUSINESS_CARD_DE':
2508  case 'BUSINESS_CARD_ES' : {$pf = array( 155.906, 240.945); break;}
2509  case 'BUSINESS_CARD_CA':
2510  case 'BUSINESS_CARD_US' : {$pf = array( 144.567, 252.283); break;}
2511  case 'BUSINESS_CARD_JP' : {$pf = array( 155.906, 257.953); break;}
2512  case 'BUSINESS_CARD_HK' : {$pf = array( 153.071, 255.118); break;}
2513  case 'BUSINESS_CARD_AU':
2514  case 'BUSINESS_CARD_DK':
2515  case 'BUSINESS_CARD_SE' : {$pf = array( 155.906, 255.118); break;}
2516  case 'BUSINESS_CARD_RU':
2517  case 'BUSINESS_CARD_CZ':
2518  case 'BUSINESS_CARD_FI':
2519  case 'BUSINESS_CARD_HU':
2520  case 'BUSINESS_CARD_IL' : {$pf = array( 141.732, 255.118); break;}
2521  // Billboards
2522  case '4SHEET' : {$pf = array( 2880.000, 4320.000); break;}
2523  case '6SHEET' : {$pf = array( 3401.575, 5102.362); break;}
2524  case '12SHEET': {$pf = array( 8640.000, 4320.000); break;}
2525  case '16SHEET': {$pf = array( 5760.000, 8640.000); break;}
2526  case '32SHEET': {$pf = array(11520.000, 8640.000); break;}
2527  case '48SHEET': {$pf = array(17280.000, 8640.000); break;}
2528  case '64SHEET': {$pf = array(23040.000, 8640.000); break;}
2529  case '96SHEET': {$pf = array(34560.000, 8640.000); break;}
2530  // Old European Sizes
2531  // - Old Imperial English Sizes
2532  case 'EN_EMPEROR' : {$pf = array( 3456.000, 5184.000); break;}
2533  case 'EN_ANTIQUARIAN' : {$pf = array( 2232.000, 3816.000); break;}
2534  case 'EN_GRAND_EAGLE' : {$pf = array( 2070.000, 3024.000); break;}
2535  case 'EN_DOUBLE_ELEPHANT' : {$pf = array( 1926.000, 2880.000); break;}
2536  case 'EN_ATLAS' : {$pf = array( 1872.000, 2448.000); break;}
2537  case 'EN_COLOMBIER' : {$pf = array( 1692.000, 2484.000); break;}
2538  case 'EN_ELEPHANT' : {$pf = array( 1656.000, 2016.000); break;}
2539  case 'EN_DOUBLE_DEMY' : {$pf = array( 1620.000, 2556.000); break;}
2540  case 'EN_IMPERIAL' : {$pf = array( 1584.000, 2160.000); break;}
2541  case 'EN_PRINCESS' : {$pf = array( 1548.000, 2016.000); break;}
2542  case 'EN_CARTRIDGE' : {$pf = array( 1512.000, 1872.000); break;}
2543  case 'EN_DOUBLE_LARGE_POST': {$pf = array( 1512.000, 2376.000); break;}
2544  case 'EN_ROYAL' : {$pf = array( 1440.000, 1800.000); break;}
2545  case 'EN_SHEET':
2546  case 'EN_HALF_POST' : {$pf = array( 1404.000, 1692.000); break;}
2547  case 'EN_SUPER_ROYAL' : {$pf = array( 1368.000, 1944.000); break;}
2548  case 'EN_DOUBLE_POST' : {$pf = array( 1368.000, 2196.000); break;}
2549  case 'EN_MEDIUM' : {$pf = array( 1260.000, 1656.000); break;}
2550  case 'EN_DEMY' : {$pf = array( 1260.000, 1620.000); break;}
2551  case 'EN_LARGE_POST' : {$pf = array( 1188.000, 1512.000); break;}
2552  case 'EN_COPY_DRAUGHT' : {$pf = array( 1152.000, 1440.000); break;}
2553  case 'EN_POST' : {$pf = array( 1116.000, 1386.000); break;}
2554  case 'EN_CROWN' : {$pf = array( 1080.000, 1440.000); break;}
2555  case 'EN_PINCHED_POST' : {$pf = array( 1062.000, 1332.000); break;}
2556  case 'EN_BRIEF' : {$pf = array( 972.000, 1152.000); break;}
2557  case 'EN_FOOLSCAP' : {$pf = array( 972.000, 1224.000); break;}
2558  case 'EN_SMALL_FOOLSCAP' : {$pf = array( 954.000, 1188.000); break;}
2559  case 'EN_POTT' : {$pf = array( 900.000, 1080.000); break;}
2560  // - Old Imperial Belgian Sizes
2561  case 'BE_GRAND_AIGLE' : {$pf = array( 1984.252, 2948.031); break;}
2562  case 'BE_COLOMBIER' : {$pf = array( 1757.480, 2409.449); break;}
2563  case 'BE_DOUBLE_CARRE': {$pf = array( 1757.480, 2607.874); break;}
2564  case 'BE_ELEPHANT' : {$pf = array( 1746.142, 2182.677); break;}
2565  case 'BE_PETIT_AIGLE' : {$pf = array( 1700.787, 2381.102); break;}
2566  case 'BE_GRAND_JESUS' : {$pf = array( 1559.055, 2069.291); break;}
2567  case 'BE_JESUS' : {$pf = array( 1530.709, 2069.291); break;}
2568  case 'BE_RAISIN' : {$pf = array( 1417.323, 1842.520); break;}
2569  case 'BE_GRAND_MEDIAN': {$pf = array( 1303.937, 1714.961); break;}
2570  case 'BE_DOUBLE_POSTE': {$pf = array( 1233.071, 1601.575); break;}
2571  case 'BE_COQUILLE' : {$pf = array( 1218.898, 1587.402); break;}
2572  case 'BE_PETIT_MEDIAN': {$pf = array( 1176.378, 1502.362); break;}
2573  case 'BE_RUCHE' : {$pf = array( 1020.472, 1303.937); break;}
2574  case 'BE_PROPATRIA' : {$pf = array( 977.953, 1218.898); break;}
2575  case 'BE_LYS' : {$pf = array( 898.583, 1125.354); break;}
2576  case 'BE_POT' : {$pf = array( 870.236, 1088.504); break;}
2577  case 'BE_ROSETTE' : {$pf = array( 765.354, 983.622); break;}
2578  // - Old Imperial French Sizes
2579  case 'FR_UNIVERS' : {$pf = array( 2834.646, 3685.039); break;}
2580  case 'FR_DOUBLE_COLOMBIER' : {$pf = array( 2551.181, 3571.654); break;}
2581  case 'FR_GRANDE_MONDE' : {$pf = array( 2551.181, 3571.654); break;}
2582  case 'FR_DOUBLE_SOLEIL' : {$pf = array( 2267.717, 3401.575); break;}
2583  case 'FR_DOUBLE_JESUS' : {$pf = array( 2154.331, 3174.803); break;}
2584  case 'FR_GRAND_AIGLE' : {$pf = array( 2125.984, 3004.724); break;}
2585  case 'FR_PETIT_AIGLE' : {$pf = array( 1984.252, 2664.567); break;}
2586  case 'FR_DOUBLE_RAISIN' : {$pf = array( 1842.520, 2834.646); break;}
2587  case 'FR_JOURNAL' : {$pf = array( 1842.520, 2664.567); break;}
2588  case 'FR_COLOMBIER_AFFICHE': {$pf = array( 1785.827, 2551.181); break;}
2589  case 'FR_DOUBLE_CAVALIER' : {$pf = array( 1757.480, 2607.874); break;}
2590  case 'FR_CLOCHE' : {$pf = array( 1700.787, 2267.717); break;}
2591  case 'FR_SOLEIL' : {$pf = array( 1700.787, 2267.717); break;}
2592  case 'FR_DOUBLE_CARRE' : {$pf = array( 1587.402, 2551.181); break;}
2593  case 'FR_DOUBLE_COQUILLE' : {$pf = array( 1587.402, 2494.488); break;}
2594  case 'FR_JESUS' : {$pf = array( 1587.402, 2154.331); break;}
2595  case 'FR_RAISIN' : {$pf = array( 1417.323, 1842.520); break;}
2596  case 'FR_CAVALIER' : {$pf = array( 1303.937, 1757.480); break;}
2597  case 'FR_DOUBLE_COURONNE' : {$pf = array( 1303.937, 2040.945); break;}
2598  case 'FR_CARRE' : {$pf = array( 1275.591, 1587.402); break;}
2599  case 'FR_COQUILLE' : {$pf = array( 1247.244, 1587.402); break;}
2600  case 'FR_DOUBLE_TELLIERE' : {$pf = array( 1247.244, 1927.559); break;}
2601  case 'FR_DOUBLE_CLOCHE' : {$pf = array( 1133.858, 1700.787); break;}
2602  case 'FR_DOUBLE_POT' : {$pf = array( 1133.858, 1757.480); break;}
2603  case 'FR_ECU' : {$pf = array( 1133.858, 1474.016); break;}
2604  case 'FR_COURONNE' : {$pf = array( 1020.472, 1303.937); break;}
2605  case 'FR_TELLIERE' : {$pf = array( 963.780, 1247.244); break;}
2606  case 'FR_POT' : {$pf = array( 878.740, 1133.858); break;}
2607  // DEFAULT ISO A4
2608  default: {$pf = array( 595.276, 841.890); break;}
2609  }
2610  return $pf;
2611  }
2612 
2668  protected function setPageFormat($format, $orientation='P') {
2669  if (!empty($format) AND isset($this->pagedim[$this->page])) {
2670  // remove inherited values
2671  unset($this->pagedim[$this->page]);
2672  }
2673  if (is_string($format)) {
2674  // get page measures from format name
2675  $pf = $this->getPageSizeFromFormat($format);
2676  $this->fwPt = $pf[0];
2677  $this->fhPt = $pf[1];
2678  } else {
2679  // the boundaries of the physical medium on which the page shall be displayed or printed
2680  if (isset($format['MediaBox'])) {
2681  $this->setPageBoxes($this->page, 'MediaBox', $format['MediaBox']['llx'], $format['MediaBox']['lly'], $format['MediaBox']['urx'], $format['MediaBox']['ury'], false);
2682  $this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2683  $this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2684  } else {
2685  if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2686  $pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2687  } else {
2688  if (!isset($format['format'])) {
2689  // default value
2690  $format['format'] = 'A4';
2691  }
2692  $pf = $this->getPageSizeFromFormat($format['format']);
2693  }
2694  $this->fwPt = $pf[0];
2695  $this->fhPt = $pf[1];
2696  $this->setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true);
2697  }
2698  // the visible region of default user space
2699  if (isset($format['CropBox'])) {
2700  $this->setPageBoxes($this->page, 'CropBox', $format['CropBox']['llx'], $format['CropBox']['lly'], $format['CropBox']['urx'], $format['CropBox']['ury'], false);
2701  }
2702  // the region to which the contents of the page shall be clipped when output in a production environment
2703  if (isset($format['BleedBox'])) {
2704  $this->setPageBoxes($this->page, 'BleedBox', $format['BleedBox']['llx'], $format['BleedBox']['lly'], $format['BleedBox']['urx'], $format['BleedBox']['ury'], false);
2705  }
2706  // the intended dimensions of the finished page after trimming
2707  if (isset($format['TrimBox'])) {
2708  $this->setPageBoxes($this->page, 'TrimBox', $format['TrimBox']['llx'], $format['TrimBox']['lly'], $format['TrimBox']['urx'], $format['TrimBox']['ury'], false);
2709  }
2710  // the page's meaningful content (including potential white space)
2711  if (isset($format['ArtBox'])) {
2712  $this->setPageBoxes($this->page, 'ArtBox', $format['ArtBox']['llx'], $format['ArtBox']['lly'], $format['ArtBox']['urx'], $format['ArtBox']['ury'], false);
2713  }
2714  // specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2715  if (isset($format['BoxColorInfo'])) {
2716  $this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2717  }
2718  if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2719  // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2720  $this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2721  }
2722  if (isset($format['PZ'])) {
2723  // The page's preferred zoom (magnification) factor
2724  $this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2725  }
2726  if (isset($format['trans'])) {
2727  // The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2728  if (isset($format['trans']['Dur'])) {
2729  // The page's display duration
2730  $this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2731  }
2732  $stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2733  if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2734  // The transition style that shall be used when moving to this page from another during a presentation
2735  $this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2736  $valid_effect = array('Split', 'Blinds');
2737  $valid_vals = array('H', 'V');
2738  if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2739  $this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2740  }
2741  $valid_effect = array('Split', 'Box', 'Fly');
2742  $valid_vals = array('I', 'O');
2743  if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2744  $this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2745  }
2746  $valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2747  if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2748  if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2749  OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2750  OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2751  $this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2752  }
2753  }
2754  if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2755  $this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2756  }
2757  if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2758  $this->pagedim[$this->page]['trans']['B'] = 'true';
2759  }
2760  } else {
2761  $this->pagedim[$this->page]['trans']['S'] = 'R';
2762  }
2763  if (isset($format['trans']['D'])) {
2764  // The duration of the transition effect, in seconds
2765  $this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2766  } else {
2767  $this->pagedim[$this->page]['trans']['D'] = 1;
2768  }
2769  }
2770  }
2771  $this->setPageOrientation($orientation);
2772  }
2773 
2786  public function setPageBoxes($page, $type, $llx, $lly, $urx, $ury, $points=false) {
2787  if (!isset($this->pagedim[$page])) {
2788  // initialize array
2789  $this->pagedim[$page] = array();
2790  }
2791  $pageboxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
2792  if (!in_array($type, $pageboxes)) {
2793  return;
2794  }
2795  if ($points) {
2796  $k = 1;
2797  } else {
2798  $k = $this->k;
2799  }
2800  $this->pagedim[$page][$type]['llx'] = ($llx * $k);
2801  $this->pagedim[$page][$type]['lly'] = ($lly * $k);
2802  $this->pagedim[$page][$type]['urx'] = ($urx * $k);
2803  $this->pagedim[$page][$type]['ury'] = ($ury * $k);
2804  }
2805 
2812  protected function swapPageBoxCoordinates($page) {
2813  $pageboxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
2814  foreach ($pageboxes as $type) {
2815  // swap X and Y coordinates
2816  if (isset($this->pagedim[$page][$type])) {
2817  $tmp = $this->pagedim[$page][$type]['llx'];
2818  $this->pagedim[$page][$type]['llx'] = $this->pagedim[$page][$type]['lly'];
2819  $this->pagedim[$page][$type]['lly'] = $tmp;
2820  $tmp = $this->pagedim[$page][$type]['urx'];
2821  $this->pagedim[$page][$type]['urx'] = $this->pagedim[$page][$type]['ury'];
2822  $this->pagedim[$page][$type]['ury'] = $tmp;
2823  }
2824  }
2825  }
2826 
2835  public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
2836  if (!isset($this->pagedim[$this->page]['MediaBox'])) {
2837  // the boundaries of the physical medium on which the page shall be displayed or printed
2838  $this->setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true);
2839  }
2840  if (!isset($this->pagedim[$this->page]['CropBox'])) {
2841  // the visible region of default user space
2842  $this->setPageBoxes($this->page, 'CropBox', $this->pagedim[$this->page]['MediaBox']['llx'], $this->pagedim[$this->page]['MediaBox']['lly'], $this->pagedim[$this->page]['MediaBox']['urx'], $this->pagedim[$this->page]['MediaBox']['ury'], true);
2843  }
2844  if (!isset($this->pagedim[$this->page]['BleedBox'])) {
2845  // the region to which the contents of the page shall be clipped when output in a production environment
2846  $this->setPageBoxes($this->page, 'BleedBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true);
2847  }
2848  if (!isset($this->pagedim[$this->page]['TrimBox'])) {
2849  // the intended dimensions of the finished page after trimming
2850  $this->setPageBoxes($this->page, 'TrimBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true);
2851  }
2852  if (!isset($this->pagedim[$this->page]['ArtBox'])) {
2853  // the page's meaningful content (including potential white space)
2854  $this->setPageBoxes($this->page, 'ArtBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true);
2855  }
2856  if (!isset($this->pagedim[$this->page]['Rotate'])) {
2857  // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2858  $this->pagedim[$this->page]['Rotate'] = 0;
2859  }
2860  if (!isset($this->pagedim[$this->page]['PZ'])) {
2861  // The page's preferred zoom (magnification) factor
2862  $this->pagedim[$this->page]['PZ'] = 1;
2863  }
2864  if ($this->fwPt > $this->fhPt) {
2865  // landscape
2866  $default_orientation = 'L';
2867  } else {
2868  // portrait
2869  $default_orientation = 'P';
2870  }
2871  $valid_orientations = array('P', 'L');
2872  if (empty($orientation)) {
2873  $orientation = $default_orientation;
2874  } else {
2875  $orientation = strtoupper($orientation{0});
2876  }
2877  if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2878  $this->CurOrientation = $orientation;
2879  $this->wPt = $this->fhPt;
2880  $this->hPt = $this->fwPt;
2881  } else {
2882  $this->CurOrientation = $default_orientation;
2883  $this->wPt = $this->fwPt;
2884  $this->hPt = $this->fhPt;
2885  }
2886  if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
2887  // swap X and Y coordinates (change page orientation)
2888  $this->swapPageBoxCoordinates($this->page);
2889  }
2890  $this->w = $this->wPt / $this->k;
2891  $this->h = $this->hPt / $this->k;
2892  if ($this->empty_string($autopagebreak)) {
2893  if (isset($this->AutoPageBreak)) {
2894  $autopagebreak = $this->AutoPageBreak;
2895  } else {
2896  $autopagebreak = true;
2897  }
2898  }
2899  if ($this->empty_string($bottommargin)) {
2900  if (isset($this->bMargin)) {
2901  $bottommargin = $this->bMargin;
2902  } else {
2903  // default value = 2 cm
2904  $bottommargin = 2 * 28.35 / $this->k;
2905  }
2906  }
2907  $this->SetAutoPageBreak($autopagebreak, $bottommargin);
2908  // store page dimensions
2909  $this->pagedim[$this->page]['w'] = $this->wPt;
2910  $this->pagedim[$this->page]['h'] = $this->hPt;
2911  $this->pagedim[$this->page]['wk'] = $this->w;
2912  $this->pagedim[$this->page]['hk'] = $this->h;
2913  $this->pagedim[$this->page]['tm'] = $this->tMargin;
2914  $this->pagedim[$this->page]['bm'] = $bottommargin;
2915  $this->pagedim[$this->page]['lm'] = $this->lMargin;
2916  $this->pagedim[$this->page]['rm'] = $this->rMargin;
2917  $this->pagedim[$this->page]['pb'] = $autopagebreak;
2918  $this->pagedim[$this->page]['or'] = $this->CurOrientation;
2919  $this->pagedim[$this->page]['olm'] = $this->original_lMargin;
2920  $this->pagedim[$this->page]['orm'] = $this->original_rMargin;
2921  }
2922 
2940  public function setSpacesRE($re='/[^\S\xa0]/') {
2941  $this->re_spaces = $re;
2942  $re_parts = explode('/', $re);
2943  // get pattern parts
2944  $this->re_space = array();
2945  if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2946  $this->re_space['p'] = $re_parts[1];
2947  } else {
2948  $this->re_space['p'] = '[\s]';
2949  }
2950  // set pattern modifiers
2951  if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2952  $this->re_space['m'] = $re_parts[2];
2953  } else {
2954  $this->re_space['m'] = '';
2955  }
2956  }
2957 
2965  public function setRTL($enable, $resetx=true) {
2966  $enable = $enable ? true : false;
2967  $resetx = ($resetx AND ($enable != $this->rtl));
2968  $this->rtl = $enable;
2969  $this->tmprtl = false;
2970  if ($resetx) {
2971  $this->Ln(0);
2972  }
2973  }
2974 
2981  public function getRTL() {
2982  return $this->rtl;
2983  }
2984 
2991  public function setTempRTL($mode) {
2992  $newmode = false;
2993  switch (strtoupper($mode)) {
2994  case 'LTR':
2995  case 'L': {
2996  if ($this->rtl) {
2997  $newmode = 'L';
2998  }
2999  break;
3000  }
3001  case 'RTL':
3002  case 'R': {
3003  if (!$this->rtl) {
3004  $newmode = 'R';
3005  }
3006  break;
3007  }
3008  case false:
3009  default: {
3010  $newmode = false;
3011  break;
3012  }
3013  }
3014  $this->tmprtl = $newmode;
3015  }
3016 
3023  public function isRTLTextDir() {
3024  return ($this->rtl OR ($this->tmprtl == 'R'));
3025  }
3026 
3034  public function setLastH($h) {
3035  $this->lasth = $h;
3036  }
3037 
3043  public function resetLastH() {
3044  $this->lasth = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
3045  }
3046 
3053  public function getLastH() {
3054  return $this->lasth;
3055  }
3056 
3064  public function setImageScale($scale) {
3065  $this->imgscale = $scale;
3066  }
3067 
3075  public function getImageScale() {
3076  return $this->imgscale;
3077  }
3078 
3088  public function getPageDimensions($pagenum='') {
3089  if (empty($pagenum)) {
3090  $pagenum = $this->page;
3091  }
3092  return $this->pagedim[$pagenum];
3093  }
3094 
3104  public function getPageWidth($pagenum='') {
3105  if (empty($pagenum)) {
3106  return $this->w;
3107  }
3108  return $this->pagedim[$pagenum]['w'];
3109  }
3110 
3120  public function getPageHeight($pagenum='') {
3121  if (empty($pagenum)) {
3122  return $this->h;
3123  }
3124  return $this->pagedim[$pagenum]['h'];
3125  }
3126 
3136  public function getBreakMargin($pagenum='') {
3137  if (empty($pagenum)) {
3138  return $this->bMargin;
3139  }
3140  return $this->pagedim[$pagenum]['bm'];
3141  }
3142 
3150  public function getScaleFactor() {
3151  return $this->k;
3152  }
3153 
3164  public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
3165  //Set left, top and right margins
3166  $this->lMargin = $left;
3167  $this->tMargin = $top;
3168  if ($right == -1) {
3169  $right = $left;
3170  }
3171  $this->rMargin = $right;
3172  if ($keepmargins) {
3173  // overwrite original values
3174  $this->original_lMargin = $this->lMargin;
3175  $this->original_rMargin = $this->rMargin;
3176  }
3177  }
3178 
3186  public function SetLeftMargin($margin) {
3187  //Set left margin
3188  $this->lMargin = $margin;
3189  if (($this->page > 0) AND ($this->x < $margin)) {
3190  $this->x = $margin;
3191  }
3192  }
3193 
3201  public function SetTopMargin($margin) {
3202  //Set top margin
3203  $this->tMargin = $margin;
3204  if (($this->page > 0) AND ($this->y < $margin)) {
3205  $this->y = $margin;
3206  }
3207  }
3208 
3216  public function SetRightMargin($margin) {
3217  $this->rMargin = $margin;
3218  if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
3219  $this->x = $this->w - $margin;
3220  }
3221  }
3222 
3230  public function SetCellPadding($pad) {
3231  if ($pad >= 0) {
3232  $this->cell_padding['L'] = $pad;
3233  $this->cell_padding['T'] = $pad;
3234  $this->cell_padding['R'] = $pad;
3235  $this->cell_padding['B'] = $pad;
3236  }
3237  }
3238 
3249  public function setCellPaddings($left='', $top='', $right='', $bottom='') {
3250  if (($left !== '') AND ($left >= 0)) {
3251  $this->cell_padding['L'] = $left;
3252  }
3253  if (($top !== '') AND ($top >= 0)) {
3254  $this->cell_padding['T'] = $top;
3255  }
3256  if (($right !== '') AND ($right >= 0)) {
3257  $this->cell_padding['R'] = $right;
3258  }
3259  if (($bottom !== '') AND ($bottom >= 0)) {
3260  $this->cell_padding['B'] = $bottom;
3261  }
3262  }
3263 
3271  public function getCellPaddings() {
3272  return $this->cell_padding;
3273  }
3274 
3285  public function setCellMargins($left='', $top='', $right='', $bottom='') {
3286  if (($left !== '') AND ($left >= 0)) {
3287  $this->cell_margin['L'] = $left;
3288  }
3289  if (($top !== '') AND ($top >= 0)) {
3290  $this->cell_margin['T'] = $top;
3291  }
3292  if (($right !== '') AND ($right >= 0)) {
3293  $this->cell_margin['R'] = $right;
3294  }
3295  if (($bottom !== '') AND ($bottom >= 0)) {
3296  $this->cell_margin['B'] = $bottom;
3297  }
3298  }
3299 
3307  public function getCellMargins() {
3308  return $this->cell_margin;
3309  }
3310 
3318  protected function adjustCellPadding($brd=0) {
3319  if (empty($brd)) {
3320  return;
3321  }
3322  if (is_string($brd)) {
3323  // convert string to array
3324  $slen = strlen($brd);
3325  $newbrd = array();
3326  for ($i = 0; $i < $slen; ++$i) {
3327  $newbrd[$brd{$i}] = true;
3328  }
3329  $brd = $newbrd;
3330  } elseif (($brd === 1) OR ($brd === true) OR (is_numeric($brd) AND (intval($brd) > 0))) {
3331  $brd = array('LRTB' => true);
3332  }
3333  if (!is_array($brd)) {
3334  return;
3335  }
3336  // store current cell padding
3337  $cp = $this->cell_padding;
3338  // select border mode
3339  if (isset($brd['mode'])) {
3340  $mode = $brd['mode'];
3341  unset($brd['mode']);
3342  } else {
3343  $mode = 'normal';
3344  }
3345  // process borders
3346  foreach ($brd as $border => $style) {
3347  $line_width = $this->LineWidth;
3348  if (is_array($style) AND isset($style['width'])) {
3349  // get border width
3350  $line_width = $style['width'];
3351  }
3352  $adj = 0; // line width inside the cell
3353  switch ($mode) {
3354  case 'ext': {
3355  $adj = 0;
3356  break;
3357  }
3358  case 'int': {
3359  $adj = $line_width;
3360  break;
3361  }
3362  case 'normal':
3363  default: {
3364  $adj = ($line_width / 2);
3365  break;
3366  }
3367  }
3368  // correct internal cell padding if required to avoid overlap between text and lines
3369  if ((strpos($border,'T') !== false) AND ($this->cell_padding['T'] < $adj)) {
3370  $this->cell_padding['T'] = $adj;
3371  }
3372  if ((strpos($border,'R') !== false) AND ($this->cell_padding['R'] < $adj)) {
3373  $this->cell_padding['R'] = $adj;
3374  }
3375  if ((strpos($border,'B') !== false) AND ($this->cell_padding['B'] < $adj)) {
3376  $this->cell_padding['B'] = $adj;
3377  }
3378  if ((strpos($border,'L') !== false) AND ($this->cell_padding['L'] < $adj)) {
3379  $this->cell_padding['L'] = $adj;
3380  }
3381  }
3382  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']));
3383  }
3384 
3393  public function SetAutoPageBreak($auto, $margin=0) {
3394  //Set auto page break mode and triggering margin
3395  $this->AutoPageBreak = $auto;
3396  $this->bMargin = $margin;
3397  $this->PageBreakTrigger = $this->h - $margin;
3398  }
3399 
3408  public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
3409  //Set display mode in viewer
3410  if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
3411  $this->ZoomMode = $zoom;
3412  } else {
3413  $this->Error('Incorrect zoom display mode: '.$zoom);
3414  }
3415  switch ($layout) {
3416  case 'default':
3417  case 'single':
3418  case 'SinglePage': {
3419  $this->LayoutMode = 'SinglePage';
3420  break;
3421  }
3422  case 'continuous':
3423  case 'OneColumn': {
3424  $this->LayoutMode = 'OneColumn';
3425  break;
3426  }
3427  case 'two':
3428  case 'TwoColumnLeft': {
3429  $this->LayoutMode = 'TwoColumnLeft';
3430  break;
3431  }
3432  case 'TwoColumnRight': {
3433  $this->LayoutMode = 'TwoColumnRight';
3434  break;
3435  }
3436  case 'TwoPageLeft': {
3437  $this->LayoutMode = 'TwoPageLeft';
3438  break;
3439  }
3440  case 'TwoPageRight': {
3441  $this->LayoutMode = 'TwoPageRight';
3442  break;
3443  }
3444  default: {
3445  $this->LayoutMode = 'SinglePage';
3446  }
3447  }
3448  switch ($mode) {
3449  case 'UseNone': {
3450  $this->PageMode = 'UseNone';
3451  break;
3452  }
3453  case 'UseOutlines': {
3454  $this->PageMode = 'UseOutlines';
3455  break;
3456  }
3457  case 'UseThumbs': {
3458  $this->PageMode = 'UseThumbs';
3459  break;
3460  }
3461  case 'FullScreen': {
3462  $this->PageMode = 'FullScreen';
3463  break;
3464  }
3465  case 'UseOC': {
3466  $this->PageMode = 'UseOC';
3467  break;
3468  }
3469  case '': {
3470  $this->PageMode = 'UseAttachments';
3471  break;
3472  }
3473  default: {
3474  $this->PageMode = 'UseNone';
3475  }
3476  }
3477  }
3478 
3486  public function SetCompression($compress) {
3487  //Set page compression
3488  if (function_exists('gzcompress')) {
3489  $this->compress = $compress ? true : false;
3490  } else {
3491  $this->compress = false;
3492  }
3493  }
3494 
3502  public function SetTitle($title) {
3503  //Title of document
3504  $this->title = $title;
3505  }
3506 
3514  public function SetSubject($subject) {
3515  //Subject of document
3516  $this->subject = $subject;
3517  }
3518 
3526  public function SetAuthor($author) {
3527  //Author of document
3528  $this->author = $author;
3529  }
3530 
3538  public function SetKeywords($keywords) {
3539  //Keywords of document
3540  $this->keywords = $keywords;
3541  }
3542 
3550  public function SetCreator($creator) {
3551  //Creator of document
3552  $this->creator = $creator;
3553  }
3554 
3562  public function Error($msg) {
3563  // unset all class variables
3564  $this->_destroy(true);
3565  // exit program and print error
3566  die('<strong>TCPDF ERROR: </strong>'.$msg);
3567  }
3568 
3577  public function Open() {
3578  //Begin document
3579  $this->state = 1;
3580  }
3581 
3590  public function Close() {
3591  if ($this->state == 3) {
3592  return;
3593  }
3594  if ($this->page == 0) {
3595  $this->AddPage();
3596  }
3597  // save current graphic settings
3598  $gvars = $this->getGraphicVars();
3599  $this->lastpage(true);
3600  $this->SetAutoPageBreak(false);
3601  $this->x = 0;
3602  $this->y = $this->h - (1 / $this->k);
3603  $this->lMargin = 0;
3604  $this->_out('q');
3605  $this->setVisibility('screen');
3606  $this->SetFont('helvetica', '', 1);
3607  $this->SetTextColor(255, 255, 255);
3608  $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";
3609  $lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
3610  $this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
3611  $this->setVisibility('all');
3612  $this->_out('Q');
3613  // restore graphic settings
3614  $this->setGraphicVars($gvars);
3615  // close page
3616  $this->endPage();
3617  // close document
3618  $this->_enddoc();
3619  // unset all class variables (except critical ones)
3620  $this->_destroy(false);
3621  }
3622 
3631  public function setPage($pnum, $resetmargins=false) {
3632  if (($pnum == $this->page) AND ($this->state == 2)) {
3633  return;
3634  }
3635  if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3636  $this->state = 2;
3637  // save current graphic settings
3638  //$gvars = $this->getGraphicVars();
3639  $oldpage = $this->page;
3640  $this->page = $pnum;
3641  $this->wPt = $this->pagedim[$this->page]['w'];
3642  $this->hPt = $this->pagedim[$this->page]['h'];
3643  $this->w = $this->pagedim[$this->page]['wk'];
3644  $this->h = $this->pagedim[$this->page]['hk'];
3645  $this->tMargin = $this->pagedim[$this->page]['tm'];
3646  $this->bMargin = $this->pagedim[$this->page]['bm'];
3647  $this->original_lMargin = $this->pagedim[$this->page]['olm'];
3648  $this->original_rMargin = $this->pagedim[$this->page]['orm'];
3649  $this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3650  $this->CurOrientation = $this->pagedim[$this->page]['or'];
3651  $this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3652  // restore graphic settings
3653  //$this->setGraphicVars($gvars);
3654  if ($resetmargins) {
3655  $this->lMargin = $this->pagedim[$this->page]['olm'];
3656  $this->rMargin = $this->pagedim[$this->page]['orm'];
3657  $this->SetY($this->tMargin);
3658  } else {
3659  // account for booklet mode
3660  if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3661  $deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3662  $this->lMargin += $deltam;
3663  $this->rMargin -= $deltam;
3664  }
3665  }
3666  } else {
3667  $this->Error('Wrong page number on setPage() function: '.$pnum);
3668  }
3669  }
3670 
3678  public function lastPage($resetmargins=false) {
3679  $this->setPage($this->getNumPages(), $resetmargins);
3680  }
3681 
3689  public function getPage() {
3690  return $this->page;
3691  }
3692 
3700  public function getNumPages() {
3701  return $this->numpages;
3702  }
3703 
3712  public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3713  $this->AddPage($orientation, $format, $keepmargins, true);
3714  }
3715 
3722  public function endTOCPage() {
3723  $this->endPage(true);
3724  }
3725 
3737  public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3738  if ($this->inxobj) {
3739  // we are inside an XObject template
3740  return;
3741  }
3742  if (!isset($this->original_lMargin) OR $keepmargins) {
3743  $this->original_lMargin = $this->lMargin;
3744  }
3745  if (!isset($this->original_rMargin) OR $keepmargins) {
3746  $this->original_rMargin = $this->rMargin;
3747  }
3748  // terminate previous page
3749  $this->endPage();
3750  // start new page
3751  $this->startPage($orientation, $format, $tocpage);
3752  }
3753 
3761  public function endPage($tocpage=false) {
3762  // check if page is already closed
3763  if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3764  return;
3765  }
3766  $this->InFooter = true;
3767  // print page footer
3768  $this->setFooter();
3769  // close page
3770  $this->_endpage();
3771  // mark page as closed
3772  $this->pageopen[$this->page] = false;
3773  $this->InFooter = false;
3774  if ($tocpage) {
3775  $this->tocpage = false;
3776  }
3777  }
3778 
3788  public function startPage($orientation='', $format='', $tocpage=false) {
3789  if ($tocpage) {
3790  $this->tocpage = true;
3791  }
3792  if ($this->numpages > $this->page) {
3793  // this page has been already added
3794  $this->setPage($this->page + 1);
3795  $this->SetY($this->tMargin);
3796  return;
3797  }
3798  // start a new page
3799  if ($this->state == 0) {
3800  $this->Open();
3801  }
3802  ++$this->numpages;
3803  $this->swapMargins($this->booklet);
3804  // save current graphic settings
3805  $gvars = $this->getGraphicVars();
3806  // start new page
3807  $this->_beginpage($orientation, $format);
3808  // mark page as open
3809  $this->pageopen[$this->page] = true;
3810  // restore graphic settings
3811  $this->setGraphicVars($gvars);
3812  // mark this point
3813  $this->setPageMark();
3814  // print page header
3815  $this->setHeader();
3816  // restore graphic settings
3817  $this->setGraphicVars($gvars);
3818  // mark this point
3819  $this->setPageMark();
3820  // print table header (if any)
3821  $this->setTableHeader();
3822  // set mark for empty page check
3823  $this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
3824  }
3825 
3834  public function setPageMark() {
3835  $this->intmrk[$this->page] = $this->pagelen[$this->page];
3836  $this->bordermrk[$this->page] = $this->intmrk[$this->page];
3837  $this->setContentMark();
3838  }
3839 
3847  protected function setContentMark($page=0) {
3848  if ($page <= 0) {
3849  $page = $this->page;
3850  }
3851  if (isset($this->footerlen[$page])) {
3852  $this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3853  } else {
3854  $this->cntmrk[$page] = $this->pagelen[$page];
3855  }
3856  }
3857 
3866  public function setHeaderData($ln='', $lw=0, $ht='', $hs='') {
3867  $this->header_logo = $ln;
3868  $this->header_logo_width = $lw;
3869  $this->header_title = $ht;
3870  $this->header_string = $hs;
3871  }
3872 
3880  public function getHeaderData() {
3881  $ret = array();
3882  $ret['logo'] = $this->header_logo;
3883  $ret['logo_width'] = $this->header_logo_width;
3884  $ret['title'] = $this->header_title;
3885  $ret['string'] = $this->header_string;
3886  return $ret;
3887  }
3888 
3895  public function setHeaderMargin($hm=10) {
3896  $this->header_margin = $hm;
3897  }
3898 
3905  public function getHeaderMargin() {
3906  return $this->header_margin;
3907  }
3908 
3915  public function setFooterMargin($fm=10) {
3916  $this->footer_margin = $fm;
3917  }
3918 
3925  public function getFooterMargin() {
3926  return $this->footer_margin;
3927  }
3933  public function setPrintHeader($val=true) {
3934  $this->print_header = $val;
3935  }
3936 
3942  public function setPrintFooter($val=true) {
3943  $this->print_footer = $val;
3944  }
3945 
3951  public function getImageRBX() {
3952  return $this->img_rb_x;
3953  }
3954 
3960  public function getImageRBY() {
3961  return $this->img_rb_y;
3962  }
3963 
3969  public function Header() {
3970  $ormargins = $this->getOriginalMargins();
3971  $headerfont = $this->getHeaderFont();
3972  $headerdata = $this->getHeaderData();
3973  if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
3974  $this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3975  $imgy = $this->getImageRBY();
3976  } else {
3977  $imgy = $this->GetY();
3978  }
3979  $cell_height = round(($this->getCellHeightRatio() * $headerfont[2]) / $this->getScaleFactor(), 2);
3980  // set starting margin for text data cell
3981  if ($this->getRTL()) {
3982  $header_x = $ormargins['right'] + ($headerdata['logo_width'] * 1.1);
3983  } else {
3984  $header_x = $ormargins['left'] + ($headerdata['logo_width'] * 1.1);
3985  }
3986  $this->SetTextColor(0, 0, 0);
3987  // header title
3988  $this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
3989  $this->SetX($header_x);
3990  $this->Cell(0, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
3991  // header string
3992  $this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
3993  $this->SetX($header_x);
3994  $this->MultiCell(0, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false);
3995  // print an ending header line
3996  $this->SetLineStyle(array('width' => 0.85 / $this->getScaleFactor(), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)));
3997  $this->SetY((2.835 / $this->getScaleFactor()) + max($imgy, $this->GetY()));
3998  if ($this->getRTL()) {
3999  $this->SetX($ormargins['right']);
4000  } else {
4001  $this->SetX($ormargins['left']);
4002  }
4003  $this->Cell(0, 0, '', 'T', 0, 'C');
4004  }
4005 
4011  public function Footer() {
4012  $cur_y = $this->GetY();
4013  $ormargins = $this->getOriginalMargins();
4014  $this->SetTextColor(0, 0, 0);
4015  //set style for cell border
4016  $line_width = 0.85 / $this->getScaleFactor();
4017  $this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)));
4018  //print document barcode
4019  $barcode = $this->getBarcode();
4020  if (!empty($barcode)) {
4021  $this->Ln($line_width);
4022  $barcode_width = round(($this->getPageWidth() - $ormargins['left'] - $ormargins['right']) / 3);
4023  $style = array(
4024  'position' => $this->rtl?'R':'L',
4025  'align' => $this->rtl?'R':'L',
4026  'stretch' => false,
4027  'fitwidth' => true,
4028  'cellfitalign' => '',
4029  'border' => false,
4030  'padding' => 0,
4031  'fgcolor' => array(0,0,0),
4032  'bgcolor' => false,
4033  'text' => false
4034  );
4035  $this->write1DBarcode($barcode, 'C128B', '', $cur_y + $line_width, '', (($this->getFooterMargin() / 3) - $line_width), 0.3, $style, '');
4036  }
4037  if (empty($this->pagegroups)) {
4038  $pagenumtxt = $this->l['w_page'].' '.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
4039  } else {
4040  $pagenumtxt = $this->l['w_page'].' '.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
4041  }
4042  $this->SetY($cur_y);
4043  //Print page number
4044  if ($this->getRTL()) {
4045  $this->SetX($ormargins['right']);
4046  $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
4047  } else {
4048  $this->SetX($ormargins['left']);
4049  $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'R');
4050  }
4051  }
4052 
4058  protected function setHeader() {
4059  if ($this->print_header) {
4060  $this->setGraphicVars($this->default_graphic_vars);
4061  $temp_thead = $this->thead;
4062  $temp_theadMargins = $this->theadMargins;
4063  $lasth = $this->lasth;
4064  $this->_out('q');
4065  $this->rMargin = $this->original_rMargin;
4066  $this->lMargin = $this->original_lMargin;
4067  $this->SetCellPadding(0);
4068  //set current position
4069  if ($this->rtl) {
4070  $this->SetXY($this->original_rMargin, $this->header_margin);
4071  } else {
4072  $this->SetXY($this->original_lMargin, $this->header_margin);
4073  }
4074  $this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
4075  $this->Header();
4076  //restore position
4077  if ($this->rtl) {
4078  $this->SetXY($this->original_rMargin, $this->tMargin);
4079  } else {
4080  $this->SetXY($this->original_lMargin, $this->tMargin);
4081  }
4082  $this->_out('Q');
4083  $this->lasth = $lasth;
4084  $this->thead = $temp_thead;
4085  $this->theadMargins = $temp_theadMargins;
4086  $this->newline = false;
4087  }
4088  }
4089 
4095  protected function setFooter() {
4096  //Page footer
4097  // save current graphic settings
4098  $gvars = $this->getGraphicVars();
4099  // mark this point
4100  $this->footerpos[$this->page] = $this->pagelen[$this->page];
4101  $this->_out("\n");
4102  if ($this->print_footer) {
4103  $this->setGraphicVars($this->default_graphic_vars);
4104  $this->current_column = 0;
4105  $this->num_columns = 1;
4106  $temp_thead = $this->thead;
4107  $temp_theadMargins = $this->theadMargins;
4108  $lasth = $this->lasth;
4109  $this->_out('q');
4110  $this->rMargin = $this->original_rMargin;
4111  $this->lMargin = $this->original_lMargin;
4112  $this->SetCellPadding(0);
4113  //set current position
4114  $footer_y = $this->h - $this->footer_margin;
4115  if ($this->rtl) {
4116  $this->SetXY($this->original_rMargin, $footer_y);
4117  } else {
4118  $this->SetXY($this->original_lMargin, $footer_y);
4119  }
4120  $this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
4121  $this->Footer();
4122  //restore position
4123  if ($this->rtl) {
4124  $this->SetXY($this->original_rMargin, $this->tMargin);
4125  } else {
4126  $this->SetXY($this->original_lMargin, $this->tMargin);
4127  }
4128  $this->_out('Q');
4129  $this->lasth = $lasth;
4130  $this->thead = $temp_thead;
4131  $this->theadMargins = $temp_theadMargins;
4132  }
4133  // restore graphic settings
4134  $this->setGraphicVars($gvars);
4135  $this->current_column = $gvars['current_column'];
4136  $this->num_columns = $gvars['num_columns'];
4137  // calculate footer length
4138  $this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
4139  }
4140 
4146  protected function setTableHeader() {
4147  if ($this->num_columns > 1) {
4148  // multi column mode
4149  return;
4150  }
4151  if (isset($this->theadMargins['top'])) {
4152  // restore the original top-margin
4153  $this->tMargin = $this->theadMargins['top'];
4154  $this->pagedim[$this->page]['tm'] = $this->tMargin;
4155  $this->y = $this->tMargin;
4156  }
4157  if (!$this->empty_string($this->thead) AND (!$this->inthead)) {
4158  // set margins
4159  $prev_lMargin = $this->lMargin;
4160  $prev_rMargin = $this->rMargin;
4161  $prev_cell_padding = $this->cell_padding;
4162  $this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
4163  $this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
4164  $this->cell_padding = $this->theadMargins['cell_padding'];
4165  if ($this->rtl) {
4166  $this->x = $this->w - $this->rMargin;
4167  } else {
4168  $this->x = $this->lMargin;
4169  }
4170  // print table header
4171  $this->writeHTML($this->thead, false, false, false, false, '');
4172  // set new top margin to skip the table headers
4173  if (!isset($this->theadMargins['top'])) {
4174  $this->theadMargins['top'] = $this->tMargin;
4175  }
4176  $this->tMargin = $this->y;
4177  $this->pagedim[$this->page]['tm'] = $this->tMargin;
4178  $this->lasth = 0;
4179  $this->lMargin = $prev_lMargin;
4180  $this->rMargin = $prev_rMargin;
4181  $this->cell_padding = $prev_cell_padding;
4182  }
4183  }
4184 
4192  public function PageNo() {
4193  return $this->page;
4194  }
4195 
4208  public function AddSpotColor($name, $c, $m, $y, $k) {
4209  if (!isset($this->spot_colors[$name])) {
4210  $i = 1 + count($this->spot_colors);
4211  $this->spot_colors[$name] = array('i' => $i, 'c' => $c, 'm' => $m, 'y' => $y, 'k' => $k);
4212  }
4213  }
4214 
4226  public function SetDrawColorArray($color, $ret=false) {
4227  if (is_array($color)) {
4228  $color = array_values($color);
4229  $r = isset($color[0]) ? $color[0] : -1;
4230  $g = isset($color[1]) ? $color[1] : -1;
4231  $b = isset($color[2]) ? $color[2] : -1;
4232  $k = isset($color[3]) ? $color[3] : -1;
4233  if ($r >= 0) {
4234  return $this->SetDrawColor($r, $g, $b, $k, $ret);
4235  }
4236  }
4237  return '';
4238  }
4239 
4252  public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false) {
4253  // set default values
4254  if (!is_numeric($col1)) {
4255  $col1 = 0;
4256  }
4257  if (!is_numeric($col2)) {
4258  $col2 = -1;
4259  }
4260  if (!is_numeric($col3)) {
4261  $col3 = -1;
4262  }
4263  if (!is_numeric($col4)) {
4264  $col4 = -1;
4265  }
4266  //Set color for all stroking operations
4267  if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
4268  // Grey scale
4269  $this->DrawColor = sprintf('%.3F G', $col1/255);
4270  $this->strokecolor = array('G' => $col1);
4271  } elseif ($col4 == -1) {
4272  // RGB
4273  $this->DrawColor = sprintf('%.3F %.3F %.3F RG', $col1/255, $col2/255, $col3/255);
4274  $this->strokecolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
4275  } else {
4276  // CMYK
4277  $this->DrawColor = sprintf('%.3F %.3F %.3F %.3F K', $col1/100, $col2/100, $col3/100, $col4/100);
4278  $this->strokecolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
4279  }
4280  if ($this->page > 0) {
4281  if (!$ret) {
4282  $this->_out($this->DrawColor);
4283  }
4284  return $this->DrawColor;
4285  }
4286  return '';
4287  }
4288 
4297  public function SetDrawSpotColor($name, $tint=100) {
4298  if (!isset($this->spot_colors[$name])) {
4299  $this->Error('Undefined spot color: '.$name);
4300  }
4301  $this->DrawColor = sprintf('/CS%d CS %.3F SCN', $this->spot_colors[$name]['i'], $tint/100);
4302  if ($this->page > 0) {
4303  $this->_out($this->DrawColor);
4304  }
4305  }
4306 
4316  public function SetFillColorArray($color) {
4317  if (is_array($color)) {
4318  $color = array_values($color);
4319  $r = isset($color[0]) ? $color[0] : -1;
4320  $g = isset($color[1]) ? $color[1] : -1;
4321  $b = isset($color[2]) ? $color[2] : -1;
4322  $k = isset($color[3]) ? $color[3] : -1;
4323  if ($r >= 0) {
4324  $this->SetFillColor($r, $g, $b, $k);
4325  }
4326  }
4327  }
4328 
4339  public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1) {
4340  // set default values
4341  if (!is_numeric($col1)) {
4342  $col1 = 0;
4343  }
4344  if (!is_numeric($col2)) {
4345  $col2 = -1;
4346  }
4347  if (!is_numeric($col3)) {
4348  $col3 = -1;
4349  }
4350  if (!is_numeric($col4)) {
4351  $col4 = -1;
4352  }
4353  //Set color for all filling operations
4354  if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
4355  // Grey scale
4356  $this->FillColor = sprintf('%.3F g', $col1/255);
4357  $this->bgcolor = array('G' => $col1);
4358  } elseif ($col4 == -1) {
4359  // RGB
4360  $this->FillColor = sprintf('%.3F %.3F %.3F rg', $col1/255, $col2/255, $col3/255);
4361  $this->bgcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
4362  } else {
4363  // CMYK
4364  $this->FillColor = sprintf('%.3F %.3F %.3F %.3F k', $col1/100, $col2/100, $col3/100, $col4/100);
4365  $this->bgcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
4366  }
4367  $this->ColorFlag = ($this->FillColor != $this->TextColor);
4368  if ($this->page > 0) {
4369  $this->_out($this->FillColor);
4370  }
4371  }
4372 
4381  public function SetFillSpotColor($name, $tint=100) {
4382  if (!isset($this->spot_colors[$name])) {
4383  $this->Error('Undefined spot color: '.$name);
4384  }
4385  $this->FillColor = sprintf('/CS%d cs %.3F scn', $this->spot_colors[$name]['i'], $tint/100);
4386  $this->ColorFlag = ($this->FillColor != $this->TextColor);
4387  if ($this->page > 0) {
4388  $this->_out($this->FillColor);
4389  }
4390  }
4391 
4400  public function SetTextColorArray($color) {
4401  if (is_array($color)) {
4402  $color = array_values($color);
4403  $r = isset($color[0]) ? $color[0] : -1;
4404  $g = isset($color[1]) ? $color[1] : -1;
4405  $b = isset($color[2]) ? $color[2] : -1;
4406  $k = isset($color[3]) ? $color[3] : -1;
4407  if ($r >= 0) {
4408  $this->SetTextColor($r, $g, $b, $k);
4409  }
4410  }
4411  }
4412 
4423  public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1) {
4424  // set default values
4425  if (!is_numeric($col1)) {
4426  $col1 = 0;
4427  }
4428  if (!is_numeric($col2)) {
4429  $col2 = -1;
4430  }
4431  if (!is_numeric($col3)) {
4432  $col3 = -1;
4433  }
4434  if (!is_numeric($col4)) {
4435  $col4 = -1;
4436  }
4437  //Set color for text
4438  if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
4439  // Grey scale
4440  $this->TextColor = sprintf('%.3F g', $col1/255);
4441  $this->fgcolor = array('G' => $col1);
4442  } elseif ($col4 == -1) {
4443  // RGB
4444  $this->TextColor = sprintf('%.3F %.3F %.3F rg', $col1/255, $col2/255, $col3/255);
4445  $this->fgcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
4446  } else {
4447  // CMYK
4448  $this->TextColor = sprintf('%.3F %.3F %.3F %.3F k', $col1/100, $col2/100, $col3/100, $col4/100);
4449  $this->fgcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
4450  }
4451  $this->ColorFlag = ($this->FillColor != $this->TextColor);
4452  }
4453 
4462  public function SetTextSpotColor($name, $tint=100) {
4463  if (!isset($this->spot_colors[$name])) {
4464  $this->Error('Undefined spot color: '.$name);
4465  }
4466  $this->TextColor = sprintf('/CS%d cs %.3F scn', $this->spot_colors[$name]['i'], $tint/100);
4467  $this->ColorFlag = ($this->FillColor != $this->TextColor);
4468  if ($this->page > 0) {
4469  $this->_out($this->TextColor);
4470  }
4471  }
4472 
4485  public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4486  return $this->GetArrStringWidth($this->utf8Bidi($this->UTF8StringToArray($s), $s, $this->tmprtl), $fontname, $fontstyle, $fontsize, $getarray);
4487  }
4488 
4501  public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4502  // store current values
4503  if (!$this->empty_string($fontname)) {
4504  $prev_FontFamily = $this->FontFamily;
4505  $prev_FontStyle = $this->FontStyle;
4506  $prev_FontSizePt = $this->FontSizePt;
4507  $this->SetFont($fontname, $fontstyle, $fontsize);
4508  }
4509  // convert UTF-8 array to Latin1 if required
4510  $sa = $this->UTF8ArrToLatin1($sa);
4511  $w = 0; // total width
4512  $wa = array(); // array of characters widths
4513  foreach ($sa as $ck => $char) {
4514  // character width
4515  $cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4516  $wa[] = $cw;
4517  $w += $cw;
4518  }
4519  // restore previous values
4520  if (!$this->empty_string($fontname)) {
4521  $this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt);
4522  }
4523  if ($getarray) {
4524  return $wa;
4525  }
4526  return $w;
4527  }
4528 
4538  public function GetCharWidth($char, $notlast=true) {
4539  // get raw width
4540  $chw = $this->getRawCharWidth($char);
4541  if (($this->font_spacing != 0) AND $notlast) {
4542  // increase/decrease font spacing
4543  $chw += $this->font_spacing;
4544  }
4545  if ($this->font_stretching != 100) {
4546  // fixed stretching mode
4547  $chw *= ($this->font_stretching / 100);
4548  }
4549  return $chw;
4550  }
4551 
4560  public function getRawCharWidth($char) {
4561  if ($char == 173) {
4562  // SHY character will not be printed
4563  return (0);
4564  }
4565  $cw = &$this->CurrentFont['cw'];
4566  if (isset($cw[$char])) {
4567  $w = $cw[$char];
4568  } elseif (isset($this->CurrentFont['dw'])) {
4569  // default width
4570  $w = $this->CurrentFont['dw'];
4571  } elseif (isset($cw[32])) {
4572  // default width
4573  $w = $cw[32];
4574  } else {
4575  $w = 600;
4576  }
4577  return ($w * $this->FontSize / 1000);
4578  }
4579 
4587  public function GetNumChars($s) {
4588  if ($this->isUnicodeFont()) {
4589  return count($this->UTF8StringToArray($s));
4590  }
4591  return strlen($s);
4592  }
4593 
4599  protected function getFontsList() {
4600  $fontsdir = opendir($this->_getfontpath());
4601  while (($file = readdir($fontsdir)) !== false) {
4602  if (substr($file, -4) == '.php') {
4603  array_push($this->fontlist, strtolower(basename($file, '.php')));
4604  }
4605  }
4606  closedir($fontsdir);
4607  }
4608 
4622  public function AddFont($family, $style='', $fontfile='', $subset='default') {
4623  if ($subset === 'default') {
4624  $subset = $this->font_subsetting;
4625  }
4626  if ($this->empty_string($family)) {
4627  if (!$this->empty_string($this->FontFamily)) {
4628  $family = $this->FontFamily;
4629  } else {
4630  $this->Error('Empty font family');
4631  }
4632  }
4633  // move embedded styles on $style
4634  if (substr($family, -1) == 'I') {
4635  $style .= 'I';
4636  $family = substr($family, 0, -1);
4637  }
4638  if (substr($family, -1) == 'B') {
4639  $style .= 'B';
4640  $family = substr($family, 0, -1);
4641  }
4642  // normalize family name
4643  $family = strtolower($family);
4644  if ((!$this->isunicode) AND ($family == 'arial')) {
4645  $family = 'helvetica';
4646  }
4647  if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4648  $style = '';
4649  }
4650  $tempstyle = strtoupper($style);
4651  $style = '';
4652  // underline
4653  if (strpos($tempstyle, 'U') !== false) {
4654  $this->underline = true;
4655  } else {
4656  $this->underline = false;
4657  }
4658  // line-through (deleted)
4659  if (strpos($tempstyle, 'D') !== false) {
4660  $this->linethrough = true;
4661  } else {
4662  $this->linethrough = false;
4663  }
4664  // overline
4665  if (strpos($tempstyle, 'O') !== false) {
4666  $this->overline = true;
4667  } else {
4668  $this->overline = false;
4669  }
4670  // bold
4671  if (strpos($tempstyle, 'B') !== false) {
4672  $style .= 'B';
4673  }
4674  // oblique
4675  if (strpos($tempstyle, 'I') !== false) {
4676  $style .= 'I';
4677  }
4678  $bistyle = $style;
4679  $fontkey = $family.$style;
4680  $font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
4681  $fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4682  // check if the font has been already added
4683  $fb = $this->getFontBuffer($fontkey);
4684  if ($fb !== false) {
4685  if ($this->inxobj) {
4686  // we are inside an XObject template
4687  $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
4688  }
4689  return $fontdata;
4690  }
4691  if (isset($type)) {
4692  unset($type);
4693  }
4694  if (isset($cw)) {
4695  unset($cw);
4696  }
4697  // get specified font directory (if any)
4698  $fontdir = false;
4699  if (!$this->empty_string($fontfile)) {
4700  $fontdir = dirname($fontfile);
4701  if ($this->empty_string($fontdir) OR ($fontdir == '.')) {
4702  $fontdir = '';
4703  } else {
4704  $fontdir .= '/';
4705  }
4706  }
4707  // search and include font file
4708  if ($this->empty_string($fontfile) OR (!file_exists($fontfile))) {
4709  // build a standard filenames for specified font
4710  $fontfile1 = str_replace(' ', '', $family).strtolower($style).'.php';
4711  $fontfile2 = str_replace(' ', '', $family).'.php';
4712  // search files on various directories
4713  if (($fontdir !== false) AND file_exists($fontdir.$fontfile1)) {
4714  $fontfile = $fontdir.$fontfile1;
4715  } elseif (file_exists($this->_getfontpath().$fontfile1)) {
4716  $fontfile = $this->_getfontpath().$fontfile1;
4717  } elseif (file_exists($fontfile1)) {
4718  $fontfile = $fontfile1;
4719  } elseif (($fontdir !== false) AND file_exists($fontdir.$fontfile2)) {
4720  $fontfile = $fontdir.$fontfile2;
4721  } elseif (file_exists($this->_getfontpath().$fontfile2)) {
4722  $fontfile = $this->_getfontpath().$fontfile2;
4723  } else {
4724  $fontfile = $fontfile2;
4725  }
4726  }
4727  // include font file
4728  if (file_exists($fontfile)) {
4729  include($fontfile);
4730  } else {
4731  $this->Error('Could not include font definition file: '.$family.'');
4732  }
4733  // check font parameters
4734  if ((!isset($type)) OR (!isset($cw))) {
4735  $this->Error('The font definition file has a bad format: '.$fontfile.'');
4736  }
4737  // SET default parameters
4738  if (!isset($file) OR $this->empty_string($file)) {
4739  $file = '';
4740  }
4741  if (!isset($enc) OR $this->empty_string($enc)) {
4742  $enc = '';
4743  }
4744  if (!isset($cidinfo) OR $this->empty_string($cidinfo)) {
4745  $cidinfo = array('Registry'=>'Adobe','Ordering'=>'Identity','Supplement'=>0);
4746  $cidinfo['uni2cid'] = array();
4747  }
4748  if (!isset($ctg) OR $this->empty_string($ctg)) {
4749  $ctg = '';
4750  }
4751  if (!isset($desc) OR $this->empty_string($desc)) {
4752  $desc = array();
4753  }
4754  if (!isset($up) OR $this->empty_string($up)) {
4755  $up = -100;
4756  }
4757  if (!isset($ut) OR $this->empty_string($ut)) {
4758  $ut = 50;
4759  }
4760  if (!isset($cw) OR $this->empty_string($cw)) {
4761  $cw = array();
4762  }
4763  if (!isset($dw) OR $this->empty_string($dw)) {
4764  // set default width
4765  if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4766  $dw = $desc['MissingWidth'];
4767  } elseif (isset($cw[32])) {
4768  $dw = $cw[32];
4769  } else {
4770  $dw = 600;
4771  }
4772  }
4773  ++$this->numfonts;
4774  if ($type == 'cidfont0') {
4775  // register CID font (all styles at once)
4776  $styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4777  $sname = $name.$styles[$bistyle];
4778  // artificial bold
4779  if (strpos($bistyle, 'B') !== false) {
4780  if (isset($desc['StemV'])) {
4781  $desc['StemV'] *= 2;
4782  } else {
4783  $desc['StemV'] = 120;
4784  }
4785  }
4786  // artificial italic
4787  if (strpos($bistyle, 'I') !== false) {
4788  if (isset($desc['ItalicAngle'])) {
4789  $desc['ItalicAngle'] -= 11;
4790  } else {
4791  $desc['ItalicAngle'] = -11;
4792  }
4793  }
4794  } elseif ($type == 'core') {
4795  $name = $this->CoreFonts[$fontkey];
4796  $subset = false;
4797  } elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4798  $subset = false;
4799  } elseif ($type == 'TrueTypeUnicode') {
4800  $enc = 'Identity-H';
4801  } else {
4802  $this->Error('Unknow font type: '.$type.'');
4803  }
4804  // initialize subsetchars to contain default ASCII values (0-255)
4805  $subsetchars = array_fill(0, 256, true);
4806  $this->setFontBuffer($fontkey, array('fontkey' => $fontkey, 'i' => $this->numfonts, 'type' => $type, 'name' => $name, 'desc' => $desc, 'up' => $up, 'ut' => $ut, 'cw' => $cw, 'dw' => $dw, 'enc' => $enc, 'cidinfo' => $cidinfo, 'file' => $file, 'ctg' => $ctg, 'subset' => $subset, 'subsetchars' => $subsetchars));
4807  if ($this->inxobj) {
4808  // we are inside an XObject template
4809  $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
4810  }
4811  if (isset($diff) AND (!empty($diff))) {
4812  //Search existing encodings
4813  $d = 0;
4814  $nb = count($this->diffs);
4815  for ($i=1; $i <= $nb; ++$i) {
4816  if ($this->diffs[$i] == $diff) {
4817  $d = $i;
4818  break;
4819  }
4820  }
4821  if ($d == 0) {
4822  $d = $nb + 1;
4823  $this->diffs[$d] = $diff;
4824  }
4825  $this->setFontSubBuffer($fontkey, 'diff', $d);
4826  }
4827  if (!$this->empty_string($file)) {
4828  if (!isset($this->FontFiles[$file])) {
4829  if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4830  $this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4831  } elseif ($type != 'core') {
4832  $this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4833  }
4834  } else {
4835  // update fontkeys that are sharing this font file
4836  $this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
4837  if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
4838  $this->FontFiles[$file]['fontkeys'][] = $fontkey;
4839  }
4840  }
4841  }
4842  return $fontdata;
4843  }
4844 
4861  public function SetFont($family, $style='', $size=0, $fontfile='', $subset='default') {
4862  //Select a font; size given in points
4863  if ($size == 0) {
4864  $size = $this->FontSizePt;
4865  }
4866  // try to add font (if not already added)
4867  $fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4868  $this->FontFamily = $fontdata['family'];
4869  $this->FontStyle = $fontdata['style'];
4870  $this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
4871  $this->SetFontSize($size);
4872  }
4873 
4882  public function SetFontSize($size, $out=true) {
4883  // font size in points
4884  $this->FontSizePt = $size;
4885  // font size in user units
4886  $this->FontSize = $size / $this->k;
4887  // calculate some font metrics
4888  if (isset($this->CurrentFont['desc']['FontBBox'])) {
4889  $bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4890  $font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4891  } else {
4892  $font_height = $size * 1.219;
4893  }
4894  if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
4895  $font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
4896  }
4897  if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
4898  $font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
4899  }
4900  if (!isset($font_ascent) AND !isset($font_descent)) {
4901  // core font
4902  $font_ascent = 0.76 * $font_height;
4903  $font_descent = $font_height - $font_ascent;
4904  } elseif (!isset($font_descent)) {
4905  $font_descent = $font_height - $font_ascent;
4906  } elseif (!isset($font_ascent)) {
4907  $font_ascent = $font_height - $font_descent;
4908  }
4909  $this->FontAscent = $font_ascent / $this->k;
4910  $this->FontDescent = $font_descent / $this->k;
4911  if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i']))) {
4912  $this->_out(sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4913  }
4914  }
4915 
4926  public function getFontDescent($font, $style='', $size=0) {
4927  $fontdata = $this->AddFont($font, $style);
4928  $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4929  if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
4930  $descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
4931  } else {
4932  $descent = 1.219 * 0.24 * $size;
4933  }
4934  return ($descent / $this->k);
4935  }
4936 
4947  public function getFontAscent($font, $style='', $size=0) {
4948  $fontdata = $this->AddFont($font, $style);
4949  $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4950  if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
4951  $ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
4952  } else {
4953  $ascent = 1.219 * 0.76 * $size;
4954  }
4955  return ($ascent / $this->k);
4956  }
4957 
4964  public function SetDefaultMonospacedFont($font) {
4965  $this->default_monospaced_font = $font;
4966  }
4967 
4975  public function AddLink() {
4976  //Create a new internal link
4977  $n = count($this->links) + 1;
4978  $this->links[$n] = array(0, 0);
4979  return $n;
4980  }
4981 
4991  public function SetLink($link, $y=0, $page=-1) {
4992  if ($y == -1) {
4993  $y = $this->y;
4994  }
4995  if ($page == -1) {
4996  $page = $this->page;
4997  }
4998  $this->links[$link] = array($page, $y);
4999  }
5000 
5014  public function Link($x, $y, $w, $h, $link, $spaces=0) {
5015  $this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
5016  }
5017 
5031  public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
5032  if ($this->inxobj) {
5033  // store parameters for later use on template
5034  $this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
5035  return;
5036  }
5037  if ($x === '') {
5038  $x = $this->x;
5039  }
5040  if ($y === '') {
5041  $y = $this->y;
5042  }
5043  // check page for no-write regions and adapt page margins if necessary
5044  $this->checkPageRegions($h, $x, $y);
5045  // recalculate coordinates to account for graphic transformations
5046  if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
5047  for ($i=$this->transfmatrix_key; $i > 0; --$i) {
5048  $maxid = count($this->transfmatrix[$i]) - 1;
5049  for ($j=$maxid; $j >= 0; --$j) {
5050  $ctm = $this->transfmatrix[$i][$j];
5051  if (isset($ctm['a'])) {
5052  $x = $x * $this->k;
5053  $y = ($this->h - $y) * $this->k;
5054  $w = $w * $this->k;
5055  $h = $h * $this->k;
5056  // top left
5057  $xt = $x;
5058  $yt = $y;
5059  $x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
5060  $y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
5061  // top right
5062  $xt = $x + $w;
5063  $yt = $y;
5064  $x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
5065  $y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
5066  // bottom left
5067  $xt = $x;
5068  $yt = $y - $h;
5069  $x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
5070  $y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
5071  // bottom right
5072  $xt = $x + $w;
5073  $yt = $y - $h;
5074  $x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
5075  $y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
5076  // new coordinates (rectangle area)
5077  $x = min($x1, $x2, $x3, $x4);
5078  $y = max($y1, $y2, $y3, $y4);
5079  $w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
5080  $h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
5081  $x = $x / $this->k;
5082  $y = $this->h - ($y / $this->k);
5083  }
5084  }
5085  }
5086  }
5087  if ($this->page <= 0) {
5088  $page = 1;
5089  } else {
5090  $page = $this->page;
5091  }
5092  if (!isset($this->PageAnnots[$page])) {
5093  $this->PageAnnots[$page] = array();
5094  }
5095  ++$this->n;
5096  $this->PageAnnots[$page][] = array('n' => $this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
5097  if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!$this->empty_string($opt['FS'])) AND file_exists($opt['FS']) AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
5098  ++$this->n;
5099  $this->embeddedfiles[basename($opt['FS'])] = array('n' => $this->n, 'file' => $opt['FS']);
5100  }
5101  // Add widgets annotation's icons
5102  if (isset($opt['mk']['i']) AND file_exists($opt['mk']['i'])) {
5103  $this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
5104  }
5105  if (isset($opt['mk']['ri']) AND file_exists($opt['mk']['ri'])) {
5106  $this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
5107  }
5108  if (isset($opt['mk']['ix']) AND file_exists($opt['mk']['ix'])) {
5109  $this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
5110  }
5111  }
5112 
5119  protected function _putEmbeddedFiles() {
5120  reset($this->embeddedfiles);
5121  foreach ($this->embeddedfiles as $filename => $filedata) {
5122  $data = file_get_contents($filedata['file']);
5123  $filter = '';
5124  if ($this->compress) {
5125  $data = gzcompress($data);
5126  $filter = ' /Filter /FlateDecode';
5127  }
5128  $stream = $this->_getrawstream($data, $filedata['n']);
5129  $out = $this->_getobj($filedata['n'])."\n";
5130  $out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' >>';
5131  $out .= ' stream'."\n".$stream."\n".'endstream';
5132  $out .= "\n".'endobj';
5133  $this->_out($out);
5134  }
5135  }
5136 
5161  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) {
5164  $this->setTextRenderingMode($fstroke, $ffill, $fclip);
5165  $this->SetXY($x, $y, $rtloff);
5166  $this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
5167  // restore previous rendering mode
5168  $this->textrendermode = $textrendermode;
5169  $this->textstrokewidth = $textstrokewidth;
5170  }
5171 
5181  public function AcceptPageBreak() {
5182  if ($this->num_columns > 1) {
5183  // multi column mode
5184  if($this->current_column < ($this->num_columns - 1)) {
5185  // go to next column
5186  $this->selectColumn($this->current_column + 1);
5187  } else {
5188  // add a new page
5189  $this->AddPage();
5190  // set first column
5191  $this->selectColumn(0);
5192  }
5193  // avoid page breaking from checkPageBreak()
5194  return false;
5195  }
5196  return $this->AutoPageBreak;
5197  }
5198 
5208  protected function checkPageBreak($h=0, $y='', $addpage=true) {
5209  if ($this->empty_string($y)) {
5210  $y = $this->y;
5211  }
5212  $current_page = $this->page;
5213  if ((($y + $h) > $this->PageBreakTrigger) AND (!$this->InFooter) AND ($this->AcceptPageBreak())) {
5214  if ($addpage) {
5215  //Automatic page break
5216  $x = $this->x;
5217  $this->AddPage($this->CurOrientation);
5218  $this->y = $this->tMargin;
5219  $oldpage = $this->page - 1;
5220  if ($this->rtl) {
5221  if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
5222  $this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
5223  } else {
5224  $this->x = $x;
5225  }
5226  } else {
5227  if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
5228  $this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
5229  } else {
5230  $this->x = $x;
5231  }
5232  }
5233  }
5234  $this->newline = true;
5235  return true;
5236  }
5237  if ($current_page != $this->page) {
5238  // account for columns mode
5239  $this->newline = true;
5240  return true;
5241  }
5242  return false;
5243  }
5244 
5261  public function removeSHY($txt='') {
5262  $txt = preg_replace('/([\\xc2]{1}[\\xad]{1})/', '', $txt);
5263  if (!$this->isunicode) {
5264  $txt = preg_replace('/([\\xad]{1})/', '', $txt);
5265  }
5266  return $txt;
5267  }
5268 
5288  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') {
5289  $prev_cell_margin = $this->cell_margin;
5290  $prev_cell_padding = $this->cell_padding;
5291  $this->adjustCellPadding($border);
5292  if (!$ignore_min_height) {
5293  $min_cell_height = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
5294  if ($h < $min_cell_height) {
5295  $h = $min_cell_height;
5296  }
5297  }
5298  $this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5299  $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5300  $this->cell_padding = $prev_cell_padding;
5301  $this->cell_margin = $prev_cell_margin;
5302  }
5303 
5324  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') {
5325  $prev_cell_margin = $this->cell_margin;
5326  $prev_cell_padding = $this->cell_padding;
5327  $txt = $this->removeSHY($txt);
5328  $rs = ''; //string to be returned
5329  $this->adjustCellPadding($border);
5330  if (!$ignore_min_height) {
5331  $min_cell_height = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
5332  if ($h < $min_cell_height) {
5333  $h = $min_cell_height;
5334  }
5335  }
5336  // check page for no-write regions and adapt page margins if necessary
5337  $this->checkPageRegions($h);
5338  $k = $this->k;
5339  if ($this->rtl) {
5340  $x = $this->x - $this->cell_margin['R'];
5341  } else {
5342  $x = $this->x + $this->cell_margin['L'];
5343  }
5344  $y = $this->y + $this->cell_margin['T'];
5345  $prev_font_stretching = $this->font_stretching;
5346  $prev_font_spacing = $this->font_spacing;
5347  // cell vertical alignment
5348  switch ($calign) {
5349  case 'A': {
5350  // font top
5351  switch ($valign) {
5352  case 'T': {
5353  // top
5354  $y -= $this->cell_padding['T'];
5355  break;
5356  }
5357  case 'B': {
5358  // bottom
5359  $y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5360  break;
5361  }
5362  default:
5363  case 'C':
5364  case 'M': {
5365  // center
5366  $y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5367  break;
5368  }
5369  }
5370  break;
5371  }
5372  case 'L': {
5373  // font baseline
5374  switch ($valign) {
5375  case 'T': {
5376  // top
5377  $y -= ($this->cell_padding['T'] + $this->FontAscent);
5378  break;
5379  }
5380  case 'B': {
5381  // bottom
5382  $y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5383  break;
5384  }
5385  default:
5386  case 'C':
5387  case 'M': {
5388  // center
5389  $y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5390  break;
5391  }
5392  }
5393  break;
5394  }
5395  case 'D': {
5396  // font bottom
5397  switch ($valign) {
5398  case 'T': {
5399  // top
5400  $y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5401  break;
5402  }
5403  case 'B': {
5404  // bottom
5405  $y -= ($h - $this->cell_padding['B']);
5406  break;
5407  }
5408  default:
5409  case 'C':
5410  case 'M': {
5411  // center
5412  $y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5413  break;
5414  }
5415  }
5416  break;
5417  }
5418  case 'B': {
5419  // cell bottom
5420  $y -= $h;
5421  break;
5422  }
5423  case 'C':
5424  case 'M': {
5425  // cell center
5426  $y -= ($h / 2);
5427  break;
5428  }
5429  default:
5430  case 'T': {
5431  // cell top
5432  break;
5433  }
5434  }
5435  // text vertical alignment
5436  switch ($valign) {
5437  case 'T': {
5438  // top
5439  $yt = $y + $this->cell_padding['T'];
5440  break;
5441  }
5442  case 'B': {
5443  // bottom
5444  $yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5445  break;
5446  }
5447  default:
5448  case 'C':
5449  case 'M': {
5450  // center
5451  $yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5452  break;
5453  }
5454  }
5455  $basefonty = $yt + $this->FontAscent;
5456  if ($this->empty_string($w) OR ($w <= 0)) {
5457  if ($this->rtl) {
5458  $w = $x - $this->lMargin;
5459  } else {
5460  $w = $this->w - $this->rMargin - $x;
5461  }
5462  }
5463  $s = '';
5464  // fill and borders
5465  if (is_string($border) AND (strlen($border) == 4)) {
5466  // full border
5467  $border = 1;
5468  }
5469  if ($fill OR ($border == 1)) {
5470  if ($fill) {
5471  $op = ($border == 1) ? 'B' : 'f';
5472  } else {
5473  $op = 'S';
5474  }
5475  if ($this->rtl) {
5476  $xk = (($x - $w) * $k);
5477  } else {
5478  $xk = ($x * $k);
5479  }
5480  $s .= sprintf('%.2F %.2F %.2F %.2F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5481  }
5482  // draw borders
5483  $s .= $this->getCellBorder($x, $y, $w, $h, $border);
5484  if ($txt != '') {
5485  $txt2 = $txt;
5486  if ($this->isunicode) {
5487  if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
5488  $txt2 = $this->UTF8ToLatin1($txt2);
5489  } else {
5490  $unicode = $this->UTF8StringToArray($txt); // array of UTF-8 unicode values
5491  $unicode = $this->utf8Bidi($unicode, '', $this->tmprtl);
5492  if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
5493  // ---- Fix for bug #2977340 "Incorrect Thai characters position arrangement" ----
5494  // NOTE: this doesn't work with HTML justification
5495  // Symbols that could overlap on the font top (only works in LTR)
5496  $topchar = array(3611, 3613, 3615, 3650, 3651, 3652); // chars that extends on top
5497  $topsym = array(3633, 3636, 3637, 3638, 3639, 3655, 3656, 3657, 3658, 3659, 3660, 3661, 3662); // symbols with top position
5498  $numchars = count($unicode); // number of chars
5499  $unik = 0;
5500  $uniblock = array();
5501  $uniblock[$unik] = array();
5502  $uniblock[$unik][] = $unicode[0];
5503  // resolve overlapping conflicts by splitting the string in several parts
5504  for ($i = 1; $i < $numchars; ++$i) {
5505  // check if symbols overlaps at top
5506  if (in_array($unicode[$i], $topsym) AND (in_array($unicode[($i - 1)], $topsym) OR in_array($unicode[($i - 1)], $topchar))) {
5507  // move symbols to another array
5508  ++$unik;
5509  $uniblock[$unik] = array();
5510  $uniblock[$unik][] = $unicode[$i];
5511  ++$unik;
5512  $uniblock[$unik] = array();
5513  $unicode[$i] = 0x200b; // Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B)
5514  } else {
5515  $uniblock[$unik][] = $unicode[$i];
5516  }
5517  }
5518  // ---- END OF Fix for bug #2977340
5519  }
5520  $txt2 = $this->arrUTF8ToUTF16BE($unicode, false);
5521  }
5522  }
5523  $txt2 = $this->_escape($txt2);
5524  // get current text width (considering general font stretching and spacing)
5525  $txwidth = $this->GetStringWidth($txt);
5526  $width = $txwidth;
5527  // check for stretch mode
5528  if ($stretch > 0) {
5529  // calculate ratio between cell width and text width
5530  if ($width <= 0) {
5531  $ratio = 1;
5532  } else {
5533  $ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
5534  }
5535  // check if stretching is required
5536  if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
5537  // the text will be stretched to fit cell width
5538  if ($stretch > 2) {
5539  // set new character spacing
5540  $this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
5541  } else {
5542  // set new horizontal stretching
5543  $this->font_stretching *= $ratio;
5544  }
5545  // recalculate text width (the text fills the entire cell)
5546  $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5547  // reset alignment
5548  $align = '';
5549  }
5550  }
5551  if ($this->font_stretching != 100) {
5552  // apply font stretching
5553  $rs .= sprintf('BT %.2F Tz ET ', $this->font_stretching);
5554  }
5555  if ($this->font_spacing != 0) {
5556  // increase/decrease font spacing
5557  $rs .= sprintf('BT %.2F Tc ET ', ($this->font_spacing * $this->k));
5558  }
5559  if ($this->ColorFlag) {
5560  $s .= 'q '.$this->TextColor.' ';
5561  }
5562  // rendering mode
5563  $s .= sprintf('BT %d Tr %.2F w ET ', $this->textrendermode, $this->textstrokewidth);
5564  // count number of spaces
5565  $ns = substr_count($txt, chr(32));
5566  // Justification
5567  $spacewidth = 0;
5568  if (($align == 'J') AND ($ns > 0)) {
5569  if ($this->isUnicodeFont()) {
5570  // get string width without spaces
5571  $width = $this->GetStringWidth(str_replace(' ', '', $txt));
5572  // calculate average space width
5573  $spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / $this->FontSize;
5574  if ($this->font_stretching != 100) {
5575  // word spacing is affected by stretching
5576  $spacewidth /= ($this->font_stretching / 100);
5577  }
5578  // set word position to be used with TJ operator
5579  $txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%.3F', $spacewidth).' (', $txt2);
5580  $unicode_justification = true;
5581  } else {
5582  // get string width
5583  $width = $txwidth;
5584  // new space width
5585  $spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
5586  if ($this->font_stretching != 100) {
5587  // word spacing (Tw) is affected by stretching
5588  $spacewidth /= ($this->font_stretching / 100);
5589  }
5590  // set word spacing
5591  $rs .= sprintf('BT %.3F Tw ET ', $spacewidth);
5592  }
5593  $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5594  }
5595  // replace carriage return characters
5596  $txt2 = str_replace("\r", ' ', $txt2);
5597  switch ($align) {
5598  case 'C': {
5599  $dx = ($w - $width) / 2;
5600  break;
5601  }
5602  case 'R': {
5603  if ($this->rtl) {
5604  $dx = $this->cell_padding['R'];
5605  } else {
5606  $dx = $w - $width - $this->cell_padding['R'];
5607  }
5608  break;
5609  }
5610  case 'L': {
5611  if ($this->rtl) {
5612  $dx = $w - $width - $this->cell_padding['L'];
5613  } else {
5614  $dx = $this->cell_padding['L'];
5615  }
5616  break;
5617  }
5618  case 'J':
5619  default: {
5620  if ($this->rtl) {
5621  $dx = $this->cell_padding['R'];
5622  } else {
5623  $dx = $this->cell_padding['L'];
5624  }
5625  break;
5626  }
5627  }
5628  if ($this->rtl) {
5629  $xdx = $x - $dx - $width;
5630  } else {
5631  $xdx = $x + $dx;
5632  }
5633  $xdk = $xdx * $k;
5634  // print text
5635  $s .= sprintf('BT %.2F %.2F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
5636  if (isset($uniblock)) {
5637  // print overlapping characters as separate string
5638  $xshift = 0; // horizontal shift
5639  $ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
5640  $spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
5641  foreach ($uniblock as $uk => $uniarr) {
5642  if (($uk % 2) == 0) {
5643  // x space to skip
5644  if ($spacewidth != 0) {
5645  // justification shift
5646  $xshift += (count(array_keys($uniarr, 32)) * $spw);
5647  }
5648  $xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5649  } else {
5650  // character to print
5651  $topchr = $this->arrUTF8ToUTF16BE($uniarr, false);
5652  $topchr = $this->_escape($topchr);
5653  $s .= sprintf(' BT %.2F %.2F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
5654  }
5655  }
5656  }
5657  if ($this->underline) {
5658  $s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5659  }
5660  if ($this->linethrough) {
5661  $s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5662  }
5663  if ($this->overline) {
5664  $s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5665  }
5666  if ($this->ColorFlag) {
5667  $s .= ' Q';
5668  }
5669  if ($link) {
5670  $this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5671  }
5672  }
5673  // output cell
5674  if ($s) {
5675  // output cell
5676  $rs .= $s;
5677  if ($this->font_spacing != 0) {
5678  // reset font spacing mode
5679  $rs .= ' BT 0 Tc ET';
5680  }
5681  if ($this->font_stretching != 100) {
5682  // reset font stretching mode
5683  $rs .= ' BT 100 Tz ET';
5684  }
5685  }
5686  // reset word spacing
5687  if (!$this->isUnicodeFont() AND ($align == 'J')) {
5688  $rs .= ' BT 0 Tw ET';
5689  }
5690  // reset stretching and spacing
5691  $this->font_stretching = $prev_font_stretching;
5692  $this->font_spacing = $prev_font_spacing;
5693  $this->lasth = $h;
5694  if ($ln > 0) {
5695  //Go to the beginning of the next line
5696  $this->y = $y + $h + $this->cell_margin['B'];
5697  if ($ln == 1) {
5698  if ($this->rtl) {
5699  $this->x = $this->w - $this->rMargin;
5700  } else {
5701  $this->x = $this->lMargin;
5702  }
5703  }
5704  } else {
5705  // go left or right by case
5706  if ($this->rtl) {
5707  $this->x = $x - $w - $this->cell_margin['L'];
5708  } else {
5709  $this->x = $x + $w + $this->cell_margin['R'];
5710  }
5711  }
5712  $gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5713  $rs = $gstyles.$rs;
5714  $this->cell_padding = $prev_cell_padding;
5715  $this->cell_margin = $prev_cell_margin;
5716  return $rs;
5717  }
5718 
5732  protected function getCellBorder($x, $y, $w, $h, $brd) {
5733  $s = ''; // string to be returned
5734  if (empty($brd)) {
5735  return $s;
5736  }
5737  if ($brd == 1) {
5738  $brd = array('LRTB' => true);
5739  }
5740  // calculate coordinates for border
5741  $k = $this->k;
5742  if ($this->rtl) {
5743  $xeL = ($x - $w) * $k;
5744  $xeR = $x * $k;
5745  } else {
5746  $xeL = $x * $k;
5747  $xeR = ($x + $w) * $k;
5748  }
5749  $yeL = (($this->h - ($y + $h)) * $k);
5750  $yeT = (($this->h - $y) * $k);
5751  $xeT = $xeL;
5752  $xeB = $xeR;
5753  $yeR = $yeT;
5754  $yeB = $yeL;
5755  if (is_string($brd)) {
5756  // convert string to array
5757  $slen = strlen($brd);
5758  $newbrd = array();
5759  for ($i = 0; $i < $slen; ++$i) {
5760  $newbrd[$brd{$i}] = array('cap' => 'square', 'join' => 'miter');
5761  }
5762  $brd = $newbrd;
5763  }
5764  if (isset($brd['mode'])) {
5765  $mode = $brd['mode'];
5766  unset($brd['mode']);
5767  } else {
5768  $mode = 'normal';
5769  }
5770  foreach ($brd as $border => $style) {
5771  if (is_array($style) AND !empty($style)) {
5772  // apply border style
5773  $prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
5774  $s .= $this->SetLineStyle($style, true)."\n";
5775  }
5776  switch ($mode) {
5777  case 'ext': {
5778  $off = (($this->LineWidth / 2) * $k);
5779  $xL = $xeL - $off;
5780  $xR = $xeR + $off;
5781  $yT = $yeT + $off;
5782  $yL = $yeL - $off;
5783  $xT = $xL;
5784  $xB = $xR;
5785  $yR = $yT;
5786  $yB = $yL;
5787  $w += $this->LineWidth;
5788  $h += $this->LineWidth;
5789  break;
5790  }
5791  case 'int': {
5792  $off = ($this->LineWidth / 2) * $k;
5793  $xL = $xeL + $off;
5794  $xR = $xeR - $off;
5795  $yT = $yeT - $off;
5796  $yL = $yeL + $off;
5797  $xT = $xL;
5798  $xB = $xR;
5799  $yR = $yT;
5800  $yB = $yL;
5801  $w -= $this->LineWidth;
5802  $h -= $this->LineWidth;
5803  break;
5804  }
5805  case 'normal':
5806  default: {
5807  $xL = $xeL;
5808  $xT = $xeT;
5809  $xB = $xeB;
5810  $xR = $xeR;
5811  $yL = $yeL;
5812  $yT = $yeT;
5813  $yB = $yeB;
5814  $yR = $yeR;
5815  break;
5816  }
5817  }
5818  // draw borders by case
5819  if (strlen($border) == 4) {
5820  $s .= sprintf('%.2F %.2F %.2F %.2F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5821  } elseif (strlen($border) == 3) {
5822  if (strpos($border,'B') === false) { // LTR
5823  $s .= sprintf('%.2F %.2F m ', $xL, $yL);
5824  $s .= sprintf('%.2F %.2F l ', $xT, $yT);
5825  $s .= sprintf('%.2F %.2F l ', $xR, $yR);
5826  $s .= sprintf('%.2F %.2F l ', $xB, $yB);
5827  $s .= 'S ';
5828  } elseif (strpos($border,'L') === false) { // TRB
5829  $s .= sprintf('%.2F %.2F m ', $xT, $yT);
5830  $s .= sprintf('%.2F %.2F l ', $xR, $yR);
5831  $s .= sprintf('%.2F %.2F l ', $xB, $yB);
5832  $s .= sprintf('%.2F %.2F l ', $xL, $yL);
5833  $s .= 'S ';
5834  } elseif (strpos($border,'T') === false) { // RBL
5835  $s .= sprintf('%.2F %.2F m ', $xR, $yR);
5836  $s .= sprintf('%.2F %.2F l ', $xB, $yB);
5837  $s .= sprintf('%.2F %.2F l ', $xL, $yL);
5838  $s .= sprintf('%.2F %.2F l ', $xT, $yT);
5839  $s .= 'S ';
5840  } elseif (strpos($border,'R') === false) { // BLT
5841  $s .= sprintf('%.2F %.2F m ', $xB, $yB);
5842  $s .= sprintf('%.2F %.2F l ', $xL, $yL);
5843  $s .= sprintf('%.2F %.2F l ', $xT, $yT);
5844  $s .= sprintf('%.2F %.2F l ', $xR, $yR);
5845  $s .= 'S ';
5846  }
5847  } elseif (strlen($border) == 2) {
5848  if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5849  $s .= sprintf('%.2F %.2F m ', $xL, $yL);
5850  $s .= sprintf('%.2F %.2F l ', $xT, $yT);
5851  $s .= sprintf('%.2F %.2F l ', $xR, $yR);
5852  $s .= 'S ';
5853  } elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5854  $s .= sprintf('%.2F %.2F m ', $xT, $yT);
5855  $s .= sprintf('%.2F %.2F l ', $xR, $yR);
5856  $s .= sprintf('%.2F %.2F l ', $xB, $yB);
5857  $s .= 'S ';
5858  } elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5859  $s .= sprintf('%.2F %.2F m ', $xR, $yR);
5860  $s .= sprintf('%.2F %.2F l ', $xB, $yB);
5861  $s .= sprintf('%.2F %.2F l ', $xL, $yL);
5862  $s .= 'S ';
5863  } elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5864  $s .= sprintf('%.2F %.2F m ', $xB, $yB);
5865  $s .= sprintf('%.2F %.2F l ', $xL, $yL);
5866  $s .= sprintf('%.2F %.2F l ', $xT, $yT);
5867  $s .= 'S ';
5868  } elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5869  $s .= sprintf('%.2F %.2F m ', $xL, $yL);
5870  $s .= sprintf('%.2F %.2F l ', $xT, $yT);
5871  $s .= 'S ';
5872  $s .= sprintf('%.2F %.2F m ', $xR, $yR);
5873  $s .= sprintf('%.2F %.2F l ', $xB, $yB);
5874  $s .= 'S ';
5875  } elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5876  $s .= sprintf('%.2F %.2F m ', $xT, $yT);
5877  $s .= sprintf('%.2F %.2F l ', $xR, $yR);
5878  $s .= 'S ';
5879  $s .= sprintf('%.2F %.2F m ', $xB, $yB);
5880  $s .= sprintf('%.2F %.2F l ', $xL, $yL);
5881  $s .= 'S ';
5882  }
5883  } else { // strlen($border) == 1
5884  if (strpos($border,'L') !== false) { // L
5885  $s .= sprintf('%.2F %.2F m ', $xL, $yL);
5886  $s .= sprintf('%.2F %.2F l ', $xT, $yT);
5887  $s .= 'S ';
5888  } elseif (strpos($border,'T') !== false) { // T
5889  $s .= sprintf('%.2F %.2F m ', $xT, $yT);
5890  $s .= sprintf('%.2F %.2F l ', $xR, $yR);
5891  $s .= 'S ';
5892  } elseif (strpos($border,'R') !== false) { // R
5893  $s .= sprintf('%.2F %.2F m ', $xR, $yR);
5894  $s .= sprintf('%.2F %.2F l ', $xB, $yB);
5895  $s .= 'S ';
5896  } elseif (strpos($border,'B') !== false) { // B
5897  $s .= sprintf('%.2F %.2F m ', $xB, $yB);
5898  $s .= sprintf('%.2F %.2F l ', $xL, $yL);
5899  $s .= 'S ';
5900  }
5901  }
5902  if (is_array($style) AND !empty($style)) {
5903  // reset border style to previous value
5904  $s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
5905  }
5906  }
5907  return $s;
5908  }
5909 
5935  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) {
5936  $prev_cell_margin = $this->cell_margin;
5937  $prev_cell_padding = $this->cell_padding;
5938  // adjust internal padding
5939  $this->adjustCellPadding($border);
5940  $mc_padding = $this->cell_padding;
5941  $mc_margin = $this->cell_margin;
5942  $this->cell_padding['T'] = 0;
5943  $this->cell_padding['B'] = 0;
5944  $this->setCellMargins(0, 0, 0, 0);
5945  if ($this->empty_string($this->lasth) OR $reseth) {
5946  // reset row height
5947  $this->resetLastH();
5948  }
5949  if (!$this->empty_string($y)) {
5950  $this->SetY($y);
5951  } else {
5952  $y = $this->GetY();
5953  }
5954  $resth = 0;
5955  if ((!$this->InFooter) AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
5956  // spit cell in more pages/columns
5957  $newh = $this->PageBreakTrigger - $y;
5958  $resth = $h - $newh; // cell to be printed on the next page/column
5959  $h = $newh;
5960  }
5961  // get current page number
5962  $startpage = $this->page;
5963  // get current column
5964  $startcolumn = $this->current_column;
5965  if (!$this->empty_string($x)) {
5966  $this->SetX($x);
5967  } else {
5968  $x = $this->GetX();
5969  }
5970  // check page for no-write regions and adapt page margins if necessary
5971  $this->checkPageRegions(0, $x, $y);
5972  // apply margins
5973  $oy = $y + $mc_margin['T'];
5974  if ($this->rtl) {
5975  $ox = $this->w - $x - $mc_margin['R'];
5976  } else {
5977  $ox = $x + $mc_margin['L'];
5978  }
5979  $this->x = $ox;
5980  $this->y = $oy;
5981  // set width
5982  if ($this->empty_string($w) OR ($w <= 0)) {
5983  if ($this->rtl) {
5984  $w = $this->x - $this->lMargin - $mc_margin['L'];
5985  } else {
5986  $w = $this->w - $this->x - $this->rMargin - $mc_margin['R'];
5987  }
5988  }
5989  // store original margin values
5992  if ($this->rtl) {
5993  $this->rMargin = $this->w - $this->x;
5994  $this->lMargin = $this->x - $w;
5995  } else {
5996  $this->lMargin = $this->x;
5997  $this->rMargin = $this->w - $this->x - $w;
5998  }
5999  if ($autopadding) {
6000  // add top padding
6001  $this->y += $mc_padding['T'];
6002  }
6003  if ($ishtml) { // ******* Write HTML text
6004  $this->writeHTML($txt, true, 0, $reseth, true, $align);
6005  $nl = 1;
6006  } else { // ******* Write simple text
6007  // vertical alignment
6008  if ($maxh > 0) {
6009  // get text height
6010  $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
6011  if ($fitcell) {
6012  $prev_FontSizePt = $this->FontSizePt;
6013  // try to reduce font size to fit text on cell (use a quick search algorithm)
6014  $fmin = 1;
6015  $fmax = $this->FontSizePt;
6016  $prev_text_height = $text_height;
6017  $maxit = 100; // max number of iterations
6018  while ($maxit > 0) {
6019  $fmid = (($fmax + $fmin) / 2);
6020  $this->SetFontSize($fmid, false);
6021  $this->resetLastH();
6022  $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
6023  if (($text_height == $maxh) OR (($text_height < $maxh) AND ($fmin >= ($fmax - 0.01)))) {
6024  break;
6025  } elseif ($text_height < $maxh) {
6026  $fmin = $fmid;
6027  } else {
6028  $fmax = $fmid;
6029  }
6030  --$maxit;
6031  }
6032  $this->SetFontSize($this->FontSizePt);
6033  }
6034  if ($text_height < $maxh) {
6035  if ($valign == 'M') {
6036  // text vertically centered
6037  $this->y += (($maxh - $text_height) / 2);
6038  } elseif ($valign == 'B') {
6039  // text vertically aligned on bottom
6040  $this->y += ($maxh - $text_height);
6041  }
6042  }
6043  }
6044  $nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
6045  if ($fitcell) {
6046  // restore font size
6047  $this->SetFontSize($prev_FontSizePt);
6048  }
6049  }
6050  if ($autopadding) {
6051  // add bottom padding
6052  $this->y += $mc_padding['B'];
6053  }
6054  // Get end-of-text Y position
6055  $currentY = $this->y;
6056  // get latest page number
6057  $endpage = $this->page;
6058  if ($resth > 0) {
6059  $skip = ($endpage - $startpage);
6060  $tmpresth = $resth;
6061  while ($tmpresth > 0) {
6062  if ($skip <= 0) {
6063  // add a page (or trig AcceptPageBreak() for multicolumn mode)
6064  $this->checkPageBreak($this->PageBreakTrigger + 1);
6065  }
6066  if ($this->num_columns > 1) {
6067  $tmpresth -= ($this->h - $this->y - $this->bMargin);
6068  } else {
6069  $tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
6070  }
6071  --$skip;
6072  }
6073  $currentY = $this->y;
6074  $endpage = $this->page;
6075  }
6076  // get latest column
6077  $endcolumn = $this->current_column;
6078  if ($this->num_columns == 0) {
6079  $this->num_columns = 1;
6080  }
6081  // get border modes
6082  $border_start = $this->getBorderMode($border, $position='start');
6083  $border_end = $this->getBorderMode($border, $position='end');
6084  $border_middle = $this->getBorderMode($border, $position='middle');
6085  // design borders around HTML cells.
6086  for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
6087  $ccode = '';
6088  $this->setPage($page);
6089  if ($this->num_columns < 2) {
6090  // single-column mode
6091  $this->SetX($x);
6092  $this->y = $this->tMargin;
6093  }
6094  // account for margin changes
6095  if ($page > $startpage) {
6096  if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
6097  $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
6098  } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
6099  $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
6100  }
6101  }
6102  if ($startpage == $endpage) {
6103  // single page
6104  for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
6105  $this->selectColumn($column);
6106  if ($this->rtl) {
6107  $this->x -= $mc_margin['R'];
6108  } else {
6109  $this->x += $mc_margin['L'];
6110  }
6111  if ($startcolumn == $endcolumn) { // single column
6112  $cborder = $border;
6113  $h = max($h, ($currentY - $oy));
6114  $this->y = $oy;
6115  } elseif ($column == $startcolumn) { // first column
6116  $cborder = $border_start;
6117  $this->y = $oy;
6118  $h = $this->h - $this->y - $this->bMargin;
6119  } elseif ($column == $endcolumn) { // end column
6120  $cborder = $border_end;
6121  $h = $currentY - $this->y;
6122  if ($resth > $h) {
6123  $h = $resth;
6124  }
6125  } else { // middle column
6126  $cborder = $border_middle;
6127  $h = $this->h - $this->y - $this->bMargin;
6128  $resth -= $h;
6129  }
6130  $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6131  } // end for each column
6132  } elseif ($page == $startpage) { // first page
6133  for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
6134  $this->selectColumn($column);
6135  if ($this->rtl) {
6136  $this->x -= $mc_margin['R'];
6137  } else {
6138  $this->x += $mc_margin['L'];
6139  }
6140  if ($column == $startcolumn) { // first column
6141  $cborder = $border_start;
6142  $this->y = $oy;
6143  $h = $this->h - $this->y - $this->bMargin;
6144  } else { // middle column
6145  $cborder = $border_middle;
6146  $h = $this->h - $this->y - $this->bMargin;
6147  $resth -= $h;
6148  }
6149  $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6150  } // end for each column
6151  } elseif ($page == $endpage) { // last page
6152  for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
6153  $this->selectColumn($column);
6154  if ($this->rtl) {
6155  $this->x -= $mc_margin['R'];
6156  } else {
6157  $this->x += $mc_margin['L'];
6158  }
6159  if ($column == $endcolumn) {
6160  // end column
6161  $cborder = $border_end;
6162  $h = $currentY - $this->y;
6163  if ($resth > $h) {
6164  $h = $resth;
6165  }
6166  } else {
6167  // middle column
6168  $cborder = $border_middle;
6169  $h = $this->h - $this->y - $this->bMargin;
6170  $resth -= $h;
6171  }
6172  $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6173  } // end for each column
6174  } else { // middle page
6175  for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6176  $this->selectColumn($column);
6177  if ($this->rtl) {
6178  $this->x -= $mc_margin['R'];
6179  } else {
6180  $this->x += $mc_margin['L'];
6181  }
6182  $cborder = $border_middle;
6183  $h = $this->h - $this->y - $this->bMargin;
6184  $resth -= $h;
6185  $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6186  } // end for each column
6187  }
6188  if ($cborder OR $fill) {
6189  // draw border and fill
6190  if ($this->inxobj) {
6191  // we are inside an XObject template
6192  if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6193  $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6194  $pagemark = &$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6195  } else {
6196  $pagemark = &$this->xobjects[$this->xobjid]['intmrk'];
6197  }
6198  $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6199  $pstart = substr($pagebuff, 0, $pagemark);
6200  $pend = substr($pagebuff, $pagemark);
6201  $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6202  $pagemark += strlen($ccode);
6203  } else {
6204  if (end($this->transfmrk[$this->page]) !== false) {
6205  $pagemarkkey = key($this->transfmrk[$this->page]);
6206  $pagemark = &$this->transfmrk[$this->page][$pagemarkkey];
6207  } elseif ($this->InFooter) {
6208  $pagemark = &$this->footerpos[$this->page];
6209  } else {
6210  $pagemark = &$this->intmrk[$this->page];
6211  }
6212  $pagebuff = $this->getPageBuffer($this->page);
6213  $pstart = substr($pagebuff, 0, $pagemark);
6214  $pend = substr($pagebuff, $pagemark);
6215  $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6216  $pagemark += strlen($ccode);
6217  }
6218  }
6219  } // end for each page
6220  // Get end-of-cell Y position
6221  $currentY = $this->GetY();
6222  // restore original margin values
6223  $this->SetLeftMargin($lMargin);
6224  $this->SetRightMargin($rMargin);
6225  if ($ln > 0) {
6226  //Go to the beginning of the next line
6227  $this->SetY($currentY + $mc_margin['B']);
6228  if ($ln == 2) {
6229  $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6230  }
6231  } else {
6232  // go left or right by case
6233  $this->setPage($startpage);
6234  $this->y = $y;
6235  $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6236  }
6237  $this->setContentMark();
6238  $this->cell_padding = $prev_cell_padding;
6239  $this->cell_margin = $prev_cell_margin;
6240  return $nl;
6241  }
6242 
6251  protected function getBorderMode($brd, $position='start') {
6252  if ((!$this->opencell) OR empty($brd)) {
6253  return $brd;
6254  }
6255  if ($brd == 1) {
6256  $brd = 'LTRB';
6257  }
6258  if (is_string($brd)) {
6259  // convert string to array
6260  $slen = strlen($brd);
6261  $newbrd = array();
6262  for ($i = 0; $i < $slen; ++$i) {
6263  $newbrd[$brd{$i}] = array('cap' => 'square', 'join' => 'miter');
6264  }
6265  $brd = $newbrd;
6266  }
6267  foreach ($brd as $border => $style) {
6268  switch ($position) {
6269  case 'start': {
6270  if (strpos($border, 'B') !== false) {
6271  // remove bottom line
6272  $newkey = str_replace('B', '', $border);
6273  if (strlen($newkey) > 0) {
6274  $brd[$newkey] = $style;
6275  }
6276  unset($brd[$border]);
6277  }
6278  break;
6279  }
6280  case 'middle': {
6281  if (strpos($border, 'B') !== false) {
6282  // remove bottom line
6283  $newkey = str_replace('B', '', $border);
6284  if (strlen($newkey) > 0) {
6285  $brd[$newkey] = $style;
6286  }
6287  unset($brd[$border]);
6288  $border = $newkey;
6289  }
6290  if (strpos($border, 'T') !== false) {
6291  // remove bottom line
6292  $newkey = str_replace('T', '', $border);
6293  if (strlen($newkey) > 0) {
6294  $brd[$newkey] = $style;
6295  }
6296  unset($brd[$border]);
6297  }
6298  break;
6299  }
6300  case 'end': {
6301  if (strpos($border, 'T') !== false) {
6302  // remove bottom line
6303  $newkey = str_replace('T', '', $border);
6304  if (strlen($newkey) > 0) {
6305  $brd[$newkey] = $style;
6306  }
6307  unset($brd[$border]);
6308  }
6309  break;
6310  }
6311  }
6312  }
6313  return $brd;
6314  }
6315 
6329  public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6330  if ($txt === '') {
6331  // empty string
6332  return 1;
6333  }
6334  // adjust internal padding
6335  $prev_cell_padding = $this->cell_padding;
6336  $prev_lasth = $this->lasth;
6337  if (is_array($cellpadding)) {
6338  $this->cell_padding = $cellpadding;
6339  }
6340  $this->adjustCellPadding($border);
6341  if ($this->empty_string($w) OR ($w <= 0)) {
6342  if ($this->rtl) {
6343  $w = $this->x - $this->lMargin;
6344  } else {
6345  $w = $this->w - $this->rMargin - $this->x;
6346  }
6347  }
6348  $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6349  if ($reseth) {
6350  // reset row height
6351  $this->resetLastH();
6352  }
6353  $lines = 1;
6354  $sum = 0;
6355  $chars = $this->utf8Bidi($this->UTF8StringToArray($txt), $txt, $this->tmprtl);
6356  $charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6357  $length = count($chars);
6358  $lastSeparator = -1;
6359  for ($i = 0; $i < $length; ++$i) {
6360  $charWidth = $charsWidth[$i];
6361  if (preg_match($this->re_spaces, $this->unichr($chars[$i]))) {
6362  $lastSeparator = $i;
6363  }
6364  if ((($sum + $charWidth) > $wmax) OR ($chars[$i] == 10)) {
6365  ++$lines;
6366  if ($lastSeparator != -1) {
6367  $i = $lastSeparator;
6368  $lastSeparator = -1;
6369  $sum = 0;
6370  } else {
6371  $sum = $charWidth;
6372  }
6373  } else {
6374  $sum += $charWidth;
6375  }
6376  }
6377  if ($chars[($length - 1)] == 10) {
6378  --$lines;
6379  }
6380  $this->cell_padding = $prev_cell_padding;
6381  $this->lasth = $prev_lasth;
6382  return $lines;
6383  }
6384 
6432  public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6433  // adjust internal padding
6434  $prev_cell_padding = $this->cell_padding;
6435  $prev_lasth = $this->lasth;
6436  if (is_array($cellpadding)) {
6437  $this->cell_padding = $cellpadding;
6438  }
6439  $this->adjustCellPadding($border);
6440  $lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6441  $height = $lines * ($this->FontSize * $this->cell_height_ratio);
6442  if ($autopadding) {
6443  // add top and bottom padding
6444  $height += ($this->cell_padding['T'] + $this->cell_padding['B']);
6445  }
6446  $this->cell_padding = $prev_cell_padding;
6447  $this->lasth = $prev_lasth;
6448  return $height;
6449  }
6450 
6469  public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
6470  // check page for no-write regions and adapt page margins if necessary
6471  $this->checkPageRegions($h);
6472  if (strlen($txt) == 0) {
6473  // fix empty text
6474  $txt = ' ';
6475  }
6476  if ($margin === '') {
6477  // set default margins
6478  $margin = $this->cell_margin;
6479  }
6480  // remove carriage returns
6481  $s = str_replace("\r", '', $txt);
6482  // check if string contains arabic text
6483  if (preg_match($this->unicode->uni_RE_PATTERN_ARABIC, $s)) {
6484  $arabic = true;
6485  } else {
6486  $arabic = false;
6487  }
6488  // check if string contains RTL text
6489  if ($arabic OR ($this->tmprtl == 'R') OR preg_match($this->unicode->uni_RE_PATTERN_RTL, $s)) {
6490  $rtlmode = true;
6491  } else {
6492  $rtlmode = false;
6493  }
6494  // get a char width
6495  $chrwidth = $this->GetCharWidth('.');
6496  // get array of unicode values
6497  $chars = $this->UTF8StringToArray($s);
6498  // get array of chars
6499  $uchars = $this->UTF8ArrayToUniArray($chars);
6500  // get the number of characters
6501  $nb = count($chars);
6502  // replacement for SHY character (minus symbol)
6503  $shy_replacement = 45;
6504  $shy_replacement_char = $this->unichr($shy_replacement);
6505  // widht for SHY replacement
6506  $shy_replacement_width = $this->GetCharWidth($shy_replacement);
6507  // max Y
6508  $maxy = $this->y + $maxh - $h - $this->cell_padding['T'] - $this->cell_padding['B'];
6509  // calculate remaining line width ($w)
6510  if ($this->rtl) {
6511  $w = $this->x - $this->lMargin;
6512  } else {
6513  $w = $this->w - $this->rMargin - $this->x;
6514  }
6515  // max column width
6516  $wmax = $w - $wadj;
6517  if (!$firstline) {
6518  $wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
6519  }
6520  if ((!$firstline) AND (($chrwidth > $wmax) OR ($this->GetCharWidth($chars[0]) > $wmax))) {
6521  // a single character do not fit on column
6522  return '';
6523  }
6524  // minimum row height
6525  $row_height = max($h, $this->FontSize * $this->cell_height_ratio);
6526  $start_page = $this->page;
6527  $i = 0; // character position
6528  $j = 0; // current starting position
6529  $sep = -1; // position of the last blank space
6530  $shy = false; // true if the last blank is a soft hypen (SHY)
6531  $l = 0; // current string length
6532  $nl = 0; //number of lines
6533  $linebreak = false;
6534  $pc = 0; // previous character
6535  // for each character
6536  while ($i < $nb) {
6537  if (($maxh > 0) AND ($this->y >= $maxy) ) {
6538  break;
6539  }
6540  //Get the current character
6541  $c = $chars[$i];
6542  if ($c == 10) { // 10 = "\n" = new line
6543  //Explicit line break
6544  if ($align == 'J') {
6545  if ($this->rtl) {
6546  $talign = 'R';
6547  } else {
6548  $talign = 'L';
6549  }
6550  } else {
6551  $talign = $align;
6552  }
6553  $tmpstr = $this->UniArrSubString($uchars, $j, $i);
6554  if ($firstline) {
6555  $startx = $this->x;
6556  $tmparr = array_slice($chars, $j, ($i - $j));
6557  if ($rtlmode) {
6558  $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
6559  }
6560  $linew = $this->GetArrStringWidth($tmparr);
6561  unset($tmparr);
6562  if ($this->rtl) {
6563  $this->endlinex = $startx - $linew;
6564  } else {
6565  $this->endlinex = $startx + $linew;
6566  }
6567  $w = $linew;
6568  $tmpcellpadding = $this->cell_padding;
6569  if ($maxh == 0) {
6570  $this->SetCellPadding(0);
6571  }
6572  }
6573  if ($firstblock AND $this->isRTLTextDir()) {
6574  $tmpstr = $this->stringRightTrim($tmpstr);
6575  }
6576  // Skip newlines at the begining of a page or column
6577  if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
6578  $this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6579  }
6580  unset($tmpstr);
6581  if ($firstline) {
6582  $this->cell_padding = $tmpcellpadding;
6583  return ($this->UniArrSubString($uchars, $i));
6584  }
6585  ++$nl;
6586  $j = $i + 1;
6587  $l = 0;
6588  $sep = -1;
6589  $shy = false;
6590  // account for margin changes
6591  if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND (!$this->InFooter)) {
6592  $this->AcceptPageBreak();
6593  if ($this->rtl) {
6594  $this->x -= $margin['R'];
6595  } else {
6596  $this->x += $margin['L'];
6597  }
6598  $this->lMargin += $margin['L'];
6599  $this->rMargin += $margin['R'];
6600  }
6601  $w = $this->getRemainingWidth();
6602  $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6603  } else {
6604  // 160 is the non-breaking space.
6605  // 173 is SHY (Soft Hypen).
6606  // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6607  // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6608  // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6609  if (($c != 160) AND (($c == 173) OR preg_match($this->re_spaces, $this->unichr($c)))) {
6610  // update last blank space position
6611  $sep = $i;
6612  // check if is a SHY
6613  if ($c == 173) {
6614  $shy = true;
6615  if ($pc == 45) {
6616  $tmp_shy_replacement_width = 0;
6617  $tmp_shy_replacement_char = '';
6618  } else {
6619  $tmp_shy_replacement_width = $shy_replacement_width;
6620  $tmp_shy_replacement_char = $shy_replacement_char;
6621  }
6622  } else {
6623  $shy = false;
6624  }
6625  }
6626  // update string length
6627  if ($this->isUnicodeFont() AND ($arabic)) {
6628  // with bidirectional algorithm some chars may be changed affecting the line length
6629  // *** very slow ***
6630  $l = $this->GetArrStringWidth($this->utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl));
6631  } else {
6632  $l += $this->GetCharWidth($c);
6633  }
6634  if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) ) {
6635  // we have reached the end of column
6636  if ($sep == -1) {
6637  // check if the line was already started
6638  if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $chrwidth)))
6639  OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $chrwidth)))) {
6640  // print a void cell and go to next line
6641  $this->Cell($w, $h, '', 0, 1);
6642  $linebreak = true;
6643  if ($firstline) {
6644  return ($this->UniArrSubString($uchars, $j));
6645  }
6646  } else {
6647  // truncate the word because do not fit on column
6648  $tmpstr = $this->UniArrSubString($uchars, $j, $i);
6649  if ($firstline) {
6650  $startx = $this->x;
6651  $tmparr = array_slice($chars, $j, ($i - $j));
6652  if ($rtlmode) {
6653  $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
6654  }
6655  $linew = $this->GetArrStringWidth($tmparr);
6656  unset($tmparr);
6657  if ($this->rtl) {
6658  $this->endlinex = $startx - $linew;
6659  } else {
6660  $this->endlinex = $startx + $linew;
6661  }
6662  $w = $linew;
6663  $tmpcellpadding = $this->cell_padding;
6664  if ($maxh == 0) {
6665  $this->SetCellPadding(0);
6666  }
6667  }
6668  if ($firstblock AND $this->isRTLTextDir()) {
6669  $tmpstr = $this->stringRightTrim($tmpstr);
6670  }
6671  $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6672  unset($tmpstr);
6673  if ($firstline) {
6674  $this->cell_padding = $tmpcellpadding;
6675  return ($this->UniArrSubString($uchars, $i));
6676  }
6677  $j = $i;
6678  --$i;
6679  }
6680  } else {
6681  // word wrapping
6682  if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6683  $endspace = 1;
6684  } else {
6685  $endspace = 0;
6686  }
6687  if ($shy) {
6688  // add hypen (minus symbol) at the end of the line
6689  $shy_width = $tmp_shy_replacement_width;
6690  if ($this->rtl) {
6691  $shy_char_left = $tmp_shy_replacement_char;
6692  $shy_char_right = '';
6693  } else {
6694  $shy_char_left = '';
6695  $shy_char_right = $tmp_shy_replacement_char;
6696  }
6697  } else {
6698  $shy_width = 0;
6699  $shy_char_left = '';
6700  $shy_char_right = '';
6701  }
6702  $tmpstr = $this->UniArrSubString($uchars, $j, ($sep + $endspace));
6703  if ($firstline) {
6704  $startx = $this->x;
6705  $tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6706  if ($rtlmode) {
6707  $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
6708  }
6709  $linew = $this->GetArrStringWidth($tmparr);
6710  unset($tmparr);
6711  if ($this->rtl) {
6712  $this->endlinex = $startx - $linew - $shy_width;
6713  } else {
6714  $this->endlinex = $startx + $linew + $shy_width;
6715  }
6716  $w = $linew;
6717  $tmpcellpadding = $this->cell_padding;
6718  if ($maxh == 0) {
6719  $this->SetCellPadding(0);
6720  }
6721  }
6722  // print the line
6723  if ($firstblock AND $this->isRTLTextDir()) {
6724  $tmpstr = $this->stringRightTrim($tmpstr);
6725  }
6726  $this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6727  unset($tmpstr);
6728  if ($firstline) {
6729  // return the remaining text
6730  $this->cell_padding = $tmpcellpadding;
6731  return ($this->UniArrSubString($uchars, ($sep + $endspace)));
6732  }
6733  $i = $sep;
6734  $sep = -1;
6735  $shy = false;
6736  $j = ($i+1);
6737  }
6738  // account for margin changes
6739  if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND (!$this->InFooter)) {
6740  $this->AcceptPageBreak();
6741  if ($this->rtl) {
6742  $this->x -= $margin['R'];
6743  } else {
6744  $this->x += $margin['L'];
6745  }
6746  $this->lMargin += $margin['L'];
6747  $this->rMargin += $margin['R'];
6748  }
6749  $w = $this->getRemainingWidth();
6750  $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6751  if ($linebreak) {
6752  $linebreak = false;
6753  } else {
6754  ++$nl;
6755  $l = 0;
6756  }
6757  }
6758  }
6759  // save last character
6760  $pc = $c;
6761  ++$i;
6762  } // end while i < nb
6763  // print last substring (if any)
6764  if ($l > 0) {
6765  switch ($align) {
6766  case 'J':
6767  case 'C': {
6768  $w = $w;
6769  break;
6770  }
6771  case 'L': {
6772  if ($this->rtl) {
6773  $w = $w;
6774  } else {
6775  $w = $l;
6776  }
6777  break;
6778  }
6779  case 'R': {
6780  if ($this->rtl) {
6781  $w = $l;
6782  } else {
6783  $w = $w;
6784  }
6785  break;
6786  }
6787  default: {
6788  $w = $l;
6789  break;
6790  }
6791  }
6792  $tmpstr = $this->UniArrSubString($uchars, $j, $nb);
6793  if ($firstline) {
6794  $startx = $this->x;
6795  $tmparr = array_slice($chars, $j, ($nb - $j));
6796  if ($rtlmode) {
6797  $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
6798  }
6799  $linew = $this->GetArrStringWidth($tmparr);
6800  unset($tmparr);
6801  if ($this->rtl) {
6802  $this->endlinex = $startx - $linew;
6803  } else {
6804  $this->endlinex = $startx + $linew;
6805  }
6806  $w = $linew;
6807  $tmpcellpadding = $this->cell_padding;
6808  if ($maxh == 0) {
6809  $this->SetCellPadding(0);
6810  }
6811  }
6812  if ($firstblock AND $this->isRTLTextDir()) {
6813  $tmpstr = $this->stringRightTrim($tmpstr);
6814  }
6815  $this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6816  unset($tmpstr);
6817  if ($firstline) {
6818  $this->cell_padding = $tmpcellpadding;
6819  return ($this->UniArrSubString($uchars, $nb));
6820  }
6821  ++$nl;
6822  }
6823  if ($firstline) {
6824  return '';
6825  }
6826  return $nl;
6827  }
6828 
6834  protected function getRemainingWidth() {
6835  $this->checkPageRegions();
6836  if ($this->rtl) {
6837  return ($this->x - $this->lMargin);
6838  } else {
6839  return ($this->w - $this->rMargin - $this->x);
6840  }
6841  }
6842 
6851  public function UTF8ArrSubString($strarr, $start='', $end='') {
6852  if (strlen($start) == 0) {
6853  $start = 0;
6854  }
6855  if (strlen($end) == 0) {
6856  $end = count($strarr);
6857  }
6858  $string = '';
6859  for ($i=$start; $i < $end; ++$i) {
6860  $string .= $this->unichr($strarr[$i]);
6861  }
6862  return $string;
6863  }
6864 
6874  public function UniArrSubString($uniarr, $start='', $end='') {
6875  if (strlen($start) == 0) {
6876  $start = 0;
6877  }
6878  if (strlen($end) == 0) {
6879  $end = count($uniarr);
6880  }
6881  $string = '';
6882  for ($i=$start; $i < $end; ++$i) {
6883  $string .= $uniarr[$i];
6884  }
6885  return $string;
6886  }
6887 
6895  public function UTF8ArrayToUniArray($ta) {
6896  return array_map(array($this, 'unichr'), $ta);
6897  }
6898 
6907  public function unichr($c) {
6908  if (!$this->isunicode) {
6909  return chr($c);
6910  } elseif ($c <= 0x7F) {
6911  // one byte
6912  return chr($c);
6913  } elseif ($c <= 0x7FF) {
6914  // two bytes
6915  return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F);
6916  } elseif ($c <= 0xFFFF) {
6917  // three bytes
6918  return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
6919  } elseif ($c <= 0x10FFFF) {
6920  // four bytes
6921  return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
6922  } else {
6923  return '';
6924  }
6925  }
6926 
6934  public function getImageFileType($imgfile, $iminfo=array()) {
6935  $type = '';
6936  if (isset($iminfo['mime']) AND !empty($iminfo['mime'])) {
6937  $mime = explode('/', $iminfo['mime']);
6938  if ((count($mime) > 1) AND ($mime[0] == 'image') AND (!empty($mime[1]))) {
6939  $type = strtolower(trim($mime[1]));
6940  }
6941  }
6942  if (empty($type)) {
6943  $fileinfo = pathinfo($imgfile);
6944  if (isset($fileinfo['extension']) AND (!$this->empty_string($fileinfo['extension']))) {
6945  $type = strtolower(trim($fileinfo['extension']));
6946  }
6947  }
6948  if ($type == 'jpg') {
6949  $type = 'jpeg';
6950  }
6951  return $type;
6952  }
6953 
6964  protected function fitBlock(&$w, &$h, &$x, &$y, $fitonpage=false) {
6965  // resize the block to be vertically contained on a single page or single column
6966  if ($fitonpage OR $this->AutoPageBreak) {
6967  $ratio_wh = ($w / $h);
6968  if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
6969  $h = $this->PageBreakTrigger - $this->tMargin;
6970  $w = ($h * $ratio_wh);
6971  }
6972  // resize the block to be horizontally contained on a single page or single column
6973  if ($fitonpage) {
6974  $maxw = ($this->w - $this->lMargin - $this->rMargin);
6975  if ($w > $maxw) {
6976  $w = $maxw;
6977  $h = ($w / $ratio_wh);
6978  }
6979  }
6980  }
6981  // Check whether we need a new page or new column first as this does not fit
6982  $prev_x = $this->x;
6983  $prev_y = $this->y;
6984  if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
6985  $y = $this->y;
6986  if ($this->rtl) {
6987  $x += ($prev_x - $this->x);
6988  } else {
6989  $x += ($this->x - $prev_x);
6990  }
6991  }
6992  // resize the block to be contained on the remaining available page or column space
6993  if ($fitonpage) {
6994  $ratio_wh = ($w / $h);
6995  if (($y + $h) > $this->PageBreakTrigger) {
6996  $h = $this->PageBreakTrigger - $y;
6997  $w = ($h * $ratio_wh);
6998  }
6999  if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
7000  $w = $this->w - $this->rMargin - $x;
7001  $h = ($w / $ratio_wh);
7002  } elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
7003  $w = $x - $this->lMargin;
7004  $h = ($w / $ratio_wh);
7005  }
7006  }
7007  }
7008 
7041  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) {
7042  if ($x === '') {
7043  $x = $this->x;
7044  }
7045  if ($y === '') {
7046  $y = $this->y;
7047  }
7048  // check page for no-write regions and adapt page margins if necessary
7049  $this->checkPageRegions($h, $x, $y);
7050  $cached_file = false; // true when the file is cached
7051  // get image dimensions
7052  $imsize = @getimagesize($file);
7053  if ($imsize === FALSE) {
7054  // try to encode spaces on filename
7055  $file = str_replace(' ', '%20', $file);
7056  $imsize = @getimagesize($file);
7057  if ($imsize === FALSE) {
7058  if (function_exists('curl_init')) {
7059  // try to get remote file data using cURL
7060  $cs = curl_init(); // curl session
7061  curl_setopt($cs, CURLOPT_URL, $file);
7062  curl_setopt($cs, CURLOPT_BINARYTRANSFER, true);
7063  curl_setopt($cs, CURLOPT_FAILONERROR, true);
7064  curl_setopt($cs, CURLOPT_RETURNTRANSFER, true);
7065  curl_setopt($cs, CURLOPT_CONNECTTIMEOUT, 5);
7066  curl_setopt($cs, CURLOPT_TIMEOUT, 30);
7067  $imgdata = curl_exec($cs);
7068  curl_close($cs);
7069  if($imgdata !== FALSE) {
7070  // copy image to cache
7071  $file = tempnam(K_PATH_CACHE, 'img_');
7072  $fp = fopen($file, 'w');
7073  fwrite($fp, $imgdata);
7074  fclose($fp);
7075  unset($imgdata);
7076  $cached_file = true;
7077  $imsize = @getimagesize($file);
7078  if ($imsize === FALSE) {
7079  unlink($file);
7080  $cached_file = false;
7081  }
7082  }
7083  } elseif (($w > 0) AND ($h > 0)) {
7084  // get measures from specified data
7085  $pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
7086  $ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
7087  $imsize = array($pw, $ph);
7088  }
7089  }
7090  }
7091  if ($imsize === FALSE) {
7092  $this->Error('[Image] Unable to get image: '.$file);
7093  }
7094  // get original image width and height in pixels
7095  list($pixw, $pixh) = $imsize;
7096  // calculate image width and height on document
7097  if (($w <= 0) AND ($h <= 0)) {
7098  // convert image size to document unit
7099  $w = $this->pixelsToUnits($pixw);
7100  $h = $this->pixelsToUnits($pixh);
7101  } elseif ($w <= 0) {
7102  $w = $h * $pixw / $pixh;
7103  } elseif ($h <= 0) {
7104  $h = $w * $pixh / $pixw;
7105  } elseif ($fitbox AND ($w > 0) AND ($h > 0)) {
7106  // scale image dimensions proportionally to fit within the ($w, $h) box
7107  if ((($w * $pixh) / ($h * $pixw)) < 1) {
7108  $h = $w * $pixh / $pixw;
7109  } else {
7110  $w = $h * $pixw / $pixh;
7111  }
7112  }
7113  // fit the image on available space
7114  $this->fitBlock($w, $h, $x, $y, $fitonpage);
7115  // calculate new minimum dimensions in pixels
7116  $neww = round($w * $this->k * $dpi / $this->dpi);
7117  $newh = round($h * $this->k * $dpi / $this->dpi);
7118  // check if resize is necessary (resize is used only to reduce the image)
7119  $newsize = ($neww * $newh);
7120  $pixsize = ($pixw * $pixh);
7121  if (intval($resize) == 2) {
7122  $resize = true;
7123  } elseif ($newsize >= $pixsize) {
7124  $resize = false;
7125  }
7126  // check if image has been already added on document
7127  $newimage = true;
7128  if (in_array($file, $this->imagekeys)) {
7129  $newimage = false;
7130  // get existing image data
7131  $info = $this->getImageBuffer($file);
7132  // check if the newer image is larger
7133  $oldsize = ($info['w'] * $info['h']);
7134  if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7135  $newimage = true;
7136  }
7137  }
7138  if ($newimage) {
7139  //First use of image, get info
7140  $type = strtolower($type);
7141  if ($type == '') {
7142  $type = $this->getImageFileType($file, $imsize);
7143  } elseif ($type == 'jpg') {
7144  $type = 'jpeg';
7145  }
7146  $mqr = $this->get_mqr();
7147  $this->set_mqr(false);
7148  // Specific image handlers
7149  $mtd = '_parse'.$type;
7150  // GD image handler function
7151  $gdfunction = 'imagecreatefrom'.$type;
7152  $info = false;
7153  if ((method_exists($this, $mtd)) AND (!($resize AND function_exists($gdfunction)))) {
7154  // TCPDF image functions
7155  $info = $this->$mtd($file);
7156  if ($info == 'pngalpha') {
7157  return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign);
7158  }
7159  }
7160  if (!$info) {
7161  if (function_exists($gdfunction)) {
7162  // GD library
7163  $img = $gdfunction($file);
7164  if ($resize) {
7165  $imgr = imagecreatetruecolor($neww, $newh);
7166  if (($type == 'gif') OR ($type == 'png')) {
7167  $imgr = $this->_setGDImageTransparency($imgr, $img);
7168  }
7169  imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7170  if (($type == 'gif') OR ($type == 'png')) {
7171  $info = $this->_toPNG($imgr);
7172  } else {
7173  $info = $this->_toJPEG($imgr);
7174  }
7175  } else {
7176  if (($type == 'gif') OR ($type == 'png')) {
7177  $info = $this->_toPNG($img);
7178  } else {
7179  $info = $this->_toJPEG($img);
7180  }
7181  }
7182  } elseif (extension_loaded('imagick')) {
7183  // ImageMagick library
7184  $img = new Imagick();
7185  if ($type == 'SVG') {
7186  // get SVG file content
7187  $svgimg = file_get_contents($file);
7188  // get width and height
7189  $regs = array();
7190  if (preg_match('/<svg([^>]*)>/si', $svgimg, $regs)) {
7191  $svgtag = $regs[1];
7192  $tmp = array();
7193  if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7194  $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7195  $owu = sprintf('%.3F', ($ow * $dpi / 72)).$this->pdfunit;
7196  $svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7197  } else {
7198  $ow = $w;
7199  }
7200  $tmp = array();
7201  if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7202  $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7203  $ohu = sprintf('%.3F', ($oh * $dpi / 72)).$this->pdfunit;
7204  $svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7205  } else {
7206  $oh = $h;
7207  }
7208  $tmp = array();
7209  if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7210  $vbw = ($ow * $this->imgscale * $this->k);
7211  $vbh = ($oh * $this->imgscale * $this->k);
7212  $vbox = sprintf(' viewBox="0 0 %.3F %.3F" ', $vbw, $vbh);
7213  $svgtag = $vbox.$svgtag;
7214  }
7215  $svgimg = preg_replace('/<svg([^>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7216  }
7217  $img->readImageBlob($svgimg);
7218  } else {
7219  $img->readImage($file);
7220  }
7221  if ($resize) {
7222  $img->resizeImage($neww, $newh, 10, 1, false);
7223  }
7224  $img->setCompressionQuality($this->jpeg_quality);
7225  $img->setImageFormat('jpeg');
7226  $tempname = tempnam(K_PATH_CACHE, 'jpg_');
7227  $img->writeImage($tempname);
7228  $info = $this->_parsejpeg($tempname);
7229  unlink($tempname);
7230  $img->destroy();
7231  } else {
7232  return;
7233  }
7234  }
7235  if ($info === false) {
7236  //If false, we cannot process image
7237  return;
7238  }
7239  $this->set_mqr($mqr);
7240  if ($ismask) {
7241  // force grayscale
7242  $info['cs'] = 'DeviceGray';
7243  }
7244  $info['i'] = $this->numimages;
7245  if (!in_array($file, $this->imagekeys)) {
7246  ++$info['i'];
7247  }
7248  if ($imgmask !== false) {
7249  $info['masked'] = $imgmask;
7250  }
7251  // add image to document
7252  $this->setImageBuffer($file, $info);
7253  }
7254  if ($cached_file) {
7255  // remove cached file
7256  unlink($file);
7257  }
7258  // set alignment
7259  $this->img_rb_y = $y + $h;
7260  // set alignment
7261  if ($this->rtl) {
7262  if ($palign == 'L') {
7263  $ximg = $this->lMargin;
7264  } elseif ($palign == 'C') {
7265  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7266  } elseif ($palign == 'R') {
7267  $ximg = $this->w - $this->rMargin - $w;
7268  } else {
7269  $ximg = $x - $w;
7270  }
7271  $this->img_rb_x = $ximg;
7272  } else {
7273  if ($palign == 'L') {
7274  $ximg = $this->lMargin;
7275  } elseif ($palign == 'C') {
7276  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7277  } elseif ($palign == 'R') {
7278  $ximg = $this->w - $this->rMargin - $w;
7279  } else {
7280  $ximg = $x;
7281  }
7282  $this->img_rb_x = $ximg + $w;
7283  }
7284  if ($ismask OR $hidden) {
7285  // image is not displayed
7286  return $info['i'];
7287  }
7288  $xkimg = $ximg * $this->k;
7289  $this->_out(sprintf('q %.2F 0 0 %.2F %.2F %.2F cm /I%u Do Q', ($w * $this->k), ($h * $this->k), $xkimg, (($this->h - ($y + $h)) * $this->k), $info['i']));
7290  if (!empty($border)) {
7291  $bx = $this->x;
7292  $by = $this->y;
7293  $this->x = $ximg;
7294  if ($this->rtl) {
7295  $this->x += $w;
7296  }
7297  $this->y = $y;
7298  $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7299  $this->x = $bx;
7300  $this->y = $by;
7301  }
7302  if ($link) {
7303  $this->Link($ximg, $y, $w, $h, $link, 0);
7304  }
7305  // set pointer to align the next text/objects
7306  switch($align) {
7307  case 'T': {
7308  $this->y = $y;
7309  $this->x = $this->img_rb_x;
7310  break;
7311  }
7312  case 'M': {
7313  $this->y = $y + round($h/2);
7314  $this->x = $this->img_rb_x;
7315  break;
7316  }
7317  case 'B': {
7318  $this->y = $this->img_rb_y;
7319  $this->x = $this->img_rb_x;
7320  break;
7321  }
7322  case 'N': {
7323  $this->SetY($this->img_rb_y);
7324  break;
7325  }
7326  default:{
7327  break;
7328  }
7329  }
7330  $this->endlinex = $this->img_rb_x;
7331  if ($this->inxobj) {
7332  // we are inside an XObject template
7333  $this->xobjects[$this->xobjid]['images'][] = $info['i'];
7334  }
7335  return $info['i'];
7336  }
7337 
7343  public function set_mqr($mqr) {
7344  if(!defined('PHP_VERSION_ID')) {
7345  $version = PHP_VERSION;
7346  define('PHP_VERSION_ID', (($version{0} * 10000) + ($version{2} * 100) + $version{4}));
7347  }
7348  if (PHP_VERSION_ID < 50300) {
7349  @set_magic_quotes_runtime($mqr);
7350  }
7351  }
7352 
7358  public function get_mqr() {
7359  if(!defined('PHP_VERSION_ID')) {
7360  $version = PHP_VERSION;
7361  define('PHP_VERSION_ID', (($version{0} * 10000) + ($version{2} * 100) + $version{4}));
7362  }
7363  if (PHP_VERSION_ID < 50300) {
7364  return @get_magic_quotes_runtime();
7365  }
7366  return 0;
7367  }
7368 
7377  protected function _toJPEG($image) {
7378  $tempname = tempnam(K_PATH_CACHE, 'jpg_');
7379  imagejpeg($image, $tempname, $this->jpeg_quality);
7380  imagedestroy($image);
7381  $retvars = $this->_parsejpeg($tempname);
7382  // tidy up by removing temporary image
7383  unlink($tempname);
7384  return $retvars;
7385  }
7386 
7396  protected function _toPNG($image) {
7397  $tempname = tempnam(K_PATH_CACHE, 'jpg_');
7398  imagepng($image, $tempname);
7399  imagedestroy($image);
7400  $retvars = $this->_parsepng($tempname);
7401  // tidy up by removing temporary image
7402  unlink($tempname);
7403  return $retvars;
7404  }
7405 
7414  protected function _setGDImageTransparency($new_image, $image) {
7415  // transparency index
7416  $tid = imagecolortransparent($image);
7417  // default transparency color
7418  $tcol = array('red' => 255, 'green' => 255, 'blue' => 255);
7419  if ($tid >= 0) {
7420  // get the colors for the transparency index
7421  $tcol = imagecolorsforindex($image, $tid);
7422  }
7423  $tid = imagecolorallocate($new_image, $tcol['red'], $tcol['green'], $tcol['blue']);
7424  imagefill($new_image, 0, 0, $tid);
7425  imagecolortransparent($new_image, $tid);
7426  return $new_image;
7427  }
7428 
7435  protected function _parsejpeg($file) {
7436  $a = getimagesize($file);
7437  if (empty($a)) {
7438  $this->Error('Missing or incorrect image file: '.$file);
7439  }
7440  if ($a[2] != 2) {
7441  $this->Error('Not a JPEG file: '.$file);
7442  }
7443  if ((!isset($a['channels'])) OR ($a['channels'] == 3)) {
7444  $colspace = 'DeviceRGB';
7445  } elseif ($a['channels'] == 4) {
7446  $colspace = 'DeviceCMYK';
7447  } else {
7448  $colspace = 'DeviceGray';
7449  }
7450  $bpc = isset($a['bits']) ? $a['bits'] : 8;
7451  $data = file_get_contents($file);
7452  return array('w' => $a[0], 'h' => $a[1], 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'DCTDecode', 'data' => $data);
7453  }
7454 
7461  protected function _parsepng($file) {
7462  $f = fopen($file, 'rb');
7463  if ($f === false) {
7464  $this->Error('Can\'t open image file: '.$file);
7465  }
7466  //Check signature
7467  if (fread($f, 8) != chr(137).'PNG'.chr(13).chr(10).chr(26).chr(10)) {
7468  $this->Error('Not a PNG file: '.$file);
7469  }
7470  //Read header chunk
7471  fread($f, 4);
7472  if (fread($f, 4) != 'IHDR') {
7473  $this->Error('Incorrect PNG file: '.$file);
7474  }
7475  $w = $this->_freadint($f);
7476  $h = $this->_freadint($f);
7477  $bpc = ord(fread($f, 1));
7478  if ($bpc > 8) {
7479  //$this->Error('16-bit depth not supported: '.$file);
7480  fclose($f);
7481  return false;
7482  }
7483  $ct = ord(fread($f, 1));
7484  if ($ct == 0) {
7485  $colspace = 'DeviceGray';
7486  } elseif ($ct == 2) {
7487  $colspace = 'DeviceRGB';
7488  } elseif ($ct == 3) {
7489  $colspace = 'Indexed';
7490  } else {
7491  // alpha channel
7492  fclose($f);
7493  return 'pngalpha';
7494  }
7495  if (ord(fread($f, 1)) != 0) {
7496  //$this->Error('Unknown compression method: '.$file);
7497  fclose($f);
7498  return false;
7499  }
7500  if (ord(fread($f, 1)) != 0) {
7501  //$this->Error('Unknown filter method: '.$file);
7502  fclose($f);
7503  return false;
7504  }
7505  if (ord(fread($f, 1)) != 0) {
7506  //$this->Error('Interlacing not supported: '.$file);
7507  fclose($f);
7508  return false;
7509  }
7510  fread($f, 4);
7511  $parms = '/DecodeParms << /Predictor 15 /Colors '.($ct == 2 ? 3 : 1).' /BitsPerComponent '.$bpc.' /Columns '.$w.' >>';
7512  //Scan chunks looking for palette, transparency and image data
7513  $pal = '';
7514  $trns = '';
7515  $data = '';
7516  do {
7517  $n = $this->_freadint($f);
7518  $type = fread($f, 4);
7519  if ($type == 'PLTE') {
7520  //Read palette
7521  $pal = $this->rfread($f, $n);
7522  fread($f, 4);
7523  } elseif ($type == 'tRNS') {
7524  //Read transparency info
7525  $t = $this->rfread($f, $n);
7526  if ($ct == 0) {
7527  $trns = array(ord(substr($t, 1, 1)));
7528  } elseif ($ct == 2) {
7529  $trns = array(ord(substr($t, 1, 1)), ord(substr($t, 3, 1)), ord(substr($t, 5, 1)));
7530  } else {
7531  $pos = strpos($t, chr(0));
7532  if ($pos !== false) {
7533  $trns = array($pos);
7534  }
7535  }
7536  fread($f, 4);
7537  } elseif ($type == 'IDAT') {
7538  //Read image data block
7539  $data .= $this->rfread($f, $n);
7540  fread($f, 4);
7541  } elseif ($type == 'IEND') {
7542  break;
7543  } else {
7544  $this->rfread($f, $n + 4);
7545  }
7546  } while ($n);
7547  if (($colspace == 'Indexed') AND (empty($pal))) {
7548  //$this->Error('Missing palette in '.$file);
7549  fclose($f);
7550  return false;
7551  }
7552  fclose($f);
7553  return array('w' => $w, 'h' => $h, 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'FlateDecode', 'parms' => $parms, 'pal' => $pal, 'trns' => $trns, 'data' => $data);
7554  }
7555 
7566  protected function rfread($handle, $length) {
7567  $data = fread($handle, $length);
7568  if ($data === false) {
7569  return false;
7570  }
7571  $rest = $length - strlen($data);
7572  if ($rest > 0) {
7573  $data .= $this->rfread($handle, $rest);
7574  }
7575  return $data;
7576  }
7577 
7598  protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign) {
7599  // create temp image file (without alpha channel)
7600  $tempfile_plain = tempnam(K_PATH_CACHE, 'mskp_');
7601  // create temp alpha file
7602  $tempfile_alpha = tempnam(K_PATH_CACHE, 'mska_');
7603  if (extension_loaded('imagick')) { // ImageMagick
7604  // ImageMagick library
7605  $img = new Imagick();
7606  $img->readImage($file);
7607  // clone image object
7608  $imga = $img->clone();
7609  // extract alpha channel
7610  $img->separateImageChannel(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7611  $img->negateImage(true);
7612  $img->setImageFormat('png');
7613  $img->writeImage($tempfile_alpha);
7614  // remove alpha channel
7615  $imga->separateImageChannel(imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7616  $imga->setImageFormat('png');
7617  $imga->writeImage($tempfile_plain);
7618  } else { // GD library
7619  // generate images
7620  $img = imagecreatefrompng($file);
7621  $imgalpha = imagecreate($wpx, $hpx);
7622  // generate gray scale palette (0 -> 255)
7623  for ($c = 0; $c < 256; ++$c) {
7624  ImageColorAllocate($imgalpha, $c, $c, $c);
7625  }
7626  // extract alpha channel
7627  for ($xpx = 0; $xpx < $wpx; ++$xpx) {
7628  for ($ypx = 0; $ypx < $hpx; ++$ypx) {
7629  $color = imagecolorat($img, $xpx, $ypx);
7630  $alpha = ($color >> 24); // shifts off the first 24 bits (where 8x3 are used for each color), and returns the remaining 7 allocated bits (commonly used for alpha)
7631  $alpha = (((127 - $alpha) / 127) * 255); // GD alpha is only 7 bit (0 -> 127)
7632  $alpha = $this->getGDgamma($alpha); // correct gamma
7633  imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
7634  }
7635  }
7636  imagepng($imgalpha, $tempfile_alpha);
7637  imagedestroy($imgalpha);
7638  // extract image without alpha channel
7639  $imgplain = imagecreatetruecolor($wpx, $hpx);
7640  imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7641  imagepng($imgplain, $tempfile_plain);
7642  imagedestroy($imgplain);
7643  }
7644  // embed mask image
7645  $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7646  // embed image, masked with previously embedded mask
7647  $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7648  // remove temp files
7649  unlink($tempfile_alpha);
7650  unlink($tempfile_plain);
7651  }
7652 
7659  protected function getGDgamma($v) {
7660  return (pow(($v / 255), 2.2) * 255);
7661  }
7662 
7672  public function Ln($h='', $cell=false) {
7673  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'])) {
7674  // revove vertical space from the top of the column
7675  return;
7676  }
7677  if ($cell) {
7678  if ($this->rtl) {
7679  $cellpadding = $this->cell_padding['R'];
7680  } else {
7681  $cellpadding = $this->cell_padding['L'];
7682  }
7683  } else {
7684  $cellpadding = 0;
7685  }
7686  if ($this->rtl) {
7687  $this->x = $this->w - $this->rMargin - $cellpadding;
7688  } else {
7689  $this->x = $this->lMargin + $cellpadding;
7690  }
7691  if (is_string($h)) {
7692  $this->y += $this->lasth;
7693  } else {
7694  $this->y += $h;
7695  }
7696  $this->newline = true;
7697  }
7698 
7707  public function GetX() {
7708  //Get x position
7709  if ($this->rtl) {
7710  return ($this->w - $this->x);
7711  } else {
7712  return $this->x;
7713  }
7714  }
7715 
7723  public function GetAbsX() {
7724  return $this->x;
7725  }
7726 
7734  public function GetY() {
7735  return $this->y;
7736  }
7737 
7747  public function SetX($x, $rtloff=false) {
7748  if (!$rtloff AND $this->rtl) {
7749  if ($x >= 0) {
7750  $this->x = $this->w - $x;
7751  } else {
7752  $this->x = abs($x);
7753  }
7754  } else {
7755  if ($x >= 0) {
7756  $this->x = $x;
7757  } else {
7758  $this->x = $this->w + $x;
7759  }
7760  }
7761  if ($this->x < 0) {
7762  $this->x = 0;
7763  }
7764  if ($this->x > $this->w) {
7765  $this->x = $this->w;
7766  }
7767  }
7768 
7779  public function SetY($y, $resetx=true, $rtloff=false) {
7780  if ($resetx) {
7781  //reset x
7782  if (!$rtloff AND $this->rtl) {
7783  $this->x = $this->w - $this->rMargin;
7784  } else {
7785  $this->x = $this->lMargin;
7786  }
7787  }
7788  if ($y >= 0) {
7789  $this->y = $y;
7790  } else {
7791  $this->y = $this->h + $y;
7792  }
7793  if ($this->y < 0) {
7794  $this->y = 0;
7795  }
7796  if ($this->y > $this->h) {
7797  $this->y = $this->h;
7798  }
7799  }
7800 
7811  public function SetXY($x, $y, $rtloff=false) {
7812  $this->SetY($y, false, $rtloff);
7813  $this->SetX($x, $rtloff);
7814  }
7815 
7826  public function Output($name='doc.pdf', $dest='I') {
7827  //Output PDF to some destination
7828  //Finish document if necessary
7829  if ($this->state < 3) {
7830  $this->Close();
7831  }
7832  //Normalize parameters
7833  if (is_bool($dest)) {
7834  $dest = $dest ? 'D' : 'F';
7835  }
7836  $dest = strtoupper($dest);
7837  if ($dest{0} != 'F') {
7838  $name = preg_replace('/[\s]+/', '_', $name);
7839  $name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
7840  }
7841  if ($this->sign) {
7842  // *** apply digital signature to the document ***
7843  // get the document content
7844  $pdfdoc = $this->getBuffer();
7845  // remove last newline
7846  $pdfdoc = substr($pdfdoc, 0, -1);
7847  // Remove the original buffer
7848  if (isset($this->diskcache) AND $this->diskcache) {
7849  // remove buffer file from cache
7850  unlink($this->buffer);
7851  }
7852  unset($this->buffer);
7853  // remove filler space
7854  $byterange_string_len = strlen($this->byterange_string);
7855  // define the ByteRange
7856  $byte_range = array();
7857  $byte_range[0] = 0;
7858  $byte_range[1] = strpos($pdfdoc, $this->byterange_string) + $byterange_string_len + 10;
7859  $byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
7860  $byte_range[3] = strlen($pdfdoc) - $byte_range[2];
7861  $pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
7862  // replace the ByteRange
7863  $byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
7864  $byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
7865  $pdfdoc = str_replace($this->byterange_string, $byterange, $pdfdoc);
7866  // write the document to a temporary folder
7867  $tempdoc = tempnam(K_PATH_CACHE, 'tmppdf_');
7868  $f = fopen($tempdoc, 'wb');
7869  if (!$f) {
7870  $this->Error('Unable to create temporary file: '.$tempdoc);
7871  }
7872  $pdfdoc_length = strlen($pdfdoc);
7873  fwrite($f, $pdfdoc, $pdfdoc_length);
7874  fclose($f);
7875  // get digital signature via openssl library
7876  $tempsign = tempnam(K_PATH_CACHE, 'tmpsig_');
7877  if (empty($this->signature_data['extracerts'])) {
7878  openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
7879  } else {
7880  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']);
7881  }
7882  unlink($tempdoc);
7883  // read signature
7884  $signature = file_get_contents($tempsign);
7885  unlink($tempsign);
7886  // extract signature
7887  $signature = substr($signature, $pdfdoc_length);
7888  $signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
7889  $tmparr = explode("\n\n", $signature);
7890  $signature = $tmparr[1];
7891  unset($tmparr);
7892  // decode signature
7893  $signature = base64_decode(trim($signature));
7894  // convert signature to hex
7895  $signature = current(unpack('H*', $signature));
7896  $signature = str_pad($signature, $this->signature_max_length, '0');
7897  // Add signature to the document
7898  $pdfdoc = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
7899  $this->diskcache = false;
7900  $this->buffer = &$pdfdoc;
7901  $this->bufferlen = strlen($pdfdoc);
7902  }
7903  switch($dest) {
7904  case 'I': {
7905  // Send PDF to the standard output
7906  if (ob_get_contents()) {
7907  $this->Error('Some data has already been output, can\'t send PDF file');
7908  }
7909  if (php_sapi_name() != 'cli') {
7910  //We send to a browser
7911  header('Content-Type: application/pdf');
7912  if (headers_sent()) {
7913  $this->Error('Some data has already been output to browser, can\'t send PDF file');
7914  }
7915  header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7916  header('Pragma: public');
7917  header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7918  header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7919  header('Content-Length: '.$this->bufferlen);
7920  header('Content-Disposition: inline; filename="'.basename($name).'";');
7921  }
7922  echo $this->getBuffer();
7923  break;
7924  }
7925  case 'D': {
7926  // Download PDF as file
7927  if (ob_get_contents()) {
7928  $this->Error('Some data has already been output, can\'t send PDF file');
7929  }
7930  header('Content-Description: File Transfer');
7931  if (headers_sent()) {
7932  $this->Error('Some data has already been output to browser, can\'t send PDF file');
7933  }
7934  header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7935  header('Pragma: public');
7936  header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7937  header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7938  // force download dialog
7939  header('Content-Type: application/force-download');
7940  header('Content-Type: application/octet-stream', false);
7941  header('Content-Type: application/download', false);
7942  header('Content-Type: application/pdf', false);
7943  // use the Content-Disposition header to supply a recommended filename
7944  header('Content-Disposition: attachment; filename="'.basename($name).'";');
7945  header('Content-Transfer-Encoding: binary');
7946  header('Content-Length: '.$this->bufferlen);
7947  echo $this->getBuffer();
7948  break;
7949  }
7950  case 'F':
7951  case 'FI':
7952  case 'FD': {
7953  // Save PDF to a local file
7954  if ($this->diskcache) {
7955  copy($this->buffer, $name);
7956  } else {
7957  $f = fopen($name, 'wb');
7958  if (!$f) {
7959  $this->Error('Unable to create output file: '.$name);
7960  }
7961  fwrite($f, $this->getBuffer(), $this->bufferlen);
7962  fclose($f);
7963  }
7964  if ($dest == 'FI') {
7965  // send headers to browser
7966  header('Content-Type: application/pdf');
7967  header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7968  header('Pragma: public');
7969  header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7970  header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7971  header('Content-Length: '.filesize($name));
7972  header('Content-Disposition: inline; filename="'.basename($name).'";');
7973  // send document to the browser
7974  echo file_get_contents($name);
7975  } elseif ($dest == 'FD') {
7976  // send headers to browser
7977  if (ob_get_contents()) {
7978  $this->Error('Some data has already been output, can\'t send PDF file');
7979  }
7980  header('Content-Description: File Transfer');
7981  if (headers_sent()) {
7982  $this->Error('Some data has already been output to browser, can\'t send PDF file');
7983  }
7984  header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7985  header('Pragma: public');
7986  header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7987  header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7988  // force download dialog
7989  header('Content-Type: application/force-download');
7990  header('Content-Type: application/octet-stream', false);
7991  header('Content-Type: application/download', false);
7992  header('Content-Type: application/pdf', false);
7993  // use the Content-Disposition header to supply a recommended filename
7994  header('Content-Disposition: attachment; filename="'.basename($name).'";');
7995  header('Content-Transfer-Encoding: binary');
7996  header('Content-Length: '.filesize($name));
7997  // send document to the browser
7998  echo file_get_contents($name);
7999  }
8000  break;
8001  }
8002  case 'S': {
8003  // Returns PDF as a string
8004  return $this->getBuffer();
8005  }
8006  default: {
8007  $this->Error('Incorrect output destination: '.$dest);
8008  }
8009  }
8010  return '';
8011  }
8012 
8020  public function _destroy($destroyall=false, $preserve_objcopy=false) {
8021  if ($destroyall AND isset($this->diskcache) AND $this->diskcache AND (!$preserve_objcopy) AND (!$this->empty_string($this->buffer))) {
8022  // remove buffer file from cache
8023  unlink($this->buffer);
8024  }
8025  foreach (array_keys(get_object_vars($this)) as $val) {
8026  if ($destroyall OR (
8027  ($val != 'internal_encoding')
8028  AND ($val != 'state')
8029  AND ($val != 'bufferlen')
8030  AND ($val != 'buffer')
8031  AND ($val != 'diskcache')
8032  AND ($val != 'sign')
8033  AND ($val != 'signature_data')
8034  AND ($val != 'signature_max_length')
8035  AND ($val != 'byterange_string')
8036  )) {
8037  if ((!$preserve_objcopy OR ($val != 'objcopy')) AND isset($this->$val)) {
8038  unset($this->$val);
8039  }
8040  }
8041  }
8042  }
8043 
8048  protected function _dochecks() {
8049  //Check for locale-related bug
8050  if (1.1 == 1) {
8051  $this->Error('Don\'t alter the locale before including class file');
8052  }
8053  //Check for decimal separator
8054  if (sprintf('%.1F', 1.0) != '1.0') {
8055  setlocale(LC_NUMERIC, 'C');
8056  }
8057  }
8058 
8064  protected function _getfontpath() {
8065  if (!defined('K_PATH_FONTS') AND is_dir(dirname(__FILE__).'/fonts')) {
8066  define('K_PATH_FONTS', dirname(__FILE__).'/fonts/');
8067  }
8068  return defined('K_PATH_FONTS') ? K_PATH_FONTS : '';
8069  }
8070 
8075  protected function _putpages() {
8076  $nb = $this->numpages;
8077  if (!empty($this->AliasNbPages)) {
8078  $nbs = $this->formatPageNumber($nb);
8079  $nbu = $this->UTF8ToUTF16BE($nbs, false); // replacement for unicode font
8080  $alias_a = $this->_escape($this->AliasNbPages);
8081  $alias_au = $this->_escape('{'.$this->AliasNbPages.'}');
8082  if ($this->isunicode) {
8083  $alias_b = $this->_escape($this->UTF8ToLatin1($this->AliasNbPages));
8084  $alias_bu = $this->_escape($this->UTF8ToLatin1('{'.$this->AliasNbPages.'}'));
8085  $alias_c = $this->_escape($this->utf8StrRev($this->AliasNbPages, false, $this->tmprtl));
8086  $alias_cu = $this->_escape($this->utf8StrRev('{'.$this->AliasNbPages.'}', false, $this->tmprtl));
8087  }
8088  }
8089  if (!empty($this->AliasNumPage)) {
8090  $alias_pa = $this->_escape($this->AliasNumPage);
8091  $alias_pau = $this->_escape('{'.$this->AliasNumPage.'}');
8092  if ($this->isunicode) {
8093  $alias_pb = $this->_escape($this->UTF8ToLatin1($this->AliasNumPage));
8094  $alias_pbu = $this->_escape($this->UTF8ToLatin1('{'.$this->AliasNumPage.'}'));
8095  $alias_pc = $this->_escape($this->utf8StrRev($this->AliasNumPage, false, $this->tmprtl));
8096  $alias_pcu = $this->_escape($this->utf8StrRev('{'.$this->AliasNumPage.'}', false, $this->tmprtl));
8097  }
8098  }
8099  $pagegroupnum = 0;
8100  $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
8101  for ($n=1; $n <= $nb; ++$n) {
8102  $temppage = $this->getPageBuffer($n);
8103  if (!empty($this->pagegroups)) {
8104  if(isset($this->newpagegroup[$n])) {
8105  $pagegroupnum = 0;
8106  }
8107  ++$pagegroupnum;
8108  foreach ($this->pagegroups as $k => $v) {
8109  // replace total pages group numbers
8110  $vs = $this->formatPageNumber($v);
8111  $vu = $this->UTF8ToUTF16BE($vs, false);
8112  $alias_ga = $this->_escape($k);
8113  $alias_gau = $this->_escape('{'.$k.'}');
8114  if ($this->isunicode) {
8115  $alias_gb = $this->_escape($this->UTF8ToLatin1($k));
8116  $alias_gbu = $this->_escape($this->UTF8ToLatin1('{'.$k.'}'));
8117  $alias_gc = $this->_escape($this->utf8StrRev($k, false, $this->tmprtl));
8118  $alias_gcu = $this->_escape($this->utf8StrRev('{'.$k.'}', false, $this->tmprtl));
8119  }
8120  $temppage = str_replace($alias_gau, $vu, $temppage);
8121  if ($this->isunicode) {
8122  $temppage = str_replace($alias_gbu, $vu, $temppage);
8123  $temppage = str_replace($alias_gcu, $vu, $temppage);
8124  $temppage = str_replace($alias_gb, $vs, $temppage);
8125  $temppage = str_replace($alias_gc, $vs, $temppage);
8126  }
8127  $temppage = str_replace($alias_ga, $vs, $temppage);
8128  // replace page group numbers
8129  $pvs = $this->formatPageNumber($pagegroupnum);
8130  $pvu = $this->UTF8ToUTF16BE($pvs, false);
8131  $pk = str_replace('{nb', '{pnb', $k);
8132  $alias_pga = $this->_escape($pk);
8133  $alias_pgau = $this->_escape('{'.$pk.'}');
8134  if ($this->isunicode) {
8135  $alias_pgb = $this->_escape($this->UTF8ToLatin1($pk));
8136  $alias_pgbu = $this->_escape($this->UTF8ToLatin1('{'.$pk.'}'));
8137  $alias_pgc = $this->_escape($this->utf8StrRev($pk, false, $this->tmprtl));
8138  $alias_pgcu = $this->_escape($this->utf8StrRev('{'.$pk.'}', false, $this->tmprtl));
8139  }
8140  $temppage = str_replace($alias_pgau, $pvu, $temppage);
8141  if ($this->isunicode) {
8142  $temppage = str_replace($alias_pgbu, $pvu, $temppage);
8143  $temppage = str_replace($alias_pgcu, $pvu, $temppage);
8144  $temppage = str_replace($alias_pgb, $pvs, $temppage);
8145  $temppage = str_replace($alias_pgc, $pvs, $temppage);
8146  }
8147  $temppage = str_replace($alias_pga, $pvs, $temppage);
8148  }
8149  }
8150  if (!empty($this->AliasNbPages)) {
8151  // replace total pages number
8152  $temppage = str_replace($alias_au, $nbu, $temppage);
8153  if ($this->isunicode) {
8154  $temppage = str_replace($alias_bu, $nbu, $temppage);
8155  $temppage = str_replace($alias_cu, $nbu, $temppage);
8156  $temppage = str_replace($alias_b, $nbs, $temppage);
8157  $temppage = str_replace($alias_c, $nbs, $temppage);
8158  }
8159  $temppage = str_replace($alias_a, $nbs, $temppage);
8160  }
8161  if (!empty($this->AliasNumPage)) {
8162  // replace page number
8163  $pnbs = $this->formatPageNumber($n);
8164  $pnbu = $this->UTF8ToUTF16BE($pnbs, false); // replacement for unicode font
8165  $temppage = str_replace($alias_pau, $pnbu, $temppage);
8166  if ($this->isunicode) {
8167  $temppage = str_replace($alias_pbu, $pnbu, $temppage);
8168  $temppage = str_replace($alias_pcu, $pnbu, $temppage);
8169  $temppage = str_replace($alias_pb, $pnbs, $temppage);
8170  $temppage = str_replace($alias_pc, $pnbs, $temppage);
8171  }
8172  $temppage = str_replace($alias_pa, $pnbs, $temppage);
8173  }
8174  $temppage = str_replace($this->epsmarker, '', $temppage);
8175  //Page
8176  $this->page_obj_id[$n] = $this->_newobj();
8177  $out = '<<';
8178  $out .= ' /Type /Page';
8179  $out .= ' /Parent 1 0 R';
8180  $out .= ' /LastModified '.$this->_datestring();
8181  $out .= ' /Resources 2 0 R';
8182  $boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
8183  foreach ($boxes as $box) {
8184  $out .= ' /'.$box;
8185  $out .= sprintf(' [%.2F %.2F %.2F %.2F]', $this->pagedim[$n][$box]['llx'], $this->pagedim[$n][$box]['lly'], $this->pagedim[$n][$box]['urx'], $this->pagedim[$n][$box]['ury']);
8186  }
8187  if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
8188  $out .= ' /BoxColorInfo <<';
8189  foreach ($boxes as $box) {
8190  if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
8191  $out .= ' /'.$box.' <<';
8192  if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
8193  $color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
8194  $out .= ' /C [';
8195  $out .= sprintf(' %.3F %.3F %.3F', $color[0]/255, $color[1]/255, $color[2]/255);
8196  $out .= ' ]';
8197  }
8198  if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
8199  $out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
8200  }
8201  if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
8202  $out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
8203  }
8204  if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
8205  $dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
8206  $out .= ' /D [';
8207  foreach ($dashes as $dash) {
8208  $out .= sprintf(' %.3F', ($dash * $this->k));
8209  }
8210  $out .= ' ]';
8211  }
8212  $out .= ' >>';
8213  }
8214  }
8215  $out .= ' >>';
8216  }
8217  $out .= ' /Contents '.($this->n + 1).' 0 R';
8218  $out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
8219  $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
8220  if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
8221  // page transitions
8222  if (isset($this->pagedim[$n]['trans']['Dur'])) {
8223  $out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
8224  }
8225  $out .= ' /Trans <<';
8226  $out .= ' /Type /Trans';
8227  if (isset($this->pagedim[$n]['trans']['S'])) {
8228  $out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
8229  }
8230  if (isset($this->pagedim[$n]['trans']['D'])) {
8231  $out .= ' /D '.$this->pagedim[$n]['trans']['D'];
8232  }
8233  if (isset($this->pagedim[$n]['trans']['Dm'])) {
8234  $out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
8235  }
8236  if (isset($this->pagedim[$n]['trans']['M'])) {
8237  $out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
8238  }
8239  if (isset($this->pagedim[$n]['trans']['Di'])) {
8240  $out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
8241  }
8242  if (isset($this->pagedim[$n]['trans']['SS'])) {
8243  $out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
8244  }
8245  if (isset($this->pagedim[$n]['trans']['B'])) {
8246  $out .= ' /B '.$this->pagedim[$n]['trans']['B'];
8247  }
8248  $out .= ' >>';
8249  }
8250  $out .= $this->_getannotsrefs($n);
8251  $out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8252  $out .= ' >>';
8253  $out .= "\n".'endobj';
8254  $this->_out($out);
8255  //Page content
8256  $p = ($this->compress) ? gzcompress($temppage) : $temppage;
8257  $this->_newobj();
8258  $p = $this->_getrawstream($p);
8259  $this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8260  if ($this->diskcache) {
8261  // remove temporary files
8262  unlink($this->pages[$n]);
8263  }
8264  }
8265  //Pages root
8266  $out = $this->_getobj(1)."\n";
8267  $out .= '<< /Type /Pages /Kids [';
8268  foreach($this->page_obj_id as $page_obj) {
8269  $out .= ' '.$page_obj.' 0 R';
8270  }
8271  $out .= ' ] /Count '.$nb.' >>';
8272  $out .= "\n".'endobj';
8273  $this->_out($out);
8274  }
8275 
8284  protected function _putannotsrefs($n) {
8285  $this->_out($this->_getannotsrefs($n));
8286  }
8287 
8296  protected function _getannotsrefs($n) {
8297  if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
8298  return '';
8299  }
8300  $out = ' /Annots [';
8301  if (isset($this->PageAnnots[$n])) {
8302  foreach ($this->PageAnnots[$n] as $key => $val) {
8303  if (!in_array($val['n'], $this->radio_groups)) {
8304  $out .= ' '.$val['n'].' 0 R';
8305  }
8306  }
8307  // add radiobutton groups
8308  if (isset($this->radiobutton_groups[$n])) {
8309  foreach ($this->radiobutton_groups[$n] as $key => $data) {
8310  if (isset($data['n'])) {
8311  $out .= ' '.$data['n'].' 0 R';
8312  }
8313  }
8314  }
8315  }
8316  if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
8317  // set reference for signature object
8318  $out .= ' '.$this->sig_obj_id.' 0 R';
8319  }
8320  $out .= ' ]';
8321  return $out;
8322  }
8323 
8332  protected function _putannotsobjs() {
8333  // reset object counter
8334  for ($n=1; $n <= $this->numpages; ++$n) {
8335  if (isset($this->PageAnnots[$n])) {
8336  // set page annotations
8337  foreach ($this->PageAnnots[$n] as $key => $pl) {
8338  $annot_obj_id = $this->PageAnnots[$n][$key]['n'];
8339  // create annotation object for grouping radiobuttons
8340  if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
8341  $radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
8342  $annots = '<<';
8343  $annots .= ' /Type /Annot';
8344  $annots .= ' /Subtype /Widget';
8345  $annots .= ' /Rect [0 0 0 0]';
8346  $annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8347  $annots .= ' /FT /Btn';
8348  $annots .= ' /Ff 49152';
8349  $annots .= ' /Kids [';
8350  foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
8351  if ($key !== 'n') {
8352  $annots .= ' '.$data['kid'].' 0 R';
8353  if ($data['def'] !== 'Off') {
8354  $defval = $data['def'];
8355  }
8356  }
8357  }
8358  $annots .= ' ]';
8359  if (isset($defval)) {
8360  $annots .= ' /V /'.$defval;
8361  }
8362  $annots .= ' >>';
8363  $this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8364  $this->form_obj_id[] = $radio_button_obj_id;
8365  // store object id to be used on Parent entry of Kids
8366  $this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
8367  }
8368  $formfield = false;
8369  $pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
8370  $a = $pl['x'] * $this->k;
8371  $b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
8372  $c = $pl['w'] * $this->k;
8373  $d = $pl['h'] * $this->k;
8374  $rect = sprintf('%.2F %.2F %.2F %.2F', $a, $b, $a+$c, $b+$d);
8375  // create new annotation object
8376  $annots = '<</Type /Annot';
8377  $annots .= ' /Subtype /'.$pl['opt']['subtype'];
8378  $annots .= ' /Rect ['.$rect.']';
8379  $ft = array('Btn', 'Tx', 'Ch', 'Sig');
8380  if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8381  $annots .= ' /FT /'.$pl['opt']['ft'];
8382  $formfield = true;
8383  }
8384  $annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8385  $annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
8386  $annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8387  $annots .= ' /M '.$this->_datestring($annot_obj_id);
8388  if (isset($pl['opt']['f'])) {
8389  $val = 0;
8390  if (is_array($pl['opt']['f'])) {
8391  foreach ($pl['opt']['f'] as $f) {
8392  switch (strtolower($f)) {
8393  case 'invisible': {
8394  $val += 1 << 0;
8395  break;
8396  }
8397  case 'hidden': {
8398  $val += 1 << 1;
8399  break;
8400  }
8401  case 'print': {
8402  $val += 1 << 2;
8403  break;
8404  }
8405  case 'nozoom': {
8406  $val += 1 << 3;
8407  break;
8408  }
8409  case 'norotate': {
8410  $val += 1 << 4;
8411  break;
8412  }
8413  case 'noview': {
8414  $val += 1 << 5;
8415  break;
8416  }
8417  case 'readonly': {
8418  $val += 1 << 6;
8419  break;
8420  }
8421  case 'locked': {
8422  $val += 1 << 8;
8423  break;
8424  }
8425  case 'togglenoview': {
8426  $val += 1 << 9;
8427  break;
8428  }
8429  case 'lockedcontents': {
8430  $val += 1 << 10;
8431  break;
8432  }
8433  default: {
8434  break;
8435  }
8436  }
8437  }
8438  } else {
8439  $val = intval($pl['opt']['f']);
8440  }
8441  $annots .= ' /F '.intval($val);
8442  }
8443  if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8444  $annots .= ' /AS /'.$pl['opt']['as'];
8445  }
8446  if (isset($pl['opt']['ap'])) {
8447  // appearance stream
8448  $annots .= ' /AP <<';
8449  if (is_array($pl['opt']['ap'])) {
8450  foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8451  // $apmode can be: n = normal; r = rollover; d = down;
8452  $annots .= ' /'.strtoupper($apmode);
8453  if (is_array($apdef)) {
8454  $annots .= ' <<';
8455  foreach ($apdef as $apstate => $stream) {
8456  // reference to XObject that define the appearance for this mode-state
8457  $apsobjid = $this->_putAPXObject($c, $d, $stream);
8458  $annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8459  }
8460  $annots .= ' >>';
8461  } else {
8462  // reference to XObject that define the appearance for this mode
8463  $apsobjid = $this->_putAPXObject($c, $d, $apdef);
8464  $annots .= ' '.$apsobjid.' 0 R';
8465  }
8466  }
8467  } else {
8468  $annots .= $pl['opt']['ap'];
8469  }
8470  $annots .= ' >>';
8471  }
8472  if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8473  $annots .= ' /BS <<';
8474  $annots .= ' /Type /Border';
8475  if (isset($pl['opt']['bs']['w'])) {
8476  $annots .= ' /W '.intval($pl['opt']['bs']['w']);
8477  }
8478  $bstyles = array('S', 'D', 'B', 'I', 'U');
8479  if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8480  $annots .= ' /S /'.$pl['opt']['bs']['s'];
8481  }
8482  if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8483  $annots .= ' /D [';
8484  foreach ($pl['opt']['bs']['d'] as $cord) {
8485  $annots .= ' '.intval($cord);
8486  }
8487  $annots .= ']';
8488  }
8489  $annots .= ' >>';
8490  } else {
8491  $annots .= ' /Border [';
8492  if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8493  $annots .= intval($pl['opt']['border'][0]).' ';
8494  $annots .= intval($pl['opt']['border'][1]).' ';
8495  $annots .= intval($pl['opt']['border'][2]);
8496  if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8497  $annots .= ' [';
8498  foreach ($pl['opt']['border'][3] as $dash) {
8499  $annots .= intval($dash).' ';
8500  }
8501  $annots .= ']';
8502  }
8503  } else {
8504  $annots .= '0 0 0';
8505  }
8506  $annots .= ']';
8507  }
8508  if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8509  $annots .= ' /BE <<';
8510  $bstyles = array('S', 'C');
8511  if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $markups)) {
8512  $annots .= ' /S /'.$pl['opt']['bs']['s'];
8513  } else {
8514  $annots .= ' /S /S';
8515  }
8516  if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8517  $annots .= ' /I '.sprintf(' %.4F', $pl['opt']['be']['i']);
8518  }
8519  $annots .= '>>';
8520  }
8521  if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8522  $annots .= ' /C [';
8523  foreach ($pl['opt']['c'] as $col) {
8524  $col = intval($col);
8525  $color = $col <= 0 ? 0 : ($col >= 255 ? 1 : $col / 255);
8526  $annots .= sprintf(' %.4F', $color);
8527  }
8528  $annots .= ']';
8529  }
8530  //$annots .= ' /StructParent ';
8531  //$annots .= ' /OC ';
8532  $markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8533  if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8534  // this is a markup type
8535  if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8536  $annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8537  }
8538  //$annots .= ' /Popup ';
8539  if (isset($pl['opt']['ca'])) {
8540  $annots .= ' /CA '.sprintf('%.4F', floatval($pl['opt']['ca']));
8541  }
8542  if (isset($pl['opt']['rc'])) {
8543  $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8544  }
8545  $annots .= ' /CreationDate '.$this->_datestring($annot_obj_id);
8546  //$annots .= ' /IRT ';
8547  if (isset($pl['opt']['subj'])) {
8548  $annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8549  }
8550  //$annots .= ' /RT ';
8551  //$annots .= ' /IT ';
8552  //$annots .= ' /ExData ';
8553  }
8554  $lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8555  // Annotation types
8556  switch (strtolower($pl['opt']['subtype'])) {
8557  case 'text': {
8558  if (isset($pl['opt']['open'])) {
8559  $annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
8560  }
8561  $iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8562  if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8563  $annots .= ' /Name /'.$pl['opt']['name'];
8564  } else {
8565  $annots .= ' /Name /Note';
8566  }
8567  $statemodels = array('Marked', 'Review');
8568  if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
8569  $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8570  } else {
8571  $pl['opt']['statemodel'] = 'Marked';
8572  $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8573  }
8574  if ($pl['opt']['statemodel'] == 'Marked') {
8575  $states = array('Accepted', 'Unmarked');
8576  } else {
8577  $states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8578  }
8579  if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
8580  $annots .= ' /State /'.$pl['opt']['state'];
8581  } else {
8582  if ($pl['opt']['statemodel'] == 'Marked') {
8583  $annots .= ' /State /Unmarked';
8584  } else {
8585  $annots .= ' /State /None';
8586  }
8587  }
8588  break;
8589  }
8590  case 'link': {
8591  if(is_string($pl['txt'])) {
8592  // external URI link
8593  $annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8594  } else {
8595  // internal link
8596  $l = $this->links[$pl['txt']];
8597  $annots .= sprintf(' /Dest [%u 0 R /XYZ 0 %.2F null]', $this->page_obj_id[($l[0])], ($this->pagedim[$l[0]]['h'] - ($l[1] * $this->k)));
8598  }
8599  $hmodes = array('N', 'I', 'O', 'P');
8600  if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8601  $annots .= ' /H /'.$pl['opt']['h'];
8602  } else {
8603  $annots .= ' /H /I';
8604  }
8605  //$annots .= ' /PA ';
8606  //$annots .= ' /Quadpoints ';
8607  break;
8608  }
8609  case 'freetext': {
8610  if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8611  $annots .= ' /DA ('.$pl['opt']['da'].')';
8612  }
8613  if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8614  $annots .= ' /Q '.intval($pl['opt']['q']);
8615  }
8616  if (isset($pl['opt']['rc'])) {
8617  $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8618  }
8619  if (isset($pl['opt']['ds'])) {
8620  $annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8621  }
8622  if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8623  $annots .= ' /CL [';
8624  foreach ($pl['opt']['cl'] as $cl) {
8625  $annots .= sprintf('%.4F ', $cl * $this->k);
8626  }
8627  $annots .= ']';
8628  }
8629  $tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8630  if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8631  $annots .= ' /IT /'.$pl['opt']['it'];
8632  }
8633  if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8634  $l = $pl['opt']['rd'][0] * $this->k;
8635  $r = $pl['opt']['rd'][1] * $this->k;
8636  $t = $pl['opt']['rd'][2] * $this->k;
8637  $b = $pl['opt']['rd'][3] * $this->k;
8638  $annots .= ' /RD ['.sprintf('%.2F %.2F %.2F %.2F', $l, $r, $t, $b).']';
8639  }
8640  if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8641  $annots .= ' /LE /'.$pl['opt']['le'];
8642  }
8643  break;
8644  }
8645  case 'line': {
8646  break;
8647  }
8648  case 'square': {
8649  break;
8650  }
8651  case 'circle': {
8652  break;
8653  }
8654  case 'polygon': {
8655  break;
8656  }
8657  case 'polyline': {
8658  break;
8659  }
8660  case 'highlight': {
8661  break;
8662  }
8663  case 'underline': {
8664  break;
8665  }
8666  case 'squiggly': {
8667  break;
8668  }
8669  case 'strikeout': {
8670  break;
8671  }
8672  case 'stamp': {
8673  break;
8674  }
8675  case 'caret': {
8676  break;
8677  }
8678  case 'ink': {
8679  break;
8680  }
8681  case 'popup': {
8682  break;
8683  }
8684  case 'fileattachment': {
8685  if (!isset($pl['opt']['fs'])) {
8686  break;
8687  }
8688  $filename = basename($pl['opt']['fs']);
8689  if (isset($this->embeddedfiles[$filename]['n'])) {
8690  $annots .= ' /FS <</Type /Filespec /F '.$this->_datastring($filename, $annot_obj_id).' /EF <</F '.$this->embeddedfiles[$filename]['n'].' 0 R>> >>';
8691  $iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8692  if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8693  $annots .= ' /Name /'.$pl['opt']['name'];
8694  } else {
8695  $annots .= ' /Name /PushPin';
8696  }
8697  }
8698  break;
8699  }
8700  case 'sound': {
8701  if (!isset($pl['opt']['fs'])) {
8702  break;
8703  }
8704  $filename = basename($pl['opt']['fs']);
8705  if (isset($this->embeddedfiles[$filename]['n'])) {
8706  // ... TO BE COMPLETED ...
8707  // /R /C /B /E /CO /CP
8708  $annots .= ' /Sound <</Type /Filespec /F '.$this->_datastring($filename, $annot_obj_id).' /EF <</F '.$this->embeddedfiles[$filename]['n'].' 0 R>> >>';
8709  $iconsapp = array('Speaker', 'Mic');
8710  if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8711  $annots .= ' /Name /'.$pl['opt']['name'];
8712  } else {
8713  $annots .= ' /Name /Speaker';
8714  }
8715  }
8716  break;
8717  }
8718  case 'movie': {
8719  break;
8720  }
8721  case 'widget': {
8722  $hmode = array('N', 'I', 'O', 'P', 'T');
8723  if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8724  $annots .= ' /H /'.$pl['opt']['h'];
8725  }
8726  if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8727  $annots .= ' /MK <<';
8728  if (isset($pl['opt']['mk']['r'])) {
8729  $annots .= ' /R '.$pl['opt']['mk']['r'];
8730  }
8731  if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8732  $annots .= ' /BC [';
8733  foreach($pl['opt']['mk']['bc'] AS $col) {
8734  $col = intval($col);
8735  $color = $col <= 0 ? 0 : ($col >= 255 ? 1 : $col / 255);
8736  $annots .= sprintf(' %.2F', $color);
8737  }
8738  $annots .= ']';
8739  }
8740  if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8741  $annots .= ' /BG [';
8742  foreach($pl['opt']['mk']['bg'] AS $col) {
8743  $col = intval($col);
8744  $color = $col <= 0 ? 0 : ($col >= 255 ? 1 : $col / 255);
8745  $annots .= sprintf(' %.2F', $color);
8746  }
8747  $annots .= ']';
8748  }
8749  if (isset($pl['opt']['mk']['ca'])) {
8750  $annots .= ' /CA '.$pl['opt']['mk']['ca'];
8751  }
8752  if (isset($pl['opt']['mk']['rc'])) {
8753  $annots .= ' /RC '.$pl['opt']['mk']['rc'];
8754  }
8755  if (isset($pl['opt']['mk']['ac'])) {
8756  $annots .= ' /AC '.$pl['opt']['mk']['ac'];
8757  }
8758  if (isset($pl['opt']['mk']['i'])) {
8759  $info = $this->getImageBuffer($pl['opt']['mk']['i']);
8760  if ($info !== false) {
8761  $annots .= ' /I '.$info['n'].' 0 R';
8762  }
8763  }
8764  if (isset($pl['opt']['mk']['ri'])) {
8765  $info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8766  if ($info !== false) {
8767  $annots .= ' /RI '.$info['n'].' 0 R';
8768  }
8769  }
8770  if (isset($pl['opt']['mk']['ix'])) {
8771  $info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8772  if ($info !== false) {
8773  $annots .= ' /IX '.$info['n'].' 0 R';
8774  }
8775  }
8776  if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8777  $annots .= ' /IF <<';
8778  $if_sw = array('A', 'B', 'S', 'N');
8779  if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8780  $annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8781  }
8782  $if_s = array('A', 'P');
8783  if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8784  $annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8785  }
8786  if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8787  $annots .= sprintf(' /A [%.2F %.2F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8788  }
8789  if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8790  $annots .= ' /FB true';
8791  }
8792  $annots .= '>>';
8793  }
8794  if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8795  $annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8796  } else {
8797  $annots .= ' /TP 0';
8798  }
8799  $annots .= '>>';
8800  } // end MK
8801  // --- Entries for field dictionaries ---
8802  if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8803  // set parent
8804  $annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
8805  }
8806  if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8807  $annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8808  }
8809  if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8810  $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8811  }
8812  if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8813  $annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8814  }
8815  if (isset($pl['opt']['ff'])) {
8816  if (is_array($pl['opt']['ff'])) {
8817  // array of bit settings
8818  $flag = 0;
8819  foreach($pl['opt']['ff'] as $val) {
8820  $flag += 1 << ($val - 1);
8821  }
8822  } else {
8823  $flag = intval($pl['opt']['ff']);
8824  }
8825  $annots .= ' /Ff '.$flag;
8826  }
8827  if (isset($pl['opt']['maxlen'])) {
8828  $annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
8829  }
8830  if (isset($pl['opt']['v'])) {
8831  $annots .= ' /V';
8832  if (is_array($pl['opt']['v'])) {
8833  foreach ($pl['opt']['v'] AS $optval) {
8834  if (is_float($optval)) {
8835  $optval = sprintf('%.2F', $optval);
8836  }
8837  $annots .= ' '.$optval;
8838  }
8839  } else {
8840  $annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8841  }
8842  }
8843  if (isset($pl['opt']['dv'])) {
8844  $annots .= ' /DV';
8845  if (is_array($pl['opt']['dv'])) {
8846  foreach ($pl['opt']['dv'] AS $optval) {
8847  if (is_float($optval)) {
8848  $optval = sprintf('%.2F', $optval);
8849  }
8850  $annots .= ' '.$optval;
8851  }
8852  } else {
8853  $annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8854  }
8855  }
8856  if (isset($pl['opt']['rv'])) {
8857  $annots .= ' /RV';
8858  if (is_array($pl['opt']['rv'])) {
8859  foreach ($pl['opt']['rv'] AS $optval) {
8860  if (is_float($optval)) {
8861  $optval = sprintf('%.2F', $optval);
8862  }
8863  $annots .= ' '.$optval;
8864  }
8865  } else {
8866  $annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
8867  }
8868  }
8869  if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
8870  $annots .= ' /A << '.$pl['opt']['a'].' >>';
8871  }
8872  if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
8873  $annots .= ' /AA << '.$pl['opt']['aa'].' >>';
8874  }
8875  if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8876  $annots .= ' /DA ('.$pl['opt']['da'].')';
8877  }
8878  if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8879  $annots .= ' /Q '.intval($pl['opt']['q']);
8880  }
8881  if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
8882  $annots .= ' /Opt [';
8883  foreach($pl['opt']['opt'] AS $copt) {
8884  if (is_array($copt)) {
8885  $annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
8886  } else {
8887  $annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8888  }
8889  }
8890  $annots .= ']';
8891  }
8892  if (isset($pl['opt']['ti'])) {
8893  $annots .= ' /TI '.intval($pl['opt']['ti']);
8894  }
8895  if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
8896  $annots .= ' /I [';
8897  foreach($pl['opt']['i'] AS $copt) {
8898  $annots .= intval($copt).' ';
8899  }
8900  $annots .= ']';
8901  }
8902  break;
8903  }
8904  case 'screen': {
8905  break;
8906  }
8907  case 'printermark': {
8908  break;
8909  }
8910  case 'trapnet': {
8911  break;
8912  }
8913  case 'watermark': {
8914  break;
8915  }
8916  case '3d': {
8917  break;
8918  }
8919  default: {
8920  break;
8921  }
8922  }
8923  $annots .= '>>';
8924  // create new annotation object
8925  $this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
8926  if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
8927  // store reference of form object
8928  $this->form_obj_id[] = $annot_obj_id;
8929  }
8930  }
8931  }
8932  } // end for each page
8933  }
8934 
8944  protected function _putAPXObject($w=0, $h=0, $stream='') {
8945  $stream = trim($stream);
8946  $out = $this->_getobj()."\n";
8947  $this->xobjects['AX'.$this->n] = array('n' => $this->n);
8948  $out .= '<<';
8949  $out .= ' /Type /XObject';
8950  $out .= ' /Subtype /Form';
8951  $out .= ' /FormType 1';
8952  if ($this->compress) {
8953  $stream = gzcompress($stream);
8954  $out .= ' /Filter /FlateDecode';
8955  }
8956  $rect = sprintf('%.2F %.2F', $w, $h);
8957  $out .= ' /BBox [0 0 '.$rect.']';
8958  $out .= ' /Matrix [1 0 0 1 0 0]';
8959  $out .= ' /Resources <<';
8960  $out .= ' /ProcSet [/PDF /Text]';
8961  $out .= ' /Font <<';
8962  foreach ($this->annotation_fonts as $fontkey => $fontid) {
8963  $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
8964  }
8965  $out .= ' >>';
8966  $out .= ' >>';
8967  $stream = $this->_getrawstream($stream);
8968  $out .= ' /Length '.strlen($stream);
8969  $out .= ' >>';
8970  $out .= ' stream'."\n".$stream."\n".'endstream';
8971  $out .= "\n".'endobj';
8972  $this->_out($out);
8973  return $this->n;
8974  }
8975 
8985  protected function _getULONG(&$str, &$offset) {
8986  $v = unpack('Ni', substr($str, $offset, 4));
8987  $offset += 4;
8988  return $v['i'];
8989  }
8990 
9000  protected function _getUSHORT(&$str, &$offset) {
9001  $v = unpack('ni', substr($str, $offset, 2));
9002  $offset += 2;
9003  return $v['i'];
9004  }
9005 
9015  protected function _getSHORT(&$str, &$offset) {
9016  $v = unpack('si', substr($str, $offset, 2));
9017  $offset += 2;
9018  return $v['i'];
9019  }
9020 
9030  protected function _getBYTE(&$str, &$offset) {
9031  $v = unpack('Ci', substr($str, $offset, 1));
9032  ++$offset;
9033  return $v['i'];
9034  }
9035 
9045  protected function _getTrueTypeFontSubset($font, $subsetchars) {
9046  ksort($subsetchars);
9047  $offset = 0; // offset position of the font data
9048  if ($this->_getULONG($font, $offset) != 0x10000) {
9049  // sfnt version must be 0x00010000 for TrueType version 1.0.
9050  return $font;
9051  }
9052  // get number of tables
9053  $numTables = $this->_getUSHORT($font, $offset);
9054  // skip searchRange, entrySelector and rangeShift
9055  $offset += 6;
9056  // tables array
9057  $table = array();
9058  // for each table
9059  for ($i = 0; $i < $numTables; ++$i) {
9060  // get table info
9061  $tag = substr($font, $offset, 4);
9062  $offset += 4;
9063  $table[$tag] = array();
9064  $table[$tag]['checkSum'] = $this->_getULONG($font, $offset);
9065  $table[$tag]['offset'] = $this->_getULONG($font, $offset);
9066  $table[$tag]['length'] = $this->_getULONG($font, $offset);
9067  }
9068  // check magicNumber
9069  $offset = $table['head']['offset'] + 12;
9070  if ($this->_getULONG($font, $offset) != 0x5F0F3CF5) {
9071  // magicNumber must be 0x5F0F3CF5
9072  return $font;
9073  }
9074  // get offset mode (indexToLocFormat : 0 = short, 1 = long)
9075  $offset = $table['head']['offset'] + 50;
9076  $short_offset = ($this->_getSHORT($font, $offset) == 0);
9077  // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
9078  $indexToLoc = array();
9079  $offset = $table['loca']['offset'];
9080  if ($short_offset) {
9081  // short version
9082  $n = $table['loca']['length'] / 2; // numGlyphs + 1
9083  for ($i = 0; $i < $n; ++$i) {
9084  $indexToLoc[$i] = $this->_getUSHORT($font, $offset) * 2;
9085  }
9086  } else {
9087  // long version
9088  $n = $table['loca']['length'] / 4; // numGlyphs + 1
9089  for ($i = 0; $i < $n; ++$i) {
9090  $indexToLoc[$i] = $this->_getULONG($font, $offset);
9091  }
9092  }
9093  // get glyphs indexes of chars from cmap table
9094  $subsetglyphs = array(); // glyph IDs on key
9095  $subsetglyphs[0] = true; // character codes that do not correspond to any glyph in the font should be mapped to glyph index 0
9096  $offset = $table['cmap']['offset'] + 2;
9097  $numEncodingTables = $this->_getUSHORT($font, $offset);
9098  $encodingTables = array();
9099  for ($i = 0; $i < $numEncodingTables; ++$i) {
9100  $encodingTables[$i]['platformID'] = $this->_getUSHORT($font, $offset);
9101  $encodingTables[$i]['encodingID'] = $this->_getUSHORT($font, $offset);
9102  $encodingTables[$i]['offset'] = $this->_getULONG($font, $offset);
9103  }
9104  foreach ($encodingTables as $enctable) {
9105  if (($enctable['platformID'] == 3) AND ($enctable['encodingID'] == 0)) {
9106  $modesymbol = true;
9107  } else {
9108  $modesymbol = false;
9109  }
9110  $offset = $table['cmap']['offset'] + $enctable['offset'];
9111  $format = $this->_getUSHORT($font, $offset);
9112  switch ($format) {
9113  case 0: { // Format 0: Byte encoding table
9114  $offset += 4; // skip length and version/language
9115  for ($k = 0; $k < 256; ++$k) {
9116  if (isset($subsetchars[$k])) {
9117  $g = $this->_getBYTE($font, $offset);
9118  $subsetglyphs[$g] = $k;
9119  } else {
9120  ++$offset;
9121  }
9122  }
9123  break;
9124  }
9125  case 2: { // Format 2: High-byte mapping through table
9126  $offset += 4; // skip length and version
9127  // to be implemented ...
9128  break;
9129  }
9130  case 4: { // Format 4: Segment mapping to delta values
9131  $length = $this->_getUSHORT($font, $offset);
9132  $offset += 2; // skip version/language
9133  $segCount = ($this->_getUSHORT($font, $offset) / 2);
9134  $offset += 6; // skip searchRange, entrySelector, rangeShift
9135  $endCount = array(); // array of end character codes for each segment
9136  for ($k = 0; $k < $segCount; ++$k) {
9137  $endCount[$k] = $this->_getUSHORT($font, $offset);
9138  }
9139  $offset += 2; // skip reservedPad
9140  $startCount = array(); // array of start character codes for each segment
9141  for ($k = 0; $k < $segCount; ++$k) {
9142  $startCount[$k] = $this->_getUSHORT($font, $offset);
9143  }
9144  $idDelta = array(); // delta for all character codes in segment
9145  for ($k = 0; $k < $segCount; ++$k) {
9146  $idDelta[$k] = $this->_getUSHORT($font, $offset);
9147  }
9148  $idRangeOffset = array(); // Offsets into glyphIdArray or 0
9149  for ($k = 0; $k < $segCount; ++$k) {
9150  $idRangeOffset[$k] = $this->_getUSHORT($font, $offset);
9151  }
9152  $gidlen = ($length / 2) - 8 - (4 * $segCount);
9153  $glyphIdArray = array(); // glyph index array
9154  for ($k = 0; $k < $gidlen; ++$k) {
9155  $glyphIdArray[$k] = $this->_getUSHORT($font, $offset);
9156  }
9157  for ($k = 0; $k < $segCount; ++$k) {
9158  for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
9159  if (isset($subsetchars[$c])) {
9160  if ($idRangeOffset[$k] == 0) {
9161  $g = $c;
9162  } else {
9163  $gid = (($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
9164  $g = $glyphIdArray[$gid];
9165  }
9166  $g += ($idDelta[$k] - 65536);
9167  if ($g < 0) {
9168  $g = 0;
9169  }
9170  $subsetglyphs[$g] = $c;
9171  }
9172  }
9173  }
9174  break;
9175  }
9176  case 6: { // Format 6: Trimmed table mapping
9177  $offset += 4; // skip length and version/language
9178  $firstCode = $this->_getUSHORT($font, $offset);
9179  $entryCount = $this->_getUSHORT($font, $offset);
9180  for ($k = 0; $k < $entryCount; ++$k) {
9181  $c = ($k + $firstCode);
9182  if (isset($subsetchars[$c])) {
9183  $g = $this->_getUSHORT($font, $offset);
9184  $subsetglyphs[$g] = $c;
9185  } else {
9186  $offset += 2;
9187  }
9188  }
9189  break;
9190  }
9191  case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
9192  $offset += 10; // skip length and version
9193  // to be implemented ...
9194  break;
9195  }
9196  case 10: { // Format 10: Trimmed array
9197  $offset += 10; // skip length and version/language
9198  $startCharCode = $this->_getULONG($font, $offset);
9199  $numChars = $this->_getULONG($font, $offset);
9200  for ($k = 0; $k < $numChars; ++$k) {
9201  $c = ($k + $startCharCode);
9202  if (isset($subsetchars[$c])) {
9203  $g = $this->_getUSHORT($font, $offset);
9204  $subsetglyphs[$g] = $c;
9205  } else {
9206  $offset += 2;
9207  }
9208  }
9209  break;
9210  }
9211  case 12: { // Format 12: Segmented coverage
9212  $offset += 10; // skip length and version/language
9213  $nGroups = $this->_getULONG($font, $offset);
9214  for ($k = 0; $k < $nGroups; ++$k) {
9215  $startCharCode = $this->_getULONG($font, $offset);
9216  $endCharCode = $this->_getULONG($font, $offset);
9217  $startGlyphCode = $this->_getULONG($font, $offset);
9218  for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
9219  if (isset($subsetchars[$c])) {
9220  $subsetglyphs[$startGlyphCode] = $c;
9221  }
9222  ++$startGlyphCode;
9223  }
9224  }
9225  break;
9226  }
9227  }
9228  }
9229  // sort glyphs by key
9230  ksort($subsetglyphs);
9231  // add composite glyps to $subsetglyphs and remove missing glyphs
9232  foreach ($subsetglyphs as $key => $val) {
9233  if (isset($indexToLoc[$key])) {
9234  $offset = $table['glyf']['offset'] + $indexToLoc[$key];
9235  $numberOfContours = $this->_getSHORT($font, $offset);
9236  if ($numberOfContours < 0) { // composite glyph
9237  $offset += 8; // skip xMin, yMin, xMax, yMax
9238  do {
9239  $flags = $this->_getUSHORT($font, $offset);
9240  $glyphIndex = $this->_getUSHORT($font, $offset);
9241  if (!isset($subsetglyphs[$glyphIndex]) AND isset($indexToLoc[$glyphIndex])) {
9242  // add missing glyphs
9243  $subsetglyphs[$glyphIndex] = true;
9244  }
9245  // skip some bytes by case
9246  if ($flags & 1) {
9247  $offset += 4;
9248  } else {
9249  $offset += 2;
9250  }
9251  if ($flags & 8) {
9252  $offset += 2;
9253  } elseif ($flags & 64) {
9254  $offset += 4;
9255  } elseif ($flags & 128) {
9256  $offset += 8;
9257  }
9258  } while ($flags & 32);
9259  }
9260  } else {
9261  unset($subsetglyphs[$key]);
9262  }
9263  }
9264  // build new glyf table with only used glyphs
9265  $glyf = '';
9266  $glyfSize = 0;
9267  // create new empty indexToLoc table
9268  $newIndexToLoc = array_fill(0, count($indexToLoc), 0);
9269  $goffset = 0;
9270  foreach ($subsetglyphs as $glyphID => $char) {
9271  if (isset($indexToLoc[$glyphID]) AND isset($indexToLoc[($glyphID + 1)])) {
9272  $start = $indexToLoc[$glyphID];
9273  $length = ($indexToLoc[($glyphID + 1)] - $start);
9274  $glyf .= substr($font, ($table['glyf']['offset'] + $start), $length);
9275  $newIndexToLoc[$glyphID] = $goffset;
9276  $goffset += $length;
9277  }
9278  }
9279  // build new loca table
9280  $loca = '';
9281  if ($short_offset) {
9282  foreach ($newIndexToLoc as $glyphID => $offset) {
9283  $loca .= pack('n', ($offset / 2));
9284  }
9285  } else {
9286  foreach ($newIndexToLoc as $glyphID => $offset) {
9287  $loca .= pack('N', $offset);
9288  }
9289  }
9290  // array of table names to preserve (loca and glyf tables will be added later)
9291  //$table_names = array ('cmap', 'head', 'hhea', 'hmtx', 'maxp', 'name', 'OS/2', 'post', 'cvt ', 'fpgm', 'prep');
9292  // the cmap table is not needed and shall not be present, since the mapping from character codes to glyph descriptions is provided separately
9293  $table_names = array ('head', 'hhea', 'hmtx', 'maxp', 'cvt ', 'fpgm', 'prep'); // minimum required table names
9294  // get the tables to preserve
9295  $offset = 12;
9296  foreach ($table as $tag => $val) {
9297  if (in_array($tag, $table_names)) {
9298  $table[$tag]['data'] = substr($font, $table[$tag]['offset'], $table[$tag]['length']);
9299  if ($tag == 'head') {
9300  // set the checkSumAdjustment to 0
9301  $table[$tag]['data'] = substr($table[$tag]['data'], 0, 8)."\x0\x0\x0\x0".substr($table[$tag]['data'], 12);
9302  }
9303  $pad = 4 - ($table[$tag]['length'] % 4);
9304  if ($pad != 4) {
9305  // the length of a table must be a multiple of four bytes
9306  $table[$tag]['length'] += $pad;
9307  $table[$tag]['data'] .= str_repeat("\x0", $pad);
9308  }
9309  $table[$tag]['offset'] = $offset;
9310  $offset += $table[$tag]['length'];
9311  // check sum is not changed (so keep the following line commented)
9312  //$table[$tag]['checkSum'] = $this->_getTTFtableChecksum($table[$tag]['data'], $table[$tag]['length']);
9313  } else {
9314  unset($table[$tag]);
9315  }
9316  }
9317  // add loca
9318  $table['loca']['data'] = $loca;
9319  $table['loca']['length'] = strlen($loca);
9320  $pad = 4 - ($table['loca']['length'] % 4);
9321  if ($pad != 4) {
9322  // the length of a table must be a multiple of four bytes
9323  $table['loca']['length'] += $pad;
9324  $table['loca']['data'] .= str_repeat("\x0", $pad);
9325  }
9326  $table['loca']['offset'] = $offset;
9327  $table['loca']['checkSum'] = $this->_getTTFtableChecksum($table['loca']['data'], $table['loca']['length']);
9328  $offset += $table['loca']['length'];
9329  // add glyf
9330  $table['glyf']['data'] = $glyf;
9331  $table['glyf']['length'] = strlen($glyf);
9332  $pad = 4 - ($table['glyf']['length'] % 4);
9333  if ($pad != 4) {
9334  // the length of a table must be a multiple of four bytes
9335  $table['glyf']['length'] += $pad;
9336  $table['glyf']['data'] .= str_repeat("\x0", $pad);
9337  }
9338  $table['glyf']['offset'] = $offset;
9339  $table['glyf']['checkSum'] = $this->_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length']);
9340  // rebuild font
9341  $font = '';
9342  $font .= pack('N', 0x10000); // sfnt version
9343  $numTables = count($table);
9344  $font .= pack('n', $numTables); // numTables
9345  $entrySelector = floor(log($numTables, 2));
9346  $searchRange = pow(2, $entrySelector) * 16;
9347  $rangeShift = ($numTables * 16) - $searchRange;
9348  $font .= pack('n', $searchRange); // searchRange
9349  $font .= pack('n', $entrySelector); // entrySelector
9350  $font .= pack('n', $rangeShift); // rangeShift
9351  $offset = ($numTables * 16);
9352  foreach ($table as $tag => $data) {
9353  $font .= $tag; // tag
9354  $font .= pack('N', $data['checkSum']); // checkSum
9355  $font .= pack('N', ($data['offset'] + $offset)); // offset
9356  $font .= pack('N', $data['length']); // length
9357  }
9358  foreach ($table as $data) {
9359  $font .= $data['data'];
9360  }
9361  // set checkSumAdjustment on head table
9362  $checkSumAdjustment = 0xB1B0AFBA - $this->_getTTFtableChecksum($font, strlen($font));
9363  $font = substr($font, 0, $table['head']['offset'] + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + 12);
9364  return $font;
9365  }
9366 
9376  protected function _getTTFtableChecksum($table, $length) {
9377  $sum = 0;
9378  $tlen = ($length / 4);
9379  $offset = 0;
9380  for ($i = 0; $i < $tlen; ++$i) {
9381  $v = unpack('Ni', substr($table, $offset, 4));
9382  $sum += $v['i'];
9383  $offset += 4;
9384  }
9385  $sum = unpack('Ni', pack('N', $sum));
9386  return $sum['i'];
9387  }
9388 
9398  protected function _putfontwidths($font, $cidoffset=0) {
9399  ksort($font['cw']);
9400  $rangeid = 0;
9401  $range = array();
9402  $prevcid = -2;
9403  $prevwidth = -1;
9404  $interval = false;
9405  // for each character
9406  foreach ($font['cw'] as $cid => $width) {
9407  $cid -= $cidoffset;
9408  if ($font['subset'] AND ($cid > 255) AND (!isset($font['subsetchars'][$cid]))) {
9409  // ignore the unused characters (font subsetting)
9410  continue;
9411  }
9412  if ($width != $font['dw']) {
9413  if ($cid == ($prevcid + 1)) {
9414  // consecutive CID
9415  if ($width == $prevwidth) {
9416  if ($width == $range[$rangeid][0]) {
9417  $range[$rangeid][] = $width;
9418  } else {
9419  array_pop($range[$rangeid]);
9420  // new range
9421  $rangeid = $prevcid;
9422  $range[$rangeid] = array();
9423  $range[$rangeid][] = $prevwidth;
9424  $range[$rangeid][] = $width;
9425  }
9426  $interval = true;
9427  $range[$rangeid]['interval'] = true;
9428  } else {
9429  if ($interval) {
9430  // new range
9431  $rangeid = $cid;
9432  $range[$rangeid] = array();
9433  $range[$rangeid][] = $width;
9434  } else {
9435  $range[$rangeid][] = $width;
9436  }
9437  $interval = false;
9438  }
9439  } else {
9440  // new range
9441  $rangeid = $cid;
9442  $range[$rangeid] = array();
9443  $range[$rangeid][] = $width;
9444  $interval = false;
9445  }
9446  $prevcid = $cid;
9447  $prevwidth = $width;
9448  }
9449  }
9450  // optimize ranges
9451  $prevk = -1;
9452  $nextk = -1;
9453  $prevint = false;
9454  foreach ($range as $k => $ws) {
9455  $cws = count($ws);
9456  if (($k == $nextk) AND (!$prevint) AND ((!isset($ws['interval'])) OR ($cws < 4))) {
9457  if (isset($range[$k]['interval'])) {
9458  unset($range[$k]['interval']);
9459  }
9460  $range[$prevk] = array_merge($range[$prevk], $range[$k]);
9461  unset($range[$k]);
9462  } else {
9463  $prevk = $k;
9464  }
9465  $nextk = $k + $cws;
9466  if (isset($ws['interval'])) {
9467  if ($cws > 3) {
9468  $prevint = true;
9469  } else {
9470  $prevint = false;
9471  }
9472  unset($range[$k]['interval']);
9473  --$nextk;
9474  } else {
9475  $prevint = false;
9476  }
9477  }
9478  // output data
9479  $w = '';
9480  foreach ($range as $k => $ws) {
9481  if (count(array_count_values($ws)) == 1) {
9482  // interval mode is more compact
9483  $w .= ' '.$k.' '.($k + count($ws) - 1).' '.$ws[0];
9484  } else {
9485  // range mode
9486  $w .= ' '.$k.' [ '.implode(' ', $ws).' ]';
9487  }
9488  }
9489  return '/W ['.$w.' ]';
9490  }
9491 
9497  protected function _putfonts() {
9498  $nf = $this->n;
9499  foreach ($this->diffs as $diff) {
9500  //Encodings
9501  $this->_newobj();
9502  $this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
9503  }
9504  $mqr = $this->get_mqr();
9505  $this->set_mqr(false);
9506  foreach ($this->FontFiles as $file => $info) {
9507  // search and get font file to embedd
9508  $fontdir = $info['fontdir'];
9509  $file = strtolower($file);
9510  $fontfile = '';
9511  // search files on various directories
9512  if (($fontdir !== false) AND file_exists($fontdir.$file)) {
9513  $fontfile = $fontdir.$file;
9514  } elseif (file_exists($this->_getfontpath().$file)) {
9515  $fontfile = $this->_getfontpath().$file;
9516  } elseif (file_exists($file)) {
9517  $fontfile = $file;
9518  }
9519  if (!$this->empty_string($fontfile)) {
9520  $font = file_get_contents($fontfile);
9521  $compressed = (substr($file, -2) == '.z');
9522  if ((!$compressed) AND (isset($info['length2']))) {
9523  $header = (ord($font{0}) == 128);
9524  if ($header) {
9525  //Strip first binary header
9526  $font = substr($font, 6);
9527  }
9528  if ($header AND (ord($font{$info['length1']}) == 128)) {
9529  //Strip second binary header
9530  $font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
9531  }
9532  } elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
9533  if ($compressed) {
9534  // uncompress font
9535  $font = gzuncompress($font);
9536  }
9537  // merge subset characters
9538  $subsetchars = array(); // used chars
9539  foreach ($info['fontkeys'] as $fontkey) {
9540  $fontinfo = $this->getFontBuffer($fontkey);
9541  $subsetchars += $fontinfo['subsetchars'];
9542  }
9543  $font = $this->_getTrueTypeFontSubset($font, $subsetchars);
9544  if ($compressed) {
9545  // recompress font
9546  $font = gzcompress($font);
9547  }
9548  }
9549  $this->_newobj();
9550  $this->FontFiles[$file]['n'] = $this->n;
9551  $stream = $this->_getrawstream($font);
9552  $out = '<< /Length '.strlen($stream);
9553  if ($compressed) {
9554  $out .= ' /Filter /FlateDecode';
9555  }
9556  $out .= ' /Length1 '.$info['length1'];
9557  if (isset($info['length2'])) {
9558  $out .= ' /Length2 '.$info['length2'].' /Length3 0';
9559  }
9560  $out .= ' >>';
9561  $out .= ' stream'."\n".$stream."\n".'endstream';
9562  $out .= "\n".'endobj';
9563  $this->_out($out);
9564  }
9565  }
9566  $this->set_mqr($mqr);
9567  foreach ($this->fontkeys as $k) {
9568  //Font objects
9569  $font = $this->getFontBuffer($k);
9570  $type = $font['type'];
9571  $name = $font['name'];
9572  if ($type == 'core') {
9573  // standard core font
9574  $out = $this->_getobj($this->font_obj_ids[$k])."\n";
9575  $out .= '<</Type /Font';
9576  $out .= ' /Subtype /Type1';
9577  $out .= ' /BaseFont /'.$name;
9578  $out .= ' /Name /F'.$font['i'];
9579  if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
9580  $out .= ' /Encoding /WinAnsiEncoding';
9581  }
9582  if ($k == 'helvetica') {
9583  // add default font for annotations
9584  $this->annotation_fonts[$k] = $font['i'];
9585  }
9586  $out .= ' >>';
9587  $out .= "\n".'endobj';
9588  $this->_out($out);
9589  } elseif (($type == 'Type1') OR ($type == 'TrueType')) {
9590  // additional Type1 or TrueType font
9591  $out = $this->_getobj($this->font_obj_ids[$k])."\n";
9592  $out .= '<</Type /Font';
9593  $out .= ' /Subtype /'.$type;
9594  $out .= ' /BaseFont /'.$name;
9595  $out .= ' /Name /F'.$font['i'];
9596  $out .= ' /FirstChar 32 /LastChar 255';
9597  $out .= ' /Widths '.($this->n + 1).' 0 R';
9598  $out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
9599  if ($font['enc']) {
9600  if (isset($font['diff'])) {
9601  $out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
9602  } else {
9603  $out .= ' /Encoding /WinAnsiEncoding';
9604  }
9605  }
9606  $out .= ' >>';
9607  $out .= "\n".'endobj';
9608  $this->_out($out);
9609  // Widths
9610  $this->_newobj();
9611  $cw = &$font['cw'];
9612  $s = '[';
9613  for ($i = 32; $i < 256; ++$i) {
9614  $s .= $cw[$i].' ';
9615  }
9616  $s .= ']';
9617  $s .= "\n".'endobj';
9618  $this->_out($s);
9619  //Descriptor
9620  $this->_newobj();
9621  $s = '<</Type /FontDescriptor /FontName /'.$name;
9622  foreach ($font['desc'] as $fdk => $fdv) {
9623  if(is_float($fdv)) {
9624  $fdv = sprintf('%.3F', $fdv);
9625  }
9626  $s .= ' /'.$fdk.' '.$fdv.'';
9627  }
9628  if (!$this->empty_string($font['file'])) {
9629  $s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
9630  }
9631  $s .= '>>';
9632  $s .= "\n".'endobj';
9633  $this->_out($s);
9634  } else {
9635  // additional types
9636  $mtd = '_put'.strtolower($type);
9637  if (!method_exists($this, $mtd)) {
9638  $this->Error('Unsupported font type: '.$type);
9639  }
9640  $this->$mtd($font);
9641  }
9642  }
9643  }
9644 
9653  protected function _puttruetypeunicode($font) {
9654  $fontname = '';
9655  if ($font['subset']) {
9656  // change name for font subsetting
9657  $subtag = sprintf('%06u', $font['i']);
9658  $subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
9659  $fontname .= $subtag.'+';
9660  }
9661  $fontname .= $font['name'];
9662  // Type0 Font
9663  // A composite font composed of other fonts, organized hierarchically
9664  $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9665  $out .= '<< /Type /Font';
9666  $out .= ' /Subtype /Type0';
9667  $out .= ' /BaseFont /'.$fontname;
9668  $out .= ' /Name /F'.$font['i'];
9669  $out .= ' /Encoding /'.$font['enc'];
9670  $out .= ' /ToUnicode '.($this->n + 1).' 0 R';
9671  $out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
9672  $out .= ' >>';
9673  $out .= "\n".'endobj';
9674  $this->_out($out);
9675  // ToUnicode map for Identity-H
9676  $stream = "/CIDInit /ProcSet findresource begin\n";
9677  $stream .= "12 dict begin\n";
9678  $stream .= "begincmap\n";
9679  $stream .= "/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n";
9680  $stream .= "/CMapName /Adobe-Identity-UCS def\n";
9681  $stream .= "/CMapType 2 def\n";
9682  $stream .= "/WMode 0 def\n";
9683  $stream .= "1 begincodespacerange\n";
9684  $stream .= "<0000> <FFFF>\n";
9685  $stream .= "endcodespacerange\n";
9686  $stream .= "100 beginbfrange\n";
9687  $stream .= "<0000> <00ff> <0000>\n";
9688  $stream .= "<0100> <01ff> <0100>\n";
9689  $stream .= "<0200> <02ff> <0200>\n";
9690  $stream .= "<0300> <03ff> <0300>\n";
9691  $stream .= "<0400> <04ff> <0400>\n";
9692  $stream .= "<0500> <05ff> <0500>\n";
9693  $stream .= "<0600> <06ff> <0600>\n";
9694  $stream .= "<0700> <07ff> <0700>\n";
9695  $stream .= "<0800> <08ff> <0800>\n";
9696  $stream .= "<0900> <09ff> <0900>\n";
9697  $stream .= "<0a00> <0aff> <0a00>\n";
9698  $stream .= "<0b00> <0bff> <0b00>\n";
9699  $stream .= "<0c00> <0cff> <0c00>\n";
9700  $stream .= "<0d00> <0dff> <0d00>\n";
9701  $stream .= "<0e00> <0eff> <0e00>\n";
9702  $stream .= "<0f00> <0fff> <0f00>\n";
9703  $stream .= "<1000> <10ff> <1000>\n";
9704  $stream .= "<1100> <11ff> <1100>\n";
9705  $stream .= "<1200> <12ff> <1200>\n";
9706  $stream .= "<1300> <13ff> <1300>\n";
9707  $stream .= "<1400> <14ff> <1400>\n";
9708  $stream .= "<1500> <15ff> <1500>\n";
9709  $stream .= "<1600> <16ff> <1600>\n";
9710  $stream .= "<1700> <17ff> <1700>\n";
9711  $stream .= "<1800> <18ff> <1800>\n";
9712  $stream .= "<1900> <19ff> <1900>\n";
9713  $stream .= "<1a00> <1aff> <1a00>\n";
9714  $stream .= "<1b00> <1bff> <1b00>\n";
9715  $stream .= "<1c00> <1cff> <1c00>\n";
9716  $stream .= "<1d00> <1dff> <1d00>\n";
9717  $stream .= "<1e00> <1eff> <1e00>\n";
9718  $stream .= "<1f00> <1fff> <1f00>\n";
9719  $stream .= "<2000> <20ff> <2000>\n";
9720  $stream .= "<2100> <21ff> <2100>\n";
9721  $stream .= "<2200> <22ff> <2200>\n";
9722  $stream .= "<2300> <23ff> <2300>\n";
9723  $stream .= "<2400> <24ff> <2400>\n";
9724  $stream .= "<2500> <25ff> <2500>\n";
9725  $stream .= "<2600> <26ff> <2600>\n";
9726  $stream .= "<2700> <27ff> <2700>\n";
9727  $stream .= "<2800> <28ff> <2800>\n";
9728  $stream .= "<2900> <29ff> <2900>\n";
9729  $stream .= "<2a00> <2aff> <2a00>\n";
9730  $stream .= "<2b00> <2bff> <2b00>\n";
9731  $stream .= "<2c00> <2cff> <2c00>\n";
9732  $stream .= "<2d00> <2dff> <2d00>\n";
9733  $stream .= "<2e00> <2eff> <2e00>\n";
9734  $stream .= "<2f00> <2fff> <2f00>\n";
9735  $stream .= "<3000> <30ff> <3000>\n";
9736  $stream .= "<3100> <31ff> <3100>\n";
9737  $stream .= "<3200> <32ff> <3200>\n";
9738  $stream .= "<3300> <33ff> <3300>\n";
9739  $stream .= "<3400> <34ff> <3400>\n";
9740  $stream .= "<3500> <35ff> <3500>\n";
9741  $stream .= "<3600> <36ff> <3600>\n";
9742  $stream .= "<3700> <37ff> <3700>\n";
9743  $stream .= "<3800> <38ff> <3800>\n";
9744  $stream .= "<3900> <39ff> <3900>\n";
9745  $stream .= "<3a00> <3aff> <3a00>\n";
9746  $stream .= "<3b00> <3bff> <3b00>\n";
9747  $stream .= "<3c00> <3cff> <3c00>\n";
9748  $stream .= "<3d00> <3dff> <3d00>\n";
9749  $stream .= "<3e00> <3eff> <3e00>\n";
9750  $stream .= "<3f00> <3fff> <3f00>\n";
9751  $stream .= "<4000> <40ff> <4000>\n";
9752  $stream .= "<4100> <41ff> <4100>\n";
9753  $stream .= "<4200> <42ff> <4200>\n";
9754  $stream .= "<4300> <43ff> <4300>\n";
9755  $stream .= "<4400> <44ff> <4400>\n";
9756  $stream .= "<4500> <45ff> <4500>\n";
9757  $stream .= "<4600> <46ff> <4600>\n";
9758  $stream .= "<4700> <47ff> <4700>\n";
9759  $stream .= "<4800> <48ff> <4800>\n";
9760  $stream .= "<4900> <49ff> <4900>\n";
9761  $stream .= "<4a00> <4aff> <4a00>\n";
9762  $stream .= "<4b00> <4bff> <4b00>\n";
9763  $stream .= "<4c00> <4cff> <4c00>\n";
9764  $stream .= "<4d00> <4dff> <4d00>\n";
9765  $stream .= "<4e00> <4eff> <4e00>\n";
9766  $stream .= "<4f00> <4fff> <4f00>\n";
9767  $stream .= "<5000> <50ff> <5000>\n";
9768  $stream .= "<5100> <51ff> <5100>\n";
9769  $stream .= "<5200> <52ff> <5200>\n";
9770  $stream .= "<5300> <53ff> <5300>\n";
9771  $stream .= "<5400> <54ff> <5400>\n";
9772  $stream .= "<5500> <55ff> <5500>\n";
9773  $stream .= "<5600> <56ff> <5600>\n";
9774  $stream .= "<5700> <57ff> <5700>\n";
9775  $stream .= "<5800> <58ff> <5800>\n";
9776  $stream .= "<5900> <59ff> <5900>\n";
9777  $stream .= "<5a00> <5aff> <5a00>\n";
9778  $stream .= "<5b00> <5bff> <5b00>\n";
9779  $stream .= "<5c00> <5cff> <5c00>\n";
9780  $stream .= "<5d00> <5dff> <5d00>\n";
9781  $stream .= "<5e00> <5eff> <5e00>\n";
9782  $stream .= "<5f00> <5fff> <5f00>\n";
9783  $stream .= "<6000> <60ff> <6000>\n";
9784  $stream .= "<6100> <61ff> <6100>\n";
9785  $stream .= "<6200> <62ff> <6200>\n";
9786  $stream .= "<6300> <63ff> <6300>\n";
9787  $stream .= "endbfrange\n";
9788  $stream .= "100 beginbfrange\n";
9789  $stream .= "<6400> <64ff> <6400>\n";
9790  $stream .= "<6500> <65ff> <6500>\n";
9791  $stream .= "<6600> <66ff> <6600>\n";
9792  $stream .= "<6700> <67ff> <6700>\n";
9793  $stream .= "<6800> <68ff> <6800>\n";
9794  $stream .= "<6900> <69ff> <6900>\n";
9795  $stream .= "<6a00> <6aff> <6a00>\n";
9796  $stream .= "<6b00> <6bff> <6b00>\n";
9797  $stream .= "<6c00> <6cff> <6c00>\n";
9798  $stream .= "<6d00> <6dff> <6d00>\n";
9799  $stream .= "<6e00> <6eff> <6e00>\n";
9800  $stream .= "<6f00> <6fff> <6f00>\n";
9801  $stream .= "<7000> <70ff> <7000>\n";
9802  $stream .= "<7100> <71ff> <7100>\n";
9803  $stream .= "<7200> <72ff> <7200>\n";
9804  $stream .= "<7300> <73ff> <7300>\n";
9805  $stream .= "<7400> <74ff> <7400>\n";
9806  $stream .= "<7500> <75ff> <7500>\n";
9807  $stream .= "<7600> <76ff> <7600>\n";
9808  $stream .= "<7700> <77ff> <7700>\n";
9809  $stream .= "<7800> <78ff> <7800>\n";
9810  $stream .= "<7900> <79ff> <7900>\n";
9811  $stream .= "<7a00> <7aff> <7a00>\n";
9812  $stream .= "<7b00> <7bff> <7b00>\n";
9813  $stream .= "<7c00> <7cff> <7c00>\n";
9814  $stream .= "<7d00> <7dff> <7d00>\n";
9815  $stream .= "<7e00> <7eff> <7e00>\n";
9816  $stream .= "<7f00> <7fff> <7f00>\n";
9817  $stream .= "<8000> <80ff> <8000>\n";
9818  $stream .= "<8100> <81ff> <8100>\n";
9819  $stream .= "<8200> <82ff> <8200>\n";
9820  $stream .= "<8300> <83ff> <8300>\n";
9821  $stream .= "<8400> <84ff> <8400>\n";
9822  $stream .= "<8500> <85ff> <8500>\n";
9823  $stream .= "<8600> <86ff> <8600>\n";
9824  $stream .= "<8700> <87ff> <8700>\n";
9825  $stream .= "<8800> <88ff> <8800>\n";
9826  $stream .= "<8900> <89ff> <8900>\n";
9827  $stream .= "<8a00> <8aff> <8a00>\n";
9828  $stream .= "<8b00> <8bff> <8b00>\n";
9829  $stream .= "<8c00> <8cff> <8c00>\n";
9830  $stream .= "<8d00> <8dff> <8d00>\n";
9831  $stream .= "<8e00> <8eff> <8e00>\n";
9832  $stream .= "<8f00> <8fff> <8f00>\n";
9833  $stream .= "<9000> <90ff> <9000>\n";
9834  $stream .= "<9100> <91ff> <9100>\n";
9835  $stream .= "<9200> <92ff> <9200>\n";
9836  $stream .= "<9300> <93ff> <9300>\n";
9837  $stream .= "<9400> <94ff> <9400>\n";
9838  $stream .= "<9500> <95ff> <9500>\n";
9839  $stream .= "<9600> <96ff> <9600>\n";
9840  $stream .= "<9700> <97ff> <9700>\n";
9841  $stream .= "<9800> <98ff> <9800>\n";
9842  $stream .= "<9900> <99ff> <9900>\n";
9843  $stream .= "<9a00> <9aff> <9a00>\n";
9844  $stream .= "<9b00> <9bff> <9b00>\n";
9845  $stream .= "<9c00> <9cff> <9c00>\n";
9846  $stream .= "<9d00> <9dff> <9d00>\n";
9847  $stream .= "<9e00> <9eff> <9e00>\n";
9848  $stream .= "<9f00> <9fff> <9f00>\n";
9849  $stream .= "<a000> <a0ff> <a000>\n";
9850  $stream .= "<a100> <a1ff> <a100>\n";
9851  $stream .= "<a200> <a2ff> <a200>\n";
9852  $stream .= "<a300> <a3ff> <a300>\n";
9853  $stream .= "<a400> <a4ff> <a400>\n";
9854  $stream .= "<a500> <a5ff> <a500>\n";
9855  $stream .= "<a600> <a6ff> <a600>\n";
9856  $stream .= "<a700> <a7ff> <a700>\n";
9857  $stream .= "<a800> <a8ff> <a800>\n";
9858  $stream .= "<a900> <a9ff> <a900>\n";
9859  $stream .= "<aa00> <aaff> <aa00>\n";
9860  $stream .= "<ab00> <abff> <ab00>\n";
9861  $stream .= "<ac00> <acff> <ac00>\n";
9862  $stream .= "<ad00> <adff> <ad00>\n";
9863  $stream .= "<ae00> <aeff> <ae00>\n";
9864  $stream .= "<af00> <afff> <af00>\n";
9865  $stream .= "<b000> <b0ff> <b000>\n";
9866  $stream .= "<b100> <b1ff> <b100>\n";
9867  $stream .= "<b200> <b2ff> <b200>\n";
9868  $stream .= "<b300> <b3ff> <b300>\n";
9869  $stream .= "<b400> <b4ff> <b400>\n";
9870  $stream .= "<b500> <b5ff> <b500>\n";
9871  $stream .= "<b600> <b6ff> <b600>\n";
9872  $stream .= "<b700> <b7ff> <b700>\n";
9873  $stream .= "<b800> <b8ff> <b800>\n";
9874  $stream .= "<b900> <b9ff> <b900>\n";
9875  $stream .= "<ba00> <baff> <ba00>\n";
9876  $stream .= "<bb00> <bbff> <bb00>\n";
9877  $stream .= "<bc00> <bcff> <bc00>\n";
9878  $stream .= "<bd00> <bdff> <bd00>\n";
9879  $stream .= "<be00> <beff> <be00>\n";
9880  $stream .= "<bf00> <bfff> <bf00>\n";
9881  $stream .= "<c000> <c0ff> <c000>\n";
9882  $stream .= "<c100> <c1ff> <c100>\n";
9883  $stream .= "<c200> <c2ff> <c200>\n";
9884  $stream .= "<c300> <c3ff> <c300>\n";
9885  $stream .= "<c400> <c4ff> <c400>\n";
9886  $stream .= "<c500> <c5ff> <c500>\n";
9887  $stream .= "<c600> <c6ff> <c600>\n";
9888  $stream .= "<c700> <c7ff> <c700>\n";
9889  $stream .= "endbfrange\n";
9890  $stream .= "56 beginbfrange\n";
9891  $stream .= "<c800> <c8ff> <c800>\n";
9892  $stream .= "<c900> <c9ff> <c900>\n";
9893  $stream .= "<ca00> <caff> <ca00>\n";
9894  $stream .= "<cb00> <cbff> <cb00>\n";
9895  $stream .= "<cc00> <ccff> <cc00>\n";
9896  $stream .= "<cd00> <cdff> <cd00>\n";
9897  $stream .= "<ce00> <ceff> <ce00>\n";
9898  $stream .= "<cf00> <cfff> <cf00>\n";
9899  $stream .= "<d000> <d0ff> <d000>\n";
9900  $stream .= "<d100> <d1ff> <d100>\n";
9901  $stream .= "<d200> <d2ff> <d200>\n";
9902  $stream .= "<d300> <d3ff> <d300>\n";
9903  $stream .= "<d400> <d4ff> <d400>\n";
9904  $stream .= "<d500> <d5ff> <d500>\n";
9905  $stream .= "<d600> <d6ff> <d600>\n";
9906  $stream .= "<d700> <d7ff> <d700>\n";
9907  $stream .= "<d800> <d8ff> <d800>\n";
9908  $stream .= "<d900> <d9ff> <d900>\n";
9909  $stream .= "<da00> <daff> <da00>\n";
9910  $stream .= "<db00> <dbff> <db00>\n";
9911  $stream .= "<dc00> <dcff> <dc00>\n";
9912  $stream .= "<dd00> <ddff> <dd00>\n";
9913  $stream .= "<de00> <deff> <de00>\n";
9914  $stream .= "<df00> <dfff> <df00>\n";
9915  $stream .= "<e000> <e0ff> <e000>\n";
9916  $stream .= "<e100> <e1ff> <e100>\n";
9917  $stream .= "<e200> <e2ff> <e200>\n";
9918  $stream .= "<e300> <e3ff> <e300>\n";
9919  $stream .= "<e400> <e4ff> <e400>\n";
9920  $stream .= "<e500> <e5ff> <e500>\n";
9921  $stream .= "<e600> <e6ff> <e600>\n";
9922  $stream .= "<e700> <e7ff> <e700>\n";
9923  $stream .= "<e800> <e8ff> <e800>\n";
9924  $stream .= "<e900> <e9ff> <e900>\n";
9925  $stream .= "<ea00> <eaff> <ea00>\n";
9926  $stream .= "<eb00> <ebff> <eb00>\n";
9927  $stream .= "<ec00> <ecff> <ec00>\n";
9928  $stream .= "<ed00> <edff> <ed00>\n";
9929  $stream .= "<ee00> <eeff> <ee00>\n";
9930  $stream .= "<ef00> <efff> <ef00>\n";
9931  $stream .= "<f000> <f0ff> <f000>\n";
9932  $stream .= "<f100> <f1ff> <f100>\n";
9933  $stream .= "<f200> <f2ff> <f200>\n";
9934  $stream .= "<f300> <f3ff> <f300>\n";
9935  $stream .= "<f400> <f4ff> <f400>\n";
9936  $stream .= "<f500> <f5ff> <f500>\n";
9937  $stream .= "<f600> <f6ff> <f600>\n";
9938  $stream .= "<f700> <f7ff> <f700>\n";
9939  $stream .= "<f800> <f8ff> <f800>\n";
9940  $stream .= "<f900> <f9ff> <f900>\n";
9941  $stream .= "<fa00> <faff> <fa00>\n";
9942  $stream .= "<fb00> <fbff> <fb00>\n";
9943  $stream .= "<fc00> <fcff> <fc00>\n";
9944  $stream .= "<fd00> <fdff> <fd00>\n";
9945  $stream .= "<fe00> <feff> <fe00>\n";
9946  $stream .= "<ff00> <ffff> <ff00>\n";
9947  $stream .= "endbfrange\n";
9948  $stream .= "endcmap\n";
9949  $stream .= "CMapName currentdict /CMap defineresource pop\n";
9950  $stream .= "end\n";
9951  $stream .= "end";
9952  // ToUnicode Object
9953  $this->_newobj();
9954  $stream = ($this->compress) ? gzcompress($stream) : $stream;
9955  $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9956  $stream = $this->_getrawstream($stream);
9957  $this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
9958  // CIDFontType2
9959  // A CIDFont whose glyph descriptions are based on TrueType font technology
9960  $oid = $this->_newobj();
9961  $out = '<< /Type /Font';
9962  $out .= ' /Subtype /CIDFontType2';
9963  $out .= ' /BaseFont /'.$fontname;
9964  // A dictionary containing entries that define the character collection of the CIDFont.
9965  $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9966  $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9967  $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9968  $out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
9969  $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9970  $out .= ' /DW '.$font['dw']; // default width
9971  $out .= "\n".$this->_putfontwidths($font, 0);
9972  if (isset($font['ctg']) AND (!$this->empty_string($font['ctg']))) {
9973  $out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
9974  }
9975  $out .= ' >>';
9976  $out .= "\n".'endobj';
9977  $this->_out($out);
9978  // Font descriptor
9979  // A font descriptor describing the CIDFont default metrics other than its glyph widths
9980  $this->_newobj();
9981  $out = '<< /Type /FontDescriptor';
9982  $out .= ' /FontName /'.$fontname;
9983  foreach ($font['desc'] as $key => $value) {
9984  if(is_float($value)) {
9985  $value = sprintf('%.3F', $value);
9986  }
9987  $out .= ' /'.$key.' '.$value;
9988  }
9989  $fontdir = false;
9990  if (!$this->empty_string($font['file'])) {
9991  // A stream containing a TrueType font
9992  $out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
9993  $fontdir = $this->FontFiles[$font['file']]['fontdir'];
9994  }
9995  $out .= ' >>';
9996  $out .= "\n".'endobj';
9997  $this->_out($out);
9998  if (isset($font['ctg']) AND (!$this->empty_string($font['ctg']))) {
9999  $this->_newobj();
10000  // Embed CIDToGIDMap
10001  // A specification of the mapping from CIDs to glyph indices
10002  // search and get CTG font file to embedd
10003  $ctgfile = strtolower($font['ctg']);
10004  // search and get ctg font file to embedd
10005  $fontfile = '';
10006  // search files on various directories
10007  if (($fontdir !== false) AND file_exists($fontdir.$ctgfile)) {
10008  $fontfile = $fontdir.$ctgfile;
10009  } elseif (file_exists($this->_getfontpath().$ctgfile)) {
10010  $fontfile = $this->_getfontpath().$ctgfile;
10011  } elseif (file_exists($ctgfile)) {
10012  $fontfile = $ctgfile;
10013  }
10014  if ($this->empty_string($fontfile)) {
10015  $this->Error('Font file not found: '.$ctgfile);
10016  }
10017  $stream = $this->_getrawstream(file_get_contents($fontfile));
10018  $out = '<< /Length '.strlen($stream).'';
10019  if (substr($fontfile, -2) == '.z') { // check file extension
10020  // Decompresses data encoded using the public-domain
10021  // zlib/deflate compression method, reproducing the
10022  // original text or binary data
10023  $out .= ' /Filter /FlateDecode';
10024  }
10025  $out .= ' >>';
10026  $out .= ' stream'."\n".$stream."\n".'endstream';
10027  $out .= "\n".'endobj';
10028  $this->_out($out);
10029  }
10030  }
10031 
10040  protected function _putcidfont0($font) {
10041  $cidoffset = 0;
10042  if (!isset($font['cw'][1])) {
10043  $cidoffset = 31;
10044  }
10045  if (isset($font['cidinfo']['uni2cid'])) {
10046  // convert unicode to cid.
10047  $uni2cid = $font['cidinfo']['uni2cid'];
10048  $cw = array();
10049  foreach ($font['cw'] as $uni => $width) {
10050  if (isset($uni2cid[$uni])) {
10051  $cw[($uni2cid[$uni] + $cidoffset)] = $width;
10052  } elseif ($uni < 256) {
10053  $cw[$uni] = $width;
10054  } // else unknown character
10055  }
10056  $font = array_merge($font, array('cw' => $cw));
10057  }
10058  $name = $font['name'];
10059  $enc = $font['enc'];
10060  if ($enc) {
10061  $longname = $name.'-'.$enc;
10062  } else {
10063  $longname = $name;
10064  }
10065  $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
10066  $out .= '<</Type /Font';
10067  $out .= ' /Subtype /Type0';
10068  $out .= ' /BaseFont /'.$longname;
10069  $out .= ' /Name /F'.$font['i'];
10070  if ($enc) {
10071  $out .= ' /Encoding /'.$enc;
10072  }
10073  $out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
10074  $out .= ' >>';
10075  $out .= "\n".'endobj';
10076  $this->_out($out);
10077  $oid = $this->_newobj();
10078  $out = '<</Type /Font';
10079  $out .= ' /Subtype /CIDFontType0';
10080  $out .= ' /BaseFont /'.$name;
10081  $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
10082  $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
10083  $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
10084  $out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
10085  $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
10086  $out .= ' /DW '.$font['dw'];
10087  $out .= "\n".$this->_putfontwidths($font, $cidoffset);
10088  $out .= ' >>';
10089  $out .= "\n".'endobj';
10090  $this->_out($out);
10091  $this->_newobj();
10092  $s = '<</Type /FontDescriptor /FontName /'.$name;
10093  foreach ($font['desc'] as $k => $v) {
10094  if ($k != 'Style') {
10095  if(is_float($v)) {
10096  $v = sprintf('%.3F', $v);
10097  }
10098  $s .= ' /'.$k.' '.$v.'';
10099  }
10100  }
10101  $s .= '>>';
10102  $s .= "\n".'endobj';
10103  $this->_out($s);
10104  }
10105 
10110  protected function _putimages() {
10111  $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
10112  foreach ($this->imagekeys as $file) {
10113  $info = $this->getImageBuffer($file);
10114  $oid = $this->_newobj();
10115  $this->xobjects['I'.$info['i']] = array('n' => $oid);
10116  $this->setImageSubBuffer($file, 'n', $this->n);
10117  $out = '<</Type /XObject';
10118  $out .= ' /Subtype /Image';
10119  $out .= ' /Width '.$info['w'];
10120  $out .= ' /Height '.$info['h'];
10121  if (array_key_exists('masked', $info)) {
10122  $out .= ' /SMask '.($this->n - 1).' 0 R';
10123  }
10124  if ($info['cs'] == 'Indexed') {
10125  $out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
10126  } else {
10127  $out .= ' /ColorSpace /'.$info['cs'];
10128  if ($info['cs'] == 'DeviceCMYK') {
10129  $out .= ' /Decode [1 0 1 0 1 0 1 0]';
10130  }
10131  }
10132  $out .= ' /BitsPerComponent '.$info['bpc'];
10133  if (isset($info['f'])) {
10134  $out .= ' /Filter /'.$info['f'];
10135  }
10136  if (isset($info['parms'])) {
10137  $out .= ' '.$info['parms'];
10138  }
10139  if (isset($info['trns']) AND is_array($info['trns'])) {
10140  $trns='';
10141  $count_info = count($info['trns']);
10142  for ($i=0; $i < $count_info; ++$i) {
10143  $trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
10144  }
10145  $out .= ' /Mask ['.$trns.']';
10146  }
10147  $stream = $this->_getrawstream($info['data']);
10148  $out .= ' /Length '.strlen($stream).' >>';
10149  $out .= ' stream'."\n".$stream."\n".'endstream';
10150  $out .= "\n".'endobj';
10151  $this->_out($out);
10152  //Palette
10153  if ($info['cs'] == 'Indexed') {
10154  $this->_newobj();
10155  $pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
10156  $pal = $this->_getrawstream($pal);
10157  $this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
10158  }
10159  }
10160  }
10161 
10169  protected function _putxobjects() {
10170  foreach ($this->xobjects as $key => $data) {
10171  if (isset($data['outdata'])) {
10172  $stream = trim($data['outdata']);
10173  $out = $this->_getobj($data['n'])."\n";
10174  $out .= '<<';
10175  $out .= ' /Type /XObject';
10176  $out .= ' /Subtype /Form';
10177  $out .= ' /FormType 1';
10178  if ($this->compress) {
10179  $stream = gzcompress($stream);
10180  $out .= ' /Filter /FlateDecode';
10181  }
10182  $out .= sprintf(' /BBox [%.2F %.2F %.2F %.2F]', ($data['x'] * $this->k), (-$data['y'] * $this->k), (($data['w'] + $data['x']) * $this->k), (($data['h'] - $data['y']) * $this->k));
10183  $out .= ' /Matrix [1 0 0 1 0 0]';
10184  $out .= ' /Resources <<';
10185  $out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
10186  // fonts
10187  if (!empty($data['fonts'])) {
10188  $out .= ' /Font <<';
10189  foreach ($data['fonts'] as $fontkey => $fontid) {
10190  $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
10191  }
10192  $out .= ' >>';
10193  }
10194  // images or nested xobjects
10195  if (!empty($data['images']) OR !empty($data['xobjects'])) {
10196  $out .= ' /XObject <<';
10197  foreach ($data['images'] as $imgid) {
10198  $out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
10199  }
10200  foreach ($data['xobjects'] as $sub_id => $sub_objid) {
10201  $out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
10202  }
10203  $out .= ' >>';
10204  }
10205  $out .= ' >>';
10206  $stream = $this->_getrawstream($stream);
10207  $out .= ' /Length '.strlen($stream);
10208  $out .= ' >>';
10209  $out .= ' stream'."\n".$stream."\n".'endstream';
10210  $out .= "\n".'endobj';
10211  $this->_out($out);
10212  }
10213  }
10214  }
10215 
10221  protected function _putspotcolors() {
10222  foreach ($this->spot_colors as $name => $color) {
10223  $this->_newobj();
10224  $this->spot_colors[$name]['n'] = $this->n;
10225  $out = '[/Separation /'.str_replace(' ', '#20', $name);
10226  $out .= ' /DeviceCMYK <<';
10227  $out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
10228  $out .= ' '.sprintf('/C1 [%.4F %.4F %.4F %.4F] ', $color['c']/100, $color['m']/100, $color['y']/100, $color['k']/100);
10229  $out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
10230  $out .= "\n".'endobj';
10231  $this->_out($out);
10232  }
10233  }
10234 
10241  protected function _getxobjectdict() {
10242  $out = '';
10243  foreach ($this->xobjects as $id => $objid) {
10244  $out .= ' /'.$id.' '.$objid['n'].' 0 R';
10245  }
10246  return $out;
10247  }
10248 
10253  protected function _putresourcedict() {
10254  $out = $this->_getobj(2)."\n";
10255  $out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
10256  $out .= ' /Font <<';
10257  foreach ($this->fontkeys as $fontkey) {
10258  $font = $this->getFontBuffer($fontkey);
10259  $out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
10260  }
10261  $out .= ' >>';
10262  $out .= ' /XObject <<';
10263  $out .= $this->_getxobjectdict();
10264  $out .= ' >>';
10265  // visibility
10266  $out .= ' /Properties <</OC1 '.$this->n_ocg_print.' 0 R /OC2 '.$this->n_ocg_view.' 0 R>>';
10267  // transparency
10268  $out .= ' /ExtGState <<';
10269  foreach ($this->extgstates as $k => $extgstate) {
10270  if (isset($extgstate['name'])) {
10271  $out .= ' /'.$extgstate['name'];
10272  } else {
10273  $out .= ' /GS'.$k;
10274  }
10275  $out .= ' '.$extgstate['n'].' 0 R';
10276  }
10277  $out .= ' >>';
10278  // gradient patterns
10279  if (isset($this->gradients) AND (count($this->gradients) > 0)) {
10280  $out .= ' /Pattern <<';
10281  foreach ($this->gradients as $id => $grad) {
10282  $out .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
10283  }
10284  $out .= ' >>';
10285  }
10286  // gradient shadings
10287  if (isset($this->gradients) AND (count($this->gradients) > 0)) {
10288  $out .= ' /Shading <<';
10289  foreach ($this->gradients as $id => $grad) {
10290  $out .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
10291  }
10292  $out .= ' >>';
10293  }
10294  // spot colors
10295  if (isset($this->spot_colors) AND (count($this->spot_colors) > 0)) {
10296  $out .= ' /ColorSpace <<';
10297  foreach ($this->spot_colors as $color) {
10298  $out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
10299  }
10300  $out .= ' >>';
10301  }
10302  $out .= ' >>';
10303  $out .= "\n".'endobj';
10304  $this->_out($out);
10305  }
10306 
10311  protected function _putresources() {
10312  $this->_putextgstates();
10313  $this->_putocg();
10314  $this->_putfonts();
10315  $this->_putimages();
10316  $this->_putxobjects();
10317  $this->_putspotcolors();
10318  $this->_putshaders();
10319  $this->_putresourcedict();
10320  $this->_putbookmarks();
10321  $this->_putEmbeddedFiles();
10322  $this->_putannotsobjs();
10323  $this->_putjavascript();
10324  $this->_putencryption();
10325  }
10326 
10333  protected function _putinfo() {
10334  $oid = $this->_newobj();
10335  $out = '<<';
10336  if (!$this->empty_string($this->title)) {
10337  // The document's title.
10338  $out .= ' /Title '.$this->_textstring($this->title, $oid);
10339  }
10340  if (!$this->empty_string($this->author)) {
10341  // The name of the person who created the document.
10342  $out .= ' /Author '.$this->_textstring($this->author, $oid);
10343  }
10344  if (!$this->empty_string($this->subject)) {
10345  // The subject of the document.
10346  $out .= ' /Subject '.$this->_textstring($this->subject, $oid);
10347  }
10348  if (!$this->empty_string($this->keywords)) {
10349  // Keywords associated with the document.
10350  $out .= ' /Keywords '.$this->_textstring($this->keywords.' TCP'.'DF', $oid);
10351  }
10352  if (!$this->empty_string($this->creator)) {
10353  // 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.
10354  $out .= ' /Creator '.$this->_textstring($this->creator, $oid);
10355  }
10356  if (defined('PDF_PRODUCER')) {
10357  // If the document was converted to PDF from another format, the name of the conforming product that converted it to PDF.
10358  $out .= ' /Producer '.$this->_textstring(PDF_PRODUCER.' (TCP'.'DF)', $oid);
10359  } else {
10360  // default producer
10361  $out .= ' /Producer '.$this->_textstring('TCP'.'DF', $oid);
10362  }
10363  // The date and time the document was created, in human-readable form
10364  $out .= ' /CreationDate '.$this->_datestring();
10365  // The date and time the document was most recently modified, in human-readable form
10366  $out .= ' /ModDate '.$this->_datestring();
10367  // A name object indicating whether the document has been modified to include trapping information
10368  $out .= ' /Trapped /False';
10369  $out .= ' >>';
10370  $out .= "\n".'endobj';
10371  $this->_out($out);
10372  return $oid;
10373  }
10374 
10380  protected function _putcatalog() {
10381  $oid = $this->_newobj();
10382  $out = '<< /Type /Catalog';
10383  $out .= ' /Pages 1 0 R';
10384  if ($this->ZoomMode == 'fullpage') {
10385  $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
10386  } elseif ($this->ZoomMode == 'fullwidth') {
10387  $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
10388  } elseif ($this->ZoomMode == 'real') {
10389  $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
10390  } elseif (!is_string($this->ZoomMode)) {
10391  $out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %.2F]',($this->ZoomMode / 100));
10392  }
10393  if (isset($this->LayoutMode) AND (!$this->empty_string($this->LayoutMode))) {
10394  $out .= ' /PageLayout /'.$this->LayoutMode;
10395  }
10396  if (isset($this->PageMode) AND (!$this->empty_string($this->PageMode))) {
10397  $out .= ' /PageMode /'.$this->PageMode;
10398  }
10399  if (isset($this->l['a_meta_language'])) {
10400  $out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
10401  }
10402  $out .= ' /Names <<';
10403  if ((!empty($this->javascript)) OR (!empty($this->js_objects))) {
10404  $out .= ' /JavaScript '.($this->n_js).' 0 R';
10405  }
10406  $out .= ' >>';
10407  if (count($this->outlines) > 0) {
10408  $out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
10409  $out .= ' /PageMode /UseOutlines';
10410  }
10411  $out .= ' '.$this->_putviewerpreferences();
10412  $p = $this->n_ocg_print.' 0 R';
10413  $v = $this->n_ocg_view.' 0 R';
10414  $as = '<< /Event /Print /OCGs ['.$p.' '.$v.'] /Category [/Print] >> << /Event /View /OCGs ['.$p.' '.$v.'] /Category [/View] >>';
10415  $out .= ' /OCProperties << /OCGs ['.$p.' '.$v.'] /D << /ON ['.$p.'] /OFF ['.$v.'] /AS ['.$as.'] >> >>';
10416  // AcroForm
10417  if (!empty($this->form_obj_id) OR ($this->sign AND isset($this->signature_data['cert_type']))) {
10418  $out .= ' /AcroForm <<';
10419  $objrefs = '';
10420  if ($this->sign AND isset($this->signature_data['cert_type'])) {
10421  $objrefs .= $this->sig_obj_id.' 0 R';
10422  }
10423  if (!empty($this->form_obj_id)) {
10424  foreach($this->form_obj_id as $objid) {
10425  $objrefs .= ' '.$objid.' 0 R';
10426  }
10427  }
10428  $out .= ' /Fields ['.$objrefs.']';
10429  if (!empty($this->form_obj_id) AND !$this->sign) {
10430  // It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
10431  $out .= ' /NeedAppearances true';
10432  }
10433  if ($this->sign AND isset($this->signature_data['cert_type'])) {
10434  if ($this->signature_data['cert_type'] > 0) {
10435  $out .= ' /SigFlags 3';
10436  } else {
10437  $out .= ' /SigFlags 1';
10438  }
10439  }
10440  //$out .= ' /CO ';
10441  if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
10442  $out .= ' /DR <<';
10443  $out .= ' /Font <<';
10444  foreach ($this->annotation_fonts as $fontkey => $fontid) {
10445  $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
10446  }
10447  $out .= ' >> >>';
10448  }
10449  $font = $this->getFontBuffer('helvetica');
10450  $out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
10451  $out .= ' /Q '.(($this->rtl)?'2':'0');
10452  //$out .= ' /XFA ';
10453  $out .= ' >>';
10454  // signatures
10455  if ($this->sign AND isset($this->signature_data['cert_type'])) {
10456  if ($this->signature_data['cert_type'] > 0) {
10457  $out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
10458  } else {
10459  $out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
10460  }
10461  }
10462  }
10463  $out .= ' >>';
10464  $out .= "\n".'endobj';
10465  $this->_out($out);
10466  return $oid;
10467  }
10468 
10476  protected function _putviewerpreferences() {
10477  $out = '/ViewerPreferences <<';
10478  if ($this->rtl) {
10479  $out .= ' /Direction /R2L';
10480  } else {
10481  $out .= ' /Direction /L2R';
10482  }
10483  if (isset($this->viewer_preferences['HideToolbar']) AND ($this->viewer_preferences['HideToolbar'])) {
10484  $out .= ' /HideToolbar true';
10485  }
10486  if (isset($this->viewer_preferences['HideMenubar']) AND ($this->viewer_preferences['HideMenubar'])) {
10487  $out .= ' /HideMenubar true';
10488  }
10489  if (isset($this->viewer_preferences['HideWindowUI']) AND ($this->viewer_preferences['HideWindowUI'])) {
10490  $out .= ' /HideWindowUI true';
10491  }
10492  if (isset($this->viewer_preferences['FitWindow']) AND ($this->viewer_preferences['FitWindow'])) {
10493  $out .= ' /FitWindow true';
10494  }
10495  if (isset($this->viewer_preferences['CenterWindow']) AND ($this->viewer_preferences['CenterWindow'])) {
10496  $out .= ' /CenterWindow true';
10497  }
10498  if (isset($this->viewer_preferences['DisplayDocTitle']) AND ($this->viewer_preferences['DisplayDocTitle'])) {
10499  $out .= ' /DisplayDocTitle true';
10500  }
10501  if (isset($this->viewer_preferences['NonFullScreenPageMode'])) {
10502  $out .= ' /NonFullScreenPageMode /'.$this->viewer_preferences['NonFullScreenPageMode'];
10503  }
10504  if (isset($this->viewer_preferences['ViewArea'])) {
10505  $out .= ' /ViewArea /'.$this->viewer_preferences['ViewArea'];
10506  }
10507  if (isset($this->viewer_preferences['ViewClip'])) {
10508  $out .= ' /ViewClip /'.$this->viewer_preferences['ViewClip'];
10509  }
10510  if (isset($this->viewer_preferences['PrintArea'])) {
10511  $out .= ' /PrintArea /'.$this->viewer_preferences['PrintArea'];
10512  }
10513  if (isset($this->viewer_preferences['PrintClip'])) {
10514  $out .= ' /PrintClip /'.$this->viewer_preferences['PrintClip'];
10515  }
10516  if (isset($this->viewer_preferences['PrintScaling'])) {
10517  $out .= ' /PrintScaling /'.$this->viewer_preferences['PrintScaling'];
10518  }
10519  if (isset($this->viewer_preferences['Duplex']) AND (!$this->empty_string($this->viewer_preferences['Duplex']))) {
10520  $out .= ' /Duplex /'.$this->viewer_preferences['Duplex'];
10521  }
10522  if (isset($this->viewer_preferences['PickTrayByPDFSize'])) {
10523  if ($this->viewer_preferences['PickTrayByPDFSize']) {
10524  $out .= ' /PickTrayByPDFSize true';
10525  } else {
10526  $out .= ' /PickTrayByPDFSize false';
10527  }
10528  }
10529  if (isset($this->viewer_preferences['PrintPageRange'])) {
10530  $PrintPageRangeNum = '';
10531  foreach ($this->viewer_preferences['PrintPageRange'] as $k => $v) {
10532  $PrintPageRangeNum .= ' '.($v - 1).'';
10533  }
10534  $out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
10535  }
10536  if (isset($this->viewer_preferences['NumCopies'])) {
10537  $out .= ' /NumCopies '.intval($this->viewer_preferences['NumCopies']);
10538  }
10539  $out .= ' >>';
10540  return $out;
10541  }
10542 
10547  protected function _putheader() {
10548  $this->_out('%PDF-'.$this->PDFVersion);
10549  }
10550 
10555  protected function _enddoc() {
10556  $this->state = 1;
10557  $this->_putheader();
10558  $this->_putpages();
10559  $this->_putresources();
10560  // Signature
10561  if ($this->sign AND isset($this->signature_data['cert_type'])) {
10562  // widget annotation for signature
10563  $out = $this->_getobj($this->sig_obj_id)."\n";
10564  $out .= '<< /Type /Annot';
10565  $out .= ' /Subtype /Widget';
10566  $out .= ' /Rect ['.$this->signature_appearance['rect'].']';
10567  $out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
10568  $out .= ' /F 4';
10569  $out .= ' /FT /Sig';
10570  $out .= ' /T '.$this->_textstring('Signature', $this->sig_obj_id);
10571  $out .= ' /Ff 0';
10572  $out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
10573  $out .= ' >>';
10574  $out .= "\n".'endobj';
10575  $this->_out($out);
10576  // signature
10577  $this->_putsignature();
10578  }
10579  // Info
10580  $objid_info = $this->_putinfo();
10581  // Catalog
10582  $objid_catalog = $this->_putcatalog();
10583  // Cross-ref
10584  $o = $this->bufferlen;
10585  // XREF section
10586  $this->_out('xref');
10587  $this->_out('0 '.($this->n + 1));
10588  $this->_out('0000000000 65535 f ');
10589  for ($i=1; $i <= $this->n; ++$i) {
10590  $this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
10591  }
10592  // TRAILER
10593  $out = 'trailer <<';
10594  $out .= ' /Size '.($this->n + 1);
10595  $out .= ' /Root '.$objid_catalog.' 0 R';
10596  $out .= ' /Info '.$objid_info.' 0 R';
10597  if ($this->encrypted) {
10598  $out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
10599  }
10600  $out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
10601  $out .= ' >>';
10602  $this->_out($out);
10603  $this->_out('startxref');
10604  $this->_out($o);
10605  $this->_out('%%EOF');
10606  $this->state = 3; // end-of-doc
10607  if ($this->diskcache) {
10608  // remove temporary files used for images
10609  foreach ($this->imagekeys as $key) {
10610  // remove temporary files
10611  unlink($this->images[$key]);
10612  }
10613  foreach ($this->fontkeys as $key) {
10614  // remove temporary files
10615  unlink($this->fonts[$key]);
10616  }
10617  }
10618  }
10619 
10627  protected function _beginpage($orientation='', $format='') {
10628  ++$this->page;
10629  $this->setPageBuffer($this->page, '');
10630  // initialize array for graphics tranformation positions inside a page buffer
10631  $this->transfmrk[$this->page] = array();
10632  $this->state = 2;
10633  if ($this->empty_string($orientation)) {
10634  if (isset($this->CurOrientation)) {
10635  $orientation = $this->CurOrientation;
10636  } elseif ($this->fwPt > $this->fhPt) {
10637  // landscape
10638  $orientation = 'L';
10639  } else {
10640  // portrait
10641  $orientation = 'P';
10642  }
10643  }
10644  if ($this->empty_string($format)) {
10645  $this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10646  $this->setPageOrientation($orientation);
10647  } else {
10648  $this->setPageFormat($format, $orientation);
10649  }
10650  if ($this->rtl) {
10651  $this->x = $this->w - $this->rMargin;
10652  } else {
10653  $this->x = $this->lMargin;
10654  }
10655  $this->y = $this->tMargin;
10656  if (isset($this->newpagegroup[$this->page])) {
10657  // start a new group
10658  $n = sizeof($this->pagegroups) + 1;
10659  $alias = '{nb'.$n.'}';
10660  $this->pagegroups[$alias] = 1;
10661  $this->currpagegroup = $alias;
10662  } elseif ($this->currpagegroup) {
10663  ++$this->pagegroups[$this->currpagegroup];
10664  }
10665  }
10666 
10671  protected function _endpage() {
10672  $this->setVisibility('all');
10673  $this->state = 1;
10674  }
10675 
10681  protected function _newobj() {
10682  $this->_out($this->_getobj());
10683  return $this->n;
10684  }
10685 
10693  protected function _getobj($objid='') {
10694  if ($objid === '') {
10695  ++$this->n;
10696  $objid = $this->n;
10697  }
10698  $this->offsets[$objid] = $this->bufferlen;
10699  return $objid.' 0 obj';
10700  }
10701 
10709  protected function _dounderline($x, $y, $txt) {
10710  $w = $this->GetStringWidth($txt);
10711  return $this->_dounderlinew($x, $y, $w);
10712  }
10713 
10722  protected function _dounderlinew($x, $y, $w) {
10723  $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10724  return sprintf('%.2F %.2F %.2F %.2F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
10725  }
10726 
10734  protected function _dolinethrough($x, $y, $txt) {
10735  $w = $this->GetStringWidth($txt);
10736  return $this->_dolinethroughw($x, $y, $w);
10737  }
10738 
10747  protected function _dolinethroughw($x, $y, $w) {
10748  $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10749  return sprintf('%.2F %.2F %.2F %.2F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
10750  }
10751 
10760  protected function _dooverline($x, $y, $txt) {
10761  $w = $this->GetStringWidth($txt);
10762  return $this->_dooverlinew($x, $y, $w);
10763  }
10764 
10773  protected function _dooverlinew($x, $y, $w) {
10774  $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10775  return sprintf('%.2F %.2F %.2F %.2F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
10776 
10777  }
10778 
10785  protected function _freadint($f) {
10786  $a = unpack('Ni', fread($f, 4));
10787  return $a['i'];
10788  }
10789 
10796  protected function _escape($s) {
10797  // the chr(13) substitution fixes the Bugs item #1421290.
10798  return strtr($s, array(')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r'));
10799  }
10800 
10808  protected function _datastring($s, $n=0) {
10809  if ($n == 0) {
10810  $n = $this->n;
10811  }
10812  $s = $this->_encrypt_data($n, $s);
10813  return '('. $this->_escape($s).')';
10814  }
10815 
10823  protected function _datestring($n=0) {
10824  $current_time = substr_replace(date('YmdHisO'), '\'', (0 - 2), 0).'\'';
10825  return $this->_datastring('D:'.$current_time, $n);
10826  }
10827 
10835  protected function _textstring($s, $n=0) {
10836  if ($this->isunicode) {
10837  //Convert string to UTF-16BE
10838  $s = $this->UTF8ToUTF16BE($s, true);
10839  }
10840  return $this->_datastring($s, $n);
10841  }
10842 
10851  protected function _escapetext($s) {
10852  if ($this->isunicode) {
10853  if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
10854  $s = $this->UTF8ToLatin1($s);
10855  } else {
10856  //Convert string to UTF-16BE and reverse RTL language
10857  $s = $this->utf8StrRev($s, false, $this->tmprtl);
10858  }
10859  }
10860  return $this->_escape($s);
10861  }
10862 
10871  protected function _getrawstream($s, $n=0) {
10872  if ($n <= 0) {
10873  // default to current object
10874  $n = $this->n;
10875  }
10876  return $this->_encrypt_data($n, $s);
10877  }
10878 
10886  protected function _getstream($s, $n=0) {
10887  return 'stream'."\n".$this->_getrawstream($s, $n)."\n".'endstream';
10888  }
10889 
10897  protected function _putstream($s, $n=0) {
10898  $this->_out($this->_getstream($s, $n));
10899  }
10900 
10906  protected function _out($s) {
10907  if ($this->state == 2) {
10908  if ($this->inxobj) {
10909  // we are inside an XObject template
10910  $this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
10911  } elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
10912  // puts data before page footer
10913  $pagebuff = $this->getPageBuffer($this->page);
10914  $page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
10915  $footer = substr($pagebuff, -$this->footerlen[$this->page]);
10916  $this->setPageBuffer($this->page, $page.$s."\n".$footer);
10917  // update footer position
10918  $this->footerpos[$this->page] += strlen($s."\n");
10919  } else {
10920  $this->setPageBuffer($this->page, $s."\n", true);
10921  }
10922  } else {
10923  $this->setBuffer($s."\n");
10924  }
10925  }
10926 
10961  protected function UTF8StringToArray($str) {
10962  // build a unique string key
10963  $strkey = md5($str);
10964  if (isset($this->cache_UTF8StringToArray[$strkey])) {
10965  // return cached value
10966  $chrarray = $this->cache_UTF8StringToArray[$strkey]['s'];
10967  if (!isset($this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']])) {
10968  if ($this->isunicode) {
10969  foreach ($chrarray as $chr) {
10970  // store this char for font subsetting
10971  $this->CurrentFont['subsetchars'][$chr] = true;
10972  }
10973  // update font subsetchars
10974  $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
10975  }
10976  $this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']] = true;
10977  }
10978  return $chrarray;
10979  }
10980  // check cache size
10981  if ($this->cache_size_UTF8StringToArray >= $this->cache_maxsize_UTF8StringToArray) {
10982  // remove first element
10983  array_shift($this->cache_UTF8StringToArray);
10984  }
10985  // new cache array for selected string
10986  $this->cache_UTF8StringToArray[$strkey] = array('s' => array(), 'f' => array());
10987  ++$this->cache_size_UTF8StringToArray;
10988  if (!$this->isunicode) {
10989  // split string into array of equivalent codes
10990  $strarr = array();
10991  $strlen = strlen($str);
10992  for ($i=0; $i < $strlen; ++$i) {
10993  $strarr[] = ord($str{$i});
10994  }
10995  // insert new value on cache
10996  $this->cache_UTF8StringToArray[$strkey]['s'] = $strarr;
10997  $this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']] = true;
10998  return $strarr;
10999  }
11000  $unichar = -1; // last unicode char
11001  $unicode = array(); // array containing unicode values
11002  $bytes = array(); // array containing single character byte sequences
11003  $numbytes = 1; // number of octetc needed to represent the UTF-8 character
11004  $str .= ''; // force $str to be a string
11005  $length = strlen($str);
11006  for ($i = 0; $i < $length; ++$i) {
11007  $char = ord($str{$i}); // get one string character at time
11008  if (count($bytes) == 0) { // get starting octect
11009  if ($char <= 0x7F) {
11010  $unichar = $char; // use the character "as is" because is ASCII
11011  $numbytes = 1;
11012  } elseif (($char >> 0x05) == 0x06) { // 2 bytes character (0x06 = 110 BIN)
11013  $bytes[] = ($char - 0xC0) << 0x06;
11014  $numbytes = 2;
11015  } elseif (($char >> 0x04) == 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
11016  $bytes[] = ($char - 0xE0) << 0x0C;
11017  $numbytes = 3;
11018  } elseif (($char >> 0x03) == 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
11019  $bytes[] = ($char - 0xF0) << 0x12;
11020  $numbytes = 4;
11021  } else {
11022  // use replacement character for other invalid sequences
11023  $unichar = 0xFFFD;
11024  $bytes = array();
11025  $numbytes = 1;
11026  }
11027  } elseif (($char >> 0x06) == 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
11028  $bytes[] = $char - 0x80;
11029  if (count($bytes) == $numbytes) {
11030  // compose UTF-8 bytes to a single unicode value
11031  $char = $bytes[0];
11032  for ($j = 1; $j < $numbytes; ++$j) {
11033  $char += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
11034  }
11035  if ((($char >= 0xD800) AND ($char <= 0xDFFF)) OR ($char >= 0x10FFFF)) {
11036  /* The definition of UTF-8 prohibits encoding character numbers between
11037  U+D800 and U+DFFF, which are reserved for use with the UTF-16
11038  encoding form (as surrogate pairs) and do not directly represent
11039  characters. */
11040  $unichar = 0xFFFD; // use replacement character
11041  } else {
11042  $unichar = $char; // add char to array
11043  }
11044  // reset data for next char
11045  $bytes = array();
11046  $numbytes = 1;
11047  }
11048  } else {
11049  // use replacement character for other invalid sequences
11050  $unichar = 0xFFFD;
11051  $bytes = array();
11052  $numbytes = 1;
11053  }
11054  if ($unichar >= 0) {
11055  // insert unicode value into array
11056  $unicode[] = $unichar;
11057  // store this char for font subsetting
11058  $this->CurrentFont['subsetchars'][$unichar] = true;
11059  $unichar = -1;
11060  }
11061  }
11062  // update font subsetchars
11063  $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
11064  // insert new value on cache
11065  $this->cache_UTF8StringToArray[$strkey]['s'] = $unicode;
11066  $this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']] = true;
11067  return $unicode;
11068  }
11069 
11080  protected function UTF8ToUTF16BE($str, $setbom=true) {
11081  if (!$this->isunicode) {
11082  return $str; // string is not in unicode
11083  }
11084  $unicode = $this->UTF8StringToArray($str); // array containing UTF-8 unicode values
11085  return $this->arrUTF8ToUTF16BE($unicode, $setbom);
11086  }
11087 
11096  protected function UTF8ToLatin1($str) {
11097  if (!$this->isunicode) {
11098  return $str; // string is not in unicode
11099  }
11100  $outstr = ''; // string to be returned
11101  $unicode = $this->UTF8StringToArray($str); // array containing UTF-8 unicode values
11102  foreach ($unicode as $char) {
11103  if ($char < 256) {
11104  $outstr .= chr($char);
11105  } elseif (array_key_exists($char, $this->unicode->uni_utf8tolatin)) {
11106  // map from UTF-8
11107  $outstr .= chr($this->unicode->uni_utf8tolatin[$char]);
11108  } elseif ($char == 0xFFFD) {
11109  // skip
11110  } else {
11111  $outstr .= '?';
11112  }
11113  }
11114  return $outstr;
11115  }
11116 
11125  protected function UTF8ArrToLatin1($unicode) {
11126  if ((!$this->isunicode) OR $this->isUnicodeFont()) {
11127  return $unicode;
11128  }
11129  $outarr = array(); // array to be returned
11130  foreach ($unicode as $char) {
11131  if ($char < 256) {
11132  $outarr[] = $char;
11133  } elseif (array_key_exists($char, $this->unicode->uni_utf8tolatin)) {
11134  // map from UTF-8
11135  $outarr[] = $this->unicode->uni_utf8tolatin[$char];
11136  } elseif ($char == 0xFFFD) {
11137  // skip
11138  } else {
11139  $outarr[] = 63; // '?' character
11140  }
11141  }
11142  return $outarr;
11143  }
11144 
11183  protected function arrUTF8ToUTF16BE($unicode, $setbom=true) {
11184  $outstr = ''; // string to be returned
11185  if ($setbom) {
11186  $outstr .= "\xFE\xFF"; // Byte Order Mark (BOM)
11187  }
11188  foreach ($unicode as $char) {
11189  if ($char == 0x200b) {
11190  // skip Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B)
11191  } elseif ($char == 0xFFFD) {
11192  $outstr .= "\xFF\xFD"; // replacement character
11193  } elseif ($char < 0x10000) {
11194  $outstr .= chr($char >> 0x08);
11195  $outstr .= chr($char & 0xFF);
11196  } else {
11197  $char -= 0x10000;
11198  $w1 = 0xD800 | ($char >> 0x10);
11199  $w2 = 0xDC00 | ($char & 0x3FF);
11200  $outstr .= chr($w1 >> 0x08);
11201  $outstr .= chr($w1 & 0xFF);
11202  $outstr .= chr($w2 >> 0x08);
11203  $outstr .= chr($w2 & 0xFF);
11204  }
11205  }
11206  return $outstr;
11207  }
11208  // ====================================================
11209 
11216  public function setHeaderFont($font) {
11217  $this->header_font = $font;
11218  }
11219 
11226  public function getHeaderFont() {
11227  return $this->header_font;
11228  }
11229 
11236  public function setFooterFont($font) {
11237  $this->footer_font = $font;
11238  }
11239 
11246  public function getFooterFont() {
11247  return $this->footer_font;
11248  }
11249 
11256  public function setLanguageArray($language) {
11257  $this->l = $language;
11258  if (isset($this->l['a_meta_dir'])) {
11259  $this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
11260  } else {
11261  $this->rtl = false;
11262  }
11263  }
11264 
11269  public function getPDFData() {
11270  if ($this->state < 3) {
11271  $this->Close();
11272  }
11273  return $this->buffer;
11274  }
11275 
11288  public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
11289  if (!$this->empty_string($url) AND ($url{0} == '#')) {
11290  // convert url to internal link
11291  $lnkdata = explode(',', $url);
11292  if (isset($lnkdata[0])) {
11293  $page = intval(substr($lnkdata[0], 1));
11294  if (empty($page) OR ($page <= 0)) {
11295  $page = $this->page;
11296  }
11297  if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
11298  $lnky = floatval($lnkdata[1]);
11299  } else {
11300  $lnky = 0;
11301  }
11302  $url = $this->AddLink();
11303  $this->SetLink($url, $lnky, $page);
11304  }
11305  }
11306  // store current settings
11307  $prevcolor = $this->fgcolor;
11308  $prevstyle = $this->FontStyle;
11309  if (empty($color)) {
11310  $this->SetTextColorArray($this->htmlLinkColorArray);
11311  } else {
11312  $this->SetTextColorArray($color);
11313  }
11314  if ($style == -1) {
11315  $this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
11316  } else {
11317  $this->SetFont('', $this->FontStyle.$style);
11318  }
11319  $ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
11320  // restore settings
11321  $this->SetFont('', $prevstyle);
11322  $this->SetTextColorArray($prevcolor);
11323  return $ret;
11324  }
11325 
11332  public function convertHTMLColorToDec($color='#FFFFFF') {
11333  $returncolor = false;
11334  $color = preg_replace('/[\s]*/', '', $color); // remove extra spaces
11335  $color = strtolower($color);
11336  if (($dotpos = strpos($color, '.')) !== false) {
11337  // remove class parent (i.e.: color.red)
11338  $color = substr($color, ($dotpos + 1));
11339  }
11340  if (strlen($color) == 0) {
11341  return false;
11342  }
11343  // RGB ARRAY
11344  if (substr($color, 0, 3) == 'rgb') {
11345  $codes = substr($color, 4);
11346  $codes = str_replace(')', '', $codes);
11347  $returncolor = explode(',', $codes);
11348  return $returncolor;
11349  }
11350  // CMYK ARRAY
11351  if (substr($color, 0, 4) == 'cmyk') {
11352  $codes = substr($color, 5);
11353  $codes = str_replace(')', '', $codes);
11354  $returncolor = explode(',', $codes);
11355  return $returncolor;
11356  }
11357  // COLOR NAME
11358  if (substr($color, 0, 1) != '#') {
11359  // decode color name
11360  if (isset($this->webcolor[$color])) {
11361  $color_code = $this->webcolor[$color];
11362  } else {
11363  return false;
11364  }
11365  } else {
11366  $color_code = substr($color, 1);
11367  }
11368  // RGB VALUE
11369  switch (strlen($color_code)) {
11370  case 3: {
11371  // three-digit hexadecimal representation
11372  $r = substr($color_code, 0, 1);
11373  $g = substr($color_code, 1, 1);
11374  $b = substr($color_code, 2, 1);
11375  $returncolor['R'] = hexdec($r.$r);
11376  $returncolor['G'] = hexdec($g.$g);
11377  $returncolor['B'] = hexdec($b.$b);
11378  break;
11379  }
11380  case 6: {
11381  // six-digit hexadecimal representation
11382  $returncolor['R'] = hexdec(substr($color_code, 0, 2));
11383  $returncolor['G'] = hexdec(substr($color_code, 2, 2));
11384  $returncolor['B'] = hexdec(substr($color_code, 4, 2));
11385  break;
11386  }
11387  }
11388  return $returncolor;
11389  }
11390 
11398  public function pixelsToUnits($px) {
11399  return ($px / ($this->imgscale * $this->k));
11400  }
11401 
11409  public function unhtmlentities($text_to_convert) {
11410  return html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
11411  }
11412 
11413  // ENCRYPTION METHODS ----------------------------------
11414 
11423  protected function getRandomSeed($seed='') {
11424  $seed .= microtime();
11425  if (function_exists('openssl_random_pseudo_bytes')) {
11426  $seed .= openssl_random_pseudo_bytes(512);
11427  }
11428  $seed .= uniqid('', true);
11429  $seed .= rand();
11430  $seed .= getmypid();
11431  $seed .= __FILE__;
11432  $seed .= $this->bufferlen;
11433  if (isset($_SERVER['REMOTE_ADDR'])) {
11434  $seed .= $_SERVER['REMOTE_ADDR'];
11435  }
11436  if (isset($_SERVER['HTTP_USER_AGENT'])) {
11437  $seed .= $_SERVER['HTTP_USER_AGENT'];
11438  }
11439  if (isset($_SERVER['HTTP_ACCEPT'])) {
11440  $seed .= $_SERVER['HTTP_ACCEPT'];
11441  }
11442  if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
11443  $seed .= $_SERVER['HTTP_ACCEPT_ENCODING'];
11444  }
11445  if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
11446  $seed .= $_SERVER['HTTP_ACCEPT_LANGUAGE'];
11447  }
11448  if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) {
11449  $seed .= $_SERVER['HTTP_ACCEPT_CHARSET'];
11450  }
11451  $seed .= rand();
11452  $seed .= uniqid('', true);
11453  $seed .= microtime();
11454  return $seed;
11455  }
11456 
11466  protected function _objectkey($n) {
11467  $objkey = $this->encryptdata['key'].pack('VXxx', $n);
11468  if ($this->encryptdata['mode'] == 2) { // AES-128
11469  // AES padding
11470  $objkey .= "\x73\x41\x6C\x54"; // sAlT
11471  }
11472  $objkey = substr($this->_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
11473  $objkey = substr($objkey, 0, 16);
11474  return $objkey;
11475  }
11476 
11486  protected function _encrypt_data($n, $s) {
11487  if (!$this->encrypted) {
11488  return $s;
11489  }
11490  switch ($this->encryptdata['mode']) {
11491  case 0: // RC4-40
11492  case 1: { // RC4-128
11493  $s = $this->_RC4($this->_objectkey($n), $s);
11494  break;
11495  }
11496  case 2: { // AES-128
11497  $s = $this->_AES($this->_objectkey($n), $s);
11498  break;
11499  }
11500  case 3: { // AES-256
11501  $s = $this->_AES($this->encryptdata['key'], $s);
11502  break;
11503  }
11504  }
11505  return $s;
11506  }
11507 
11514  protected function _putencryption() {
11515  if (!$this->encrypted) {
11516  return;
11517  }
11518  $this->encryptdata['objid'] = $this->_newobj();
11519  $out = '<<';
11520  if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
11521  $this->encryptdata['Filter'] = 'Standard';
11522  }
11523  $out .= ' /Filter /'.$this->encryptdata['Filter'];
11524  if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
11525  $out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
11526  }
11527  if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
11528  $this->encryptdata['V'] = 1;
11529  }
11530  // V is a code specifying the algorithm to be used in encrypting and decrypting the document
11531  $out .= ' /V '.$this->encryptdata['V'];
11532  if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
11533  // The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
11534  $out .= ' /Length '.$this->encryptdata['Length'];
11535  } else {
11536  $out .= ' /Length 40';
11537  }
11538  if ($this->encryptdata['V'] >= 4) {
11539  if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
11540  $this->encryptdata['StmF'] = 'Identity';
11541  }
11542  if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
11543  // The name of the crypt filter that shall be used when decrypting all strings in the document.
11544  $this->encryptdata['StrF'] = 'Identity';
11545  }
11546  // A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
11547  if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
11548  $out .= ' /CF <<';
11549  $out .= ' /'.$this->encryptdata['StmF'].' <<';
11550  $out .= ' /Type /CryptFilter';
11551  if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
11552  // The method used
11553  $out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
11554  if ($this->encryptdata['pubkey']) {
11555  $out .= ' /Recipients [';
11556  foreach ($this->encryptdata['Recipients'] as $rec) {
11557  $out .= ' <'.$rec.'>';
11558  }
11559  $out .= ' ]';
11560  if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
11561  $out .= ' /EncryptMetadata false';
11562  } else {
11563  $out .= ' /EncryptMetadata true';
11564  }
11565  }
11566  } else {
11567  $out .= ' /CFM /None';
11568  }
11569  if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
11570  // The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
11571  $out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
11572  } else {
11573  $out .= ' /AuthEvent /DocOpen';
11574  }
11575  if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
11576  // The bit length of the encryption key.
11577  $out .= ' /Length '.$this->encryptdata['CF']['Length'];
11578  }
11579  $out .= ' >> >>';
11580  }
11581  // The name of the crypt filter that shall be used by default when decrypting streams.
11582  $out .= ' /StmF /'.$this->encryptdata['StmF'];
11583  // The name of the crypt filter that shall be used when decrypting all strings in the document.
11584  $out .= ' /StrF /'.$this->encryptdata['StrF'];
11585  if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
11586  // The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
11587  $out .= ' /EFF /'.$this->encryptdata[''];
11588  }
11589  }
11590  // Additional encryption dictionary entries for the standard security handler
11591  if ($this->encryptdata['pubkey']) {
11592  if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
11593  $out .= ' /Recipients [';
11594  foreach ($this->encryptdata['Recipients'] as $rec) {
11595  $out .= ' <'.$rec.'>';
11596  }
11597  $out .= ' ]';
11598  }
11599  } else {
11600  $out .= ' /R';
11601  if ($this->encryptdata['V'] == 5) { // AES-256
11602  $out .= ' 5';
11603  $out .= ' /OE ('.$this->_escape($this->encryptdata['OE']).')';
11604  $out .= ' /UE ('.$this->_escape($this->encryptdata['UE']).')';
11605  $out .= ' /Perms ('.$this->_escape($this->encryptdata['perms']).')';
11606  } elseif ($this->encryptdata['V'] == 4) { // AES-128
11607  $out .= ' 4';
11608  } elseif ($this->encryptdata['V'] < 2) { // RC-40
11609  $out .= ' 2';
11610  } else { // RC-128
11611  $out .= ' 3';
11612  }
11613  $out .= ' /O ('.$this->_escape($this->encryptdata['O']).')';
11614  $out .= ' /U ('.$this->_escape($this->encryptdata['U']).')';
11615  $out .= ' /P '.$this->encryptdata['P'];
11616  if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
11617  $out .= ' /EncryptMetadata false';
11618  } else {
11619  $out .= ' /EncryptMetadata true';
11620  }
11621  }
11622  $out .= ' >>';
11623  $out .= "\n".'endobj';
11624  $this->_out($out);
11625  }
11626 
11637  protected function _RC4($key, $text) {
11638  if (function_exists('mcrypt_decrypt') AND ($out = @mcrypt_decrypt(MCRYPT_ARCFOUR, $key, $text, MCRYPT_MODE_STREAM, ''))) {
11639  // try to use mcrypt function if exist
11640  return $out;
11641  }
11642  if ($this->last_enc_key != $key) {
11643  $k = str_repeat($key, ((256 / strlen($key)) + 1));
11644  $rc4 = range(0, 255);
11645  $j = 0;
11646  for ($i = 0; $i < 256; ++$i) {
11647  $t = $rc4[$i];
11648  $j = ($j + $t + ord($k{$i})) % 256;
11649  $rc4[$i] = $rc4[$j];
11650  $rc4[$j] = $t;
11651  }
11652  $this->last_enc_key = $key;
11653  $this->last_enc_key_c = $rc4;
11654  } else {
11655  $rc4 = $this->last_enc_key_c;
11656  }
11657  $len = strlen($text);
11658  $a = 0;
11659  $b = 0;
11660  $out = '';
11661  for ($i = 0; $i < $len; ++$i) {
11662  $a = ($a + 1) % 256;
11663  $t = $rc4[$a];
11664  $b = ($b + $t) % 256;
11665  $rc4[$a] = $rc4[$b];
11666  $rc4[$b] = $t;
11667  $k = $rc4[($rc4[$a] + $rc4[$b]) % 256];
11668  $out .= chr(ord($text{$i}) ^ $k);
11669  }
11670  return $out;
11671  }
11672 
11683  protected function _AES($key, $text) {
11684  // padding (RFC 2898, PKCS #5: Password-Based Cryptography Specification Version 2.0)
11685  $padding = 16 - (strlen($text) % 16);
11686  $text .= str_repeat(chr($padding), $padding);
11687  $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
11688  $text = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);
11689  $text = $iv.$text;
11690  return $text;
11691  }
11692 
11701  protected function _md5_16($str) {
11702  return pack('H*', md5($str));
11703  }
11704 
11712  protected function _Uvalue() {
11713  if ($this->encryptdata['mode'] == 0) { // RC4-40
11714  return $this->_RC4($this->encryptdata['key'], $this->enc_padding);
11715  } elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
11716  $tmp = $this->_md5_16($this->enc_padding.$this->encryptdata['fileid']);
11717  $enc = $this->_RC4($this->encryptdata['key'], $tmp);
11718  $len = strlen($tmp);
11719  for ($i = 1; $i <= 19; ++$i) {
11720  $ek = '';
11721  for ($j = 0; $j < $len; ++$j) {
11722  $ek .= chr(ord($this->encryptdata['key']{$j}) ^ $i);
11723  }
11724  $enc = $this->_RC4($ek, $enc);
11725  }
11726  $enc .= str_repeat("\x00", 16);
11727  return substr($enc, 0, 32);
11728  } elseif ($this->encryptdata['mode'] == 3) { // AES-256
11729  $seed = $this->_md5_16($this->getRandomSeed());
11730  // User Validation Salt
11731  $this->encryptdata['UVS'] = substr($seed, 0, 8);
11732  // User Key Salt
11733  $this->encryptdata['UKS'] = substr($seed, 8, 16);
11734  return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
11735  }
11736  }
11737 
11745  protected function _UEvalue() {
11746  $hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
11747  $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
11748  return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
11749  }
11750 
11758  protected function _Ovalue() {
11759  if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
11760  $tmp = $this->_md5_16($this->encryptdata['owner_password']);
11761  if ($this->encryptdata['mode'] > 0) {
11762  for ($i = 0; $i < 50; ++$i) {
11763  $tmp = $this->_md5_16($tmp);
11764  }
11765  }
11766  $owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
11767  $enc = $this->_RC4($owner_key, $this->encryptdata['user_password']);
11768  if ($this->encryptdata['mode'] > 0) {
11769  $len = strlen($owner_key);
11770  for ($i = 1; $i <= 19; ++$i) {
11771  $ek = '';
11772  for ($j = 0; $j < $len; ++$j) {
11773  $ek .= chr(ord($owner_key{$j}) ^ $i);
11774  }
11775  $enc = $this->_RC4($ek, $enc);
11776  }
11777  }
11778  return $enc;
11779  } elseif ($this->encryptdata['mode'] == 3) { // AES-256
11780  $seed = $this->_md5_16($this->getRandomSeed());
11781  // Owner Validation Salt
11782  $this->encryptdata['OVS'] = substr($seed, 0, 8);
11783  // Owner Key Salt
11784  $this->encryptdata['OKS'] = substr($seed, 8, 16);
11785  return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
11786  }
11787  }
11788 
11796  protected function _OEvalue() {
11797  $hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
11798  $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
11799  return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
11800  }
11801 
11809  protected function _fixAES256Password($password) {
11810  $psw = ''; // password to be returned
11811  $psw_array = $this->utf8Bidi($this->UTF8StringToArray($password), $password, $this->rtl);
11812  foreach ($psw_array as $c) {
11813  $psw .= $this->unichr($c);
11814  }
11815  return substr($psw, 0, 127);
11816  }
11817 
11824  protected function _generateencryptionkey() {
11825  $keybytelen = ($this->encryptdata['Length'] / 8);
11826  if (!$this->encryptdata['pubkey']) { // standard mode
11827  if ($this->encryptdata['mode'] == 3) { // AES-256
11828  // generate 256 bit random key
11829  $this->encryptdata['key'] = substr(hash('sha256', $this->getRandomSeed(), true), 0, $keybytelen);
11830  // truncate passwords
11831  $this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
11832  $this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
11833  // Compute U value
11834  $this->encryptdata['U'] = $this->_Uvalue();
11835  // Compute UE value
11836  $this->encryptdata['UE'] = $this->_UEvalue();
11837  // Compute O value
11838  $this->encryptdata['O'] = $this->_Ovalue();
11839  // Compute OE value
11840  $this->encryptdata['OE'] = $this->_OEvalue();
11841  // Compute P value
11842  $this->encryptdata['P'] = $this->encryptdata['protection'];
11843  // Computing the encryption dictionary's Perms (permissions) value
11844  $perms = $this->getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
11845  $perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
11846  if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
11847  $perms .= 'F';
11848  } else {
11849  $perms .= 'T';
11850  }
11851  $perms .= 'adb'; // bytes 9-11
11852  $perms .= 'nick'; // bytes 12-15
11853  $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB));
11854  $this->encryptdata['perms'] = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->encryptdata['key'], $perms, MCRYPT_MODE_ECB, $iv);
11855  } else { // RC4-40, RC4-128, AES-128
11856  // Pad passwords
11857  $this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].$this->enc_padding, 0, 32);
11858  $this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].$this->enc_padding, 0, 32);
11859  // Compute O value
11860  $this->encryptdata['O'] = $this->_Ovalue();
11861  // get default permissions (reverse byte order)
11862  $permissions = $this->getEncPermissionsString($this->encryptdata['protection']);
11863  // Compute encryption key
11864  $tmp = $this->_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
11865  if ($this->encryptdata['mode'] > 0) {
11866  for ($i = 0; $i < 50; ++$i) {
11867  $tmp = $this->_md5_16(substr($tmp, 0, $keybytelen));
11868  }
11869  }
11870  $this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
11871  // Compute U value
11872  $this->encryptdata['U'] = $this->_Uvalue();
11873  // Compute P value
11874  $this->encryptdata['P'] = $this->encryptdata['protection'];
11875  }
11876  } else { // Public-Key mode
11877  // random 20-byte seed
11878  $seed = sha1($this->getRandomSeed(), true);
11879  $recipient_bytes = '';
11880  foreach ($this->encryptdata['pubkeys'] as $pubkey) {
11881  // for each public certificate
11882  if (isset($pubkey['p'])) {
11883  $pkprotection = $this->getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
11884  } else {
11885  $pkprotection = $this->encryptdata['protection'];
11886  }
11887  // get default permissions (reverse byte order)
11888  $pkpermissions = $this->getEncPermissionsString($pkprotection);
11889  // envelope data
11890  $envelope = $seed.$pkpermissions;
11891  // write the envelope data to a temporary file
11892  $tempkeyfile = tempnam(K_PATH_CACHE, 'tmpkey_');
11893  $f = fopen($tempkeyfile, 'wb');
11894  if (!$f) {
11895  $this->Error('Unable to create temporary key file: '.$tempkeyfile);
11896  }
11897  $envelope_lenght = strlen($envelope);
11898  fwrite($f, $envelope, $envelope_lenght);
11899  fclose($f);
11900  $tempencfile = tempnam(K_PATH_CACHE, 'tmpenc_');
11901  if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_DETACHED | PKCS7_BINARY)) {
11902  $this->Error('Unable to encrypt the file: '.$tempkeyfile);
11903  }
11904  unlink($tempkeyfile);
11905  // read encryption signature
11906  $signature = file_get_contents($tempencfile, false, null, $envelope_lenght);
11907  unlink($tempencfile);
11908  // extract signature
11909  $signature = substr($signature, strpos($signature, 'Content-Disposition'));
11910  $tmparr = explode("\n\n", $signature);
11911  $signature = trim($tmparr[1]);
11912  unset($tmparr);
11913  // decode signature
11914  $signature = base64_decode($signature);
11915  // convert signature to hex
11916  $hexsignature = current(unpack('H*', $signature));
11917  // store signature on recipients array
11918  $this->encryptdata['Recipients'][] = $hexsignature;
11919  // The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
11920  $recipient_bytes .= $signature;
11921  }
11922  // calculate encryption key
11923  if ($this->encryptdata['mode'] == 3) { // AES-256
11924  $this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
11925  } else { // RC4-40, RC4-128, AES-128
11926  $this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
11927  }
11928  }
11929  }
11930 
11939  protected function getUserPermissionCode($permissions, $mode=0) {
11940  $options = array(
11941  'owner' => 2, // bit 2 -- inverted logic: cleared by default
11942  'print' => 4, // bit 3
11943  'modify' => 8, // bit 4
11944  'copy' => 16, // bit 5
11945  'annot-forms' => 32, // bit 6
11946  'fill-forms' => 256, // bit 9
11947  'extract' => 512, // bit 10
11948  'assemble' => 1024,// bit 11
11949  'print-high' => 2048 // bit 12
11950  );
11951  $protection = 2147422012; // 32 bit: (01111111 11111111 00001111 00111100)
11952  foreach ($permissions as $permission) {
11953  if (!isset($options[$permission])) {
11954  $this->Error('Incorrect permission: '.$permission);
11955  }
11956  if (($mode > 0) OR ($options[$permission] <= 32)) {
11957  // set only valid permissions
11958  if ($options[$permission] == 2) {
11959  // the logic for bit 2 is inverted (cleared by default)
11960  $protection += $options[$permission];
11961  } else {
11962  $protection -= $options[$permission];
11963  }
11964  }
11965  }
11966  return $protection;
11967  }
11968 
11983  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) {
11984  $this->encryptdata['protection'] = $this->getUserPermissionCode($permissions, $mode);
11985  if (($pubkeys !== null) AND (is_array($pubkeys))) {
11986  // public-key mode
11987  $this->encryptdata['pubkeys'] = $pubkeys;
11988  if ($mode == 0) {
11989  // public-Key Security requires at least 128 bit
11990  $mode = 1;
11991  }
11992  if (!function_exists('openssl_pkcs7_encrypt')) {
11993  $this->Error('Public-Key Security requires openssl library.');
11994  }
11995  // Set Public-Key filter (availabe are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
11996  $this->encryptdata['pubkey'] = true;
11997  $this->encryptdata['Filter'] = 'Adobe.PubSec';
11998  $this->encryptdata['StmF'] = 'DefaultCryptFilter';
11999  $this->encryptdata['StrF'] = 'DefaultCryptFilter';
12000  } else {
12001  // standard mode (password mode)
12002  $this->encryptdata['pubkey'] = false;
12003  $this->encryptdata['Filter'] = 'Standard';
12004  $this->encryptdata['StmF'] = 'StdCF';
12005  $this->encryptdata['StrF'] = 'StdCF';
12006  }
12007  if ($mode > 1) { // AES
12008  if (!extension_loaded('mcrypt')) {
12009  $this->Error('AES encryption requires mcrypt library (http://www.php.net/manual/en/mcrypt.requirements.php).');
12010  }
12011  if (mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
12012  $this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
12013  }
12014  if (($mode == 3) AND !function_exists('hash')) {
12015  // the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
12016  $this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
12017  }
12018  }
12019  if ($owner_pass === null) {
12020  $owner_pass = md5($this->getRandomSeed());
12021  }
12022  $this->encryptdata['user_password'] = $user_pass;
12023  $this->encryptdata['owner_password'] = $owner_pass;
12024  $this->encryptdata['mode'] = $mode;
12025  switch ($mode) {
12026  case 0: { // RC4 40 bit
12027  $this->encryptdata['V'] = 1;
12028  $this->encryptdata['Length'] = 40;
12029  $this->encryptdata['CF']['CFM'] = 'V2';
12030  break;
12031  }
12032  case 1: { // RC4 128 bit
12033  $this->encryptdata['V'] = 2;
12034  $this->encryptdata['Length'] = 128;
12035  $this->encryptdata['CF']['CFM'] = 'V2';
12036  if ($this->encryptdata['pubkey']) {
12037  $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
12038  $this->encryptdata['Recipients'] = array();
12039  }
12040  break;
12041  }
12042  case 2: { // AES 128 bit
12043  $this->encryptdata['V'] = 4;
12044  $this->encryptdata['Length'] = 128;
12045  $this->encryptdata['CF']['CFM'] = 'AESV2';
12046  $this->encryptdata['CF']['Length'] = 128;
12047  if ($this->encryptdata['pubkey']) {
12048  $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
12049  $this->encryptdata['Recipients'] = array();
12050  }
12051  break;
12052  }
12053  case 3: { // AES 256 bit
12054  $this->encryptdata['V'] = 5;
12055  $this->encryptdata['Length'] = 256;
12056  $this->encryptdata['CF']['CFM'] = 'AESV3';
12057  $this->encryptdata['CF']['Length'] = 256;
12058  if ($this->encryptdata['pubkey']) {
12059  $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
12060  $this->encryptdata['Recipients'] = array();
12061  }
12062  break;
12063  }
12064  }
12065  $this->encrypted = true;
12066  $this->encryptdata['fileid'] = $this->convertHexStringToString($this->file_id);
12067  $this->_generateencryptionkey();
12068  }
12069 
12078  protected function convertHexStringToString($bs) {
12079  $string = ''; // string to be returned
12080  $bslenght = strlen($bs);
12081  if (($bslenght % 2) != 0) {
12082  // padding
12083  $bs .= '0';
12084  ++$bslenght;
12085  }
12086  for ($i = 0; $i < $bslenght; $i += 2) {
12087  $string .= chr(hexdec($bs{$i}.$bs{($i + 1)}));
12088  }
12089  return $string;
12090  }
12091 
12100  protected function convertStringToHexString($s) {
12101  $bs = '';
12102  $chars = preg_split('//', $s, -1, PREG_SPLIT_NO_EMPTY);
12103  foreach ($chars as $c) {
12104  $bs .= sprintf('%02s', dechex(ord($c)));
12105  }
12106  return $bs;
12107  }
12108 
12117  protected function getEncPermissionsString($protection) {
12118  $binprot = sprintf('%032b', $protection);
12119  $str = chr(bindec(substr($binprot, 24, 8)));
12120  $str .= chr(bindec(substr($binprot, 16, 8)));
12121  $str .= chr(bindec(substr($binprot, 8, 8)));
12122  $str .= chr(bindec(substr($binprot, 0, 8)));
12123  return $str;
12124  }
12125 
12126  // END OF ENCRYPTION FUNCTIONS -------------------------
12127 
12128  // START TRANSFORMATIONS SECTION -----------------------
12129 
12138  public function StartTransform() {
12139  $this->_out('q');
12140  if ($this->inxobj) {
12141  // we are inside an XObject template
12142  $this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
12143  } else {
12144  $this->transfmrk[$this->page][] = $this->pagelen[$this->page];
12145  }
12146  ++$this->transfmatrix_key;
12147  $this->transfmatrix[$this->transfmatrix_key] = array();
12148  }
12149 
12158  public function StopTransform() {
12159  $this->_out('Q');
12160  if (isset($this->transfmatrix[$this->transfmatrix_key])) {
12161  array_pop($this->transfmatrix[$this->transfmatrix_key]);
12162  --$this->transfmatrix_key;
12163  }
12164  if ($this->inxobj) {
12165  // we are inside an XObject template
12166  array_pop($this->xobjects[$this->xobjid]['transfmrk']);
12167  } else {
12168  array_pop($this->transfmrk[$this->page]);
12169  }
12170  }
12180  public function ScaleX($s_x, $x='', $y='') {
12181  $this->Scale($s_x, 100, $x, $y);
12182  }
12183 
12193  public function ScaleY($s_y, $x='', $y='') {
12194  $this->Scale(100, $s_y, $x, $y);
12195  }
12196 
12206  public function ScaleXY($s, $x='', $y='') {
12207  $this->Scale($s, $s, $x, $y);
12208  }
12209 
12220  public function Scale($s_x, $s_y, $x='', $y='') {
12221  if ($x === '') {
12222  $x = $this->x;
12223  }
12224  if ($y === '') {
12225  $y = $this->y;
12226  }
12227  if (($s_x == 0) OR ($s_y == 0)) {
12228  $this->Error('Please do not use values equal to zero for scaling');
12229  }
12230  $y = ($this->h - $y) * $this->k;
12231  $x *= $this->k;
12232  //calculate elements of transformation matrix
12233  $s_x /= 100;
12234  $s_y /= 100;
12235  $tm = array();
12236  $tm[0] = $s_x;
12237  $tm[1] = 0;
12238  $tm[2] = 0;
12239  $tm[3] = $s_y;
12240  $tm[4] = $x * (1 - $s_x);
12241  $tm[5] = $y * (1 - $s_y);
12242  //scale the coordinate system
12243  $this->Transform($tm);
12244  }
12245 
12253  public function MirrorH($x='') {
12254  $this->Scale(-100, 100, $x);
12255  }
12256 
12264  public function MirrorV($y='') {
12265  $this->Scale(100, -100, '', $y);
12266  }
12267 
12276  public function MirrorP($x='',$y='') {
12277  $this->Scale(-100, -100, $x, $y);
12278  }
12279 
12289  public function MirrorL($angle=0, $x='',$y='') {
12290  $this->Scale(-100, 100, $x, $y);
12291  $this->Rotate(-2*($angle-90), $x, $y);
12292  }
12293 
12301  public function TranslateX($t_x) {
12302  $this->Translate($t_x, 0);
12303  }
12304 
12312  public function TranslateY($t_y) {
12313  $this->Translate(0, $t_y);
12314  }
12315 
12324  public function Translate($t_x, $t_y) {
12325  //calculate elements of transformation matrix
12326  $tm = array();
12327  $tm[0] = 1;
12328  $tm[1] = 0;
12329  $tm[2] = 0;
12330  $tm[3] = 1;
12331  $tm[4] = $t_x * $this->k;
12332  $tm[5] = -$t_y * $this->k;
12333  //translate the coordinate system
12334  $this->Transform($tm);
12335  }
12336 
12346  public function Rotate($angle, $x='', $y='') {
12347  if ($x === '') {
12348  $x = $this->x;
12349  }
12350  if ($y === '') {
12351  $y = $this->y;
12352  }
12353  $y = ($this->h - $y) * $this->k;
12354  $x *= $this->k;
12355  //calculate elements of transformation matrix
12356  $tm = array();
12357  $tm[0] = cos(deg2rad($angle));
12358  $tm[1] = sin(deg2rad($angle));
12359  $tm[2] = -$tm[1];
12360  $tm[3] = $tm[0];
12361  $tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
12362  $tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
12363  //rotate the coordinate system around ($x,$y)
12364  $this->Transform($tm);
12365  }
12366 
12376  public function SkewX($angle_x, $x='', $y='') {
12377  $this->Skew($angle_x, 0, $x, $y);
12378  }
12379 
12389  public function SkewY($angle_y, $x='', $y='') {
12390  $this->Skew(0, $angle_y, $x, $y);
12391  }
12392 
12403  public function Skew($angle_x, $angle_y, $x='', $y='') {
12404  if ($x === '') {
12405  $x = $this->x;
12406  }
12407  if ($y === '') {
12408  $y = $this->y;
12409  }
12410  if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
12411  $this->Error('Please use values between -90 and +90 degrees for Skewing.');
12412  }
12413  $x *= $this->k;
12414  $y = ($this->h - $y) * $this->k;
12415  //calculate elements of transformation matrix
12416  $tm = array();
12417  $tm[0] = 1;
12418  $tm[1] = tan(deg2rad($angle_y));
12419  $tm[2] = tan(deg2rad($angle_x));
12420  $tm[3] = 1;
12421  $tm[4] = -$tm[2] * $y;
12422  $tm[5] = -$tm[1] * $x;
12423  //skew the coordinate system
12424  $this->Transform($tm);
12425  }
12426 
12434  protected function Transform($tm) {
12435  $this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
12436  // add tranformation matrix
12437  $this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
12438  // update transformation mark
12439  if ($this->inxobj) {
12440  // we are inside an XObject template
12441  if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
12442  $key = key($this->xobjects[$this->xobjid]['transfmrk']);
12443  $this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
12444  }
12445  } elseif (end($this->transfmrk[$this->page]) !== false) {
12446  $key = key($this->transfmrk[$this->page]);
12447  $this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
12448  }
12449  }
12450 
12451  // END TRANSFORMATIONS SECTION -------------------------
12452 
12453  // START GRAPHIC FUNCTIONS SECTION ---------------------
12454  // The following section is based on the code provided by David Hernandez Sanz
12455 
12463  public function SetLineWidth($width) {
12464  //Set line width
12465  $this->LineWidth = $width;
12466  $this->linestyleWidth = sprintf('%.2F w', ($width * $this->k));
12467  if ($this->page > 0) {
12468  $this->_out($this->linestyleWidth);
12469  }
12470  }
12471 
12479  public function GetLineWidth() {
12480  return $this->LineWidth;
12481  }
12482 
12506  public function SetLineStyle($style, $ret=false) {
12507  $s = ''; // string to be returned
12508  if (!is_array($style)) {
12509  return;
12510  }
12511  extract($style);
12512  if (isset($width)) {
12513  $this->LineWidth = $width;
12514  $this->linestyleWidth = sprintf('%.2F w', ($width * $this->k));
12515  $s .= $this->linestyleWidth.' ';
12516  }
12517  if (isset($cap)) {
12518  $ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
12519  if (isset($ca[$cap])) {
12520  $this->linestyleCap = $ca[$cap].' J';
12521  $s .= $this->linestyleCap.' ';
12522  }
12523  }
12524  if (isset($join)) {
12525  $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
12526  if (isset($ja[$join])) {
12527  $this->linestyleJoin = $ja[$join].' j';
12528  $s .= $this->linestyleJoin.' ';
12529  }
12530  }
12531  if (isset($dash)) {
12532  $dash_string = '';
12533  if ($dash) {
12534  if (preg_match('/^.+,/', $dash) > 0) {
12535  $tab = explode(',', $dash);
12536  } else {
12537  $tab = array($dash);
12538  }
12539  $dash_string = '';
12540  foreach ($tab as $i => $v) {
12541  if ($i) {
12542  $dash_string .= ' ';
12543  }
12544  $dash_string .= sprintf('%.2F', $v);
12545  }
12546  }
12547  if (!isset($phase) OR !$dash) {
12548  $phase = 0;
12549  }
12550  $this->linestyleDash = sprintf('[%s] %.2F d', $dash_string, $phase);
12551  $s .= $this->linestyleDash.' ';
12552  }
12553  if (isset($color)) {
12554  $s .= $this->SetDrawColorArray($color, true).' ';
12555  }
12556  if (!$ret) {
12557  $this->_out($s);
12558  }
12559  return $s;
12560  }
12561 
12569  protected function _outPoint($x, $y) {
12570  $this->_out(sprintf('%.2F %.2F m', $x * $this->k, ($this->h - $y) * $this->k));
12571  }
12572 
12581  protected function _outLine($x, $y) {
12582  $this->_out(sprintf('%.2F %.2F l', $x * $this->k, ($this->h - $y) * $this->k));
12583  }
12584 
12595  protected function _outRect($x, $y, $w, $h, $op) {
12596  $this->_out(sprintf('%.2F %.2F %.2F %.2F re %s', $x * $this->k, ($this->h - $y) * $this->k, $w * $this->k, -$h * $this->k, $op));
12597  }
12598 
12611  protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
12612  $this->_out(sprintf('%.2F %.2F %.2F %.2F %.2F %.2F 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));
12613  }
12614 
12625  protected function _outCurveV($x2, $y2, $x3, $y3) {
12626  $this->_out(sprintf('%.2F %.2F %.2F %.2F v', $x2 * $this->k, ($this->h - $y2) * $this->k, $x3 * $this->k, ($this->h - $y3) * $this->k));
12627  }
12628 
12641  protected function _outCurveY($x1, $y1, $x3, $y3) {
12642  $this->_out(sprintf('%.2F %.2F %.2F %.2F y', $x1 * $this->k, ($this->h - $y1) * $this->k, $x3 * $this->k, ($this->h - $y3) * $this->k));
12643  }
12644 
12656  public function Line($x1, $y1, $x2, $y2, $style=array()) {
12657  if (is_array($style)) {
12658  $this->SetLineStyle($style);
12659  }
12660  $this->_outPoint($x1, $y1);
12661  $this->_outLine($x2, $y2);
12662  $this->_out('S');
12663  }
12664 
12684  public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
12685  if (!(false === strpos($style, 'F')) AND !empty($fill_color)) {
12686  $this->SetFillColorArray($fill_color);
12687  }
12688  $op = $this->getPathPaintOperator($style);
12689  if ((!$border_style) OR (isset($border_style['all']))) {
12690  if (isset($border_style['all']) AND $border_style['all']) {
12691  $this->SetLineStyle($border_style['all']);
12692  $border_style = array();
12693  }
12694  }
12695  $this->_outRect($x, $y, $w, $h, $op);
12696  if ($border_style) {
12697  $border_style2 = array();
12698  foreach ($border_style as $line => $value) {
12699  $length = strlen($line);
12700  for ($i = 0; $i < $length; ++$i) {
12701  $border_style2[$line[$i]] = $value;
12702  }
12703  }
12704  $border_style = $border_style2;
12705  if (isset($border_style['L']) AND $border_style['L']) {
12706  $this->Line($x, $y, $x, $y + $h, $border_style['L']);
12707  }
12708  if (isset($border_style['T']) AND $border_style['T']) {
12709  $this->Line($x, $y, $x + $w, $y, $border_style['T']);
12710  }
12711  if (isset($border_style['R']) AND $border_style['R']) {
12712  $this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
12713  }
12714  if (isset($border_style['B']) AND $border_style['B']) {
12715  $this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
12716  }
12717  }
12718  }
12719 
12739  public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
12740  if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12741  $this->SetFillColorArray($fill_color);
12742  }
12743  $op = $this->getPathPaintOperator($style);
12744  if ($line_style) {
12745  $this->SetLineStyle($line_style);
12746  }
12747  $this->_outPoint($x0, $y0);
12748  $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
12749  $this->_out($op);
12750  }
12751 
12766  public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
12767  if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12768  $this->SetFillColorArray($fill_color);
12769  }
12770  $op = $this->getPathPaintOperator($style);
12771  if ($op == 'f') {
12772  $line_style = array();
12773  }
12774  if ($line_style) {
12775  $this->SetLineStyle($line_style);
12776  }
12777  $this->_outPoint($x0, $y0);
12778  foreach ($segments as $segment) {
12779  list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
12780  $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
12781  }
12782  $this->_out($op);
12783  }
12784 
12803  public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
12804  if ($this->empty_string($ry) OR ($ry == 0)) {
12805  $ry = $rx;
12806  }
12807  if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12808  $this->SetFillColorArray($fill_color);
12809  }
12810  $op = $this->getPathPaintOperator($style);
12811  if ($op == 'f') {
12812  $line_style = array();
12813  }
12814  if ($line_style) {
12815  $this->SetLineStyle($line_style);
12816  }
12817  $this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc);
12818  $this->_out($op);
12819  }
12820 
12837  protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2) {
12838  $k = $this->k;
12839  if ($nc < 2) {
12840  $nc = 2;
12841  }
12842  if ($pie) {
12843  // center of the arc
12844  $this->_outPoint($xc, $yc);
12845  }
12846  $xang = deg2rad((float) $xang);
12847  $angs = deg2rad((float) $angs);
12848  $angf = deg2rad((float) $angf);
12849  $as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
12850  $af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
12851  if ($as < 0) {
12852  $as += (2 * M_PI);
12853  }
12854  if ($af < 0) {
12855  $af += (2 * M_PI);
12856  }
12857  if ($as > $af) {
12858  // reverse rotation go clockwise
12859  $as -= (2 * M_PI);
12860  }
12861  $total_angle = ($af - $as);
12862  if ($nc < 2) {
12863  $nc = 2;
12864  }
12865  // total arcs to draw
12866  $nc *= (2 * abs($total_angle) / M_PI);
12867  $nc = round($nc) + 1;
12868  // angle of each arc
12869  $arcang = $total_angle / $nc;
12870  // center point in PDF coordiantes
12871  $x0 = $xc;
12872  $y0 = ($this->h - $yc);
12873  // starting angle
12874  $ang = $as;
12875  $alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
12876  $cos_xang = cos($xang);
12877  $sin_xang = sin($xang);
12878  $cos_ang = cos($ang);
12879  $sin_ang = sin($ang);
12880  // first arc point
12881  $px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
12882  $py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
12883  // first Bezier control point
12884  $qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
12885  $qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
12886  if ($pie) {
12887  $this->_outLine($px1, $this->h - $py1);
12888  } else {
12889  $this->_outPoint($px1, $this->h - $py1);
12890  }
12891  // draw arcs
12892  for ($i = 1; $i <= $nc; ++$i) {
12893  // starting angle
12894  $ang = $as + ($i * $arcang);
12895  $cos_xang = cos($xang);
12896  $sin_xang = sin($xang);
12897  $cos_ang = cos($ang);
12898  $sin_ang = sin($ang);
12899  // second arc point
12900  $px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
12901  $py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
12902  // second Bezier control point
12903  $qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
12904  $qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
12905  // draw arc
12906  $this->_outCurve(($px1 + $qx1), ($this->h - ($py1 + $qy1)), ($px2 - $qx2), ($this->h - ($py2 - $qy2)), $px2, ($this->h - $py2));
12907  // move to next point
12908  $px1 = $px2;
12909  $py1 = $py2;
12910  $qx1 = $qx2;
12911  $qy1 = $qy2;
12912  }
12913  if ($pie) {
12914  $this->_outLine($xc, $yc);
12915  }
12916  }
12917 
12933  public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
12934  $this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
12935  }
12936 
12952  public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
12953  $this->Polygon($p, $style, $line_style, $fill_color, false);
12954  }
12955 
12971  public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
12972  $nc = count($p); // number of coordinates
12973  $np = $nc / 2; // number of points
12974  if ($closed) {
12975  // close polygon by adding the first 2 points at the end (one line)
12976  for ($i = 0; $i < 4; ++$i) {
12977  $p[$nc + $i] = $p[$i];
12978  }
12979  // copy style for the last added line
12980  if (isset($line_style[0])) {
12981  $line_style[$np] = $line_style[0];
12982  }
12983  $nc += 4;
12984  }
12985  if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12986  $this->SetFillColorArray($fill_color);
12987  }
12988  $op = $this->getPathPaintOperator($style);
12989  if ($op == 'f') {
12990  $line_style = array();
12991  }
12992  $draw = true;
12993  if ($line_style) {
12994  if (isset($line_style['all'])) {
12995  $this->SetLineStyle($line_style['all']);
12996  } else {
12997  $draw = false;
12998  if ($op == 'B') {
12999  // draw fill
13000  $op = 'f';
13001  $this->_outPoint($p[0], $p[1]);
13002  for ($i = 2; $i < $nc; $i = $i + 2) {
13003  $this->_outLine($p[$i], $p[$i + 1]);
13004  }
13005  $this->_out($op);
13006  }
13007  // draw outline
13008  $this->_outPoint($p[0], $p[1]);
13009  for ($i = 2; $i < $nc; $i = $i + 2) {
13010  $line_num = ($i / 2) - 1;
13011  if (isset($line_style[$line_num])) {
13012  if ($line_style[$line_num] != 0) {
13013  if (is_array($line_style[$line_num])) {
13014  $this->_out('S');
13015  $this->SetLineStyle($line_style[$line_num]);
13016  $this->_outPoint($p[$i - 2], $p[$i - 1]);
13017  $this->_outLine($p[$i], $p[$i + 1]);
13018  $this->_out('S');
13019  $this->_outPoint($p[$i], $p[$i + 1]);
13020  } else {
13021  $this->_outLine($p[$i], $p[$i + 1]);
13022  }
13023  }
13024  } else {
13025  $this->_outLine($p[$i], $p[$i + 1]);
13026  }
13027  }
13028  $this->_out($op);
13029  }
13030  }
13031  if ($draw) {
13032  $this->_outPoint($p[0], $p[1]);
13033  for ($i = 2; $i < $nc; $i = $i + 2) {
13034  $this->_outLine($p[$i], $p[$i + 1]);
13035  }
13036  $this->_out($op);
13037  }
13038  }
13039 
13069  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()) {
13070  if (3 > $ns) {
13071  $ns = 3;
13072  }
13073  if ($draw_circle) {
13074  $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
13075  }
13076  $p = array();
13077  for ($i = 0; $i < $ns; ++$i) {
13078  $a = $angle + ($i * 360 / $ns);
13079  $a_rad = deg2rad((float) $a);
13080  $p[] = $x0 + ($r * sin($a_rad));
13081  $p[] = $y0 + ($r * cos($a_rad));
13082  }
13083  $this->Polygon($p, $style, $line_style, $fill_color);
13084  }
13085 
13117  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()) {
13118  if ($nv < 2) {
13119  $nv = 2;
13120  }
13121  if ($draw_circle) {
13122  $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
13123  }
13124  $p2 = array();
13125  $visited = array();
13126  for ($i = 0; $i < $nv; ++$i) {
13127  $a = $angle + ($i * 360 / $nv);
13128  $a_rad = deg2rad((float) $a);
13129  $p2[] = $x0 + ($r * sin($a_rad));
13130  $p2[] = $y0 + ($r * cos($a_rad));
13131  $visited[] = false;
13132  }
13133  $p = array();
13134  $i = 0;
13135  do {
13136  $p[] = $p2[$i * 2];
13137  $p[] = $p2[($i * 2) + 1];
13138  $visited[$i] = true;
13139  $i += $ng;
13140  $i %= $nv;
13141  } while (!$visited[$i]);
13142  $this->Polygon($p, $style, $line_style, $fill_color);
13143  }
13144 
13159  public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
13160  $this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
13161  }
13162 
13178  public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
13179  if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
13180  // Not rounded
13181  $this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
13182  return;
13183  }
13184  // Rounded
13185  if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
13186  $this->SetFillColorArray($fill_color);
13187  }
13188  $op = $this->getPathPaintOperator($style);
13189  if ($op == 'f') {
13190  $border_style = array();
13191  }
13192  if ($border_style) {
13193  $this->SetLineStyle($border_style);
13194  }
13195  $MyArc = 4 / 3 * (sqrt(2) - 1);
13196  $this->_outPoint($x + $rx, $y);
13197  $xc = $x + $w - $rx;
13198  $yc = $y + $ry;
13199  $this->_outLine($xc, $y);
13200  if ($round_corner[0]) {
13201  $this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
13202  } else {
13203  $this->_outLine($x + $w, $y);
13204  }
13205  $xc = $x + $w - $rx;
13206  $yc = $y + $h - $ry;
13207  $this->_outLine($x + $w, $yc);
13208  if ($round_corner[1]) {
13209  $this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
13210  } else {
13211  $this->_outLine($x + $w, $y + $h);
13212  }
13213  $xc = $x + $rx;
13214  $yc = $y + $h - $ry;
13215  $this->_outLine($xc, $y + $h);
13216  if ($round_corner[2]) {
13217  $this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
13218  } else {
13219  $this->_outLine($x, $y + $h);
13220  }
13221  $xc = $x + $rx;
13222  $yc = $y + $ry;
13223  $this->_outLine($x, $yc);
13224  if ($round_corner[3]) {
13225  $this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
13226  } else {
13227  $this->_outLine($x, $y);
13228  $this->_outLine($x + $rx, $y);
13229  }
13230  $this->_out($op);
13231  }
13232 
13245  public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
13246  // getting arrow direction angle
13247  // 0 deg angle is when both arms go along X axis. angle grows clockwise.
13248  $dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
13249  if ($dir_angle < 0) {
13250  $dir_angle += (2 * M_PI);
13251  }
13252  $arm_angle = deg2rad($arm_angle);
13253  $sx1 = $x1;
13254  $sy1 = $y1;
13255  if ($head_style > 0) {
13256  // calculate the stopping point for the arrow shaft
13257  $sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
13258  $sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
13259  }
13260  // main arrow line / shaft
13261  $this->Line($x0, $y0, $sx1, $sy1);
13262  // left arrowhead arm tip
13263  $x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
13264  $y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
13265  // right arrowhead arm tip
13266  $x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
13267  $y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
13268  $mode = 'D';
13269  $style = array();
13270  switch ($head_style) {
13271  case 0: {
13272  // draw only arrowhead arms
13273  $mode = 'D';
13274  $style = array(1, 1, 0);
13275  break;
13276  }
13277  case 1: {
13278  // draw closed arrowhead, but no fill
13279  $mode = 'D';
13280  break;
13281  }
13282  case 2: {
13283  // closed and filled arrowhead
13284  $mode = 'DF';
13285  break;
13286  }
13287  case 3: {
13288  // filled arrowhead
13289  $mode = 'F';
13290  break;
13291  }
13292  }
13293  $this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
13294  }
13295 
13296  // END GRAPHIC FUNCTIONS SECTION -----------------------
13297 
13298  // BIDIRECTIONAL TEXT SECTION --------------------------
13299 
13310  protected function utf8StrRev($str, $setbom=false, $forcertl=false) {
13311  return $this->utf8StrArrRev($this->UTF8StringToArray($str), $str, $setbom, $forcertl);
13312  }
13313 
13325  protected function utf8StrArrRev($arr, $str='', $setbom=false, $forcertl=false) {
13326  return $this->arrUTF8ToUTF16BE($this->utf8Bidi($arr, $str, $forcertl), $setbom);
13327  }
13328 
13339  protected function utf8Bidi($ta, $str='', $forcertl=false) {
13340  // paragraph embedding level
13341  $pel = 0;
13342  // max level
13343  $maxlevel = 0;
13344  if ($this->empty_string($str)) {
13345  // create string from array
13346  $str = $this->UTF8ArrSubString($ta);
13347  }
13348  // check if string contains arabic text
13349  if (preg_match($this->unicode->uni_RE_PATTERN_ARABIC, $str)) {
13350  $arabic = true;
13351  } else {
13352  $arabic = false;
13353  }
13354  // check if string contains RTL text
13355  if (!($forcertl OR $arabic OR preg_match($this->unicode->uni_RE_PATTERN_RTL, $str))) {
13356  return $ta;
13357  }
13358 
13359  // get number of chars
13360  $numchars = count($ta);
13361 
13362  if ($forcertl == 'R') {
13363  $pel = 1;
13364  } elseif ($forcertl == 'L') {
13365  $pel = 0;
13366  } else {
13367  // P2. In each paragraph, find the first character of type L, AL, or R.
13368  // P3. If a character is found in P2 and it is of type AL or R, then set the paragraph embedding level to one; otherwise, set it to zero.
13369  for ($i=0; $i < $numchars; ++$i) {
13370  $type = $this->unicode->uni_type[$ta[$i]];
13371  if ($type == 'L') {
13372  $pel = 0;
13373  break;
13374  } elseif (($type == 'AL') OR ($type == 'R')) {
13375  $pel = 1;
13376  break;
13377  }
13378  }
13379  }
13380 
13381  // Current Embedding Level
13382  $cel = $pel;
13383  // directional override status
13384  $dos = 'N';
13385  $remember = array();
13386  // start-of-level-run
13387  $sor = $pel % 2 ? 'R' : 'L';
13388  $eor = $sor;
13389 
13390  // Array of characters data
13391  $chardata = Array();
13392 
13393  // X1. Begin by setting the current embedding level to the paragraph embedding level. Set the directional override status to neutral. Process each character iteratively, applying rules X2 through X9. Only embedding levels from 0 to 61 are valid in this phase.
13394  // In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
13395  for ($i=0; $i < $numchars; ++$i) {
13396  if ($ta[$i] == $this->unicode->uni_RLE) {
13397  // X2. With each RLE, compute the least greater odd embedding level.
13398  // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
13399  // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
13400  $next_level = $cel + ($cel % 2) + 1;
13401  if ($next_level < 62) {
13402  $remember[] = array('num' => $this->unicode->uni_RLE, 'cel' => $cel, 'dos' => $dos);
13403  $cel = $next_level;
13404  $dos = 'N';
13405  $sor = $eor;
13406  $eor = $cel % 2 ? 'R' : 'L';
13407  }
13408  } elseif ($ta[$i] == $this->unicode->uni_LRE) {
13409  // X3. With each LRE, compute the least greater even embedding level.
13410  // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
13411  // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
13412  $next_level = $cel + 2 - ($cel % 2);
13413  if ( $next_level < 62 ) {
13414  $remember[] = array('num' => $this->unicode->uni_LRE, 'cel' => $cel, 'dos' => $dos);
13415  $cel = $next_level;
13416  $dos = 'N';
13417  $sor = $eor;
13418  $eor = $cel % 2 ? 'R' : 'L';
13419  }
13420  } elseif ($ta[$i] == $this->unicode->uni_RLO) {
13421  // X4. With each RLO, compute the least greater odd embedding level.
13422  // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to right-to-left.
13423  // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
13424  $next_level = $cel + ($cel % 2) + 1;
13425  if ($next_level < 62) {
13426  $remember[] = array('num' => $this->unicode->uni_RLO, 'cel' => $cel, 'dos' => $dos);
13427  $cel = $next_level;
13428  $dos = 'R';
13429  $sor = $eor;
13430  $eor = $cel % 2 ? 'R' : 'L';
13431  }
13432  } elseif ($ta[$i] == $this->unicode->uni_LRO) {
13433  // X5. With each LRO, compute the least greater even embedding level.
13434  // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to left-to-right.
13435  // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
13436  $next_level = $cel + 2 - ($cel % 2);
13437  if ( $next_level < 62 ) {
13438  $remember[] = array('num' => $this->unicode->uni_LRO, 'cel' => $cel, 'dos' => $dos);
13439  $cel = $next_level;
13440  $dos = 'L';
13441  $sor = $eor;
13442  $eor = $cel % 2 ? 'R' : 'L';
13443  }
13444  } elseif ($ta[$i] == $this->unicode->uni_PDF) {
13445  // X7. With each PDF, determine the matching embedding or override code. If there was a valid matching code, restore (pop) the last remembered (pushed) embedding level and directional override.
13446  if (count($remember)) {
13447  $last = count($remember ) - 1;
13448  if (($remember[$last]['num'] == $this->unicode->uni_RLE) OR
13449  ($remember[$last]['num'] == $this->unicode->uni_LRE) OR
13450  ($remember[$last]['num'] == $this->unicode->uni_RLO) OR
13451  ($remember[$last]['num'] == $this->unicode->uni_LRO)) {
13452  $match = array_pop($remember);
13453  $cel = $match['cel'];
13454  $dos = $match['dos'];
13455  $sor = $eor;
13456  $eor = ($cel > $match['cel'] ? $cel : $match['cel']) % 2 ? 'R' : 'L';
13457  }
13458  }
13459  } elseif (($ta[$i] != $this->unicode->uni_RLE) AND
13460  ($ta[$i] != $this->unicode->uni_LRE) AND
13461  ($ta[$i] != $this->unicode->uni_RLO) AND
13462  ($ta[$i] != $this->unicode->uni_LRO) AND
13463  ($ta[$i] != $this->unicode->uni_PDF)) {
13464  // X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
13465  // a. Set the level of the current character to the current embedding level.
13466  // b. Whenever the directional override status is not neutral, reset the current character type to the directional override status.
13467  if ($dos != 'N') {
13468  $chardir = $dos;
13469  } else {
13470  if (isset($this->unicode->uni_type[$ta[$i]])) {
13471  $chardir = $this->unicode->uni_type[$ta[$i]];
13472  } else {
13473  $chardir = 'L';
13474  }
13475  }
13476  // stores string characters and other information
13477  $chardata[] = array('char' => $ta[$i], 'level' => $cel, 'type' => $chardir, 'sor' => $sor, 'eor' => $eor);
13478  }
13479  } // end for each char
13480 
13481  // X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph. Paragraph separators are not included in the embedding.
13482  // X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes.
13483  // X10. The remaining rules are applied to each run of characters at the same level. For each run, determine the start-of-level-run (sor) and end-of-level-run (eor) type, either L or R. This depends on the higher of the two levels on either side of the boundary (at the start or end of the paragraph, the level of the 'other' run is the base embedding level). If the higher level is odd, the type is R; otherwise, it is L.
13484 
13485  // 3.3.3 Resolving Weak Types
13486  // Weak types are now resolved one level run at a time. At level run boundaries where the type of the character on the other side of the boundary is required, the type assigned to sor or eor is used.
13487  // Nonspacing marks are now resolved based on the previous characters.
13488  $numchars = count($chardata);
13489 
13490  // W1. Examine each nonspacing mark (NSM) in the level run, and change the type of the NSM to the type of the previous character. If the NSM is at the start of the level run, it will get the type of sor.
13491  $prevlevel = -1; // track level changes
13492  $levcount = 0; // counts consecutive chars at the same level
13493  for ($i=0; $i < $numchars; ++$i) {
13494  if ($chardata[$i]['type'] == 'NSM') {
13495  if ($levcount) {
13496  $chardata[$i]['type'] = $chardata[$i]['sor'];
13497  } elseif ($i > 0) {
13498  $chardata[$i]['type'] = $chardata[($i-1)]['type'];
13499  }
13500  }
13501  if ($chardata[$i]['level'] != $prevlevel) {
13502  $levcount = 0;
13503  } else {
13504  ++$levcount;
13505  }
13506  $prevlevel = $chardata[$i]['level'];
13507  }
13508 
13509  // W2. Search backward from each instance of a European number until the first strong type (R, L, AL, or sor) is found. If an AL is found, change the type of the European number to Arabic number.
13510  $prevlevel = -1;
13511  $levcount = 0;
13512  for ($i=0; $i < $numchars; ++$i) {
13513  if ($chardata[$i]['char'] == 'EN') {
13514  for ($j=$levcount; $j >= 0; $j--) {
13515  if ($chardata[$j]['type'] == 'AL') {
13516  $chardata[$i]['type'] = 'AN';
13517  } elseif (($chardata[$j]['type'] == 'L') OR ($chardata[$j]['type'] == 'R')) {
13518  break;
13519  }
13520  }
13521  }
13522  if ($chardata[$i]['level'] != $prevlevel) {
13523  $levcount = 0;
13524  } else {
13525  ++$levcount;
13526  }
13527  $prevlevel = $chardata[$i]['level'];
13528  }
13529 
13530  // W3. Change all ALs to R.
13531  for ($i=0; $i < $numchars; ++$i) {
13532  if ($chardata[$i]['type'] == 'AL') {
13533  $chardata[$i]['type'] = 'R';
13534  }
13535  }
13536 
13537  // W4. A single European separator between two European numbers changes to a European number. A single common separator between two numbers of the same type changes to that type.
13538  $prevlevel = -1;
13539  $levcount = 0;
13540  for ($i=0; $i < $numchars; ++$i) {
13541  if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
13542  if (($chardata[$i]['type'] == 'ES') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
13543  $chardata[$i]['type'] = 'EN';
13544  } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
13545  $chardata[$i]['type'] = 'EN';
13546  } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'AN') AND ($chardata[($i+1)]['type'] == 'AN')) {
13547  $chardata[$i]['type'] = 'AN';
13548  }
13549  }
13550  if ($chardata[$i]['level'] != $prevlevel) {
13551  $levcount = 0;
13552  } else {
13553  ++$levcount;
13554  }
13555  $prevlevel = $chardata[$i]['level'];
13556  }
13557 
13558  // W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
13559  $prevlevel = -1;
13560  $levcount = 0;
13561  for ($i=0; $i < $numchars; ++$i) {
13562  if ($chardata[$i]['type'] == 'ET') {
13563  if (($levcount > 0) AND ($chardata[($i-1)]['type'] == 'EN')) {
13564  $chardata[$i]['type'] = 'EN';
13565  } else {
13566  $j = $i+1;
13567  while (($j < $numchars) AND ($chardata[$j]['level'] == $prevlevel)) {
13568  if ($chardata[$j]['type'] == 'EN') {
13569  $chardata[$i]['type'] = 'EN';
13570  break;
13571  } elseif ($chardata[$j]['type'] != 'ET') {
13572  break;
13573  }
13574  ++$j;
13575  }
13576  }
13577  }
13578  if ($chardata[$i]['level'] != $prevlevel) {
13579  $levcount = 0;
13580  } else {
13581  ++$levcount;
13582  }
13583  $prevlevel = $chardata[$i]['level'];
13584  }
13585 
13586  // W6. Otherwise, separators and terminators change to Other Neutral.
13587  $prevlevel = -1;
13588  $levcount = 0;
13589  for ($i=0; $i < $numchars; ++$i) {
13590  if (($chardata[$i]['type'] == 'ET') OR ($chardata[$i]['type'] == 'ES') OR ($chardata[$i]['type'] == 'CS')) {
13591  $chardata[$i]['type'] = 'ON';
13592  }
13593  if ($chardata[$i]['level'] != $prevlevel) {
13594  $levcount = 0;
13595  } else {
13596  ++$levcount;
13597  }
13598  $prevlevel = $chardata[$i]['level'];
13599  }
13600 
13601  //W7. Search backward from each instance of a European number until the first strong type (R, L, or sor) is found. If an L is found, then change the type of the European number to L.
13602  $prevlevel = -1;
13603  $levcount = 0;
13604  for ($i=0; $i < $numchars; ++$i) {
13605  if ($chardata[$i]['char'] == 'EN') {
13606  for ($j=$levcount; $j >= 0; $j--) {
13607  if ($chardata[$j]['type'] == 'L') {
13608  $chardata[$i]['type'] = 'L';
13609  } elseif ($chardata[$j]['type'] == 'R') {
13610  break;
13611  }
13612  }
13613  }
13614  if ($chardata[$i]['level'] != $prevlevel) {
13615  $levcount = 0;
13616  } else {
13617  ++$levcount;
13618  }
13619  $prevlevel = $chardata[$i]['level'];
13620  }
13621 
13622  // N1. A sequence of neutrals takes the direction of the surrounding strong text if the text on both sides has the same direction. European and Arabic numbers act as if they were R in terms of their influence on neutrals. Start-of-level-run (sor) and end-of-level-run (eor) are used at level run boundaries.
13623  $prevlevel = -1;
13624  $levcount = 0;
13625  for ($i=0; $i < $numchars; ++$i) {
13626  if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
13627  if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
13628  $chardata[$i]['type'] = 'L';
13629  } elseif (($chardata[$i]['type'] == 'N') AND
13630  (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
13631  (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
13632  $chardata[$i]['type'] = 'R';
13633  } elseif ($chardata[$i]['type'] == 'N') {
13634  // N2. Any remaining neutrals take the embedding direction
13635  $chardata[$i]['type'] = $chardata[$i]['sor'];
13636  }
13637  } elseif (($levcount == 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
13638  // first char
13639  if (($chardata[$i]['type'] == 'N') AND ($chardata[$i]['sor'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
13640  $chardata[$i]['type'] = 'L';
13641  } elseif (($chardata[$i]['type'] == 'N') AND
13642  (($chardata[$i]['sor'] == 'R') OR ($chardata[$i]['sor'] == 'EN') OR ($chardata[$i]['sor'] == 'AN')) AND
13643  (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
13644  $chardata[$i]['type'] = 'R';
13645  } elseif ($chardata[$i]['type'] == 'N') {
13646  // N2. Any remaining neutrals take the embedding direction
13647  $chardata[$i]['type'] = $chardata[$i]['sor'];
13648  }
13649  } elseif (($levcount > 0) AND ((($i+1) == $numchars) OR (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] != $prevlevel))) {
13650  //last char
13651  if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[$i]['eor'] == 'L')) {
13652  $chardata[$i]['type'] = 'L';
13653  } elseif (($chardata[$i]['type'] == 'N') AND
13654  (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
13655  (($chardata[$i]['eor'] == 'R') OR ($chardata[$i]['eor'] == 'EN') OR ($chardata[$i]['eor'] == 'AN'))) {
13656  $chardata[$i]['type'] = 'R';
13657  } elseif ($chardata[$i]['type'] == 'N') {
13658  // N2. Any remaining neutrals take the embedding direction
13659  $chardata[$i]['type'] = $chardata[$i]['sor'];
13660  }
13661  } elseif ($chardata[$i]['type'] == 'N') {
13662  // N2. Any remaining neutrals take the embedding direction
13663  $chardata[$i]['type'] = $chardata[$i]['sor'];
13664  }
13665  if ($chardata[$i]['level'] != $prevlevel) {
13666  $levcount = 0;
13667  } else {
13668  ++$levcount;
13669  }
13670  $prevlevel = $chardata[$i]['level'];
13671  }
13672 
13673  // I1. For all characters with an even (left-to-right) embedding direction, those of type R go up one level and those of type AN or EN go up two levels.
13674  // I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
13675  for ($i=0; $i < $numchars; ++$i) {
13676  $odd = $chardata[$i]['level'] % 2;
13677  if ($odd) {
13678  if (($chardata[$i]['type'] == 'L') OR ($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
13679  $chardata[$i]['level'] += 1;
13680  }
13681  } else {
13682  if ($chardata[$i]['type'] == 'R') {
13683  $chardata[$i]['level'] += 1;
13684  } elseif (($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
13685  $chardata[$i]['level'] += 2;
13686  }
13687  }
13688  $maxlevel = max($chardata[$i]['level'],$maxlevel);
13689  }
13690 
13691  // L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
13692  // 1. Segment separators,
13693  // 2. Paragraph separators,
13694  // 3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and
13695  // 4. Any sequence of white space characters at the end of the line.
13696  for ($i=0; $i < $numchars; ++$i) {
13697  if (($chardata[$i]['type'] == 'B') OR ($chardata[$i]['type'] == 'S')) {
13698  $chardata[$i]['level'] = $pel;
13699  } elseif ($chardata[$i]['type'] == 'WS') {
13700  $j = $i+1;
13701  while ($j < $numchars) {
13702  if ((($chardata[$j]['type'] == 'B') OR ($chardata[$j]['type'] == 'S')) OR
13703  (($j == ($numchars-1)) AND ($chardata[$j]['type'] == 'WS'))) {
13704  $chardata[$i]['level'] = $pel;
13705  break;
13706  } elseif ($chardata[$j]['type'] != 'WS') {
13707  break;
13708  }
13709  ++$j;
13710  }
13711  }
13712  }
13713 
13714  // Arabic Shaping
13715  // Cursively connected scripts, such as Arabic or Syriac, require the selection of positional character shapes that depend on adjacent characters. Shaping is logically applied after the Bidirectional Algorithm is used and is limited to characters within the same directional run.
13716  if ($arabic) {
13717  $endedletter = array(1569,1570,1571,1572,1573,1575,1577,1583,1584,1585,1586,1608,1688);
13718  $alfletter = array(1570,1571,1573,1575);
13719  $chardata2 = $chardata;
13720  $laaletter = false;
13721  $charAL = array();
13722  $x = 0;
13723  for ($i=0; $i < $numchars; ++$i) {
13724  if (($this->unicode->uni_type[$chardata[$i]['char']] == 'AL') OR ($chardata[$i]['char'] == 32) OR ($chardata[$i]['char'] == 8204)) {
13725  $charAL[$x] = $chardata[$i];
13726  $charAL[$x]['i'] = $i;
13727  $chardata[$i]['x'] = $x;
13728  ++$x;
13729  }
13730  }
13731  $numAL = $x;
13732  for ($i=0; $i < $numchars; ++$i) {
13733  $thischar = $chardata[$i];
13734  if ($i > 0) {
13735  $prevchar = $chardata[($i-1)];
13736  } else {
13737  $prevchar = false;
13738  }
13739  if (($i+1) < $numchars) {
13740  $nextchar = $chardata[($i+1)];
13741  } else {
13742  $nextchar = false;
13743  }
13744  if ($this->unicode->uni_type[$thischar['char']] == 'AL') {
13745  $x = $thischar['x'];
13746  if ($x > 0) {
13747  $prevchar = $charAL[($x-1)];
13748  } else {
13749  $prevchar = false;
13750  }
13751  if (($x+1) < $numAL) {
13752  $nextchar = $charAL[($x+1)];
13753  } else {
13754  $nextchar = false;
13755  }
13756  // if laa letter
13757  if (($prevchar !== false) AND ($prevchar['char'] == 1604) AND (in_array($thischar['char'], $alfletter))) {
13758  $arabicarr = $this->unicode->uni_laa_array;
13759  $laaletter = true;
13760  if ($x > 1) {
13761  $prevchar = $charAL[($x-2)];
13762  } else {
13763  $prevchar = false;
13764  }
13765  } else {
13766  $arabicarr = $this->unicode->uni_arabicsubst;
13767  $laaletter = false;
13768  }
13769  if (($prevchar !== false) AND ($nextchar !== false) AND
13770  (($this->unicode->uni_type[$prevchar['char']] == 'AL') OR ($this->unicode->uni_type[$prevchar['char']] == 'NSM')) AND
13771  (($this->unicode->uni_type[$nextchar['char']] == 'AL') OR ($this->unicode->uni_type[$nextchar['char']] == 'NSM')) AND
13772  ($prevchar['type'] == $thischar['type']) AND
13773  ($nextchar['type'] == $thischar['type']) AND
13774  ($nextchar['char'] != 1567)) {
13775  if (in_array($prevchar['char'], $endedletter)) {
13776  if (isset($arabicarr[$thischar['char']][2])) {
13777  // initial
13778  $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
13779  }
13780  } else {
13781  if (isset($arabicarr[$thischar['char']][3])) {
13782  // medial
13783  $chardata2[$i]['char'] = $arabicarr[$thischar['char']][3];
13784  }
13785  }
13786  } elseif (($nextchar !== false) AND
13787  (($this->unicode->uni_type[$nextchar['char']] == 'AL') OR ($this->unicode->uni_type[$nextchar['char']] == 'NSM')) AND
13788  ($nextchar['type'] == $thischar['type']) AND
13789  ($nextchar['char'] != 1567)) {
13790  if (isset($arabicarr[$chardata[$i]['char']][2])) {
13791  // initial
13792  $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
13793  }
13794  } elseif ((($prevchar !== false) AND
13795  (($this->unicode->uni_type[$prevchar['char']] == 'AL') OR ($this->unicode->uni_type[$prevchar['char']] == 'NSM')) AND
13796  ($prevchar['type'] == $thischar['type'])) OR
13797  (($nextchar !== false) AND ($nextchar['char'] == 1567))) {
13798  // final
13799  if (($i > 1) AND ($thischar['char'] == 1607) AND
13800  ($chardata[$i-1]['char'] == 1604) AND
13801  ($chardata[$i-2]['char'] == 1604)) {
13802  //Allah Word
13803  // mark characters to delete with false
13804  $chardata2[$i-2]['char'] = false;
13805  $chardata2[$i-1]['char'] = false;
13806  $chardata2[$i]['char'] = 65010;
13807  } else {
13808  if (($prevchar !== false) AND in_array($prevchar['char'], $endedletter)) {
13809  if (isset($arabicarr[$thischar['char']][0])) {
13810  // isolated
13811  $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
13812  }
13813  } else {
13814  if (isset($arabicarr[$thischar['char']][1])) {
13815  // final
13816  $chardata2[$i]['char'] = $arabicarr[$thischar['char']][1];
13817  }
13818  }
13819  }
13820  } elseif (isset($arabicarr[$thischar['char']][0])) {
13821  // isolated
13822  $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
13823  }
13824  // if laa letter
13825  if ($laaletter) {
13826  // mark characters to delete with false
13827  $chardata2[($charAL[($x-1)]['i'])]['char'] = false;
13828  }
13829  } // end if AL (Arabic Letter)
13830  } // end for each char
13831  /*
13832  * Combining characters that can occur with Arabic Shadda (0651 HEX, 1617 DEC) are replaced.
13833  * Putting the combining mark and shadda in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner.
13834  */
13835  $cw = &$this->CurrentFont['cw'];
13836  for ($i = 0; $i < ($numchars-1); ++$i) {
13837  if (($chardata2[$i]['char'] == 1617) AND (isset($this->unicode->uni_diacritics[($chardata2[$i+1]['char'])]))) {
13838  // check if the subtitution font is defined on current font
13839  if (isset($cw[($this->unicode->uni_diacritics[($chardata2[$i+1]['char'])])])) {
13840  $chardata2[$i]['char'] = false;
13841  $chardata2[$i+1]['char'] = $this->unicode->uni_diacritics[($chardata2[$i+1]['char'])];
13842  }
13843  }
13844  }
13845  // remove marked characters
13846  foreach ($chardata2 as $key => $value) {
13847  if ($value['char'] === false) {
13848  unset($chardata2[$key]);
13849  }
13850  }
13851  $chardata = array_values($chardata2);
13852  $numchars = count($chardata);
13853  unset($chardata2);
13854  unset($arabicarr);
13855  unset($laaletter);
13856  unset($charAL);
13857  }
13858 
13859  // L2. From the highest level found in the text to the lowest odd level on each line, including intermediate levels not actually present in the text, reverse any contiguous sequence of characters that are at that level or higher.
13860  for ($j=$maxlevel; $j > 0; $j--) {
13861  $ordarray = Array();
13862  $revarr = Array();
13863  $onlevel = false;
13864  for ($i=0; $i < $numchars; ++$i) {
13865  if ($chardata[$i]['level'] >= $j) {
13866  $onlevel = true;
13867  if (isset($this->unicode->uni_mirror[$chardata[$i]['char']])) {
13868  // L4. A character is depicted by a mirrored glyph if and only if (a) the resolved directionality of that character is R, and (b) the Bidi_Mirrored property value of that character is true.
13869  $chardata[$i]['char'] = $this->unicode->uni_mirror[$chardata[$i]['char']];
13870  }
13871  $revarr[] = $chardata[$i];
13872  } else {
13873  if ($onlevel) {
13874  $revarr = array_reverse($revarr);
13875  $ordarray = array_merge($ordarray, $revarr);
13876  $revarr = Array();
13877  $onlevel = false;
13878  }
13879  $ordarray[] = $chardata[$i];
13880  }
13881  }
13882  if ($onlevel) {
13883  $revarr = array_reverse($revarr);
13884  $ordarray = array_merge($ordarray, $revarr);
13885  }
13886  $chardata = $ordarray;
13887  }
13888 
13889  $ordarray = array();
13890  for ($i=0; $i < $numchars; ++$i) {
13891  $ordarray[] = $chardata[$i]['char'];
13892  // store char values for subsetting
13893  $this->CurrentFont['subsetchars'][$chardata[$i]['char']] = true;
13894  }
13895  // update font subsetchars
13896  $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
13897  return $ordarray;
13898  }
13899 
13900  // END OF BIDIRECTIONAL TEXT SECTION -------------------
13901 
13912  public function Bookmark($txt, $level=0, $y=-1, $page='') {
13913  if ($level < 0) {
13914  $level = 0;
13915  }
13916  if (isset($this->outlines[0])) {
13917  $lastoutline = end($this->outlines);
13918  $maxlevel = $lastoutline['l'] + 1;
13919  } else {
13920  $maxlevel = 0;
13921  }
13922  if ($level > $maxlevel) {
13923  $level = $maxlevel;
13924  }
13925  if ($y == -1) {
13926  $y = $this->GetY();
13927  }
13928  if (empty($page)) {
13929  $page = $this->PageNo();
13930  if (empty($page)) {
13931  return;
13932  }
13933  }
13934  $this->outlines[] = array('t' => $txt, 'l' => $level, 'y' => $y, 'p' => $page);
13935  }
13936 
13943  protected function _putbookmarks() {
13944  $nb = count($this->outlines);
13945  if ($nb == 0) {
13946  return;
13947  }
13948  // get sorting columns
13949  $outline_p = array();
13950  $outline_y = array();
13951  foreach ($this->outlines as $key => $row) {
13952  $outline_p[$key] = $row['p'];
13953  $outline_k[$key] = $key;
13954  }
13955  // sort outlines by page and original position
13956  array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
13957  $lru = array();
13958  $level = 0;
13959  foreach ($this->outlines as $i => $o) {
13960  if ($o['l'] > 0) {
13961  $parent = $lru[($o['l'] - 1)];
13962  //Set parent and last pointers
13963  $this->outlines[$i]['parent'] = $parent;
13964  $this->outlines[$parent]['last'] = $i;
13965  if ($o['l'] > $level) {
13966  //Level increasing: set first pointer
13967  $this->outlines[$parent]['first'] = $i;
13968  }
13969  } else {
13970  $this->outlines[$i]['parent'] = $nb;
13971  }
13972  if (($o['l'] <= $level) AND ($i > 0)) {
13973  //Set prev and next pointers
13974  $prev = $lru[$o['l']];
13975  $this->outlines[$prev]['next'] = $i;
13976  $this->outlines[$i]['prev'] = $prev;
13977  }
13978  $lru[$o['l']] = $i;
13979  $level = $o['l'];
13980  }
13981  //Outline items
13982  $n = $this->n + 1;
13983  $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';
13984  foreach ($this->outlines as $i => $o) {
13985  if (isset($this->page_obj_id[($o['p'])])) {
13986  $oid = $this->_newobj();
13987  // covert HTML title to string
13988  $title = preg_replace($nltags, "\n", $o['t']);
13989  $title = preg_replace("/[\r]+/si", '', $title);
13990  $title = preg_replace("/[\n]+/si", "\n", $title);
13991  $title = strip_tags($title);
13992  $title = $this->stringTrim($title);
13993  $out = '<</Title '.$this->_textstring($title, $oid);
13994  $out .= ' /Parent '.($n + $o['parent']).' 0 R';
13995  if (isset($o['prev'])) {
13996  $out .= ' /Prev '.($n + $o['prev']).' 0 R';
13997  }
13998  if (isset($o['next'])) {
13999  $out .= ' /Next '.($n + $o['next']).' 0 R';
14000  }
14001  if (isset($o['first'])) {
14002  $out .= ' /First '.($n + $o['first']).' 0 R';
14003  }
14004  if (isset($o['last'])) {
14005  $out .= ' /Last '.($n + $o['last']).' 0 R';
14006  }
14007  $out .= ' '.sprintf('/Dest [%u 0 R /XYZ 0 %.2F null]', $this->page_obj_id[($o['p'])], ($this->pagedim[$o['p']]['h'] - ($o['y'] * $this->k)));
14008  $out .= ' /Count 0 >>';
14009  $out .= "\n".'endobj';
14010  $this->_out($out);
14011  }
14012  }
14013  //Outline root
14014  $this->OutlineRoot = $this->_newobj();
14015  $this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
14016  }
14017 
14018  // --- JAVASCRIPT ------------------------------------------------------
14019 
14027  public function IncludeJS($script) {
14028  $this->javascript .= $script;
14029  }
14030 
14040  public function addJavascriptObject($script, $onload=false) {
14041  ++$this->n;
14042  $this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
14043  return $this->n;
14044  }
14045 
14052  protected function _putjavascript() {
14053  if (empty($this->javascript) AND empty($this->js_objects)) {
14054  return;
14055  }
14056  if (strpos($this->javascript, 'this.addField') > 0) {
14057  if (!$this->ur['enabled']) {
14058  //$this->setUserRights();
14059  }
14060  // the following two lines are used to avoid form fields duplication after saving
14061  // The addField method only works when releasing user rights (UR3)
14062  $jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%.2F,%.2F,%.2F,%.2F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
14063  $jsb = "getField('tcpdfdocsaved').value='saved';";
14064  $this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
14065  }
14066  $this->n_js = $this->_newobj();
14067  $out = ' << /Names [';
14068  if (!empty($this->javascript)) {
14069  $out .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
14070  }
14071  if (!empty($this->js_objects)) {
14072  foreach ($this->js_objects as $key => $val) {
14073  if ($val['onload']) {
14074  $out .= ' (JS'.$key.') '.$key.' 0 R';
14075  }
14076  }
14077  }
14078  $out .= ' ] >>';
14079  $out .= "\n".'endobj';
14080  $this->_out($out);
14081  // default Javascript object
14082  if (!empty($this->javascript)) {
14083  $obj_id = $this->_newobj();
14084  $out = '<< /S /JavaScript';
14085  $out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
14086  $out .= ' >>';
14087  $out .= "\n".'endobj';
14088  $this->_out($out);
14089  }
14090  // additional Javascript objects
14091  if (!empty($this->js_objects)) {
14092  foreach ($this->js_objects as $key => $val) {
14093  $out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
14094  $this->_out($out);
14095  }
14096  }
14097  }
14098 
14106  protected function _JScolor($color) {
14107  static $aColors = array('transparent', 'black', 'white', 'red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'dkGray', 'gray', 'ltGray');
14108  if (substr($color,0,1) == '#') {
14109  return sprintf("['RGB',%.3F,%.3F,%.3F]", hexdec(substr($color,1,2))/255, hexdec(substr($color,3,2))/255, hexdec(substr($color,5,2))/255);
14110  }
14111  if (!in_array($color,$aColors)) {
14112  $this->Error('Invalid color: '.$color);
14113  }
14114  return 'color.'.$color;
14115  }
14116 
14130  protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
14131  if ($this->rtl) {
14132  $x = $x - $w;
14133  }
14134  // the followind avoid fields duplication after saving the document
14135  $this->javascript .= "if(getField('tcpdfdocsaved').value != 'saved') {";
14136  $k = $this->k;
14137  $this->javascript .= sprintf("f".$name."=this.addField('%s','%s',%u,[%.2F,%.2F,%.2F,%.2F]);", $name, $type, $this->PageNo()-1, $x*$k, ($this->h-$y)*$k+1, ($x+$w)*$k, ($this->h-$y-$h)*$k+1)."\n";
14138  $this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
14139  while (list($key, $val) = each($prop)) {
14140  if (strcmp(substr($key, -5), 'Color') == 0) {
14141  $val = $this->_JScolor($val);
14142  } else {
14143  $val = "'".$val."'";
14144  }
14145  $this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
14146  }
14147  if ($this->rtl) {
14148  $this->x -= $w;
14149  } else {
14150  $this->x += $w;
14151  }
14152  $this->javascript .= '}';
14153  }
14154 
14155  // --- FORM FIELDS -----------------------------------------------------
14156 
14165  protected function getAnnotOptFromJSProp($prop) {
14166  if (isset($prop['aopt']) AND is_array($prop['aopt'])) {
14167  // the annotation options area lready defined
14168  return $prop['aopt'];
14169  }
14170  $opt = array(); // value to be returned
14171  // alignment: Controls how the text is laid out within the text field.
14172  if (isset($prop['alignment'])) {
14173  switch ($prop['alignment']) {
14174  case 'left': {
14175  $opt['q'] = 0;
14176  break;
14177  }
14178  case 'center': {
14179  $opt['q'] = 1;
14180  break;
14181  }
14182  case 'right': {
14183  $opt['q'] = 2;
14184  break;
14185  }
14186  default: {
14187  $opt['q'] = ($this->rtl)?2:0;
14188  break;
14189  }
14190  }
14191  }
14192  // lineWidth: Specifies the thickness of the border when stroking the perimeter of a field's rectangle.
14193  if (isset($prop['lineWidth'])) {
14194  $linewidth = intval($prop['lineWidth']);
14195  } else {
14196  $linewidth = 1;
14197  }
14198  // borderStyle: The border style for a field.
14199  if (isset($prop['borderStyle'])) {
14200  switch ($prop['borderStyle']) {
14201  case 'border.d':
14202  case 'dashed': {
14203  $opt['border'] = array(0, 0, $linewidth, array(3, 2));
14204  $opt['bs'] = array('w'=>$linewidth, 's'=>'D', 'd'=>array(3, 2));
14205  break;
14206  }
14207  case 'border.b':
14208  case 'beveled': {
14209  $opt['border'] = array(0, 0, $linewidth);
14210  $opt['bs'] = array('w'=>$linewidth, 's'=>'B');
14211  break;
14212  }
14213  case 'border.i':
14214  case 'inset': {
14215  $opt['border'] = array(0, 0, $linewidth);
14216  $opt['bs'] = array('w'=>$linewidth, 's'=>'I');
14217  break;
14218  }
14219  case 'border.u':
14220  case 'underline': {
14221  $opt['border'] = array(0, 0, $linewidth);
14222  $opt['bs'] = array('w'=>$linewidth, 's'=>'U');
14223  break;
14224  }
14225  default:
14226  case 'border.s':
14227  case 'solid': {
14228  $opt['border'] = array(0, 0, $linewidth);
14229  $opt['bs'] = array('w'=>$linewidth, 's'=>'S');
14230  break;
14231  }
14232  }
14233  }
14234  if (isset($prop['border']) AND is_array($prop['border'])) {
14235  $opt['border'] = $prop['border'];
14236  }
14237  if (!isset($opt['mk'])) {
14238  $opt['mk'] = array();
14239  }
14240  if (!isset($opt['mk']['if'])) {
14241  $opt['mk']['if'] = array();
14242  }
14243  $opt['mk']['if']['a'] = array(0.5, 0.5);
14244  // buttonAlignX: Controls how space is distributed from the left of the button face with respect to the icon.
14245  if (isset($prop['buttonAlignX'])) {
14246  $opt['mk']['if']['a'][0] = $prop['buttonAlignX'];
14247  }
14248  // buttonAlignY: Controls how unused space is distributed from the bottom of the button face with respect to the icon.
14249  if (isset($prop['buttonAlignY'])) {
14250  $opt['mk']['if']['a'][1] = $prop['buttonAlignY'];
14251  }
14252  // buttonFitBounds: If true, the extent to which the icon may be scaled is set to the bounds of the button field.
14253  if (isset($prop['buttonFitBounds']) AND ($prop['buttonFitBounds'] == 'true')) {
14254  $opt['mk']['if']['fb'] = true;
14255  }
14256  // buttonScaleHow: Controls how the icon is scaled (if necessary) to fit inside the button face.
14257  if (isset($prop['buttonScaleHow'])) {
14258  switch ($prop['buttonScaleHow']) {
14259  case 'scaleHow.proportional': {
14260  $opt['mk']['if']['s'] = 'P';
14261  break;
14262  }
14263  case 'scaleHow.anamorphic': {
14264  $opt['mk']['if']['s'] = 'A';
14265  break;
14266  }
14267  }
14268  }
14269  // buttonScaleWhen: Controls when an icon is scaled to fit inside the button face.
14270  if (isset($prop['buttonScaleWhen'])) {
14271  switch ($prop['buttonScaleWhen']) {
14272  case 'scaleWhen.always': {
14273  $opt['mk']['if']['sw'] = 'A';
14274  break;
14275  }
14276  case 'scaleWhen.never': {
14277  $opt['mk']['if']['sw'] = 'N';
14278  break;
14279  }
14280  case 'scaleWhen.tooBig': {
14281  $opt['mk']['if']['sw'] = 'B';
14282  break;
14283  }
14284  case 'scaleWhen.tooSmall': {
14285  $opt['mk']['if']['sw'] = 'S';
14286  break;
14287  }
14288  }
14289  }
14290  // buttonPosition: Controls how the text and the icon of the button are positioned with respect to each other within the button face.
14291  if (isset($prop['buttonPosition'])) {
14292  switch ($prop['buttonPosition']) {
14293  case 0:
14294  case 'position.textOnly': {
14295  $opt['mk']['tp'] = 0;
14296  break;
14297  }
14298  case 1:
14299  case 'position.iconOnly': {
14300  $opt['mk']['tp'] = 1;
14301  break;
14302  }
14303  case 2:
14304  case 'position.iconTextV': {
14305  $opt['mk']['tp'] = 2;
14306  break;
14307  }
14308  case 3:
14309  case 'position.textIconV': {
14310  $opt['mk']['tp'] = 3;
14311  break;
14312  }
14313  case 4:
14314  case 'position.iconTextH': {
14315  $opt['mk']['tp'] = 4;
14316  break;
14317  }
14318  case 5:
14319  case 'position.textIconH': {
14320  $opt['mk']['tp'] = 5;
14321  break;
14322  }
14323  case 6:
14324  case 'position.overlay': {
14325  $opt['mk']['tp'] = 6;
14326  break;
14327  }
14328  }
14329  }
14330  // fillColor: Specifies the background color for a field.
14331  if (isset($prop['fillColor'])) {
14332  if (is_array($prop['fillColor'])) {
14333  $opt['mk']['bg'] = $prop['fillColor'];
14334  } else {
14335  $opt['mk']['bg'] = $this->convertHTMLColorToDec($prop['fillColor']);
14336  }
14337  }
14338  // strokeColor: Specifies the stroke color for a field that is used to stroke the rectangle of the field with a line as large as the line width.
14339  if (isset($prop['strokeColor'])) {
14340  if (is_array($prop['strokeColor'])) {
14341  $opt['mk']['bc'] = $prop['strokeColor'];
14342  } else {
14343  $opt['mk']['bc'] = $this->convertHTMLColorToDec($prop['strokeColor']);
14344  }
14345  }
14346  // rotation: The rotation of a widget in counterclockwise increments.
14347  if (isset($prop['rotation'])) {
14348  $opt['mk']['r'] = $prop['rotation'];
14349  }
14350  // charLimit: Limits the number of characters that a user can type into a text field.
14351  if (isset($prop['charLimit'])) {
14352  $opt['maxlen'] = intval($prop['charLimit']);
14353  }
14354  if (!isset($ff)) {
14355  $ff = 0;
14356  }
14357  // readonly: The read-only characteristic of a field. If a field is read-only, the user can see the field but cannot change it.
14358  if (isset($prop['readonly']) AND ($prop['readonly'] == 'true')) {
14359  $ff += 1 << 0;
14360  }
14361  // required: Specifies whether a field requires a value.
14362  if (isset($prop['required']) AND ($prop['required'] == 'true')) {
14363  $ff += 1 << 1;
14364  }
14365  // multiline: Controls how text is wrapped within the field.
14366  if (isset($prop['multiline']) AND ($prop['multiline'] == 'true')) {
14367  $ff += 1 << 12;
14368  }
14369  // password: Specifies whether the field should display asterisks when data is entered in the field.
14370  if (isset($prop['password']) AND ($prop['password'] == 'true')) {
14371  $ff += 1 << 13;
14372  }
14373  // NoToggleToOff: If set, exactly one radio button shall be selected at all times; selecting the currently selected button has no effect.
14374  if (isset($prop['NoToggleToOff']) AND ($prop['NoToggleToOff'] == 'true')) {
14375  $ff += 1 << 14;
14376  }
14377  // Radio: If set, the field is a set of radio buttons.
14378  if (isset($prop['Radio']) AND ($prop['Radio'] == 'true')) {
14379  $ff += 1 << 15;
14380  }
14381  // Pushbutton: If set, the field is a pushbutton that does not retain a permanent value.
14382  if (isset($prop['Pushbutton']) AND ($prop['Pushbutton'] == 'true')) {
14383  $ff += 1 << 16;
14384  }
14385  // Combo: If set, the field is a combo box; if clear, the field is a list box.
14386  if (isset($prop['Combo']) AND ($prop['Combo'] == 'true')) {
14387  $ff += 1 << 17;
14388  }
14389  // editable: Controls whether a combo box is editable.
14390  if (isset($prop['editable']) AND ($prop['editable'] == 'true')) {
14391  $ff += 1 << 18;
14392  }
14393  // Sort: If set, the field's option items shall be sorted alphabetically.
14394  if (isset($prop['Sort']) AND ($prop['Sort'] == 'true')) {
14395  $ff += 1 << 19;
14396  }
14397  // fileSelect: If true, sets the file-select flag in the Options tab of the text field (Field is Used for File Selection).
14398  if (isset($prop['fileSelect']) AND ($prop['fileSelect'] == 'true')) {
14399  $ff += 1 << 20;
14400  }
14401  // multipleSelection: If true, indicates that a list box allows a multiple selection of items.
14402  if (isset($prop['multipleSelection']) AND ($prop['multipleSelection'] == 'true')) {
14403  $ff += 1 << 21;
14404  }
14405  // doNotSpellCheck: If true, spell checking is not performed on this editable text field.
14406  if (isset($prop['doNotSpellCheck']) AND ($prop['doNotSpellCheck'] == 'true')) {
14407  $ff += 1 << 22;
14408  }
14409  // doNotScroll: If true, the text field does not scroll and the user, therefore, is limited by the rectangular region designed for the field.
14410  if (isset($prop['doNotScroll']) AND ($prop['doNotScroll'] == 'true')) {
14411  $ff += 1 << 23;
14412  }
14413  // comb: If set to true, the field background is drawn as series of boxes (one for each character in the value of the field) and each character of the content is drawn within those boxes. The number of boxes drawn is determined from the charLimit property. It applies only to text fields. The setter will also raise if any of the following field properties are also set multiline, password, and fileSelect. A side-effect of setting this property is that the doNotScroll property is also set.
14414  if (isset($prop['comb']) AND ($prop['comb'] == 'true')) {
14415  $ff += 1 << 24;
14416  }
14417  // radiosInUnison: If false, even if a group of radio buttons have the same name and export value, they behave in a mutually exclusive fashion, like HTML radio buttons.
14418  if (isset($prop['radiosInUnison']) AND ($prop['radiosInUnison'] == 'true')) {
14419  $ff += 1 << 25;
14420  }
14421  // richText: If true, the field allows rich text formatting.
14422  if (isset($prop['richText']) AND ($prop['richText'] == 'true')) {
14423  $ff += 1 << 25;
14424  }
14425  // commitOnSelChange: Controls whether a field value is committed after a selection change.
14426  if (isset($prop['commitOnSelChange']) AND ($prop['commitOnSelChange'] == 'true')) {
14427  $ff += 1 << 26;
14428  }
14429  $opt['ff'] = $ff;
14430  // defaultValue: The default value of a field - that is, the value that the field is set to when the form is reset.
14431  if (isset($prop['defaultValue'])) {
14432  $opt['dv'] = $prop['defaultValue'];
14433  }
14434  $f = 4; // default value for annotation flags
14435  // readonly: The read-only characteristic of a field. If a field is read-only, the user can see the field but cannot change it.
14436  if (isset($prop['readonly']) AND ($prop['readonly'] == 'true')) {
14437  $f += 1 << 6;
14438  }
14439  // display: Controls whether the field is hidden or visible on screen and in print.
14440  if (isset($prop['display'])) {
14441  if ($prop['display'] == 'display.visible') {
14442  //
14443  } elseif ($prop['display'] == 'display.hidden') {
14444  $f += 1 << 1;
14445  } elseif ($prop['display'] == 'display.noPrint') {
14446  $f -= 1 << 2;
14447  } elseif ($prop['display'] == 'display.noView') {
14448  $f += 1 << 5;
14449  }
14450  }
14451  $opt['f'] = $f;
14452  // currentValueIndices: Reads and writes single or multiple values of a list box or combo box.
14453  if (isset($prop['currentValueIndices']) AND is_array($prop['currentValueIndices'])) {
14454  $opt['i'] = $prop['currentValueIndices'];
14455  }
14456  // value: The value of the field data that the user has entered.
14457  if (isset($prop['value'])) {
14458  if (is_array($prop['value'])) {
14459  $opt['opt'] = array();
14460  foreach ($prop['value'] AS $key => $optval) {
14461  // exportValues: An array of strings representing the export values for the field.
14462  if (isset($prop['exportValues'][$key])) {
14463  $opt['opt'][$key] = array($prop['exportValues'][$key], $prop['value'][$key]);
14464  } else {
14465  $opt['opt'][$key] = $prop['value'][$key];
14466  }
14467  }
14468  } else {
14469  $opt['v'] = $prop['value'];
14470  }
14471  }
14472  // richValue: This property specifies the text contents and formatting of a rich text field.
14473  if (isset($prop['richValue'])) {
14474  $opt['rv'] = $prop['richValue'];
14475  }
14476  // submitName: If nonempty, used during form submission instead of name. Only applicable if submitting in HTML format (that is, URL-encoded).
14477  if (isset($prop['submitName'])) {
14478  $opt['tm'] = $prop['submitName'];
14479  }
14480  // name: Fully qualified field name.
14481  if (isset($prop['name'])) {
14482  $opt['t'] = $prop['name'];
14483  }
14484  // userName: The user name (short description string) of the field.
14485  if (isset($prop['userName'])) {
14486  $opt['tu'] = $prop['userName'];
14487  }
14488  // highlight: Defines how a button reacts when a user clicks it.
14489  if (isset($prop['highlight'])) {
14490  switch ($prop['highlight']) {
14491  case 'none':
14492  case 'highlight.n': {
14493  $opt['h'] = 'N';
14494  break;
14495  }
14496  case 'invert':
14497  case 'highlight.i': {
14498  $opt['h'] = 'i';
14499  break;
14500  }
14501  case 'push':
14502  case 'highlight.p': {
14503  $opt['h'] = 'P';
14504  break;
14505  }
14506  case 'outline':
14507  case 'highlight.o': {
14508  $opt['h'] = 'O';
14509  break;
14510  }
14511  }
14512  }
14513  // Unsupported options:
14514  // - calcOrderIndex: Changes the calculation order of fields in the document.
14515  // - delay: Delays the redrawing of a field's appearance.
14516  // - defaultStyle: This property defines the default style attributes for the form field.
14517  // - style: Allows the user to set the glyph style of a check box or radio button.
14518  // - textColor, textFont, textSize
14519  return $opt;
14520  }
14521 
14529  public function setFormDefaultProp($prop=array()) {
14530  $this->default_form_prop = $prop;
14531  }
14532 
14540  public function getFormDefaultProp() {
14541  return $this->default_form_prop;
14542  }
14543 
14558  public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
14559  if ($x === '') {
14560  $x = $this->x;
14561  }
14562  if ($y === '') {
14563  $y = $this->y;
14564  }
14565  // check page for no-write regions and adapt page margins if necessary
14566  $this->checkPageRegions($h, $x, $y);
14567  if ($js) {
14568  $this->_addfield('text', $name, $x, $y, $w, $h, $prop);
14569  return;
14570  }
14571  // get default style
14572  $prop = array_merge($this->getFormDefaultProp(), $prop);
14573  // get annotation data
14574  $popt = $this->getAnnotOptFromJSProp($prop);
14575  // set default appearance stream
14576  $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
14577  $fontstyle = sprintf('/F%d %.2F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
14578  $popt['da'] = $fontstyle;
14579  $popt['ap'] = array();
14580  $popt['ap']['n'] = 'q BT '.$fontstyle.' ET Q';
14581  // merge options
14582  $opt = array_merge($popt, $opt);
14583  // remove some conflicting options
14584  unset($opt['bs']);
14585  // set remaining annotation data
14586  $opt['Subtype'] = 'Widget';
14587  $opt['ft'] = 'Tx';
14588  $opt['t'] = $name;
14589  /*
14590  Additional annotation's parameters (check _putannotsobj() method):
14591  //$opt['f']
14592  //$opt['ap']
14593  //$opt['as']
14594  //$opt['bs']
14595  //$opt['be']
14596  //$opt['c']
14597  //$opt['border']
14598  //$opt['h']
14599  //$opt['mk']
14600  //$opt['mk']['r']
14601  //$opt['mk']['bc']
14602  //$opt['mk']['bg']
14603  //$opt['mk']['ca']
14604  //$opt['mk']['rc']
14605  //$opt['mk']['ac']
14606  //$opt['mk']['i']
14607  //$opt['mk']['ri']
14608  //$opt['mk']['ix']
14609  //$opt['mk']['if']
14610  //$opt['mk']['if']['sw']
14611  //$opt['mk']['if']['s']
14612  //$opt['mk']['if']['a']
14613  //$opt['mk']['if']['fb']
14614  //$opt['mk']['tp']
14615  //$opt['tu']
14616  //$opt['tm']
14617  //$opt['ff']
14618  //$opt['v']
14619  //$opt['dv']
14620  //$opt['a']
14621  //$opt['aa']
14622  //$opt['q']
14623  */
14624  $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
14625  if ($this->rtl) {
14626  $this->x -= $w;
14627  } else {
14628  $this->x += $w;
14629  }
14630  }
14631 
14647  public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
14648  if ($x === '') {
14649  $x = $this->x;
14650  }
14651  if ($y === '') {
14652  $y = $this->y;
14653  }
14654  // check page for no-write regions and adapt page margins if necessary
14655  $this->checkPageRegions($w, $x, $y);
14656  if ($js) {
14657  $this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
14658  return;
14659  }
14660  if ($this->empty_string($onvalue)) {
14661  $onvalue = 'On';
14662  }
14663  if ($checked) {
14664  $defval = $onvalue;
14665  } else {
14666  $defval = 'Off';
14667  }
14668  // set data for parent group
14669  if (!isset($this->radiobutton_groups[$this->page])) {
14670  $this->radiobutton_groups[$this->page] = array();
14671  }
14672  if (!isset($this->radiobutton_groups[$this->page][$name])) {
14673  $this->radiobutton_groups[$this->page][$name] = array();
14674  ++$this->n;
14675  $this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
14676  $this->radio_groups[] = $this->n;
14677  $kid = ($this->n + 2);
14678  } else {
14679  $kid = ($this->n + 1);
14680  }
14681  // save object ID to be added on Kids entry on parent object
14682  $this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
14683  // get default style
14684  $prop = array_merge($this->getFormDefaultProp(), $prop);
14685  $prop['NoToggleToOff'] = 'true';
14686  $prop['Radio'] = 'true';
14687  $prop['borderStyle'] = 'inset';
14688  // get annotation data
14689  $popt = $this->getAnnotOptFromJSProp($prop);
14690  // set additional default values
14691  $font = 'zapfdingbats';
14692  $this->AddFont($font);
14693  $tmpfont = $this->getFontBuffer($font);
14694  $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
14695  $fontstyle = sprintf('/F%d %.2F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
14696  $popt['da'] = $fontstyle;
14697  $popt['ap'] = array();
14698  $popt['ap']['n'] = array();
14699  $popt['ap']['n'][$onvalue] = 'q BT '.$fontstyle.' 0 0 Td (8) Tj ET Q';
14700  $popt['ap']['n']['Off'] = 'q BT '.$fontstyle.' 0 0 Td (8) Tj ET Q';
14701  if (!isset($popt['mk'])) {
14702  $popt['mk'] = array();
14703  }
14704  $popt['mk']['ca'] = '(l)';
14705  // merge options
14706  $opt = array_merge($popt, $opt);
14707  // set remaining annotation data
14708  $opt['Subtype'] = 'Widget';
14709  $opt['ft'] = 'Btn';
14710  if ($checked) {
14711  $opt['v'] = array('/'.$onvalue);
14712  $opt['as'] = $onvalue;
14713  } else {
14714  $opt['as'] = 'Off';
14715  }
14716  $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
14717  if ($this->rtl) {
14718  $this->x -= $w;
14719  } else {
14720  $this->x += $w;
14721  }
14722  }
14723 
14739  public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
14740  if ($x === '') {
14741  $x = $this->x;
14742  }
14743  if ($y === '') {
14744  $y = $this->y;
14745  }
14746  // check page for no-write regions and adapt page margins if necessary
14747  $this->checkPageRegions($h, $x, $y);
14748  if ($js) {
14749  $this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
14750  $s = '';
14751  foreach ($values as $value) {
14752  $s .= "'".addslashes($value)."',";
14753  }
14754  $this->javascript .= 'f'.$name.'.setItems(['.substr($s, 0, -1)."]);\n";
14755  return;
14756  }
14757  // get default style
14758  $prop = array_merge($this->getFormDefaultProp(), $prop);
14759  // get annotation data
14760  $popt = $this->getAnnotOptFromJSProp($prop);
14761  // set additional default values
14762  $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
14763  $fontstyle = sprintf('/F%d %.2F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
14764  $popt['da'] = $fontstyle;
14765  $popt['ap'] = array();
14766  $popt['ap']['n'] = 'q BT '.$fontstyle.' ET Q';
14767  // merge options
14768  $opt = array_merge($popt, $opt);
14769  // set remaining annotation data
14770  $opt['Subtype'] = 'Widget';
14771  $opt['ft'] = 'Ch';
14772  $opt['t'] = $name;
14773  $opt['opt'] = $values;
14774  $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
14775  if ($this->rtl) {
14776  $this->x -= $w;
14777  } else {
14778  $this->x += $w;
14779  }
14780  }
14781 
14797  public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
14798  if ($x === '') {
14799  $x = $this->x;
14800  }
14801  if ($y === '') {
14802  $y = $this->y;
14803  }
14804  // check page for no-write regions and adapt page margins if necessary
14805  $this->checkPageRegions($h, $x, $y);
14806  if ($js) {
14807  $this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
14808  $s = '';
14809  foreach ($values as $value) {
14810  $s .= "'".addslashes($value)."',";
14811  }
14812  $this->javascript .= 'f'.$name.'.setItems(['.substr($s, 0, -1)."]);\n";
14813  return;
14814  }
14815  // get default style
14816  $prop = array_merge($this->getFormDefaultProp(), $prop);
14817  $prop['Combo'] = true;
14818  // get annotation data
14819  $popt = $this->getAnnotOptFromJSProp($prop);
14820  // set additional default options
14821  $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
14822  $fontstyle = sprintf('/F%d %.2F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
14823  $popt['da'] = $fontstyle;
14824  $popt['ap'] = array();
14825  $popt['ap']['n'] = 'q BT '.$fontstyle.' ET Q';
14826  // merge options
14827  $opt = array_merge($popt, $opt);
14828  // set remaining annotation data
14829  $opt['Subtype'] = 'Widget';
14830  $opt['ft'] = 'Ch';
14831  $opt['t'] = $name;
14832  $opt['opt'] = $values;
14833  $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
14834  if ($this->rtl) {
14835  $this->x -= $w;
14836  } else {
14837  $this->x += $w;
14838  }
14839  }
14840 
14856  public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
14857  if ($x === '') {
14858  $x = $this->x;
14859  }
14860  if ($y === '') {
14861  $y = $this->y;
14862  }
14863  // check page for no-write regions and adapt page margins if necessary
14864  $this->checkPageRegions($w, $x, $y);
14865  if ($js) {
14866  $this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
14867  return;
14868  }
14869  if (!isset($prop['value'])) {
14870  $prop['value'] = array('Yes');
14871  }
14872  // get default style
14873  $prop = array_merge($this->getFormDefaultProp(), $prop);
14874  $prop['borderStyle'] = 'inset';
14875  // get annotation data
14876  $popt = $this->getAnnotOptFromJSProp($prop);
14877  // set additional default options
14878  $font = 'zapfdingbats';
14879  $this->AddFont($font);
14880  $tmpfont = $this->getFontBuffer($font);
14881  $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
14882  $fontstyle = sprintf('/F%d %.2F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
14883  $popt['da'] = $fontstyle;
14884  $popt['ap'] = array();
14885  $popt['ap']['n'] = array();
14886  $popt['ap']['n']['Yes'] = 'q BT '.$fontstyle.' 0 0 Td (8) Tj ET Q';
14887  $popt['ap']['n']['Off'] = 'q BT '.$fontstyle.' 0 0 Td (8) Tj ET Q';
14888  // merge options
14889  $opt = array_merge($popt, $opt);
14890  // set remaining annotation data
14891  $opt['Subtype'] = 'Widget';
14892  $opt['ft'] = 'Btn';
14893  $opt['t'] = $name;
14894  $opt['opt'] = array($onvalue);
14895  if ($checked) {
14896  $opt['v'] = array('/0');
14897  $opt['as'] = 'Yes';
14898  } else {
14899  $opt['v'] = array('/Off');
14900  $opt['as'] = 'Off';
14901  }
14902  $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
14903  if ($this->rtl) {
14904  $this->x -= $w;
14905  } else {
14906  $this->x += $w;
14907  }
14908  }
14909 
14926  public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
14927  if ($x === '') {
14928  $x = $this->x;
14929  }
14930  if ($y === '') {
14931  $y = $this->y;
14932  }
14933  // check page for no-write regions and adapt page margins if necessary
14934  $this->checkPageRegions($h, $x, $y);
14935  if ($js) {
14936  $this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
14937  $this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
14938  $this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
14939  $this->javascript .= 'f'.$name.".highlight='push';\n";
14940  $this->javascript .= 'f'.$name.".print=false;\n";
14941  return;
14942  }
14943  // get default style
14944  $prop = array_merge($this->getFormDefaultProp(), $prop);
14945  $prop['Pushbutton'] = 'true';
14946  $prop['highlight'] = 'push';
14947  $prop['display'] = 'display.noPrint';
14948  // get annotation data
14949  $popt = $this->getAnnotOptFromJSProp($prop);
14950  $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
14951  $fontstyle = sprintf('/F%d %.2F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
14952  $popt['da'] = $fontstyle;
14953  $popt['ap'] = array();
14954  $popt['ap']['n'] = 'q BT '.$fontstyle.' ET Q';
14955  // set additional default options
14956  if (!isset($popt['mk'])) {
14957  $popt['mk'] = array();
14958  }
14959  $ann_obj_id = ($this->n + 1);
14960  if (!empty($action) AND !is_array($action)) {
14961  $ann_obj_id = ($this->n + 2);
14962  }
14963  $popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
14964  $popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
14965  $popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
14966  // merge options
14967  $opt = array_merge($popt, $opt);
14968  // set remaining annotation data
14969  $opt['Subtype'] = 'Widget';
14970  $opt['ft'] = 'Btn';
14971  $opt['t'] = $caption;
14972  $opt['v'] = $name;
14973  if (!empty($action)) {
14974  if (is_array($action)) {
14975  // form action options as on section 12.7.5 of PDF32000_2008.
14976  $opt['aa'] = '/D <<';
14977  $bmode = array('SubmitForm', 'ResetForm', 'ImportData');
14978  foreach ($action AS $key => $val) {
14979  if (($key == 'S') AND in_array($val, $bmode)) {
14980  $opt['aa'] .= ' /S /'.$val;
14981  } elseif (($key == 'F') AND (!empty($val))) {
14982  $opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
14983  } elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
14984  $opt['aa'] .= ' /Fields [';
14985  foreach ($val AS $field) {
14986  $opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
14987  }
14988  $opt['aa'] .= ']';
14989  } elseif (($key == 'Flags')) {
14990  $ff = 0;
14991  if (is_array($val)) {
14992  foreach ($val AS $flag) {
14993  switch ($flag) {
14994  case 'Include/Exclude': {
14995  $ff += 1 << 0;
14996  break;
14997  }
14998  case 'IncludeNoValueFields': {
14999  $ff += 1 << 1;
15000  break;
15001  }
15002  case 'ExportFormat': {
15003  $ff += 1 << 2;
15004  break;
15005  }
15006  case 'GetMethod': {
15007  $ff += 1 << 3;
15008  break;
15009  }
15010  case 'SubmitCoordinates': {
15011  $ff += 1 << 4;
15012  break;
15013  }
15014  case 'XFDF': {
15015  $ff += 1 << 5;
15016  break;
15017  }
15018  case 'IncludeAppendSaves': {
15019  $ff += 1 << 6;
15020  break;
15021  }
15022  case 'IncludeAnnotations': {
15023  $ff += 1 << 7;
15024  break;
15025  }
15026  case 'SubmitPDF': {
15027  $ff += 1 << 8;
15028  break;
15029  }
15030  case 'CanonicalFormat': {
15031  $ff += 1 << 9;
15032  break;
15033  }
15034  case 'ExclNonUserAnnots': {
15035  $ff += 1 << 10;
15036  break;
15037  }
15038  case 'ExclFKey': {
15039  $ff += 1 << 11;
15040  break;
15041  }
15042  case 'EmbedForm': {
15043  $ff += 1 << 13;
15044  break;
15045  }
15046  }
15047  }
15048  } else {
15049  $ff = intval($val);
15050  }
15051  $opt['aa'] .= ' /Flags '.$ff;
15052  }
15053  }
15054  $opt['aa'] .= ' >>';
15055  } else {
15056  // Javascript action or raw action command
15057  $js_obj_id = $this->addJavascriptObject($action);
15058  $opt['aa'] = '/D '.$js_obj_id.' 0 R';
15059  }
15060  }
15061  $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
15062  if ($this->rtl) {
15063  $this->x -= $w;
15064  } else {
15065  $this->x += $w;
15066  }
15067  }
15068 
15069  // --- END FORMS FIELDS ------------------------------------------------
15070 
15078  protected function _putsignature() {
15079  if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
15080  return;
15081  }
15082  $out = $this->_getobj($this->sig_obj_id + 1)."\n";
15083  $out .= '<< /Type /Sig';
15084  $out .= ' /Filter /Adobe.PPKLite';
15085  $out .= ' /SubFilter /adbe.pkcs7.detached';
15086  $out .= ' '.$this->byterange_string;
15087  $out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
15088  $out .= ' /Reference ['; // array of signature reference dictionaries
15089  $out .= ' << /Type /SigRef';
15090  if ($this->signature_data['cert_type'] > 0) {
15091  $out .= ' /TransformMethod /DocMDP';
15092  $out .= ' /TransformParams <<';
15093  $out .= ' /Type /TransformParams';
15094  $out .= ' /V /1.2';
15095  $out .= ' /P '.$this->signature_data['cert_type'];
15096  } else {
15097  $out .= ' /TransformMethod /UR3';
15098  $out .= ' /TransformParams <<';
15099  $out .= ' /Type /TransformParams';
15100  $out .= ' /V /2.2';
15101  if (!$this->empty_string($this->ur['document'])) {
15102  $out .= ' /Document['.$this->ur['document'].']';
15103  }
15104  if (!$this->empty_string($this->ur['form'])) {
15105  $out .= ' /Form['.$this->ur['form'].']';
15106  }
15107  if (!$this->empty_string($this->ur['signature'])) {
15108  $out .= ' /Signature['.$this->ur['signature'].']';
15109  }
15110  if (!$this->empty_string($this->ur['annots'])) {
15111  $out .= ' /Annots['.$this->ur['annots'].']';
15112  }
15113  if (!$this->empty_string($this->ur['ef'])) {
15114  $out .= ' /EF['.$this->ur['ef'].']';
15115  }
15116  if (!$this->empty_string($this->ur['formex'])) {
15117  $out .= ' /FormEX['.$this->ur['formex'].']';
15118  }
15119  }
15120  $out .= ' >>'; // close TransformParams
15121  // optional digest data (values must be calculated and replaced later)
15122  //$out .= ' /Data ********** 0 R';
15123  //$out .= ' /DigestMethod/MD5';
15124  //$out .= ' /DigestLocation[********** 34]';
15125  //$out .= ' /DigestValue<********************************>';
15126  $out .= ' >>';
15127  $out .= ' ]'; // end of reference
15128  if (isset($this->signature_data['info']['Name']) AND !$this->empty_string($this->signature_data['info']['Name'])) {
15129  $out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name']);
15130  }
15131  if (isset($this->signature_data['info']['Location']) AND !$this->empty_string($this->signature_data['info']['Location'])) {
15132  $out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location']);
15133  }
15134  if (isset($this->signature_data['info']['Reason']) AND !$this->empty_string($this->signature_data['info']['Reason'])) {
15135  $out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason']);
15136  }
15137  if (isset($this->signature_data['info']['ContactInfo']) AND !$this->empty_string($this->signature_data['info']['ContactInfo'])) {
15138  $out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo']);
15139  }
15140  $out .= ' /M '.$this->_datestring();
15141  $out .= ' >>';
15142  $out .= "\n".'endobj';
15143  $this->_out($out);
15144  }
15145 
15163  public function setUserRights(
15164  $enable=true,
15165  $document='/FullSave',
15166  $annots='/Create/Delete/Modify/Copy/Import/Export',
15167  $form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
15168  $signature='/Modify',
15169  $ef='/Create/Delete/Modify/Import',
15170  $formex='') {
15171  $this->ur['enabled'] = $enable;
15172  $this->ur['document'] = $document;
15173  $this->ur['annots'] = $annots;
15174  $this->ur['form'] = $form;
15175  $this->ur['signature'] = $signature;
15176  $this->ur['ef'] = $ef;
15177  $this->ur['formex'] = $formex;
15178  if (!$this->sign) {
15179  $this->setSignature('', '', '', '', 0, array());
15180  }
15181  }
15182 
15199  public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array()) {
15200  // to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
15201  // to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
15202  // to convert pfx certificate to pem: openssl
15203  // OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
15204  $this->sign = true;
15205  ++$this->n;
15206  $this->sig_obj_id = $this->n; // signature widget
15207  ++$this->n; // signature object ($this->sig_obj_id + 1)
15208  $this->signature_data = array();
15209  if (strlen($signing_cert) == 0) {
15210  $signing_cert = 'file://'.dirname(__FILE__).'/tcpdf.crt';
15211  $private_key_password = 'tcpdfdemo';
15212  }
15213  if (strlen($private_key) == 0) {
15214  $private_key = $signing_cert;
15215  }
15216  $this->signature_data['signcert'] = $signing_cert;
15217  $this->signature_data['privkey'] = $private_key;
15218  $this->signature_data['password'] = $private_key_password;
15219  $this->signature_data['extracerts'] = $extracerts;
15220  $this->signature_data['cert_type'] = $cert_type;
15221  $this->signature_data['info'] = $info;
15222  }
15223 
15235  public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1) {
15236  if (($page < 1) OR ($page > $this->numpages)) {
15237  $this->signature_appearance['page'] = $this->page;
15238  } else {
15239  $this->signature_appearance['page'] = intval($page);
15240  }
15241  $a = $x * $this->k;
15242  $b = $this->pagedim[($this->signature_appearance['page'])]['h'] - (($y + $h) * $this->k);
15243  $c = $w * $this->k;
15244  $d = $h * $this->k;
15245  $this->signature_appearance['rect'] = sprintf('%.2F %.2F %.2F %.2F', $a, $b, $a+$c, $b+$d);
15246  }
15247 
15255  public function startPageGroup($page='') {
15256  if (empty($page)) {
15257  $page = $this->page + 1;
15258  }
15259  $this->newpagegroup[$page] = true;
15260  }
15261 
15270  public function AliasNbPages($alias='{nb}') {
15271  $this->AliasNbPages = $alias;
15272  }
15273 
15282  public function getAliasNbPages() {
15283  if ($this->isUnicodeFont()) {
15284  return '{'.$this->AliasNbPages.'}';
15285  }
15286  return $this->AliasNbPages;
15287  }
15288 
15297  public function AliasNumPage($alias='{pnb}') {
15298  //Define an alias for total number of pages
15299  $this->AliasNumPage = $alias;
15300  }
15301 
15310  public function getAliasNumPage() {
15311  if ($this->isUnicodeFont()) {
15312  return '{'.$this->AliasNumPage.'}';
15313  }
15314  return $this->AliasNumPage;
15315  }
15316 
15323  public function getGroupPageNo() {
15324  return $this->pagegroups[$this->currpagegroup];
15325  }
15326 
15333  public function getGroupPageNoFormatted() {
15334  return $this->formatPageNumber($this->getGroupPageNo());
15335  }
15336 
15345  public function getPageGroupAlias() {
15346  if ($this->isUnicodeFont()) {
15347  return '{'.$this->currpagegroup.'}';
15348  }
15349  return $this->currpagegroup;
15350  }
15351 
15360  public function getPageNumGroupAlias() {
15361  if ($this->isUnicodeFont()) {
15362  return '{'.str_replace('{nb', '{pnb', $this->currpagegroup).'}';
15363  }
15364  return str_replace('{nb', '{pnb', $this->currpagegroup);
15365  }
15366 
15374  protected function formatPageNumber($num) {
15375  return number_format((float)$num, 0, '', '.');
15376  }
15377 
15386  protected function formatTOCPageNumber($num) {
15387  return number_format((float)$num, 0, '', '.');
15388  }
15389 
15396  public function PageNoFormatted() {
15397  return $this->formatPageNumber($this->PageNo());
15398  }
15399 
15405  protected function _putocg() {
15406  $this->n_ocg_print = $this->_newobj();
15407  $this->_out('<< /Type /OCG /Name '.$this->_textstring('print', $this->n_ocg_print).' /Usage << /Print <</PrintState /ON>> /View <</ViewState /OFF>> >> >>'."\n".'endobj');
15408  $this->n_ocg_view = $this->_newobj();
15409  $this->_out('<< /Type /OCG /Name '.$this->_textstring('view', $this->n_ocg_view).' /Usage << /Print <</PrintState /OFF>> /View <</ViewState /ON>> >> >>'."\n".'endobj');
15410  }
15411 
15420  public function setVisibility($v) {
15421  if ($this->openMarkedContent) {
15422  // close existing open marked-content
15423  $this->_out('EMC');
15424  $this->openMarkedContent = false;
15425  }
15426  switch($v) {
15427  case 'print': {
15428  $this->_out('/OC /OC1 BDC');
15429  $this->openMarkedContent = true;
15430  break;
15431  }
15432  case 'screen': {
15433  $this->_out('/OC /OC2 BDC');
15434  $this->openMarkedContent = true;
15435  break;
15436  }
15437  case 'all': {
15438  $this->_out('');
15439  break;
15440  }
15441  default: {
15442  $this->Error('Incorrect visibility: '.$v);
15443  break;
15444  }
15445  }
15446  $this->visibility = $v;
15447  }
15448 
15456  protected function addExtGState($parms) {
15457  $n = count($this->extgstates) + 1;
15458  // check if this ExtGState already exist
15459  for ($i = 1; $i < $n; ++$i) {
15460  if ($this->extgstates[$i]['parms'] == $parms) {
15461  // return reference to existing ExtGState
15462  return $i;
15463  }
15464  }
15465  $this->extgstates[$n]['parms'] = $parms;
15466  return $n;
15467  }
15468 
15475  protected function setExtGState($gs) {
15476  $this->_out(sprintf('/GS%d gs', $gs));
15477  }
15478 
15485  protected function _putextgstates() {
15486  $ne = count($this->extgstates);
15487  for ($i = 1; $i <= $ne; ++$i) {
15488  $this->extgstates[$i]['n'] = $this->_newobj();
15489  $out = '<< /Type /ExtGState';
15490  foreach ($this->extgstates[$i]['parms'] as $k => $v) {
15491  if (is_float($v)) {
15492  $v = sprintf('%.2F', $v);
15493  }
15494  $out .= ' /'.$k.' '.$v;
15495  }
15496  $out .= ' >>';
15497  $out .= "\n".'endobj';
15498  $this->_out($out);
15499  }
15500  }
15501 
15509  public function setAlpha($alpha, $bm='Normal') {
15510  $gs = $this->addExtGState(array('ca' => $alpha, 'CA' => $alpha, 'BM' => '/'.$bm, 'AIS' => 'false'));
15511  $this->setExtGState($gs);
15512  }
15513 
15520  public function setJPEGQuality($quality) {
15521  if (($quality < 1) OR ($quality > 100)) {
15522  $quality = 75;
15523  }
15524  $this->jpeg_quality = intval($quality);
15525  }
15526 
15533  public function setDefaultTableColumns($cols=4) {
15534  $this->default_table_columns = intval($cols);
15535  }
15536 
15543  public function setCellHeightRatio($h) {
15544  $this->cell_height_ratio = $h;
15545  }
15546 
15552  public function getCellHeightRatio() {
15553  return $this->cell_height_ratio;
15554  }
15555 
15562  public function setPDFVersion($version='1.7') {
15563  $this->PDFVersion = $version;
15564  }
15565 
15575  public function setViewerPreferences($preferences) {
15576  $this->viewer_preferences = $preferences;
15577  }
15578 
15592  public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
15593  $bars = explode(',', $colors);
15594  $numbars = count($bars); // number of bars to print
15595  // set bar measures
15596  if ($vertical) {
15597  $coords = array(0, 0, 0, 1);
15598  $wb = $w / $numbars; // bar width
15599  $hb = $h; // bar height
15600  $xd = $wb; // delta x
15601  $yd = 0; // delta y
15602  } else {
15603  $coords = array(1, 0, 0, 0);
15604  $wb = $w; // bar width
15605  $hb = $h / $numbars; // bar height
15606  $xd = 0; // delta x
15607  $yd = $hb; // delta y
15608  }
15609  $xb = $x;
15610  $yb = $y;
15611  foreach ($bars as $col) {
15612  switch ($col) {
15613  // set transition colors
15614  case 'A': { // BLACK
15615  $col_a = array(255);
15616  $col_b = array(0);
15617  break;
15618  }
15619  case 'W': { // WHITE
15620  $col_a = array(0);
15621  $col_b = array(255);
15622  break;
15623  }
15624  case 'R': { // R
15625  $col_a = array(255,255,255);
15626  $col_b = array(255,0,0);
15627  break;
15628  }
15629  case 'G': { // G
15630  $col_a = array(255,255,255);
15631  $col_b = array(0,255,0);
15632  break;
15633  }
15634  case 'B': { // B
15635  $col_a = array(255,255,255);
15636  $col_b = array(0,0,255);
15637  break;
15638  }
15639  case 'C': { // C
15640  $col_a = array(0,0,0,0);
15641  $col_b = array(100,0,0,0);
15642  break;
15643  }
15644  case 'M': { // M
15645  $col_a = array(0,0,0,0);
15646  $col_b = array(0,100,0,0);
15647  break;
15648  }
15649  case 'Y': { // Y
15650  $col_a = array(0,0,0,0);
15651  $col_b = array(0,0,100,0);
15652  break;
15653  }
15654  case 'K': { // K
15655  $col_a = array(0,0,0,0);
15656  $col_b = array(0,0,0,100);
15657  break;
15658  }
15659  default: { // GRAY
15660  $col_a = array(255);
15661  $col_b = array(0);
15662  break;
15663  }
15664  }
15665  if ($transition) {
15666  // color gradient
15667  $this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
15668  } else {
15669  // color rectangle
15670  $this->SetFillColorArray($col_b);
15671  $this->Rect($xb, $yb, $wb, $hb, 'F', array());
15672  }
15673  $xb += $xd;
15674  $yb += $yd;
15675  }
15676  }
15677 
15690  public function cropMark($x, $y, $w, $h, $type='A,B,C,D', $color=array(0,0,0)) {
15691  $this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
15692  $crops = explode(',', $type);
15693  $numcrops = count($crops); // number of crop marks to print
15694  $dw = $w / 4; // horizontal space to leave before the intersection point
15695  $dh = $h / 4; // vertical space to leave before the intersection point
15696  foreach ($crops as $crop) {
15697  switch ($crop) {
15698  case 'A': {
15699  $x1 = $x;
15700  $y1 = $y - $h;
15701  $x2 = $x;
15702  $y2 = $y - $dh;
15703  $x3 = $x - $w;
15704  $y3 = $y;
15705  $x4 = $x - $dw;
15706  $y4 = $y;
15707  break;
15708  }
15709  case 'B': {
15710  $x1 = $x;
15711  $y1 = $y - $h;
15712  $x2 = $x;
15713  $y2 = $y - $dh;
15714  $x3 = $x + $dw;
15715  $y3 = $y;
15716  $x4 = $x + $w;
15717  $y4 = $y;
15718  break;
15719  }
15720  case 'C': {
15721  $x1 = $x - $w;
15722  $y1 = $y;
15723  $x2 = $x - $dw;
15724  $y2 = $y;
15725  $x3 = $x;
15726  $y3 = $y + $dh;
15727  $x4 = $x;
15728  $y4 = $y + $h;
15729  break;
15730  }
15731  case 'D': {
15732  $x1 = $x + $dw;
15733  $y1 = $y;
15734  $x2 = $x + $w;
15735  $y2 = $y;
15736  $x3 = $x;
15737  $y3 = $y + $dh;
15738  $x4 = $x;
15739  $y4 = $y + $h;
15740  break;
15741  }
15742  }
15743  $this->Line($x1, $y1, $x2, $y2);
15744  $this->Line($x3, $y3, $x4, $y4);
15745  }
15746  }
15747 
15760  public function registrationMark($x, $y, $r, $double=false, $cola=array(0,0,0), $colb=array(255,255,255)) {
15761  $line_style = array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
15762  $this->SetFillColorArray($cola);
15763  $this->PieSector($x, $y, $r, 90, 180, 'F');
15764  $this->PieSector($x, $y, $r, 270, 360, 'F');
15765  $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
15766  if ($double) {
15767  $r2 = $r * 0.5;
15768  $this->SetFillColorArray($colb);
15769  $this->PieSector($x, $y, $r2, 90, 180, 'F');
15770  $this->PieSector($x, $y, $r2, 270, 360, 'F');
15771  $this->SetFillColorArray($cola);
15772  $this->PieSector($x, $y, $r2, 0, 90, 'F');
15773  $this->PieSector($x, $y, $r2, 180, 270, 'F');
15774  $this->Circle($x, $y, $r2, 0, 360, 'C', $line_style, array(), 8);
15775  }
15776  }
15777 
15791  public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
15792  $this->Clip($x, $y, $w, $h);
15793  $this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
15794  }
15795 
15809  public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
15810  $this->Clip($x, $y, $w, $h);
15811  $this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
15812  }
15813 
15832  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) {
15833  $this->Clip($x, $y, $w, $h);
15834  $n = count($this->gradients) + 1;
15835  $this->gradients[$n] = array();
15836  $this->gradients[$n]['type'] = 6; //coons patch mesh
15837  $this->gradients[$n]['coords'] = array();
15838  $this->gradients[$n]['antialias'] = $antialias;
15839  $this->gradients[$n]['colors'] = array();
15840  $this->gradients[$n]['transparency'] = false;
15841  //check the coords array if it is the simple array or the multi patch array
15842  if (!isset($coords[0]['f'])) {
15843  //simple array -> convert to multi patch array
15844  if (!isset($col1[1])) {
15845  $col1[1] = $col1[2] = $col1[0];
15846  }
15847  if (!isset($col2[1])) {
15848  $col2[1] = $col2[2] = $col2[0];
15849  }
15850  if (!isset($col3[1])) {
15851  $col3[1] = $col3[2] = $col3[0];
15852  }
15853  if (!isset($col4[1])) {
15854  $col4[1] = $col4[2] = $col4[0];
15855  }
15856  $patch_array[0]['f'] = 0;
15857  $patch_array[0]['points'] = $coords;
15858  $patch_array[0]['colors'][0]['r'] = $col1[0];
15859  $patch_array[0]['colors'][0]['g'] = $col1[1];
15860  $patch_array[0]['colors'][0]['b'] = $col1[2];
15861  $patch_array[0]['colors'][1]['r'] = $col2[0];
15862  $patch_array[0]['colors'][1]['g'] = $col2[1];
15863  $patch_array[0]['colors'][1]['b'] = $col2[2];
15864  $patch_array[0]['colors'][2]['r'] = $col3[0];
15865  $patch_array[0]['colors'][2]['g'] = $col3[1];
15866  $patch_array[0]['colors'][2]['b'] = $col3[2];
15867  $patch_array[0]['colors'][3]['r'] = $col4[0];
15868  $patch_array[0]['colors'][3]['g'] = $col4[1];
15869  $patch_array[0]['colors'][3]['b'] = $col4[2];
15870  } else {
15871  //multi patch array
15872  $patch_array = $coords;
15873  }
15874  $bpcd = 65535; //16 bits per coordinate
15875  //build the data stream
15876  $this->gradients[$n]['stream'] = '';
15877  $count_patch = count($patch_array);
15878  for ($i=0; $i < $count_patch; ++$i) {
15879  $this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
15880  $count_points = count($patch_array[$i]['points']);
15881  for ($j=0; $j < $count_points; ++$j) {
15882  //each point as 16 bit
15883  $patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
15884  if ($patch_array[$i]['points'][$j] < 0) {
15885  $patch_array[$i]['points'][$j] = 0;
15886  }
15887  if ($patch_array[$i]['points'][$j] > $bpcd) {
15888  $patch_array[$i]['points'][$j] = $bpcd;
15889  }
15890  $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
15891  $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
15892  }
15893  $count_cols = count($patch_array[$i]['colors']);
15894  for ($j=0; $j < $count_cols; ++$j) {
15895  //each color component as 8 bit
15896  $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
15897  $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
15898  $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
15899  }
15900  }
15901  //paint the gradient
15902  $this->_out('/Sh'.$n.' sh');
15903  //restore previous Graphic State
15904  $this->_out('Q');
15905  }
15906 
15917  protected function Clip($x, $y, $w, $h) {
15918  if ($this->rtl) {
15919  $x = $this->w - $x - $w;
15920  }
15921  //save current Graphic State
15922  $s = 'q';
15923  //set clipping area
15924  $s .= sprintf(' %.2F %.2F %.2F %.2F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
15925  //set up transformation matrix for gradient
15926  $s .= sprintf(' %.3F 0 0 %.3F %.3F %.3F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
15927  $this->_out($s);
15928  }
15929 
15941  public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
15942  $n = count($this->gradients) + 1;
15943  $this->gradients[$n] = array();
15944  $this->gradients[$n]['type'] = $type;
15945  $this->gradients[$n]['coords'] = $coords;
15946  $this->gradients[$n]['antialias'] = $antialias;
15947  $this->gradients[$n]['colors'] = array();
15948  $this->gradients[$n]['transparency'] = false;
15949  // color space
15950  $numcolspace = count($stops[0]['color']);
15951  $bcolor = array_values($background);
15952  switch($numcolspace) {
15953  case 4: { // CMYK
15954  $this->gradients[$n]['colspace'] = 'DeviceCMYK';
15955  if (!empty($background)) {
15956  $this->gradients[$n]['background'] = sprintf('%.3F %.3F %.3F %.3F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
15957  }
15958  break;
15959  }
15960  case 3: { // RGB
15961  $this->gradients[$n]['colspace'] = 'DeviceRGB';
15962  if (!empty($background)) {
15963  $this->gradients[$n]['background'] = sprintf('%.3F %.3F %.3F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
15964  }
15965  break;
15966  }
15967  case 1: { // Gray scale
15968  $this->gradients[$n]['colspace'] = 'DeviceGray';
15969  if (!empty($background)) {
15970  $this->gradients[$n]['background'] = sprintf('%.3F', $bcolor[0]/255);
15971  }
15972  break;
15973  }
15974  }
15975  $num_stops = count($stops);
15976  $last_stop_id = $num_stops - 1;
15977  foreach ($stops as $key => $stop) {
15978  $this->gradients[$n]['colors'][$key] = array();
15979  // offset represents a location along the gradient vector
15980  if (isset($stop['offset'])) {
15981  $this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
15982  } else {
15983  if ($key == 0) {
15984  $this->gradients[$n]['colors'][$key]['offset'] = 0;
15985  } elseif ($key == $last_stop_id) {
15986  $this->gradients[$n]['colors'][$key]['offset'] = 1;
15987  } else {
15988  $offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
15989  $this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
15990  }
15991  }
15992  if (isset($stop['opacity'])) {
15993  $this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
15994  if ($stop['opacity'] < 1) {
15995  $this->gradients[$n]['transparency'] = true;
15996  }
15997  } else {
15998  $this->gradients[$n]['colors'][$key]['opacity'] = 1;
15999  }
16000  // exponent for the exponential interpolation function
16001  if (isset($stop['exponent'])) {
16002  $this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
16003  } else {
16004  $this->gradients[$n]['colors'][$key]['exponent'] = 1;
16005  }
16006  // set colors
16007  $color = array_values($stop['color']);
16008  switch($numcolspace) {
16009  case 4: { // CMYK
16010  $this->gradients[$n]['colors'][$key]['color'] = sprintf('%.3F %.3F %.3F %.3F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
16011  break;
16012  }
16013  case 3: { // RGB
16014  $this->gradients[$n]['colors'][$key]['color'] = sprintf('%.3F %.3F %.3F', $color[0]/255, $color[1]/255, $color[2]/255);
16015  break;
16016  }
16017  case 1: { // Gray scale
16018  $this->gradients[$n]['colors'][$key]['color'] = sprintf('%.3F', $color[0]/255);
16019  break;
16020  }
16021  }
16022  }
16023  if ($this->gradients[$n]['transparency']) {
16024  // paint luminosity gradient
16025  $this->_out('/TGS'.$n.' gs');
16026  }
16027  //paint the gradient
16028  $this->_out('/Sh'.$n.' sh');
16029  //restore previous Graphic State
16030  $this->_out('Q');
16031  }
16032 
16039  function _putshaders() {
16040  $idt = count($this->gradients); //index for transparency gradients
16041  foreach ($this->gradients as $id => $grad) {
16042  if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
16043  $fc = $this->_newobj();
16044  $out = '<<';
16045  $out .= ' /FunctionType 3';
16046  $out .= ' /Domain [0 1]';
16047  $functions = '';
16048  $bounds = '';
16049  $encode = '';
16050  $i = 1;
16051  $num_cols = count($grad['colors']);
16052  $lastcols = $num_cols - 1;
16053  for ($i = 1; $i < $num_cols; ++$i) {
16054  $functions .= ($fc + $i).' 0 R ';
16055  if ($i < $lastcols) {
16056  $bounds .= sprintf('%.3F ', $grad['colors'][$i]['offset']);
16057  }
16058  $encode .= '0 1 ';
16059  }
16060  $out .= ' /Functions ['.trim($functions).']';
16061  $out .= ' /Bounds ['.trim($bounds).']';
16062  $out .= ' /Encode ['.trim($encode).']';
16063  $out .= ' >>';
16064  $out .= "\n".'endobj';
16065  $this->_out($out);
16066  for ($i = 1; $i < $num_cols; ++$i) {
16067  $this->_newobj();
16068  $out = '<<';
16069  $out .= ' /FunctionType 2';
16070  $out .= ' /Domain [0 1]';
16071  $out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
16072  $out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
16073  $out .= ' /N '.$grad['colors'][$i]['exponent'];
16074  $out .= ' >>';
16075  $out .= "\n".'endobj';
16076  $this->_out($out);
16077  }
16078  // set transparency fuctions
16079  if ($grad['transparency']) {
16080  $ft = $this->_newobj();
16081  $out = '<<';
16082  $out .= ' /FunctionType 3';
16083  $out .= ' /Domain [0 1]';
16084  $functions = '';
16085  $i = 1;
16086  $num_cols = count($grad['colors']);
16087  for ($i = 1; $i < $num_cols; ++$i) {
16088  $functions .= ($ft + $i).' 0 R ';
16089  }
16090  $out .= ' /Functions ['.trim($functions).']';
16091  $out .= ' /Bounds ['.trim($bounds).']';
16092  $out .= ' /Encode ['.trim($encode).']';
16093  $out .= ' >>';
16094  $out .= "\n".'endobj';
16095  $this->_out($out);
16096  for ($i = 1; $i < $num_cols; ++$i) {
16097  $this->_newobj();
16098  $out = '<<';
16099  $out .= ' /FunctionType 2';
16100  $out .= ' /Domain [0 1]';
16101  $out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
16102  $out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
16103  $out .= ' /N '.$grad['colors'][$i]['exponent'];
16104  $out .= ' >>';
16105  $out .= "\n".'endobj';
16106  $this->_out($out);
16107  }
16108  }
16109  }
16110  // set shading object
16111  $this->_newobj();
16112  $out = '<< /ShadingType '.$grad['type'];
16113  if (isset($grad['colspace'])) {
16114  $out .= ' /ColorSpace /'.$grad['colspace'];
16115  } else {
16116  $out .= ' /ColorSpace /DeviceRGB';
16117  }
16118  if (isset($grad['background']) AND !empty($grad['background'])) {
16119  $out .= ' /Background ['.$grad['background'].']';
16120  }
16121  if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
16122  $out .= ' /AntiAlias true';
16123  }
16124  if ($grad['type'] == 2) {
16125  $out .= ' '.sprintf('/Coords [%.3F %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
16126  $out .= ' /Domain [0 1]';
16127  $out .= ' /Function '.$fc.' 0 R';
16128  $out .= ' /Extend [true true]';
16129  $out .= ' >>';
16130  } elseif ($grad['type'] == 3) {
16131  //x0, y0, r0, x1, y1, r1
16132  //at this this time radius of inner circle is 0
16133  $out .= ' '.sprintf('/Coords [%.3F %.3F 0 %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
16134  $out .= ' /Domain [0 1]';
16135  $out .= ' /Function '.$fc.' 0 R';
16136  $out .= ' /Extend [true true]';
16137  $out .= ' >>';
16138  } elseif ($grad['type'] == 6) {
16139  $out .= ' /BitsPerCoordinate 16';
16140  $out .= ' /BitsPerComponent 8';
16141  $out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
16142  $out .= ' /BitsPerFlag 8';
16143  $stream = $this->_getrawstream($grad['stream']);
16144  $out .= ' /Length '.strlen($stream);
16145  $out .= ' >>';
16146  $out .= ' stream'."\n".$stream."\n".'endstream';
16147  }
16148  $out .= "\n".'endobj';
16149  $this->_out($out);
16150  if ($grad['transparency']) {
16151  $shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
16152  $shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
16153  }
16154  $this->gradients[$id]['id'] = $this->n;
16155  // set pattern object
16156  $this->_newobj();
16157  $out = '<< /Type /Pattern /PatternType 2';
16158  $out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
16159  $out .= ' >>';
16160  $out .= "\n".'endobj';
16161  $this->_out($out);
16162  $this->gradients[$id]['pattern'] = $this->n;
16163  // set shading and pattern for transparency mask
16164  if ($grad['transparency']) {
16165  // luminosity pattern
16166  $idgs = $id + $idt;
16167  $this->_newobj();
16168  $this->_out($shading_transparency);
16169  $this->gradients[$idgs]['id'] = $this->n;
16170  $this->_newobj();
16171  $out = '<< /Type /Pattern /PatternType 2';
16172  $out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
16173  $out .= ' >>';
16174  $out .= "\n".'endobj';
16175  $this->_out($out);
16176  $this->gradients[$idgs]['pattern'] = $this->n;
16177  // luminosity XObject
16178  $oid = $this->_newobj();
16179  $this->xobjects['LX'.$oid] = array('n' => $oid);
16180  $filter = '';
16181  $stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
16182  if ($this->compress) {
16183  $filter = ' /Filter /FlateDecode';
16184  $stream = gzcompress($stream);
16185  }
16186  $stream = $this->_getrawstream($stream);
16187  $out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
16188  $out .= ' /Length '.strlen($stream);
16189  $rect = sprintf('%.2F %.2F', $this->wPt, $this->hPt);
16190  $out .= ' /BBox [0 0 '.$rect.']';
16191  $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
16192  $out .= ' /Resources <<';
16193  $out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
16194  $out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
16195  $out .= ' >>';
16196  $out .= ' >> ';
16197  $out .= ' stream'."\n".$stream."\n".'endstream';
16198  $out .= "\n".'endobj';
16199  $this->_out($out);
16200  // SMask
16201  $this->_newobj();
16202  $out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
16203  $this->_out($out);
16204  // ExtGState
16205  $this->_newobj();
16206  $out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
16207  $this->_out($out);
16208  $this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
16209  }
16210  }
16211  }
16212 
16228  public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
16229  $this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
16230  }
16231 
16249  public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
16250  if ($this->rtl) {
16251  $xc = $this->w - $xc;
16252  }
16253  $op = $this->getPathPaintOperator($style);
16254  if ($op == 'f') {
16255  $line_style = array();
16256  }
16257  if ($cw) {
16258  $d = $b;
16259  $b = 360 - $a + $o;
16260  $a = 360 - $d + $o;
16261  } else {
16262  $b += $o;
16263  $a += $o;
16264  }
16265  $this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
16266  $this->_out($op);
16267  }
16268 
16289  public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false) {
16290  if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
16291  // convert EPS to raster image using GD or ImageMagick libraries
16292  return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
16293  }
16294  if ($x === '') {
16295  $x = $this->x;
16296  }
16297  if ($y === '') {
16298  $y = $this->y;
16299  }
16300  // check page for no-write regions and adapt page margins if necessary
16301  $this->checkPageRegions($h, $x, $y);
16302  $k = $this->k;
16303  $data = file_get_contents($file);
16304  if ($data === false) {
16305  $this->Error('EPS file not found: '.$file);
16306  }
16307  $regs = array();
16308  // EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
16309  preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
16310  if (count($regs) > 1) {
16311  $version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
16312  if (strpos($version_str, 'Adobe Illustrator') !== false) {
16313  $versexp = explode(' ', $version_str);
16314  $version = (float)array_pop($versexp);
16315  if ($version >= 9) {
16316  $this->Error('This version of Adobe Illustrator file is not supported: '.$file);
16317  }
16318  }
16319  }
16320  // strip binary bytes in front of PS-header
16321  $start = strpos($data, '%!PS-Adobe');
16322  if ($start > 0) {
16323  $data = substr($data, $start);
16324  }
16325  // find BoundingBox params
16326  preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
16327  if (count($regs) > 1) {
16328  list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
16329  } else {
16330  $this->Error('No BoundingBox found in EPS file: '.$file);
16331  }
16332  $start = strpos($data, '%%EndSetup');
16333  if ($start === false) {
16334  $start = strpos($data, '%%EndProlog');
16335  }
16336  if ($start === false) {
16337  $start = strpos($data, '%%BoundingBox');
16338  }
16339  $data = substr($data, $start);
16340  $end = strpos($data, '%%PageTrailer');
16341  if ($end===false) {
16342  $end = strpos($data, 'showpage');
16343  }
16344  if ($end) {
16345  $data = substr($data, 0, $end);
16346  }
16347  // calculate image width and height on document
16348  if (($w <= 0) AND ($h <= 0)) {
16349  $w = ($x2 - $x1) / $k;
16350  $h = ($y2 - $y1) / $k;
16351  } elseif ($w <= 0) {
16352  $w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
16353  } elseif ($h <= 0) {
16354  $h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
16355  }
16356  // fit the image on available space
16357  $this->fitBlock($w, $h, $x, $y, $fitonpage);
16358  if ($this->rasterize_vector_images) {
16359  // convert EPS to raster image using GD or ImageMagick libraries
16360  return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
16361  }
16362  // set scaling factors
16363  $scale_x = $w / (($x2 - $x1) / $k);
16364  $scale_y = $h / (($y2 - $y1) / $k);
16365  // set alignment
16366  $this->img_rb_y = $y + $h;
16367  // set alignment
16368  if ($this->rtl) {
16369  if ($palign == 'L') {
16370  $ximg = $this->lMargin;
16371  } elseif ($palign == 'C') {
16372  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
16373  } elseif ($palign == 'R') {
16374  $ximg = $this->w - $this->rMargin - $w;
16375  } else {
16376  $ximg = $x - $w;
16377  }
16378  $this->img_rb_x = $ximg;
16379  } else {
16380  if ($palign == 'L') {
16381  $ximg = $this->lMargin;
16382  } elseif ($palign == 'C') {
16383  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
16384  } elseif ($palign == 'R') {
16385  $ximg = $this->w - $this->rMargin - $w;
16386  } else {
16387  $ximg = $x;
16388  }
16389  $this->img_rb_x = $ximg + $w;
16390  }
16391  if ($useBoundingBox) {
16392  $dx = $ximg * $k - $x1;
16393  $dy = $y * $k - $y1;
16394  } else {
16395  $dx = $ximg * $k;
16396  $dy = $y * $k;
16397  }
16398  // save the current graphic state
16399  $this->_out('q'.$this->epsmarker);
16400  // translate
16401  $this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
16402  // scale
16403  if (isset($scale_x)) {
16404  $this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
16405  }
16406  // handle pc/unix/mac line endings
16407  preg_match('/[\r\n]+/s', $data, $regs);
16408  $lines = explode($regs[0], $data);
16409  $u=0;
16410  $cnt = count($lines);
16411  for ($i=0; $i < $cnt; ++$i) {
16412  $line = $lines[$i];
16413  if (($line == '') OR ($line{0} == '%')) {
16414  continue;
16415  }
16416  $len = strlen($line);
16417  $chunks = explode(' ', $line);
16418  $cmd = array_pop($chunks);
16419  // RGB
16420  if (($cmd == 'Xa') OR ($cmd == 'XA')) {
16421  $b = array_pop($chunks);
16422  $g = array_pop($chunks);
16423  $r = array_pop($chunks);
16424  $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!
16425  continue;
16426  }
16427  switch ($cmd) {
16428  case 'm':
16429  case 'l':
16430  case 'v':
16431  case 'y':
16432  case 'c':
16433  case 'k':
16434  case 'K':
16435  case 'g':
16436  case 'G':
16437  case 's':
16438  case 'S':
16439  case 'J':
16440  case 'j':
16441  case 'w':
16442  case 'M':
16443  case 'd':
16444  case 'n': {
16445  $this->_out($line);
16446  break;
16447  }
16448  case 'x': {// custom fill color
16449  list($c,$m,$y,$k) = $chunks;
16450  $this->_out(''.$c.' '.$m.' '.$y.' '.$k.' k');
16451  break;
16452  }
16453  case 'X': { // custom stroke color
16454  list($c,$m,$y,$k) = $chunks;
16455  $this->_out(''.$c.' '.$m.' '.$y.' '.$k.' K');
16456  break;
16457  }
16458  case 'Y':
16459  case 'N':
16460  case 'V':
16461  case 'L':
16462  case 'C': {
16463  $line{$len-1} = strtolower($cmd);
16464  $this->_out($line);
16465  break;
16466  }
16467  case 'b':
16468  case 'B': {
16469  $this->_out($cmd . '*');
16470  break;
16471  }
16472  case 'f':
16473  case 'F': {
16474  if ($u > 0) {
16475  $isU = false;
16476  $max = min($i+5, $cnt);
16477  for ($j=$i+1; $j < $max; ++$j) {
16478  $isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
16479  }
16480  if ($isU) {
16481  $this->_out('f*');
16482  }
16483  } else {
16484  $this->_out('f*');
16485  }
16486  break;
16487  }
16488  case '*u': {
16489  ++$u;
16490  break;
16491  }
16492  case '*U': {
16493  --$u;
16494  break;
16495  }
16496  }
16497  }
16498  // restore previous graphic state
16499  $this->_out($this->epsmarker.'Q');
16500  if (!empty($border)) {
16501  $bx = $this->x;
16502  $by = $this->y;
16503  $this->x = $ximg;
16504  if ($this->rtl) {
16505  $this->x += $w;
16506  }
16507  $this->y = $y;
16508  $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
16509  $this->x = $bx;
16510  $this->y = $by;
16511  }
16512  if ($link) {
16513  $this->Link($ximg, $y, $w, $h, $link, 0);
16514  }
16515  // set pointer to align the next text/objects
16516  switch($align) {
16517  case 'T':{
16518  $this->y = $y;
16519  $this->x = $this->img_rb_x;
16520  break;
16521  }
16522  case 'M':{
16523  $this->y = $y + round($h/2);
16524  $this->x = $this->img_rb_x;
16525  break;
16526  }
16527  case 'B':{
16528  $this->y = $this->img_rb_y;
16529  $this->x = $this->img_rb_x;
16530  break;
16531  }
16532  case 'N':{
16533  $this->SetY($this->img_rb_y);
16534  break;
16535  }
16536  default:{
16537  break;
16538  }
16539  }
16540  $this->endlinex = $this->img_rb_x;
16541  }
16542 
16548  public function setBarcode($bc='') {
16549  $this->barcode = $bc;
16550  }
16551 
16558  public function getBarcode() {
16559  return $this->barcode;
16560  }
16561 
16592  public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style='', $align='') {
16593  if ($this->empty_string(trim($code))) {
16594  return;
16595  }
16596  require_once(dirname(__FILE__).'/barcodes.php');
16597  // save current graphic settings
16598  $gvars = $this->getGraphicVars();
16599  // create new barcode object
16600  $barcodeobj = new TCPDFBarcode($code, $type);
16601  $arrcode = $barcodeobj->getBarcodeArray();
16602  if ($arrcode === false) {
16603  $this->Error('Error in 1D barcode string');
16604  }
16605  // set default values
16606  if (!isset($style['position'])) {
16607  $style['position'] = '';
16608  } elseif ($style['position'] == 'S') {
16609  // keep this for backward compatibility
16610  $style['position'] = '';
16611  $style['stretch'] = true;
16612  }
16613  if (!isset($style['fitwidth'])) {
16614  if (!isset($style['stretch'])) {
16615  $style['fitwidth'] = true;
16616  } else {
16617  $style['fitwidth'] = false;
16618  }
16619  }
16620  if ($style['fitwidth']) {
16621  // disable stretch
16622  $style['stretch'] = false;
16623  }
16624  if (!isset($style['stretch'])) {
16625  if (($w === '') OR ($w <= 0)) {
16626  $style['stretch'] = false;
16627  } else {
16628  $style['stretch'] = true;
16629  }
16630  }
16631  if (!isset($style['fgcolor'])) {
16632  $style['fgcolor'] = array(0,0,0); // default black
16633  }
16634  if (!isset($style['bgcolor'])) {
16635  $style['bgcolor'] = false; // default transparent
16636  }
16637  if (!isset($style['border'])) {
16638  $style['border'] = false;
16639  }
16640  $fontsize = 0;
16641  if (!isset($style['text'])) {
16642  $style['text'] = false;
16643  }
16644  if ($style['text'] AND isset($style['font'])) {
16645  if (isset($style['fontsize'])) {
16646  $fontsize = $style['fontsize'];
16647  }
16648  $this->SetFont($style['font'], '', $fontsize);
16649  }
16650  if (!isset($style['stretchtext'])) {
16651  $style['stretchtext'] = 4;
16652  }
16653  if ($x === '') {
16654  $x = $this->x;
16655  }
16656  if ($y === '') {
16657  $y = $this->y;
16658  }
16659  // check page for no-write regions and adapt page margins if necessary
16660  $this->checkPageRegions($h, $x, $y);
16661  if (($w === '') OR ($w <= 0)) {
16662  if ($this->rtl) {
16663  $w = $x - $this->lMargin;
16664  } else {
16665  $w = $this->w - $this->rMargin - $x;
16666  }
16667  }
16668  // padding
16669  if (!isset($style['padding'])) {
16670  $padding = 0;
16671  } elseif ($style['padding'] === 'auto') {
16672  $padding = 10 * ($w / ($arrcode['maxw'] + 20));
16673  } else {
16674  $padding = floatval($style['padding']);
16675  }
16676  // horizontal padding
16677  if (!isset($style['hpadding'])) {
16678  $hpadding = $padding;
16679  } elseif ($style['hpadding'] === 'auto') {
16680  $hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
16681  } else {
16682  $hpadding = floatval($style['hpadding']);
16683  }
16684  // vertical padding
16685  if (!isset($style['vpadding'])) {
16686  $vpadding = $padding;
16687  } elseif ($style['vpadding'] === 'auto') {
16688  $vpadding = ($hpadding / 2);
16689  } else {
16690  $vpadding = floatval($style['vpadding']);
16691  }
16692  // calculate xres (single bar width)
16693  $max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
16694  if ($style['stretch']) {
16695  $xres = $max_xres;
16696  } else {
16697  if ($this->empty_string($xres)) {
16698  $xres = (0.141 * $this->k); // default bar width = 0.4 mm
16699  }
16700  if ($xres > $max_xres) {
16701  // correct xres to fit on $w
16702  $xres = $max_xres;
16703  }
16704  if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
16705  OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
16706  $hpadding = 10 * $xres;
16707  if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
16708  $vpadding = ($hpadding / 2);
16709  }
16710  }
16711  }
16712  if ($style['fitwidth']) {
16713  $wold = $w;
16714  $w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
16715  if (isset($style['cellfitalign'])) {
16716  switch ($style['cellfitalign']) {
16717  case 'L': {
16718  if ($this->rtl) {
16719  $x -= ($wold - $w);
16720  }
16721  break;
16722  }
16723  case 'R': {
16724  if (!$this->rtl) {
16725  $x += ($wold - $w);
16726  }
16727  break;
16728  }
16729  case 'C': {
16730  if ($this->rtl) {
16731  $x -= (($wold - $w) / 2);
16732  } else {
16733  $x += (($wold - $w) / 2);
16734  }
16735  break;
16736  }
16737  default : {
16738  break;
16739  }
16740  }
16741  }
16742  }
16743  $text_height = ($this->cell_height_ratio * $fontsize / $this->k);
16744  // height
16745  if (($h === '') OR ($h <= 0)) {
16746  // set default height
16747  $h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
16748  }
16749  $barh = $h - $text_height - (2 * $vpadding);
16750  if ($barh <=0) {
16751  // try to reduce font or padding to fit barcode on available height
16752  if ($text_height > $h) {
16753  $fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
16754  $text_height = ($this->cell_height_ratio * $fontsize / $this->k);
16755  $this->SetFont($style['font'], '', $fontsize);
16756  }
16757  if ($vpadding > 0) {
16758  $vpadding = (($h - $text_height) / 4);
16759  }
16760  $barh = $h - $text_height - (2 * $vpadding);
16761  }
16762  // fit the barcode on available space
16763  $this->fitBlock($w, $h, $x, $y, false);
16764  // set alignment
16765  $this->img_rb_y = $y + $h;
16766  // set alignment
16767  if ($this->rtl) {
16768  if ($style['position'] == 'L') {
16769  $xpos = $this->lMargin;
16770  } elseif ($style['position'] == 'C') {
16771  $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
16772  } elseif ($style['position'] == 'R') {
16773  $xpos = $this->w - $this->rMargin - $w;
16774  } else {
16775  $xpos = $x - $w;
16776  }
16777  $this->img_rb_x = $xpos;
16778  } else {
16779  if ($style['position'] == 'L') {
16780  $xpos = $this->lMargin;
16781  } elseif ($style['position'] == 'C') {
16782  $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
16783  } elseif ($style['position'] == 'R') {
16784  $xpos = $this->w - $this->rMargin - $w;
16785  } else {
16786  $xpos = $x;
16787  }
16788  $this->img_rb_x = $xpos + $w;
16789  }
16790  $xpos_rect = $xpos;
16791  if (!isset($style['align'])) {
16792  $style['align'] = 'C';
16793  }
16794  switch ($style['align']) {
16795  case 'L': {
16796  $xpos = $xpos_rect + $hpadding;
16797  break;
16798  }
16799  case 'R': {
16800  $xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
16801  break;
16802  }
16803  case 'C':
16804  default : {
16805  $xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
16806  break;
16807  }
16808  }
16809  $xpos_text = $xpos;
16810  // barcode is always printed in LTR direction
16811  $tempRTL = $this->rtl;
16812  $this->rtl = false;
16813  // print background color
16814  if ($style['bgcolor']) {
16815  $this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
16816  } elseif ($style['border']) {
16817  $this->Rect($xpos_rect, $y, $w, $h, 'D');
16818  }
16819  // set foreground color
16820  $this->SetDrawColorArray($style['fgcolor']);
16821  $this->SetTextColorArray($style['fgcolor']);
16822  // print bars
16823  foreach ($arrcode['bcode'] as $k => $v) {
16824  $bw = ($v['w'] * $xres);
16825  if ($v['t']) {
16826  // draw a vertical bar
16827  $ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
16828  $this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
16829  }
16830  $xpos += $bw;
16831  }
16832  // print text
16833  if ($style['text']) {
16834  if (isset($style['label']) AND !$this->empty_string($style['label'])) {
16835  $label = $style['label'];
16836  } else {
16837  $label = $code;
16838  }
16839  $txtwidth = ($arrcode['maxw'] * $xres);
16840  if ($this->GetStringWidth($label) > $txtwidth) {
16841  $style['stretchtext'] = 2;
16842  }
16843  // print text
16844  $this->x = $xpos_text;
16845  $this->y = $y + $vpadding + $barh;
16846  $cellpadding = $this->cell_padding;
16847  $this->SetCellPadding(0);
16848  $this->Cell($txtwidth, '', $label, 0, 0, 'C', 0, '', $style['stretchtext'], false, 'T', 'T');
16849  $this->cell_padding = $cellpadding;
16850  }
16851  // restore original direction
16852  $this->rtl = $tempRTL;
16853  // restore previous settings
16854  $this->setGraphicVars($gvars);
16855  // set pointer to align the next text/objects
16856  switch($align) {
16857  case 'T':{
16858  $this->y = $y;
16859  $this->x = $this->img_rb_x;
16860  break;
16861  }
16862  case 'M':{
16863  $this->y = $y + round($h / 2);
16864  $this->x = $this->img_rb_x;
16865  break;
16866  }
16867  case 'B':{
16868  $this->y = $this->img_rb_y;
16869  $this->x = $this->img_rb_x;
16870  break;
16871  }
16872  case 'N':{
16873  $this->SetY($this->img_rb_y);
16874  break;
16875  }
16876  default:{
16877  break;
16878  }
16879  }
16880  $this->endlinex = $this->img_rb_x;
16881  }
16882 
16898  public function writeBarcode($x, $y, $w, $h, $type, $style, $font, $xres, $code) {
16899  // convert old settings for the new write1DBarcode() function.
16900  $xres = 1 / $xres;
16901  $newstyle = array(
16902  'position' => '',
16903  'align' => '',
16904  'stretch' => false,
16905  'fitwidth' => false,
16906  'cellfitalign' => '',
16907  'border' => false,
16908  'padding' => 0,
16909  'fgcolor' => array(0,0,0),
16910  'bgcolor' => false,
16911  'text' => true,
16912  'font' => $font,
16913  'fontsize' => 8,
16914  'stretchtext' => 4
16915  );
16916  if ($style & 1) {
16917  $newstyle['border'] = true;
16918  }
16919  if ($style & 2) {
16920  $newstyle['bgcolor'] = false;
16921  }
16922  if ($style & 4) {
16923  $newstyle['position'] = 'C';
16924  } elseif ($style & 8) {
16925  $newstyle['position'] = 'L';
16926  } elseif ($style & 16) {
16927  $newstyle['position'] = 'R';
16928  }
16929  if ($style & 128) {
16930  $newstyle['text'] = true;
16931  }
16932  if ($style & 256) {
16933  $newstyle['stretchtext'] = 4;
16934  }
16935  $this->write1DBarcode($code, $type, $x, $y, $w, $h, $xres, $newstyle, '');
16936  }
16937 
16963  public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style='', $align='', $distort=false) {
16964  if ($this->empty_string(trim($code))) {
16965  return;
16966  }
16967  require_once(dirname(__FILE__).'/2dbarcodes.php');
16968  // save current graphic settings
16969  $gvars = $this->getGraphicVars();
16970  // create new barcode object
16971  $barcodeobj = new TCPDF2DBarcode($code, $type);
16972  $arrcode = $barcodeobj->getBarcodeArray();
16973  if (($arrcode === false) OR empty($arrcode)) {
16974  $this->Error('Error in 2D barcode string');
16975  }
16976  // set default values
16977  if (!isset($style['position'])) {
16978  $style['position'] = '';
16979  }
16980  if (!isset($style['fgcolor'])) {
16981  $style['fgcolor'] = array(0,0,0); // default black
16982  }
16983  if (!isset($style['bgcolor'])) {
16984  $style['bgcolor'] = false; // default transparent
16985  }
16986  if (!isset($style['border'])) {
16987  $style['border'] = false;
16988  }
16989  // padding
16990  if (!isset($style['padding'])) {
16991  $style['padding'] = 0;
16992  } elseif ($style['padding'] === 'auto') {
16993  $style['padding'] = 4;
16994  }
16995  if (!isset($style['hpadding'])) {
16996  $style['hpadding'] = $style['padding'];
16997  } elseif ($style['hpadding'] === 'auto') {
16998  $style['hpadding'] = 4;
16999  }
17000  if (!isset($style['vpadding'])) {
17001  $style['vpadding'] = $style['padding'];
17002  } elseif ($style['vpadding'] === 'auto') {
17003  $style['vpadding'] = 4;
17004  }
17005  // cell (module) dimension
17006  if (!isset($style['module_width'])) {
17007  $style['module_width'] = 1; // width of a single module in points
17008  }
17009  if (!isset($style['module_height'])) {
17010  $style['module_height'] = 1; // height of a single module in points
17011  }
17012  if ($x === '') {
17013  $x = $this->x;
17014  }
17015  if ($y === '') {
17016  $y = $this->y;
17017  }
17018  // check page for no-write regions and adapt page margins if necessary
17019  $this->checkPageRegions($h, $x, $y);
17020  // number of barcode columns and rows
17021  $rows = $arrcode['num_rows'];
17022  $cols = $arrcode['num_cols'];
17023  // module width and height
17024  $mw = $style['module_width'];
17025  $mh = $style['module_height'];
17026  // get max dimensions
17027  if ($this->rtl) {
17028  $maxw = $x - $this->lMargin;
17029  } else {
17030  $maxw = $this->w - $this->rMargin - $x;
17031  }
17032  $maxh = ($this->h - $this->tMargin - $this->bMargin);
17033  $ratioHW = ($rows * $mh) / ($cols * $mw);
17034  $ratioWH = ($cols * $mw) / ($rows * $mh);
17035  if (!$distort) {
17036  if (($maxw * $ratioHW) > $maxh) {
17037  $maxw = $maxh * $ratioWH;
17038  }
17039  if (($maxh * $ratioWH) > $maxw) {
17040  $maxh = $maxw * $ratioHW;
17041  }
17042  }
17043  // set maximum dimesions
17044  if ($w > $maxw) {
17045  $w = $maxw;
17046  }
17047  if ($h > $maxh) {
17048  $h = $maxh;
17049  }
17050  $hpad = (2 * $style['hpadding']);
17051  $vpad = (2 * $style['vpadding']);
17052  // set dimensions
17053  if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
17054  $w = ($cols + $hpad) * ($mw / $this->k);
17055  $h = ($rows + $vpad) * ($mh / $this->k);
17056  } elseif (($w === '') OR ($w <= 0)) {
17057  $w = $h * $ratioWH;
17058  } elseif (($h === '') OR ($h <= 0)) {
17059  $h = $w * $ratioHW;
17060  }
17061  // barcode size (excluding padding)
17062  $bw = ($w * $cols) / ($cols + $hpad);
17063  $bh = ($h * $rows) / ($rows + $vpad);
17064  // dimension of single barcode cell unit
17065  $cw = $bw / $cols;
17066  $ch = $bh / $rows;
17067  if (!$distort) {
17068  if (($cw / $ch) > ($mw / $mh)) {
17069  // correct horizontal distortion
17070  $cw = $ch * $mw / $mh;
17071  $bw = $cw * $cols;
17072  $style['hpadding'] = ($w - $bw) / (2 * $cw);
17073  } else {
17074  // correct vertical distortion
17075  $ch = $cw * $mh / $mw;
17076  $bh = $ch * $rows;
17077  $style['vpadding'] = ($h - $bh) / (2 * $ch);
17078  }
17079  }
17080  // fit the barcode on available space
17081  $this->fitBlock($w, $h, $x, $y, false);
17082  // set alignment
17083  $this->img_rb_y = $y + $h;
17084  // set alignment
17085  if ($this->rtl) {
17086  if ($style['position'] == 'L') {
17087  $xpos = $this->lMargin;
17088  } elseif ($style['position'] == 'C') {
17089  $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
17090  } elseif ($style['position'] == 'R') {
17091  $xpos = $this->w - $this->rMargin - $w;
17092  } else {
17093  $xpos = $x - $w;
17094  }
17095  $this->img_rb_x = $xpos;
17096  } else {
17097  if ($style['position'] == 'L') {
17098  $xpos = $this->lMargin;
17099  } elseif ($style['position'] == 'C') {
17100  $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
17101  } elseif ($style['position'] == 'R') {
17102  $xpos = $this->w - $this->rMargin - $w;
17103  } else {
17104  $xpos = $x;
17105  }
17106  $this->img_rb_x = $xpos + $w;
17107  }
17108  $xstart = $xpos + ($style['hpadding'] * $cw);
17109  $ystart = $y + ($style['vpadding'] * $ch);
17110  // barcode is always printed in LTR direction
17111  $tempRTL = $this->rtl;
17112  $this->rtl = false;
17113  // print background color
17114  if ($style['bgcolor']) {
17115  $this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
17116  } elseif ($style['border']) {
17117  $this->Rect($xpos, $y, $w, $h, 'D');
17118  }
17119  // set foreground color
17120  $this->SetDrawColorArray($style['fgcolor']);
17121  // print barcode cells
17122  // for each row
17123  for ($r = 0; $r < $rows; ++$r) {
17124  $xr = $xstart;
17125  // for each column
17126  for ($c = 0; $c < $cols; ++$c) {
17127  if ($arrcode['bcode'][$r][$c] == 1) {
17128  // draw a single barcode cell
17129  $this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
17130  }
17131  $xr += $cw;
17132  }
17133  $ystart += $ch;
17134  }
17135  // restore original direction
17136  $this->rtl = $tempRTL;
17137  // restore previous settings
17138  $this->setGraphicVars($gvars);
17139  // set pointer to align the next text/objects
17140  switch($align) {
17141  case 'T':{
17142  $this->y = $y;
17143  $this->x = $this->img_rb_x;
17144  break;
17145  }
17146  case 'M':{
17147  $this->y = $y + round($h/2);
17148  $this->x = $this->img_rb_x;
17149  break;
17150  }
17151  case 'B':{
17152  $this->y = $this->img_rb_y;
17153  $this->x = $this->img_rb_x;
17154  break;
17155  }
17156  case 'N':{
17157  $this->SetY($this->img_rb_y);
17158  break;
17159  }
17160  default:{
17161  break;
17162  }
17163  }
17164  $this->endlinex = $this->img_rb_x;
17165  }
17166 
17186  public function getMargins() {
17187  $ret = array(
17188  'left' => $this->lMargin,
17189  'right' => $this->rMargin,
17190  'top' => $this->tMargin,
17191  'bottom' => $this->bMargin,
17192  'header' => $this->header_margin,
17193  'footer' => $this->footer_margin,
17194  'cell' => $this->cell_padding,
17195  'padding_left' => $this->cell_padding['L'],
17196  'padding_top' => $this->cell_padding['T'],
17197  'padding_right' => $this->cell_padding['R'],
17198  'padding_bottom' => $this->cell_padding['B']
17199  );
17200  return $ret;
17201  }
17202 
17213  public function getOriginalMargins() {
17214  $ret = array(
17215  'left' => $this->original_lMargin,
17216  'right' => $this->original_rMargin
17217  );
17218  return $ret;
17219  }
17220 
17227  public function getFontSize() {
17228  return $this->FontSize;
17229  }
17230 
17237  public function getFontSizePt() {
17238  return $this->FontSizePt;
17239  }
17240 
17247  public function getFontFamily() {
17248  return $this->FontFamily;
17249  }
17250 
17257  public function getFontStyle() {
17258  return $this->FontStyle;
17259  }
17260 
17269  protected function extractCSSproperties($cssdata) {
17270  if (empty($cssdata)) {
17271  return array();
17272  }
17273  // remove comments
17274  $cssdata = preg_replace('/\/\*[^\*]*\*\//', '', $cssdata);
17275  // remove newlines and multiple spaces
17276  $cssdata = preg_replace('/[\s]+/', ' ', $cssdata);
17277  // remove some spaces
17278  $cssdata = preg_replace('/[\s]*([;:\{\}]{1})[\s]*/', '\\1', $cssdata);
17279  // remove empty blocks
17280  $cssdata = preg_replace('/([^\}\{]+)\{\}/', '', $cssdata);
17281  // replace media type parenthesis
17282  $cssdata = preg_replace('/@media[\s]+([^\{]*)\{/i', '@media \\1§', $cssdata);
17283  $cssdata = preg_replace('/\}\}/si', '}§', $cssdata);
17284  // trim string
17285  $cssdata = trim($cssdata);
17286  // find media blocks (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
17287  $cssblocks = array();
17288  $matches = array();
17289  if (preg_match_all('/@media[\s]+([^\§]*)§([^§]*)§/i', $cssdata, $matches) > 0) {
17290  foreach ($matches[1] as $key => $type) {
17291  $cssblocks[$type] = $matches[2][$key];
17292  }
17293  // remove media blocks
17294  $cssdata = preg_replace('/@media[\s]+([^\§]*)§([^§]*)§/i', '', $cssdata);
17295  }
17296  // keep 'all' and 'print' media, other media types are discarded
17297  if (isset($cssblocks['all']) AND !empty($cssblocks['all'])) {
17298  $cssdata .= $cssblocks['all'];
17299  }
17300  if (isset($cssblocks['print']) AND !empty($cssblocks['print'])) {
17301  $cssdata .= $cssblocks['print'];
17302  }
17303  // reset css blocks array
17304  $cssblocks = array();
17305  $matches = array();
17306  // explode css data string into array
17307  if (substr($cssdata, -1) == '}') {
17308  // remove last parethesis
17309  $cssdata = substr($cssdata, 0, -1);
17310  }
17311  $matches = explode('}', $cssdata);
17312  foreach ($matches as $key => $block) {
17313  // index 0 contains the CSS selector, index 1 contains CSS properties
17314  $cssblocks[$key] = explode('{', $block);
17315  if (!isset($cssblocks[$key][1])) {
17316  // remove empty definitions
17317  unset($cssblocks[$key]);
17318  }
17319  }
17320  // split groups of selectors (comma-separated list of selectors)
17321  foreach ($cssblocks as $key => $block) {
17322  if (strpos($block[0], ',') > 0) {
17323  $selectors = explode(',', $block[0]);
17324  foreach ($selectors as $sel) {
17325  $cssblocks[] = array(0 => trim($sel), 1 => $block[1]);
17326  }
17327  unset($cssblocks[$key]);
17328  }
17329  }
17330  // covert array to selector => properties
17331  $cssdata = array();
17332  foreach ($cssblocks as $block) {
17333  $selector = $block[0];
17334  // calculate selector's specificity
17335  $matches = array();
17336  $a = 0; // the declaration is not from is a 'style' attribute
17337  $b = intval(preg_match_all('/[\#]/', $selector, $matches)); // number of ID attributes
17338  $c = intval(preg_match_all('/[\[\.]/', $selector, $matches)); // number of other attributes
17339  $c += intval(preg_match_all('/[\:]link|visited|hover|active|focus|target|lang|enabled|disabled|checked|indeterminate|root|nth|first|last|only|empty|contains|not/i', $selector, $matches)); // number of pseudo-classes
17340  $d = intval(preg_match_all('/[>\+\~\s]{1}[a-zA-Z0-9\*]+/', ' '.$selector, $matches)); // number of element names
17341  $d += intval(preg_match_all('/[\:][\:]/', $selector, $matches)); // number of pseudo-elements
17342  $specificity = $a.$b.$c.$d;
17343  // add specificity to the beginning of the selector
17344  $cssdata[$specificity.' '.$selector] = $block[1];
17345  }
17346  // sort selectors alphabetically to account for specificity
17347  ksort($cssdata, SORT_STRING);
17348  // return array
17349  return $cssdata;
17350  }
17351 
17361  protected function isValidCSSSelectorForTag($dom, $key, $selector) {
17362  $valid = false; // value to be returned
17363  $tag = $dom[$key]['value'];
17364  $class = array();
17365  if (isset($dom[$key]['attribute']['class']) AND !empty($dom[$key]['attribute']['class'])) {
17366  $class = explode(' ', strtolower($dom[$key]['attribute']['class']));
17367  }
17368  $id = '';
17369  if (isset($dom[$key]['attribute']['id']) AND !empty($dom[$key]['attribute']['id'])) {
17370  $id = strtolower($dom[$key]['attribute']['id']);
17371  }
17372  $selector = preg_replace('/([>\+\~\s]{1})([\.]{1})([^>\+\~\s]*)/si', '\\1*.\\3', $selector);
17373  $matches = array();
17374  if (preg_match_all('/([>\+\~\s]{1})([a-zA-Z0-9\*]+)([^>\+\~\s]*)/si', $selector, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE) > 0) {
17375  $parentop = array_pop($matches[1]);
17376  $operator = $parentop[0];
17377  $offset = $parentop[1];
17378  $lasttag = array_pop($matches[2]);
17379  $lasttag = strtolower(trim($lasttag[0]));
17380  if (($lasttag == '*') OR ($lasttag == $tag)) {
17381  // the last element on selector is our tag or 'any tag'
17382  $attrib = array_pop($matches[3]);
17383  $attrib = strtolower(trim($attrib[0]));
17384  if (!empty($attrib)) {
17385  // check if matches class, id, attribute, pseudo-class or pseudo-element
17386  switch ($attrib{0}) {
17387  case '.': { // class
17388  if (in_array(substr($attrib, 1), $class)) {
17389  $valid = true;
17390  }
17391  break;
17392  }
17393  case '#': { // ID
17394  if (substr($attrib, 1) == $id) {
17395  $valid = true;
17396  }
17397  break;
17398  }
17399  case '[': { // attribute
17400  $attrmatch = array();
17401  if (preg_match('/\[([a-zA-Z0-9]*)[\s]*([\~\^\$\*\|\=]*)[\s]*["]?([^"\]]*)["]?\]/i', $attrib, $attrmatch) > 0) {
17402  $att = strtolower($attrmatch[1]);
17403  $val = $attrmatch[3];
17404  if (isset($dom[$key]['attribute'][$att])) {
17405  switch ($attrmatch[2]) {
17406  case '=': {
17407  if ($dom[$key]['attribute'][$att] == $val) {
17408  $valid = true;
17409  }
17410  break;
17411  }
17412  case '~=': {
17413  if (in_array($val, explode(' ', $dom[$key]['attribute'][$att]))) {
17414  $valid = true;
17415  }
17416  break;
17417  }
17418  case '^=': {
17419  if ($val == substr($dom[$key]['attribute'][$att], 0, strlen($val))) {
17420  $valid = true;
17421  }
17422  break;
17423  }
17424  case '$=': {
17425  if ($val == substr($dom[$key]['attribute'][$att], -strlen($val))) {
17426  $valid = true;
17427  }
17428  break;
17429  }
17430  case '*=': {
17431  if (strpos($dom[$key]['attribute'][$att], $val) !== false) {
17432  $valid = true;
17433  }
17434  break;
17435  }
17436  case '|=': {
17437  if ($dom[$key]['attribute'][$att] == $val) {
17438  $valid = true;
17439  } elseif (preg_match('/'.$val.'[\-]{1}/i', $dom[$key]['attribute'][$att]) > 0) {
17440  $valid = true;
17441  }
17442  break;
17443  }
17444  default: {
17445  $valid = true;
17446  }
17447  }
17448  }
17449  }
17450  break;
17451  }
17452  case ':': { // pseudo-class or pseudo-element
17453  if ($attrib{1} == ':') { // pseudo-element
17454  // pseudo-elements are not supported!
17455  // (::first-line, ::first-letter, ::before, ::after)
17456  } else { // pseudo-class
17457  // pseudo-classes are not supported!
17458  // (:root, :nth-child(n), :nth-last-child(n), :nth-of-type(n), :nth-last-of-type(n), :first-child, :last-child, :first-of-type, :last-of-type, :only-child, :only-of-type, :empty, :link, :visited, :active, :hover, :focus, :target, :lang(fr), :enabled, :disabled, :checked)
17459  }
17460  break;
17461  }
17462  } // end of switch
17463  } else {
17464  $valid = true;
17465  }
17466  if ($valid AND ($offset > 0)) {
17467  $valid = false;
17468  // check remaining selector part
17469  $selector = substr($selector, 0, $offset);
17470  switch ($operator) {
17471  case ' ': { // descendant of an element
17472  while ($dom[$key]['parent'] > 0) {
17473  if ($this->isValidCSSSelectorForTag($dom, $dom[$key]['parent'], $selector)) {
17474  $valid = true;
17475  break;
17476  } else {
17477  $key = $dom[$key]['parent'];
17478  }
17479  }
17480  break;
17481  }
17482  case '>': { // child of an element
17483  $valid = $this->isValidCSSSelectorForTag($dom, $dom[$key]['parent'], $selector);
17484  break;
17485  }
17486  case '+': { // immediately preceded by an element
17487  for ($i = ($key - 1); $i > $dom[$key]['parent']; --$i) {
17488  if ($dom[$i]['tag'] AND $dom[$i]['opening']) {
17489  $valid = $this->isValidCSSSelectorForTag($dom, $i, $selector);
17490  break;
17491  }
17492  }
17493  break;
17494  }
17495  case '~': { // preceded by an element
17496  for ($i = ($key - 1); $i > $dom[$key]['parent']; --$i) {
17497  if ($dom[$i]['tag'] AND $dom[$i]['opening']) {
17498  if ($this->isValidCSSSelectorForTag($dom, $i, $selector)) {
17499  break;
17500  }
17501  }
17502  }
17503  break;
17504  }
17505  }
17506  }
17507  }
17508  }
17509  return $valid;
17510  }
17511 
17521  protected function getTagStyleFromCSS($dom, $key, $css) {
17522  $tagstyle = ''; // style to be returned
17523  // get all styles that apply
17524  foreach($css as $selector => $style) {
17525  // remove specificity
17526  $selector = substr($selector, strpos($selector, ' '));
17527  // check if this selector apply to current tag
17528  if ($this->isValidCSSSelectorForTag($dom, $key, $selector)) {
17529  // apply style
17530  $tagstyle .= ';'.$style;
17531  }
17532  }
17533  if (isset($dom[$key]['attribute']['style'])) {
17534  // attach inline style (latest properties have high priority)
17535  $tagstyle .= ';'.$dom[$key]['attribute']['style'];
17536  }
17537  // remove multiple semicolons
17538  $tagstyle = preg_replace('/[;]+/', ';', $tagstyle);
17539  return $tagstyle;
17540  }
17541 
17549  protected function getCSSBorderWidth($width) {
17550  if ($width == 'thin') {
17551  $width = (2 / $this->k);
17552  } elseif ($width == 'medium') {
17553  $width = (4 / $this->k);
17554  } elseif ($width == 'thick') {
17555  $width = (6 / $this->k);
17556  } else {
17557  $width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
17558  }
17559  return $width;
17560  }
17561 
17569  protected function getCSSBorderDashStyle($style) {
17570  switch (strtolower($style)) {
17571  case 'none':
17572  case 'hidden': {
17573  $dash = -1;
17574  break;
17575  }
17576  case 'dotted': {
17577  $dash = 1;
17578  break;
17579  }
17580  case 'dashed': {
17581  $dash = 3;
17582  break;
17583  }
17584  case 'double':
17585  case 'groove':
17586  case 'ridge':
17587  case 'inset':
17588  case 'outset':
17589  case 'solid':
17590  default: {
17591  $dash = 0;
17592  break;
17593  }
17594  }
17595  return $dash;
17596  }
17597 
17605  protected function getCSSBorderStyle($cssborder) {
17606  $bprop = preg_split('/[\s]+/', trim($cssborder));
17607  $border = array(); // value to be returned
17608  switch (count($bprop)) {
17609  case 3: {
17610  $width = $bprop[0];
17611  $style = $bprop[1];
17612  $color = $bprop[2];
17613  break;
17614  }
17615  case 2: {
17616  $width = 'medium';
17617  $style = $bprop[0];
17618  $color = $bprop[1];
17619  break;
17620  }
17621  case 1: {
17622  $width = 'medium';
17623  $style = $bprop[0];
17624  $color = 'black';
17625  break;
17626  }
17627  default: {
17628  $width = 'medium';
17629  $style = 'solid';
17630  $color = 'black';
17631  break;
17632  }
17633  }
17634  if ($style == 'none') {
17635  return array();
17636  }
17637  $border['cap'] = 'square';
17638  $border['join'] = 'miter';
17639  $border['dash'] = $this->getCSSBorderDashStyle($style);
17640  if ($border['dash'] < 0) {
17641  return array();
17642  }
17643  $border['width'] = $this->getCSSBorderWidth($width);
17644  $border['color'] = $this->convertHTMLColorToDec($color);
17645  return $border;
17646  }
17647 
17655  public function getCSSPadding($csspadding, $width=0) {
17656  $padding = preg_split('/[\s]+/', trim($csspadding));
17657  $cell_padding = array(); // value to be returned
17658  switch (count($padding)) {
17659  case 4: {
17660  $cell_padding['T'] = $padding[0];
17661  $cell_padding['R'] = $padding[1];
17662  $cell_padding['B'] = $padding[2];
17663  $cell_padding['L'] = $padding[3];
17664  break;
17665  }
17666  case 3: {
17667  $cell_padding['T'] = $padding[0];
17668  $cell_padding['R'] = $padding[1];
17669  $cell_padding['B'] = $padding[2];
17670  $cell_padding['L'] = $padding[1];
17671  break;
17672  }
17673  case 2: {
17674  $cell_padding['T'] = $padding[0];
17675  $cell_padding['R'] = $padding[1];
17676  $cell_padding['B'] = $padding[0];
17677  $cell_padding['L'] = $padding[1];
17678  break;
17679  }
17680  case 1: {
17681  $cell_padding['T'] = $padding[0];
17682  $cell_padding['R'] = $padding[0];
17683  $cell_padding['B'] = $padding[0];
17684  $cell_padding['L'] = $padding[0];
17685  break;
17686  }
17687  default: {
17688  return $this->cell_padding;
17689  }
17690  }
17691  if ($width == 0) {
17692  $width = $this->w - $this->lMargin - $this->rMargin;
17693  }
17694  $cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
17695  $cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
17696  $cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
17697  $cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
17698  return $cell_padding;
17699  }
17700 
17708  public function getCSSMargin($cssmargin, $width=0) {
17709  $margin = preg_split('/[\s]+/', trim($cssmargin));
17710  $cell_margin = array(); // value to be returned
17711  switch (count($margin)) {
17712  case 4: {
17713  $cell_margin['T'] = $margin[0];
17714  $cell_margin['R'] = $margin[1];
17715  $cell_margin['B'] = $margin[2];
17716  $cell_margin['L'] = $margin[3];
17717  break;
17718  }
17719  case 3: {
17720  $cell_margin['T'] = $margin[0];
17721  $cell_margin['R'] = $margin[1];
17722  $cell_margin['B'] = $margin[2];
17723  $cell_margin['L'] = $margin[1];
17724  break;
17725  }
17726  case 2: {
17727  $cell_margin['T'] = $margin[0];
17728  $cell_margin['R'] = $margin[1];
17729  $cell_margin['B'] = $margin[0];
17730  $cell_margin['L'] = $margin[1];
17731  break;
17732  }
17733  case 1: {
17734  $cell_margin['T'] = $margin[0];
17735  $cell_margin['R'] = $margin[0];
17736  $cell_margin['B'] = $margin[0];
17737  $cell_margin['L'] = $margin[0];
17738  break;
17739  }
17740  default: {
17741  return $this->cell_margin;
17742  }
17743  }
17744  if ($width == 0) {
17745  $width = $this->w - $this->lMargin - $this->rMargin;
17746  }
17747  $cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
17748  $cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
17749  $cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
17750  $cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
17751  return $cell_margin;
17752  }
17753 
17762  protected function getCSSFontSpacing($spacing, $parent=0) {
17763  $val = 0; // value to be returned
17764  $spacing = trim($spacing);
17765  switch ($spacing) {
17766  case 'normal': {
17767  $val = 0;
17768  break;
17769  }
17770  case 'inherit': {
17771  if ($parent == 'normal') {
17772  $val = 0;
17773  } else {
17774  $val = $parent;
17775  }
17776  break;
17777  }
17778  default: {
17779  $val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
17780  }
17781  }
17782  return $val;
17783  }
17784 
17793  protected function getCSSFontStretching($stretch, $parent=100) {
17794  $val = 100; // value to be returned
17795  $stretch = trim($stretch);
17796  switch ($stretch) {
17797  case 'ultra-condensed': {
17798  $val = 40;
17799  break;
17800  }
17801  case 'extra-condensed': {
17802  $val = 55;
17803  break;
17804  }
17805  case 'condensed': {
17806  $val = 70;
17807  break;
17808  }
17809  case 'semi-condensed': {
17810  $val = 85;
17811  break;
17812  }
17813  case 'normal': {
17814  $val = 100;
17815  break;
17816  }
17817  case 'semi-expanded': {
17818  $val = 115;
17819  break;
17820  }
17821  case 'expanded': {
17822  $val = 130;
17823  break;
17824  }
17825  case 'extra-expanded': {
17826  $val = 145;
17827  break;
17828  }
17829  case 'ultra-expanded': {
17830  $val = 160;
17831  break;
17832  }
17833  case 'wider': {
17834  $val = $parent + 10;
17835  break;
17836  }
17837  case 'narrower': {
17838  $val = $parent - 10;
17839  break;
17840  }
17841  case 'inherit': {
17842  if ($parent == 'normal') {
17843  $val = 100;
17844  } else {
17845  $val = $parent;
17846  }
17847  break;
17848  }
17849  default: {
17850  $val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
17851  }
17852  }
17853  return $val;
17854  }
17855 
17863  protected function getHtmlDomArray($html) {
17864  // array of CSS styles ( selector => properties).
17865  $css = array();
17866  // get CSS array defined at previous call
17867  $matches = array();
17868  if (preg_match_all('/<cssarray>([^<]*)<\/cssarray>/isU', $html, $matches) > 0) {
17869  if (isset($matches[1][0])) {
17870  $css = array_merge($css, unserialize($this->unhtmlentities($matches[1][0])));
17871  }
17872  $html = preg_replace('/<cssarray>(.*?)<\/cssarray>/isU', '', $html);
17873  }
17874  // extract external CSS files
17875  $matches = array();
17876  if (preg_match_all('/<link([^>]*)>/isU', $html, $matches) > 0) {
17877  foreach ($matches[1] as $key => $link) {
17878  $type = array();
17879  if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
17880  $type = array();
17881  preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
17882  // get 'all' and 'print' media, other media types are discarded
17883  // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
17884  if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
17885  $type = array();
17886  if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
17887  // read CSS data file
17888  $cssdata = file_get_contents(trim($type[1]));
17889  $css = array_merge($css, $this->extractCSSproperties($cssdata));
17890  }
17891  }
17892  }
17893  }
17894  }
17895  // extract style tags
17896  $matches = array();
17897  if (preg_match_all('/<style([^>]*)>([^<]*)<\/style>/isU', $html, $matches) > 0) {
17898  foreach ($matches[1] as $key => $media) {
17899  $type = array();
17900  preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
17901  // get 'all' and 'print' media, other media types are discarded
17902  // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
17903  if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
17904  $cssdata = $matches[2][$key];
17905  $css = array_merge($css, $this->extractCSSproperties($cssdata));
17906  }
17907  }
17908  }
17909  // create a special tag to contain the CSS array (used for table content)
17910  $csstagarray = '<cssarray>'.htmlentities(serialize($css)).'</cssarray>';
17911  // remove head and style blocks
17912  $html = preg_replace('/<head([^>]*)>(.*?)<\/head>/siU', '', $html);
17913  $html = preg_replace('/<style([^>]*)>([^<]*)<\/style>/isU', '', $html);
17914  // define block tags
17915  $blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
17916  // define self-closing tags
17917  $selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
17918  // remove all unsupported tags (the line below lists all supported tags)
17919  $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>');
17920  //replace some blank characters
17921  $html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
17922  $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);
17923  $html = preg_replace('@(\r\n|\r)@', "\n", $html);
17924  $repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
17925  $html = strtr($html, $repTable);
17926  $offset = 0;
17927  while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
17928  $html_a = substr($html, 0, $offset);
17929  $html_b = substr($html, $offset, ($pos - $offset + 6));
17930  while (preg_match("'<xre([^>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
17931  // preserve newlines on <pre> tag
17932  $html_b = preg_replace("'<xre([^>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
17933  }
17934  while (preg_match("'<xre([^>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
17935  // preserve spaces on <pre> tag
17936  $html_b = preg_replace("'<xre([^>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
17937  }
17938  $html = $html_a.$html_b.substr($html, $pos + 6);
17939  $offset = strlen($html_a.$html_b);
17940  }
17941  $offset = 0;
17942  while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
17943  $html_a = substr($html, 0, $offset);
17944  $html_b = substr($html, $offset, ($pos - $offset + 11));
17945  while (preg_match("'<textarea([^>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
17946  // preserve newlines on <textarea> tag
17947  $html_b = preg_replace("'<textarea([^>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
17948  $html_b = preg_replace("'<textarea([^>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
17949  }
17950  $html = $html_a.$html_b.substr($html, $pos + 11);
17951  $offset = strlen($html_a.$html_b);
17952  }
17953  $html = preg_replace('/([\s]*)<option/si', '<option', $html);
17954  $html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
17955  $offset = 0;
17956  while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
17957  $html_a = substr($html, 0, $offset);
17958  $html_b = substr($html, $offset, ($pos - $offset + 9));
17959  while (preg_match("'<option([^>]*)>(.*?)</option>'si", $html_b)) {
17960  $html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
17961  $html_b = preg_replace("'<option([^>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
17962  }
17963  $html = $html_a.$html_b.substr($html, $pos + 9);
17964  $offset = strlen($html_a.$html_b);
17965  }
17966  if (preg_match("'</select'si", $html)) {
17967  $html = preg_replace("'<select([^>]*)>'si", "<select\\1 opt=\"", $html);
17968  $html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
17969  }
17970  $html = str_replace("\n", ' ', $html);
17971  // restore textarea newlines
17972  $html = str_replace('<TBR>', "\n", $html);
17973  // remove extra spaces from code
17974  $html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
17975  $html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
17976  $html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
17977  $html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
17978  $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);
17979  $html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
17980  $html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
17981  $html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
17982  $html = preg_replace('/<img([^>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
17983  $html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
17984  $html = preg_replace('/<textarea([^>]*)>([^<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
17985  $html = preg_replace('/<li([^>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
17986  $html = preg_replace('/<li([^>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
17987  $html = preg_replace('/<([^>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
17988  $html = preg_replace('/[\s]<\/([^>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
17989  $html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
17990  // trim string
17991  $html = $this->stringTrim($html);
17992  // pattern for generic tag
17993  $tagpattern = '/(<[^>]+>)/';
17994  // explodes the string
17995  $a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
17996  // count elements
17997  $maxel = count($a);
17998  $elkey = 0;
17999  $key = 0;
18000  // create an array of elements
18001  $dom = array();
18002  $dom[$key] = array();
18003  // set inheritable properties fot the first void element
18004  // 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
18005  $dom[$key]['tag'] = false;
18006  $dom[$key]['block'] = false;
18007  $dom[$key]['value'] = '';
18008  $dom[$key]['parent'] = 0;
18009  $dom[$key]['fontname'] = $this->FontFamily;
18010  $dom[$key]['fontstyle'] = $this->FontStyle;
18011  $dom[$key]['fontsize'] = $this->FontSizePt;
18012  $dom[$key]['font-stretch'] = 100;
18013  $dom[$key]['letter-spacing'] = 0;
18014  $dom[$key]['stroke'] = $this->textstrokewidth;
18015  $dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
18016  $dom[$key]['clip'] = ($this->textrendermode > 3);
18017  $dom[$key]['line-height'] = $this->cell_height_ratio;
18018  $dom[$key]['bgcolor'] = false;
18019  $dom[$key]['fgcolor'] = $this->fgcolor; // color
18020  $dom[$key]['strokecolor'] = $this->strokecolor;
18021  $dom[$key]['align'] = '';
18022  $dom[$key]['listtype'] = '';
18023  $dom[$key]['text-indent'] = 0;
18024  $dom[$key]['border'] = array();
18025  $dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
18026  $thead = false; // true when we are inside the THEAD tag
18027  ++$key;
18028  $level = array();
18029  array_push($level, 0); // root
18030  while ($elkey < $maxel) {
18031  $dom[$key] = array();
18032  $element = $a[$elkey];
18033  $dom[$key]['elkey'] = $elkey;
18034  if (preg_match($tagpattern, $element)) {
18035  // html tag
18036  $element = substr($element, 1, -1);
18037  // get tag name
18038  preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
18039  $tagname = strtolower($tag[1]);
18040  // check if we are inside a table header
18041  if ($tagname == 'thead') {
18042  if ($element{0} == '/') {
18043  $thead = false;
18044  } else {
18045  $thead = true;
18046  }
18047  ++$elkey;
18048  continue;
18049  }
18050  $dom[$key]['tag'] = true;
18051  $dom[$key]['value'] = $tagname;
18052  if (in_array($dom[$key]['value'], $blocktags)) {
18053  $dom[$key]['block'] = true;
18054  } else {
18055  $dom[$key]['block'] = false;
18056  }
18057  if ($element{0} == '/') {
18058  // *** closing html tag
18059  $dom[$key]['opening'] = false;
18060  $dom[$key]['parent'] = end($level);
18061  array_pop($level);
18062  $dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
18063  $dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
18064  $dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
18065  $dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
18066  $dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
18067  $dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
18068  $dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
18069  $dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
18070  $dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
18071  $dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
18072  $dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
18073  $dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
18074  $dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
18075  $dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
18076  if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
18077  $dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
18078  }
18079  // set the number of columns in table tag
18080  if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
18081  $dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
18082  }
18083  if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18084  $dom[($dom[$key]['parent'])]['content'] = $csstagarray;
18085  for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
18086  $dom[($dom[$key]['parent'])]['content'] .= $a[$dom[$i]['elkey']];
18087  }
18088  $key = $i;
18089  $parent_table = $dom[$dom[$dom[($dom[$key]['parent'])]['parent']]['parent']];
18090  $parent_padding = 0;
18091  $parent_spacing = 0;
18092  if (isset($parent_table['attribute']['cellpadding'])) {
18093  $parent_padding = $this->getHTMLUnitToUnits($parent_table['attribute']['cellpadding'], 1, 'px');
18094  }
18095  if (isset($parent_table['attribute']['cellspacing'])) {
18096  $parent_spacing = $this->getHTMLUnitToUnits($parent_table['attribute']['cellspacing'], 1, 'px');
18097  }
18098  // mark nested tables
18099  $dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true" pcellpadding="'.$parent_padding.'" pcellspacing="'.$parent_spacing.'"', $dom[($dom[$key]['parent'])]['content']);
18100  // remove thead sections from nested tables
18101  $dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
18102  $dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
18103  }
18104  // store header rows on a new table
18105  if (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['thead'] === true)) {
18106  if ($this->empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
18107  $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
18108  }
18109  for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
18110  $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
18111  }
18112  if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
18113  $dom[($dom[$key]['parent'])]['attribute'] = array();
18114  }
18115  // header elements must be always contained in a single page
18116  $dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
18117  }
18118  if (($dom[$key]['value'] == 'table') AND (!$this->empty_string($dom[($dom[$key]['parent'])]['thead']))) {
18119  // remove the nobr attributes from the table header
18120  $dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
18121  $dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
18122  }
18123  } else {
18124  // *** opening or self-closing html tag
18125  $dom[$key]['opening'] = true;
18126  $dom[$key]['parent'] = end($level);
18127  if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
18128  // self-closing tag
18129  $dom[$key]['self'] = true;
18130  } else {
18131  // opening tag
18132  array_push($level, $key);
18133  $dom[$key]['self'] = false;
18134  }
18135  // copy some values from parent
18136  $parentkey = 0;
18137  if ($key > 0) {
18138  $parentkey = $dom[$key]['parent'];
18139  $dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
18140  $dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
18141  $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
18142  $dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
18143  $dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
18144  $dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
18145  $dom[$key]['fill'] = $dom[$parentkey]['fill'];
18146  $dom[$key]['clip'] = $dom[$parentkey]['clip'];
18147  $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
18148  $dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
18149  $dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
18150  $dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
18151  $dom[$key]['align'] = $dom[$parentkey]['align'];
18152  $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
18153  $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
18154  $dom[$key]['border'] = array();
18155  $dom[$key]['dir'] = $dom[$parentkey]['dir'];
18156  }
18157  // get attributes
18158  preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
18159  $dom[$key]['attribute'] = array(); // reset attribute array
18160  while (list($id, $name) = each($attr_array[1])) {
18161  $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
18162  }
18163  if (!empty($css)) {
18164  // merge eternal CSS style to current style
18165  $dom[$key]['attribute']['style'] = $this->getTagStyleFromCSS($dom, $key, $css);
18166  }
18167  // split style attributes
18168  if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
18169  // get style attributes
18170  preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
18171  $dom[$key]['style'] = array(); // reset style attribute array
18172  while (list($id, $name) = each($style_array[1])) {
18173  // in case of duplicate attribute the last replace the previous
18174  $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
18175  }
18176  // --- get some style attributes ---
18177  // text direction
18178  if (isset($dom[$key]['style']['direction'])) {
18179  $dom[$key]['dir'] = $dom[$key]['style']['direction'];
18180  }
18181  // font family
18182  if (isset($dom[$key]['style']['font-family'])) {
18183  $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
18184  }
18185  // list-style-type
18186  if (isset($dom[$key]['style']['list-style-type'])) {
18187  $dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
18188  if ($dom[$key]['listtype'] == 'inherit') {
18189  $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
18190  }
18191  }
18192  // text-indent
18193  if (isset($dom[$key]['style']['text-indent'])) {
18194  $dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
18195  if ($dom[$key]['text-indent'] == 'inherit') {
18196  $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
18197  }
18198  }
18199  // font size
18200  if (isset($dom[$key]['style']['font-size'])) {
18201  $fsize = trim($dom[$key]['style']['font-size']);
18202  switch ($fsize) {
18203  // absolute-size
18204  case 'xx-small': {
18205  $dom[$key]['fontsize'] = $dom[0]['fontsize'] - 4;
18206  break;
18207  }
18208  case 'x-small': {
18209  $dom[$key]['fontsize'] = $dom[0]['fontsize'] - 3;
18210  break;
18211  }
18212  case 'small': {
18213  $dom[$key]['fontsize'] = $dom[0]['fontsize'] - 2;
18214  break;
18215  }
18216  case 'medium': {
18217  $dom[$key]['fontsize'] = $dom[0]['fontsize'];
18218  break;
18219  }
18220  case 'large': {
18221  $dom[$key]['fontsize'] = $dom[0]['fontsize'] + 2;
18222  break;
18223  }
18224  case 'x-large': {
18225  $dom[$key]['fontsize'] = $dom[0]['fontsize'] + 4;
18226  break;
18227  }
18228  case 'xx-large': {
18229  $dom[$key]['fontsize'] = $dom[0]['fontsize'] + 6;
18230  break;
18231  }
18232  // relative-size
18233  case 'smaller': {
18234  $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'] - 3;
18235  break;
18236  }
18237  case 'larger': {
18238  $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'] + 3;
18239  break;
18240  }
18241  default: {
18242  $dom[$key]['fontsize'] = $this->getHTMLUnitToUnits($fsize, $dom[$parentkey]['fontsize'], 'pt', true);
18243  }
18244  }
18245  }
18246  // font-stretch
18247  if (isset($dom[$key]['style']['font-stretch'])) {
18248  $dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
18249  }
18250  // letter-spacing
18251  if (isset($dom[$key]['style']['letter-spacing'])) {
18252  $dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
18253  }
18254  // line-height
18255  if (isset($dom[$key]['style']['line-height'])) {
18256  $lineheight = trim($dom[$key]['style']['line-height']);
18257  switch ($lineheight) {
18258  // A normal line height. This is default
18259  case 'normal': {
18260  $dom[$key]['line-height'] = $dom[0]['line-height'];
18261  break;
18262  }
18263  default: {
18264  if (is_numeric($lineheight)) {
18265  $lineheight = $lineheight * 100;
18266  }
18267  $dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
18268  }
18269  }
18270  }
18271  // font style
18272  if (isset($dom[$key]['style']['font-weight']) AND (strtolower($dom[$key]['style']['font-weight']{0}) == 'b')) {
18273  $dom[$key]['fontstyle'] .= 'B';
18274  }
18275  if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style']{0}) == 'i')) {
18276  $dom[$key]['fontstyle'] .= 'I';
18277  }
18278  // font color
18279  if (isset($dom[$key]['style']['color']) AND (!$this->empty_string($dom[$key]['style']['color']))) {
18280  $dom[$key]['fgcolor'] = $this->convertHTMLColorToDec($dom[$key]['style']['color']);
18281  } elseif ($dom[$key]['value'] == 'a') {
18282  $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
18283  }
18284  // background color
18285  if (isset($dom[$key]['style']['background-color']) AND (!$this->empty_string($dom[$key]['style']['background-color']))) {
18286  $dom[$key]['bgcolor'] = $this->convertHTMLColorToDec($dom[$key]['style']['background-color']);
18287  }
18288  // text-decoration
18289  if (isset($dom[$key]['style']['text-decoration'])) {
18290  $decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
18291  foreach ($decors as $dec) {
18292  $dec = trim($dec);
18293  if (!$this->empty_string($dec)) {
18294  if ($dec{0} == 'u') {
18295  // underline
18296  $dom[$key]['fontstyle'] .= 'U';
18297  } elseif ($dec{0} == 'l') {
18298  // line-trough
18299  $dom[$key]['fontstyle'] .= 'D';
18300  } elseif ($dec{0} == 'o') {
18301  // overline
18302  $dom[$key]['fontstyle'] .= 'O';
18303  }
18304  }
18305  }
18306  } elseif ($dom[$key]['value'] == 'a') {
18307  $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
18308  }
18309  // check for width attribute
18310  if (isset($dom[$key]['style']['width'])) {
18311  $dom[$key]['width'] = $dom[$key]['style']['width'];
18312  }
18313  // check for height attribute
18314  if (isset($dom[$key]['style']['height'])) {
18315  $dom[$key]['height'] = $dom[$key]['style']['height'];
18316  }
18317  // check for text alignment
18318  if (isset($dom[$key]['style']['text-align'])) {
18319  $dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align']{0});
18320  }
18321  // check for CSS border properties
18322  if (isset($dom[$key]['style']['border'])) {
18323  $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
18324  if (!empty($borderstyle)) {
18325  $dom[$key]['border']['LTRB'] = $borderstyle;
18326  }
18327  }
18328  if (isset($dom[$key]['style']['border-color'])) {
18329  $brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
18330  if (isset($brd_colors[3])) {
18331  $dom[$key]['border']['L']['color'] = $this->convertHTMLColorToDec($brd_colors[3]);
18332  }
18333  if (isset($brd_colors[1])) {
18334  $dom[$key]['border']['R']['color'] = $this->convertHTMLColorToDec($brd_colors[1]);
18335  }
18336  if (isset($brd_colors[0])) {
18337  $dom[$key]['border']['T']['color'] = $this->convertHTMLColorToDec($brd_colors[0]);
18338  }
18339  if (isset($brd_colors[2])) {
18340  $dom[$key]['border']['B']['color'] = $this->convertHTMLColorToDec($brd_colors[2]);
18341  }
18342  }
18343  if (isset($dom[$key]['style']['border-width'])) {
18344  $brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
18345  if (isset($brd_widths[3])) {
18346  $dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
18347  }
18348  if (isset($brd_widths[1])) {
18349  $dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
18350  }
18351  if (isset($brd_widths[0])) {
18352  $dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
18353  }
18354  if (isset($brd_widths[2])) {
18355  $dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
18356  }
18357  }
18358  if (isset($dom[$key]['style']['border-style'])) {
18359  $brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
18360  if (isset($brd_styles[3])) {
18361  $dom[$key]['border']['L']['cap'] = 'square';
18362  $dom[$key]['border']['L']['join'] = 'miter';
18363  $dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
18364  if ($dom[$key]['border']['L']['dash'] < 0) {
18365  $dom[$key]['border']['L'] = array();
18366  }
18367  }
18368  if (isset($brd_styles[1])) {
18369  $dom[$key]['border']['R']['cap'] = 'square';
18370  $dom[$key]['border']['R']['join'] = 'miter';
18371  $dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
18372  if ($dom[$key]['border']['R']['dash'] < 0) {
18373  $dom[$key]['border']['R'] = array();
18374  }
18375  }
18376  if (isset($brd_styles[0])) {
18377  $dom[$key]['border']['T']['cap'] = 'square';
18378  $dom[$key]['border']['T']['join'] = 'miter';
18379  $dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
18380  if ($dom[$key]['border']['T']['dash'] < 0) {
18381  $dom[$key]['border']['T'] = array();
18382  }
18383  }
18384  if (isset($brd_styles[2])) {
18385  $dom[$key]['border']['B']['cap'] = 'square';
18386  $dom[$key]['border']['B']['join'] = 'miter';
18387  $dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
18388  if ($dom[$key]['border']['B']['dash'] < 0) {
18389  $dom[$key]['border']['B'] = array();
18390  }
18391  }
18392  }
18393  $cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
18394  foreach ($cellside as $bsk => $bsv) {
18395  if (isset($dom[$key]['style']['border-'.$bsv])) {
18396  $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
18397  if (!empty($borderstyle)) {
18398  $dom[$key]['border'][$bsk] = $borderstyle;
18399  }
18400  }
18401  if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
18402  $dom[$key]['border'][$bsk]['color'] = $this->convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color']);
18403  }
18404  if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
18405  $dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
18406  }
18407  if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
18408  $dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
18409  if ($dom[$key]['border'][$bsk]['dash'] < 0) {
18410  $dom[$key]['border'][$bsk] = array();
18411  }
18412  }
18413  }
18414  // check for CSS padding properties
18415  if (isset($dom[$key]['style']['padding'])) {
18416  $dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
18417  } else {
18418  $dom[$key]['padding'] = $this->cell_padding;
18419  }
18420  foreach ($cellside as $psk => $psv) {
18421  if (isset($dom[$key]['style']['padding-'.$psv])) {
18422  $dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
18423  }
18424  }
18425  // check for CSS margin properties
18426  if (isset($dom[$key]['style']['margin'])) {
18427  $dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
18428  } else {
18429  $dom[$key]['margin'] = $this->cell_margin;
18430  }
18431  foreach ($cellside as $psk => $psv) {
18432  if (isset($dom[$key]['style']['margin-'.$psv])) {
18433  $dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
18434  }
18435  }
18436  // page-break-inside
18437  if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
18438  $dom[$key]['attribute']['nobr'] = 'true';
18439  }
18440  // page-break-before
18441  if (isset($dom[$key]['style']['page-break-before'])) {
18442  if ($dom[$key]['style']['page-break-before'] == 'always') {
18443  $dom[$key]['attribute']['pagebreak'] = 'true';
18444  } elseif ($dom[$key]['style']['page-break-before'] == 'left') {
18445  $dom[$key]['attribute']['pagebreak'] = 'left';
18446  } elseif ($dom[$key]['style']['page-break-before'] == 'right') {
18447  $dom[$key]['attribute']['pagebreak'] = 'right';
18448  }
18449  }
18450  // page-break-after
18451  if (isset($dom[$key]['style']['page-break-after'])) {
18452  if ($dom[$key]['style']['page-break-after'] == 'always') {
18453  $dom[$key]['attribute']['pagebreakafter'] = 'true';
18454  } elseif ($dom[$key]['style']['page-break-after'] == 'left') {
18455  $dom[$key]['attribute']['pagebreakafter'] = 'left';
18456  } elseif ($dom[$key]['style']['page-break-after'] == 'right') {
18457  $dom[$key]['attribute']['pagebreakafter'] = 'right';
18458  }
18459  }
18460  }
18461  if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
18462  $borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
18463  if (!empty($borderstyle)) {
18464  $dom[$key]['border']['LTRB'] = $borderstyle;
18465  }
18466  }
18467  // check for font tag
18468  if ($dom[$key]['value'] == 'font') {
18469  // font family
18470  if (isset($dom[$key]['attribute']['face'])) {
18471  $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
18472  }
18473  // font size
18474  if (isset($dom[$key]['attribute']['size'])) {
18475  if ($key > 0) {
18476  if ($dom[$key]['attribute']['size']{0} == '+') {
18477  $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
18478  } elseif ($dom[$key]['attribute']['size']{0} == '-') {
18479  $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
18480  } else {
18481  $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
18482  }
18483  } else {
18484  $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
18485  }
18486  }
18487  }
18488  // force natural alignment for lists
18489  if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
18490  AND (!isset($dom[$key]['align']) OR $this->empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
18491  if ($this->rtl) {
18492  $dom[$key]['align'] = 'R';
18493  } else {
18494  $dom[$key]['align'] = 'L';
18495  }
18496  }
18497  if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
18498  if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
18499  $dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
18500  }
18501  }
18502  if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
18503  $dom[$key]['fontstyle'] .= 'B';
18504  }
18505  if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
18506  $dom[$key]['fontstyle'] .= 'I';
18507  }
18508  if ($dom[$key]['value'] == 'u') {
18509  $dom[$key]['fontstyle'] .= 'U';
18510  }
18511  if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
18512  $dom[$key]['fontstyle'] .= 'D';
18513  }
18514  if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
18515  $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
18516  }
18517  if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
18518  $dom[$key]['fontname'] = $this->default_monospaced_font;
18519  }
18520  if (($dom[$key]['value']{0} == 'h') AND (intval($dom[$key]['value']{1}) > 0) AND (intval($dom[$key]['value']{1}) < 7)) {
18521  // headings h1, h2, h3, h4, h5, h6
18522  if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
18523  $headsize = (4 - intval($dom[$key]['value']{1})) * 2;
18524  $dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
18525  }
18526  if (!isset($dom[$key]['style']['font-weight'])) {
18527  $dom[$key]['fontstyle'] .= 'B';
18528  }
18529  }
18530  if (($dom[$key]['value'] == 'table')) {
18531  $dom[$key]['rows'] = 0; // number of rows
18532  $dom[$key]['trids'] = array(); // IDs of TR elements
18533  $dom[$key]['thead'] = ''; // table header rows
18534  }
18535  if (($dom[$key]['value'] == 'tr')) {
18536  $dom[$key]['cols'] = 0;
18537  if ($thead) {
18538  $dom[$key]['thead'] = true;
18539  // rows on thead block are printed as a separate table
18540  } else {
18541  $dom[$key]['thead'] = false;
18542  // store the number of rows on table element
18543  ++$dom[($dom[$key]['parent'])]['rows'];
18544  // store the TR elements IDs on table element
18545  array_push($dom[($dom[$key]['parent'])]['trids'], $key);
18546  }
18547  }
18548  if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
18549  if (isset($dom[$key]['attribute']['colspan'])) {
18550  $colspan = intval($dom[$key]['attribute']['colspan']);
18551  } else {
18552  $colspan = 1;
18553  }
18554  $dom[$key]['attribute']['colspan'] = $colspan;
18555  $dom[($dom[$key]['parent'])]['cols'] += $colspan;
18556  }
18557  // text direction
18558  if (isset($dom[$key]['attribute']['dir'])) {
18559  $dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
18560  }
18561  // set foreground color attribute
18562  if (isset($dom[$key]['attribute']['color']) AND (!$this->empty_string($dom[$key]['attribute']['color']))) {
18563  $dom[$key]['fgcolor'] = $this->convertHTMLColorToDec($dom[$key]['attribute']['color']);
18564  } elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
18565  $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
18566  }
18567  // set background color attribute
18568  if (isset($dom[$key]['attribute']['bgcolor']) AND (!$this->empty_string($dom[$key]['attribute']['bgcolor']))) {
18569  $dom[$key]['bgcolor'] = $this->convertHTMLColorToDec($dom[$key]['attribute']['bgcolor']);
18570  }
18571  // set stroke color attribute
18572  if (isset($dom[$key]['attribute']['strokecolor']) AND (!$this->empty_string($dom[$key]['attribute']['strokecolor']))) {
18573  $dom[$key]['strokecolor'] = $this->convertHTMLColorToDec($dom[$key]['attribute']['strokecolor']);
18574  }
18575  // check for width attribute
18576  if (isset($dom[$key]['attribute']['width'])) {
18577  $dom[$key]['width'] = $dom[$key]['attribute']['width'];
18578  }
18579  // check for height attribute
18580  if (isset($dom[$key]['attribute']['height'])) {
18581  $dom[$key]['height'] = $dom[$key]['attribute']['height'];
18582  }
18583  // check for text alignment
18584  if (isset($dom[$key]['attribute']['align']) AND (!$this->empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
18585  $dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align']{0});
18586  }
18587  // check for text rendering mode (the following attributes do not exist in HTML)
18588  if (isset($dom[$key]['attribute']['stroke'])) {
18589  // font stroke width
18590  $dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
18591  }
18592  if (isset($dom[$key]['attribute']['fill'])) {
18593  // font fill
18594  if ($dom[$key]['attribute']['fill'] == 'true') {
18595  $dom[$key]['fill'] = true;
18596  } else {
18597  $dom[$key]['fill'] = false;
18598  }
18599  }
18600  if (isset($dom[$key]['attribute']['clip'])) {
18601  // clipping mode
18602  if ($dom[$key]['attribute']['clip'] == 'true') {
18603  $dom[$key]['clip'] = true;
18604  } else {
18605  $dom[$key]['clip'] = false;
18606  }
18607  }
18608  } // end opening tag
18609  } else {
18610  // text
18611  $dom[$key]['tag'] = false;
18612  $dom[$key]['block'] = false;
18613  $element = str_replace('$nbsp;', $this->unichr(160), $element);
18614  $dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
18615  $dom[$key]['parent'] = end($level);
18616  }
18617  ++$elkey;
18618  ++$key;
18619  }
18620  return $dom;
18621  }
18622 
18630  protected function getSpaceString() {
18631  $spacestr = chr(32);
18632  if ($this->isUnicodeFont()) {
18633  $spacestr = chr(0).chr(32);
18634  }
18635  return $spacestr;
18636  }
18637 
18658  public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
18659  return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0);
18660  }
18661 
18674  public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
18675  $gvars = $this->getGraphicVars();
18676  // store current values
18677  $prev_cell_margin = $this->cell_margin;
18678  $prev_cell_padding = $this->cell_padding;
18679  $prevPage = $this->page;
18680  $prevlMargin = $this->lMargin;
18681  $prevrMargin = $this->rMargin;
18682  $curfontname = $this->FontFamily;
18683  $curfontstyle = $this->FontStyle;
18684  $curfontsize = $this->FontSizePt;
18685  $curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
18686  $curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
18687  $curfontstretcing = $this->font_stretching;
18688  $curfontkerning = $this->font_spacing;
18689  $this->newline = true;
18690  $newline = true;
18691  $startlinepage = $this->page;
18692  $minstartliney = $this->y;
18693  $maxbottomliney = 0;
18694  $startlinex = $this->x;
18695  $startliney = $this->y;
18696  $yshift = 0;
18697  $loop = 0;
18698  $curpos = 0;
18699  $this_method_vars = array();
18700  $undo = false;
18701  $fontaligned = false;
18702  $reverse_dir = false; // true when the text direction is reversed
18703  $this->premode = false;
18704  if ($this->inxobj) {
18705  // we are inside an XObject template
18706  $pask = count($this->xobjects[$this->xobjid]['annotations']);
18707  } elseif (isset($this->PageAnnots[$this->page])) {
18708  $pask = count($this->PageAnnots[$this->page]);
18709  } else {
18710  $pask = 0;
18711  }
18712  if ($this->inxobj) {
18713  // we are inside an XObject template
18714  $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
18715  } elseif (!$this->InFooter) {
18716  if (isset($this->footerlen[$this->page])) {
18717  $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18718  } else {
18719  $this->footerpos[$this->page] = $this->pagelen[$this->page];
18720  }
18721  $startlinepos = $this->footerpos[$this->page];
18722  } else {
18723  // we are inside the footer
18724  $startlinepos = $this->pagelen[$this->page];
18725  }
18726  $lalign = $align;
18727  $plalign = $align;
18728  if ($this->rtl) {
18729  $w = $this->x - $this->lMargin;
18730  } else {
18731  $w = $this->w - $this->rMargin - $this->x;
18732  }
18733  $w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
18734  if ($cell) {
18735  if ($this->rtl) {
18736  $this->x -= $this->cell_padding['R'];
18737  $this->lMargin += $this->cell_padding['R'];
18738  } else {
18739  $this->x += $this->cell_padding['L'];
18740  $this->rMargin += $this->cell_padding['L'];
18741  }
18742  }
18743  if ($this->customlistindent >= 0) {
18744  $this->listindent = $this->customlistindent;
18745  } else {
18746  $this->listindent = $this->GetStringWidth('0000');
18747  }
18748  $this->listindentlevel = 0;
18749  // save previous states
18750  $prev_cell_height_ratio = $this->cell_height_ratio;
18751  $prev_listnum = $this->listnum;
18752  $prev_listordered = $this->listordered;
18753  $prev_listcount = $this->listcount;
18754  $prev_lispacer = $this->lispacer;
18755  $this->listnum = 0;
18756  $this->listordered = array();
18757  $this->listcount = array();
18758  $this->lispacer = '';
18759  if (($this->empty_string($this->lasth)) OR ($reseth)) {
18760  // reset row height
18761  $this->resetLastH();
18762  }
18763  $dom = $this->getHtmlDomArray($html);
18764  $maxel = count($dom);
18765  $key = 0;
18766  while ($key < $maxel) {
18767  if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
18768  // check for pagebreak
18769  if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
18770  // add a page (or trig AcceptPageBreak() for multicolumn mode)
18771  $this->checkPageBreak($this->PageBreakTrigger + 1);
18772  }
18773  if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
18774  OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
18775  // add a page (or trig AcceptPageBreak() for multicolumn mode)
18776  $this->checkPageBreak($this->PageBreakTrigger + 1);
18777  }
18778  }
18779  if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
18780  if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
18781  $dom[$key]['attribute']['nobr'] = false;
18782  } else {
18783  // store current object
18784  $this->startTransaction();
18785  // save this method vars
18786  $this_method_vars['html'] = $html;
18787  $this_method_vars['ln'] = $ln;
18788  $this_method_vars['fill'] = $fill;
18789  $this_method_vars['reseth'] = $reseth;
18790  $this_method_vars['cell'] = $cell;
18791  $this_method_vars['align'] = $align;
18792  $this_method_vars['gvars'] = $gvars;
18793  $this_method_vars['prevPage'] = $prevPage;
18794  $this_method_vars['prev_cell_margin'] = $prev_cell_margin;
18795  $this_method_vars['prev_cell_padding'] = $prev_cell_padding;
18796  $this_method_vars['prevlMargin'] = $prevlMargin;
18797  $this_method_vars['prevrMargin'] = $prevrMargin;
18798  $this_method_vars['curfontname'] = $curfontname;
18799  $this_method_vars['curfontstyle'] = $curfontstyle;
18800  $this_method_vars['curfontsize'] = $curfontsize;
18801  $this_method_vars['curfontascent'] = $curfontascent;
18802  $this_method_vars['curfontdescent'] = $curfontdescent;
18803  $this_method_vars['curfontstretcing'] = $curfontstretcing;
18804  $this_method_vars['curfontkerning'] = $curfontkerning;
18805  $this_method_vars['minstartliney'] = $minstartliney;
18806  $this_method_vars['maxbottomliney'] = $maxbottomliney;
18807  $this_method_vars['yshift'] = $yshift;
18808  $this_method_vars['startlinepage'] = $startlinepage;
18809  $this_method_vars['startlinepos'] = $startlinepos;
18810  $this_method_vars['startlinex'] = $startlinex;
18811  $this_method_vars['startliney'] = $startliney;
18812  $this_method_vars['newline'] = $newline;
18813  $this_method_vars['loop'] = $loop;
18814  $this_method_vars['curpos'] = $curpos;
18815  $this_method_vars['pask'] = $pask;
18816  $this_method_vars['lalign'] = $lalign;
18817  $this_method_vars['plalign'] = $plalign;
18818  $this_method_vars['w'] = $w;
18819  $this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
18820  $this_method_vars['prev_listnum'] = $prev_listnum;
18821  $this_method_vars['prev_listordered'] = $prev_listordered;
18822  $this_method_vars['prev_listcount'] = $prev_listcount;
18823  $this_method_vars['prev_lispacer'] = $prev_lispacer;
18824  $this_method_vars['fontaligned'] = $fontaligned;
18825  $this_method_vars['key'] = $key;
18826  $this_method_vars['dom'] = $dom;
18827  }
18828  }
18829  // print THEAD block
18830  if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
18831  if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !$this->empty_string($dom[$dom[$key]['parent']]['thead'])) {
18832  $this->inthead = true;
18833  // print table header (thead)
18834  $this->writeHTML($this->thead, false, false, false, false, '');
18835  // check if we are on a new page or on a new column
18836  if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
18837  // we are on a new page or on a new column and the total object height is less than the available vertical space.
18838  // restore previous object
18839  $this->rollbackTransaction(true);
18840  // restore previous values
18841  foreach ($this_method_vars as $vkey => $vval) {
18842  $$vkey = $vval;
18843  }
18844  // disable table header
18845  $tmp_thead = $this->thead;
18846  $this->thead = '';
18847  // add a page (or trig AcceptPageBreak() for multicolumn mode)
18848  $pre_y = $this->y;
18849  if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
18850  // fix for multicolumn mode
18851  $startliney = $this->y;
18852  }
18853  $this->start_transaction_page = $this->page;
18854  $this->start_transaction_y = $this->y;
18855  // restore table header
18856  $this->thead = $tmp_thead;
18857  // fix table border properties
18858  if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
18859  $tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
18860  } else {
18861  $tmp_cellspacing = 0;
18862  }
18863  $dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
18864  $dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
18865  $dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
18866  $xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
18867  $dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
18868  $dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
18869  // print table header (thead)
18870  $this->writeHTML($this->thead, false, false, false, false, '');
18871  }
18872  }
18873  // move $key index forward to skip THEAD block
18874  while ( ($key < $maxel) AND (!(
18875  ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
18876  OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
18877  ++$key;
18878  }
18879  }
18880  if ($dom[$key]['tag'] OR ($key == 0)) {
18881  if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
18882  $dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
18883  }
18884  // vertically align image in line
18885  if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
18886  // get image height
18887  $imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], $this->lasth, 'px');
18888  // check for automatic line break
18889  $autolinebreak = false;
18890  if (isset($dom[$key]['width']) AND ($dom[$key]['width'] > 0)) {
18891  $imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], 1, 'px', false);
18892  if (($this->rtl AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
18893  OR (!$this->rtl AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R'])))) {
18894  // add automatic line break
18895  $autolinebreak = true;
18896  $this->Ln('', $cell);
18897  // go back to evaluate this line break
18898  --$key;
18899  }
18900  }
18901  if (!$autolinebreak) {
18902  if (!$this->InFooter) {
18903  $pre_y = $this->y;
18904  // check for page break
18905  if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
18906  // fix for multicolumn mode
18907  $startliney = $this->y;
18908  }
18909  }
18910  if ($this->page > $startlinepage) {
18911  // fix line splitted over two pages
18912  if (isset($this->footerlen[$startlinepage])) {
18913  $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18914  }
18915  // line to be moved one page forward
18916  $pagebuff = $this->getPageBuffer($startlinepage);
18917  $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
18918  $tstart = substr($pagebuff, 0, $startlinepos);
18919  $tend = substr($this->getPageBuffer($startlinepage), $curpos);
18920  // remove line from previous page
18921  $this->setPageBuffer($startlinepage, $tstart.''.$tend);
18922  $pagebuff = $this->getPageBuffer($this->page);
18923  $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
18924  $tend = substr($pagebuff, $this->cntmrk[$this->page]);
18925  // add line start to current page
18926  $yshift = $minstartliney - $this->y;
18927  if ($fontaligned) {
18928  $yshift += ($curfontsize / $this->k);
18929  }
18930  $try = sprintf('1 0 0 1 0 %.3F cm', ($yshift * $this->k));
18931  $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
18932  // shift the annotations and links
18933  if (isset($this->PageAnnots[$this->page])) {
18934  $next_pask = count($this->PageAnnots[$this->page]);
18935  } else {
18936  $next_pask = 0;
18937  }
18938  if (isset($this->PageAnnots[$startlinepage])) {
18939  foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
18940  if ($pak >= $pask) {
18941  $this->PageAnnots[$this->page][] = $pac;
18942  unset($this->PageAnnots[$startlinepage][$pak]);
18943  $npak = count($this->PageAnnots[$this->page]) - 1;
18944  $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
18945  }
18946  }
18947  }
18948  $pask = $next_pask;
18949  $startlinepos = $this->cntmrk[$this->page];
18950  $startlinepage = $this->page;
18951  $startliney = $this->y;
18952  $this->newline = false;
18953  }
18954  $this->y += ((($curfontsize * $this->cell_height_ratio / $this->k) + $curfontascent - $curfontdescent) / 2) - $imgh;
18955  $minstartliney = min($this->y, $minstartliney);
18956  $maxbottomliney = ($startliney + ($this->FontSize * $this->cell_height_ratio));
18957  }
18958  } elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
18959  // account for different font size
18960  $pfontname = $curfontname;
18961  $pfontstyle = $curfontstyle;
18962  $pfontsize = $curfontsize;
18963  $fontname = isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname;
18964  $fontstyle = isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle;
18965  $fontsize = isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize;
18966  $fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
18967  $fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
18968  if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize) OR ($this->cell_height_ratio != $dom[$key]['line-height'])) {
18969  if ((!$this->newline) AND ($key < ($maxel - 1))
18970  AND ((is_numeric($fontsize) AND ($fontsize >= 0) AND is_numeric($curfontsize) AND ($curfontsize >= 0) AND ($fontsize != $curfontsize))
18971  OR ($this->cell_height_ratio != $dom[$key]['line-height']))) {
18972  if ($this->page > $startlinepage) {
18973  // fix lines splitted over two pages
18974  if (isset($this->footerlen[$startlinepage])) {
18975  $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18976  }
18977  // line to be moved one page forward
18978  $pagebuff = $this->getPageBuffer($startlinepage);
18979  $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
18980  $tstart = substr($pagebuff, 0, $startlinepos);
18981  $tend = substr($this->getPageBuffer($startlinepage), $curpos);
18982  // remove line start from previous page
18983  $this->setPageBuffer($startlinepage, $tstart.''.$tend);
18984  $pagebuff = $this->getPageBuffer($this->page);
18985  $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
18986  $tend = substr($pagebuff, $this->cntmrk[$this->page]);
18987  // add line start to current page
18988  $yshift = $minstartliney - $this->y;
18989  $try = sprintf('1 0 0 1 0 %.3F cm', ($yshift * $this->k));
18990  $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
18991  // shift the annotations and links
18992  if (isset($this->PageAnnots[$this->page])) {
18993  $next_pask = count($this->PageAnnots[$this->page]);
18994  } else {
18995  $next_pask = 0;
18996  }
18997  if (isset($this->PageAnnots[$startlinepage])) {
18998  foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
18999  if ($pak >= $pask) {
19000  $this->PageAnnots[$this->page][] = $pac;
19001  unset($this->PageAnnots[$startlinepage][$pak]);
19002  $npak = count($this->PageAnnots[$this->page]) - 1;
19003  $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
19004  }
19005  }
19006  }
19007  $pask = $next_pask;
19008  $startlinepos = $this->cntmrk[$this->page];
19009  $startlinepage = $this->page;
19010  $startliney = $this->y;
19011  }
19012  if (!isset($dom[$key]['line-height'])) {
19013  $dom[$key]['line-height'] = $this->cell_height_ratio;
19014  }
19015  if (!$dom[$key]['block']) {
19016  $this->y += (((($curfontsize * $this->cell_height_ratio ) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
19017  if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
19018  $minstartliney = min($this->y, $minstartliney);
19019  $maxbottomliney = max(($this->y + (($fontsize * $this->cell_height_ratio) / $this->k)), $maxbottomliney);
19020  }
19021  }
19022  $this->cell_height_ratio = $dom[$key]['line-height'];
19023  $fontaligned = true;
19024  }
19025  $this->SetFont($fontname, $fontstyle, $fontsize);
19026  // reset row height
19027  $this->resetLastH();
19028  $curfontname = $fontname;
19029  $curfontstyle = $fontstyle;
19030  $curfontsize = $fontsize;
19031  $curfontascent = $fontascent;
19032  $curfontdescent = $fontdescent;
19033  }
19034  }
19035  // set text rendering mode
19036  $textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
19037  $textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
19038  $textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
19039  $this->setTextRenderingMode($textstroke, $textfill, $textclip);
19040  if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
19041  $this->setFontStretching($dom[$key]['font-stretch']);
19042  }
19043  if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
19044  $this->setFontSpacing($dom[$key]['letter-spacing']);
19045  }
19046  if (($plalign == 'J') AND $dom[$key]['block']) {
19047  $plalign = '';
19048  }
19049  // get current position on page buffer
19050  $curpos = $this->pagelen[$startlinepage];
19051  if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
19052  $this->SetFillColorArray($dom[$key]['bgcolor']);
19053  $wfill = true;
19054  } else {
19055  $wfill = $fill | false;
19056  }
19057  if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
19058  $this->SetTextColorArray($dom[$key]['fgcolor']);
19059  }
19060  if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
19061  $this->SetDrawColorArray($dom[$key]['strokecolor']);
19062  }
19063  if (isset($dom[$key]['align'])) {
19064  $lalign = $dom[$key]['align'];
19065  }
19066  if ($this->empty_string($lalign)) {
19067  $lalign = $align;
19068  }
19069  }
19070  // align lines
19071  if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
19072  $newline = true;
19073  $fontaligned = false;
19074  // we are at the beginning of a new line
19075  if (isset($startlinex)) {
19076  $yshift = $minstartliney - $startliney;
19077  if (($yshift > 0) OR ($this->page > $startlinepage)) {
19078  $yshift = 0;
19079  }
19080  $t_x = 0;
19081  // the last line must be shifted to be aligned as requested
19082  $linew = abs($this->endlinex - $startlinex);
19083  if ($this->inxobj) {
19084  // we are inside an XObject template
19085  $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
19086  if (isset($opentagpos)) {
19087  $midpos = $opentagpos;
19088  } else {
19089  $midpos = 0;
19090  }
19091  if ($midpos > 0) {
19092  $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
19093  $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
19094  } else {
19095  $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
19096  $pend = '';
19097  }
19098  } else {
19099  $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
19100  if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
19101  $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
19102  $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
19103  } elseif (isset($opentagpos)) {
19104  $midpos = $opentagpos;
19105  } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
19106  $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
19107  $midpos = $this->footerpos[$startlinepage];
19108  } else {
19109  $midpos = 0;
19110  }
19111  if ($midpos > 0) {
19112  $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
19113  $pend = substr($this->getPageBuffer($startlinepage), $midpos);
19114  } else {
19115  $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
19116  $pend = '';
19117  }
19118  }
19119  if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
19120  // calculate shifting amount
19121  $tw = $w;
19122  if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
19123  $tw += $this->cell_padding['R'];
19124  }
19125  if ($this->lMargin != $prevlMargin) {
19126  $tw += ($prevlMargin - $this->lMargin);
19127  }
19128  if ($this->rMargin != $prevrMargin) {
19129  $tw += ($prevrMargin - $this->rMargin);
19130  }
19131  $one_space_width = $this->GetStringWidth(chr(32));
19132  $no = 0; // number of spaces on a line contained on a single block
19133  if ($this->isRTLTextDir()) { // RTL
19134  // remove left space if exist
19135  $pos1 = $this->revstrpos($pmid, '[(');
19136  if ($pos1 > 0) {
19137  $pos1 = intval($pos1);
19138  if ($this->isUnicodeFont()) {
19139  $pos2 = intval($this->revstrpos($pmid, '[('.chr(0).chr(32)));
19140  $spacelen = 2;
19141  } else {
19142  $pos2 = intval($this->revstrpos($pmid, '[('.chr(32)));
19143  $spacelen = 1;
19144  }
19145  if ($pos1 == $pos2) {
19146  $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
19147  if (substr($pmid, $pos1, 4) == '[()]') {
19148  $linew -= $one_space_width;
19149  } elseif ($pos1 == strpos($pmid, '[(')) {
19150  $no = 1;
19151  }
19152  }
19153  }
19154  } else { // LTR
19155  // remove right space if exist
19156  $pos1 = $this->revstrpos($pmid, ')]');
19157  if ($pos1 > 0) {
19158  $pos1 = intval($pos1);
19159  if ($this->isUnicodeFont()) {
19160  $pos2 = intval($this->revstrpos($pmid, chr(0).chr(32).')]')) + 2;
19161  $spacelen = 2;
19162  } else {
19163  $pos2 = intval($this->revstrpos($pmid, chr(32).')]')) + 1;
19164  $spacelen = 1;
19165  }
19166  if ($pos1 == $pos2) {
19167  $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
19168  $linew -= $one_space_width;
19169  }
19170  }
19171  }
19172  $mdiff = ($tw - $linew);
19173  if ($plalign == 'C') {
19174  if ($this->rtl) {
19175  $t_x = -($mdiff / 2);
19176  } else {
19177  $t_x = ($mdiff / 2);
19178  }
19179  } elseif ($plalign == 'R') {
19180  // right alignment on LTR document
19181  $t_x = $mdiff;
19182  } elseif ($plalign == 'L') {
19183  // left alignment on RTL document
19184  $t_x = -$mdiff;
19185  } elseif (($plalign == 'J') AND ($plalign == $lalign)) {
19186  // Justification
19187  if ($this->isRTLTextDir()) {
19188  // align text on the left
19189  $t_x = -$mdiff;
19190  }
19191  $ns = 0; // number of spaces
19192  $pmidtemp = $pmid;
19193  // escape special characters
19194  $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
19195  $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
19196  // search spaces
19197  if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
19198  $spacestr = $this->getSpaceString();
19199  $maxkk = count($lnstring[1]) - 1;
19200  for ($kk=0; $kk <= $maxkk; ++$kk) {
19201  // restore special characters
19202  $lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
19203  $lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
19204  // store number of spaces on the strings
19205  $lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
19206  // count total spaces on line
19207  $ns += $lnstring[2][$kk];
19208  $lnstring[3][$kk] = $ns;
19209  }
19210  if ($ns == 0) {
19211  $ns = 1;
19212  }
19213  // calculate additional space to add to each existing space
19214  $spacewidth = ($mdiff / ($ns - $no)) * $this->k;
19215  $spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
19216  if ($this->font_spacing != 0) {
19217  // fixed spacing mode
19218  $osw = -1000 * $this->font_spacing / $this->FontSize;
19219  $spacewidthu += $osw;
19220  }
19221  $nsmax = $ns;
19222  $ns = 0;
19223  reset($lnstring);
19224  $offset = 0;
19225  $strcount = 0;
19226  $prev_epsposbeg = 0;
19227  $textpos = 0;
19228  if ($this->isRTLTextDir()) {
19229  $textpos = $this->wPt;
19230  }
19231  global $spacew;
19232  while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
19233  // check if we are inside a string section '[( ... )]'
19234  $stroffset = strpos($pmid, '[(', $offset);
19235  if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
19236  // set offset to the end of string section
19237  $offset = strpos($pmid, ')]', $stroffset);
19238  while (($offset !== false) AND ($pmid{($offset - 1)} == '\\')) {
19239  $offset = strpos($pmid, ')]', ($offset + 1));
19240  }
19241  if ($offset === false) {
19242  $this->Error('HTML Justification: malformed PDF code.');
19243  }
19244  continue;
19245  }
19246  if ($this->isRTLTextDir()) {
19247  $spacew = ($spacewidth * ($nsmax - $ns));
19248  } else {
19249  $spacew = ($spacewidth * $ns);
19250  }
19251  $offset = $strpiece[2][1] + strlen($strpiece[2][0]);
19252  $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
19253  $epsposend = strpos($pmid, $this->epsmarker.'Q', $offset) + strlen($this->epsmarker.'Q');
19254  if ((($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend))
19255  OR (($epsposbeg === false) AND ($epsposend > 0) AND ($offset < $epsposend))) {
19256  // shift EPS images
19257  $trx = sprintf('1 0 0 1 %.3F 0 cm', $spacew);
19258  $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
19259  $pmid_b = substr($pmid, 0, $epsposbeg);
19260  $pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
19261  $pmid_e = substr($pmid, $epsposend);
19262  $pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
19263  $offset = $epsposend;
19264  continue;
19265 
19266  }
19267  $prev_epsposbeg = $epsposbeg;
19268  $currentxpos = 0;
19269  // shift blocks of code
19270  switch ($strpiece[2][0]) {
19271  case 'Td':
19272  case 'cm':
19273  case 'm':
19274  case 'l': {
19275  // get current X position
19276  preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
19277  $currentxpos = $xmatches[1];
19278  $textpos = $currentxpos;
19279  if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
19280  $ns = $lnstring[3][$strcount];
19281  if ($this->isRTLTextDir()) {
19282  $spacew = ($spacewidth * ($nsmax - $ns));
19283  }
19284  ++$strcount;
19285  }
19286  // justify block
19287  $pmid = preg_replace_callback('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x',
19288  create_function('$matches', 'global $spacew;
19289  $newx = sprintf("%.2F",(floatval($matches[1]) + $spacew));
19290  return "".$newx." ".$matches[2]." x*#!#*x".$matches[3].$matches[4];'), $pmid, 1);
19291  break;
19292  }
19293  case 're': {
19294  // justify block
19295  if (!$this->empty_string($this->lispacer)) {
19296  $this->lispacer = '';
19297  continue;
19298  }
19299  preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
19300  $currentxpos = $xmatches[1];
19301  global $x_diff, $w_diff;
19302  $x_diff = 0;
19303  $w_diff = 0;
19304  if ($this->isRTLTextDir()) { // RTL
19305  if ($currentxpos < $textpos) {
19306  $x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
19307  $w_diff = ($spacewidth * $lnstring[2][$strcount]);
19308  } else {
19309  if ($strcount > 0) {
19310  $x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
19311  $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
19312  }
19313  }
19314  } else { // LTR
19315  if ($currentxpos > $textpos) {
19316  if ($strcount > 0) {
19317  $x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
19318  }
19319  $w_diff = ($spacewidth * $lnstring[2][$strcount]);
19320  } else {
19321  if ($strcount > 1) {
19322  $x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
19323  }
19324  if ($strcount > 0) {
19325  $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
19326  }
19327  }
19328  }
19329  $pmid = preg_replace_callback('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x',
19330  create_function('$matches', 'global $x_diff, $w_diff;
19331  $newx = sprintf("%.2F",(floatval($matches[1]) + $x_diff));
19332  $neww = sprintf("%.2F",(floatval($matches[3]) + $w_diff));
19333  return "".$newx." ".$matches[2]." ".$neww." ".$matches[4]." x*#!#*x".$matches[5].$matches[6];'), $pmid, 1);
19334  break;
19335  }
19336  case 'c': {
19337  // get current X position
19338  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);
19339  $currentxpos = $xmatches[1];
19340  // justify block
19341  $pmid = preg_replace_callback('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$xmatches[4].')[\s]('.$xmatches[5].')[\s]('.$strpiece[1][0].')[\s](c)([\s]*)/x',
19342  create_function('$matches', 'global $spacew;
19343  $newx1 = sprintf("%.3F",(floatval($matches[1]) + $spacew));
19344  $newx2 = sprintf("%.3F",(floatval($matches[3]) + $spacew));
19345  $newx3 = sprintf("%.3F",(floatval($matches[5]) + $spacew));
19346  return "".$newx1." ".$matches[2]." ".$newx2." ".$matches[4]." ".$newx3." ".$matches[6]." x*#!#*x".$matches[7].$matches[8];'), $pmid, 1);
19347  break;
19348  }
19349  }
19350  // shift the annotations and links
19351  $cxpos = ($currentxpos / $this->k);
19352  $lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
19353  if ($this->inxobj) {
19354  // we are inside an XObject template
19355  foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
19356  if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
19357  if ($cxpos > $lmpos) {
19358  $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
19359  $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
19360  } else {
19361  $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
19362  }
19363  break;
19364  }
19365  }
19366  } elseif (isset($this->PageAnnots[$this->page])) {
19367  foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
19368  if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
19369  if ($cxpos > $lmpos) {
19370  $this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
19371  $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
19372  } else {
19373  $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
19374  }
19375  break;
19376  }
19377  }
19378  }
19379  } // end of while
19380  // remove markers
19381  $pmid = str_replace('x*#!#*x', '', $pmid);
19382  if ($this->isUnicodeFont()) {
19383  // multibyte characters
19384  $spacew = $spacewidthu;
19385  if ($this->font_stretching != 100) {
19386  // word spacing is affected by stretching
19387  $spacew /= ($this->font_stretching / 100);
19388  }
19389  $pmidtemp = $pmid;
19390  // escape special characters
19391  $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
19392  $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
19393  $pmid = preg_replace_callback("/\[\(([^\)]*)\)\]/x",
19394  create_function('$matches', 'global $spacew;
19395  $matches[1] = str_replace("#!#OP#!#", "(", $matches[1]);
19396  $matches[1] = str_replace("#!#CP#!#", ")", $matches[1]);
19397  return "[(".str_replace(chr(0).chr(32), ") ".sprintf("%.3F", $spacew)." (", $matches[1]).")]";'), $pmidtemp);
19398  if ($this->inxobj) {
19399  // we are inside an XObject template
19400  $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
19401  } else {
19402  $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
19403  }
19404  $endlinepos = strlen($pstart."\n".$pmid."\n");
19405  } else {
19406  // non-unicode (single-byte characters)
19407  if ($this->font_stretching != 100) {
19408  // word spacing (Tw) is affected by stretching
19409  $spacewidth /= ($this->font_stretching / 100);
19410  }
19411  $rs = sprintf('%.3F Tw', $spacewidth);
19412  $pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
19413  if ($this->inxobj) {
19414  // we are inside an XObject template
19415  $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
19416  } else {
19417  $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
19418  }
19419  $endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
19420  }
19421  }
19422  } // end of J
19423  } // end if $startlinex
19424  if (($t_x != 0) OR ($yshift < 0)) {
19425  // shift the line
19426  $trx = sprintf('1 0 0 1 %.3F %.3F cm', ($t_x * $this->k), ($yshift * $this->k));
19427  $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
19428  $endlinepos = strlen($pstart);
19429  if ($this->inxobj) {
19430  // we are inside an XObject template
19431  $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
19432  foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
19433  if ($pak >= $pask) {
19434  $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
19435  $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
19436  }
19437  }
19438  } else {
19439  $this->setPageBuffer($startlinepage, $pstart.$pend);
19440  // shift the annotations and links
19441  if (isset($this->PageAnnots[$this->page])) {
19442  foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
19443  if ($pak >= $pask) {
19444  $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
19445  $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
19446  }
19447  }
19448  }
19449  }
19450  $this->y -= $yshift;
19451  }
19452  }
19453  $pbrk = $this->checkPageBreak($this->lasth);
19454  $this->newline = false;
19455  $startlinex = $this->x;
19456  $startliney = $this->y;
19457  if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
19458  $startliney -= ((0.3 * $this->FontSizePt) / $this->k);
19459  } elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
19460  $startliney -= (($this->FontSizePt / 0.7) / $this->k);
19461  } else {
19462  $minstartliney = $startliney;
19463  $maxbottomliney = ($this->y + (($fontsize * $this->cell_height_ratio) / $this->k));
19464  }
19465  $startlinepage = $this->page;
19466  if (isset($endlinepos) AND (!$pbrk)) {
19467  $startlinepos = $endlinepos;
19468  } else {
19469  if ($this->inxobj) {
19470  // we are inside an XObject template
19471  $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
19472  } elseif (!$this->InFooter) {
19473  if (isset($this->footerlen[$this->page])) {
19474  $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
19475  } else {
19476  $this->footerpos[$this->page] = $this->pagelen[$this->page];
19477  }
19478  $startlinepos = $this->footerpos[$this->page];
19479  } else {
19480  $startlinepos = $this->pagelen[$this->page];
19481  }
19482  }
19483  unset($endlinepos);
19484  $plalign = $lalign;
19485  if (isset($this->PageAnnots[$this->page])) {
19486  $pask = count($this->PageAnnots[$this->page]);
19487  } else {
19488  $pask = 0;
19489  }
19490  if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table') AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
19491  $this->SetFont($fontname, $fontstyle, $fontsize);
19492  if ($wfill) {
19493  $this->SetFillColorArray($this->bgcolor);
19494  }
19495  }
19496  } // end newline
19497  if (isset($opentagpos)) {
19498  unset($opentagpos);
19499  }
19500  if ($dom[$key]['tag']) {
19501  if ($dom[$key]['opening']) {
19502  // get text indentation (if any)
19503  if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
19504  $this->textindent = $dom[$key]['text-indent'];
19505  $this->newline = true;
19506  }
19507  // table
19508  if ($dom[$key]['value'] == 'table') {
19509  // available page width
19510  if ($this->rtl) {
19511  $wtmp = $this->x - $this->lMargin;
19512  } else {
19513  $wtmp = $this->w - $this->rMargin - $this->x;
19514  }
19515  if (isset($dom[$key]['attribute']['cellspacing'])) {
19516  $cellspacing = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
19517  } else {
19518  $cellspacing = 0;
19519  }
19520  // table width
19521  if (isset($dom[$key]['width'])) {
19522  $table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
19523  } else {
19524  $table_width = $wtmp;
19525  }
19526  $table_width -= (2 * $cellspacing);
19527  if (!$this->inthead) {
19528  $this->y += $cellspacing;
19529  }
19530  if ($this->rtl) {
19531  $cellspacingx = -$cellspacing;
19532  } else {
19533  $cellspacingx = $cellspacing;
19534  }
19535  // total table width without cellspaces
19536  $table_columns_width = ($table_width - ($cellspacing * ($dom[$key]['cols'] - 1)));
19537  // minimum column width
19538  $table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
19539  // array of custom column widths
19540  $table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
19541  }
19542  // table row
19543  if ($dom[$key]['value'] == 'tr') {
19544  // reset column counter
19545  $colid = 0;
19546  }
19547  // table cell
19548  if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
19549  $trid = $dom[$key]['parent'];
19550  $table_el = $dom[$trid]['parent'];
19551  if (!isset($dom[$table_el]['cols'])) {
19552  $dom[$table_el]['cols'] = $dom[$trid]['cols'];
19553  }
19554  // store border info
19555  $tdborder = 0;
19556  if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
19557  $tdborder = $dom[$key]['border'];
19558  }
19559  $colspan = $dom[$key]['attribute']['colspan'];
19560  $old_cell_padding = $this->cell_padding;
19561  if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
19562  $current_cell_padding = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
19563  } else {
19564  $current_cell_padding = 0;
19565  }
19566  $this->SetCellPadding($current_cell_padding);
19567  if (isset($dom[$key]['height'])) {
19568  // minimum cell height
19569  $cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
19570  } else {
19571  $cellh = 0;
19572  }
19573  if (isset($dom[$key]['content'])) {
19574  $cell_content = $dom[$key]['content'];
19575  } else {
19576  $cell_content = '&nbsp;';
19577  }
19578  $tagtype = $dom[$key]['value'];
19579  $parentid = $key;
19580  while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
19581  // move $key index forward
19582  ++$key;
19583  }
19584  if (!isset($dom[$trid]['startpage'])) {
19585  $dom[$trid]['startpage'] = $this->page;
19586  } else {
19587  $this->setPage($dom[$trid]['startpage']);
19588  }
19589  if (!isset($dom[$trid]['startcolumn'])) {
19590  $dom[$trid]['startcolumn'] = $this->current_column;
19591  } elseif ($this->current_column != $dom[$trid]['startcolumn']) {
19592  $tmpx = $this->x;
19593  $this->selectColumn($dom[$trid]['startcolumn']);
19594  $this->x = $tmpx;
19595  }
19596  if (!isset($dom[$trid]['starty'])) {
19597  $dom[$trid]['starty'] = $this->y;
19598  } else {
19599  $this->y = $dom[$trid]['starty'];
19600  }
19601  if (!isset($dom[$trid]['startx'])) {
19602  $dom[$trid]['startx'] = $this->x;
19603  $this->x += $cellspacingx;
19604  } else {
19605  $this->x += ($cellspacingx / 2);
19606  }
19607  if (isset($dom[$parentid]['attribute']['rowspan'])) {
19608  $rowspan = intval($dom[$parentid]['attribute']['rowspan']);
19609  } else {
19610  $rowspan = 1;
19611  }
19612  // skip row-spanned cells started on the previous rows
19613  if (isset($dom[$table_el]['rowspans'])) {
19614  $rsk = 0;
19615  $rskmax = count($dom[$table_el]['rowspans']);
19616  while ($rsk < $rskmax) {
19617  $trwsp = $dom[$table_el]['rowspans'][$rsk];
19618  $rsstartx = $trwsp['startx'];
19619  $rsendx = $trwsp['endx'];
19620  // account for margin changes
19621  if ($trwsp['startpage'] < $this->page) {
19622  if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
19623  $dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
19624  $rsstartx -= $dl;
19625  $rsendx -= $dl;
19626  } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
19627  $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
19628  $rsstartx += $dl;
19629  $rsendx += $dl;
19630  }
19631  }
19632  if (($trwsp['rowspan'] > 0)
19633  AND ($rsstartx > ($this->x - $cellspacing - $current_cell_padding - $this->feps))
19634  AND ($rsstartx < ($this->x + $cellspacing + $current_cell_padding + $this->feps))
19635  AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
19636  // set the starting X position of the current cell
19637  $this->x = $rsendx + $cellspacingx;
19638  // increment column indicator
19639  $colid += $trwsp['colspan'];
19640  if (($trwsp['rowspan'] == 1)
19641  AND (isset($dom[$trid]['endy']))
19642  AND (isset($dom[$trid]['endpage']))
19643  AND (isset($dom[$trid]['endcolumn']))
19644  AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
19645  AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
19646  // set ending Y position for row
19647  $dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
19648  $dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
19649  }
19650  $rsk = 0;
19651  } else {
19652  ++$rsk;
19653  }
19654  }
19655  }
19656  if (isset($dom[$parentid]['width'])) {
19657  // user specified width
19658  $cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
19659  $tmpcw = ($cellw / $colspan);
19660  for ($i = 0; $i < $colspan; ++$i) {
19661  $table_colwidths[($colid + $i)] = $tmpcw;
19662  }
19663  } else {
19664  // inherit column width
19665  $cellw = 0;
19666  for ($i = 0; $i < $colspan; ++$i) {
19667  $cellw += $table_colwidths[($colid + $i)];
19668  }
19669  }
19670  $cellw += (($colspan - 1) * $cellspacing);
19671  // increment column indicator
19672  $colid += $colspan;
19673  // add rowspan information to table element
19674  if ($rowspan > 1) {
19675  $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));
19676  }
19677  $cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
19678  if ($rowspan > 1) {
19679  $dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
19680  }
19681  // push background colors
19682  if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
19683  $dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
19684  }
19685  // store border info
19686  if (isset($tdborder) AND !empty($tdborder)) {
19687  $dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
19688  }
19689  $prevLastH = $this->lasth;
19690  // store some info for multicolumn mode
19691  if ($this->rtl) {
19692  $this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
19693  } else {
19694  $this->colxshift['x'] = $this->x - $this->lMargin;
19695  }
19696  $this->colxshift['s'] = $cellspacing;
19697  $this->colxshift['p'] = $current_cell_padding;
19698  // ****** write the cell content ******
19699  $this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true);
19700  // restore some values
19701  $this->colxshift = array('x' => 0, 's' => 0, 'p' => 0);
19702  $this->lasth = $prevLastH;
19703  $this->cell_padding = $old_cell_padding;
19704  $dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
19705  // update the end of row position
19706  if ($rowspan <= 1) {
19707  if (isset($dom[$trid]['endy'])) {
19708  if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
19709  $dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
19710  } elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
19711  $dom[$trid]['endy'] = $this->y;
19712  }
19713  } else {
19714  $dom[$trid]['endy'] = $this->y;
19715  }
19716  if (isset($dom[$trid]['endpage'])) {
19717  $dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
19718  } else {
19719  $dom[$trid]['endpage'] = $this->page;
19720  }
19721  if (isset($dom[$trid]['endcolumn'])) {
19722  $dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
19723  } else {
19724  $dom[$trid]['endcolumn'] = $this->current_column;
19725  }
19726  } else {
19727  // account for row-spanned cells
19728  $dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
19729  $dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
19730  $dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
19731  $dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
19732  }
19733  if (isset($dom[$table_el]['rowspans'])) {
19734  // update endy and endpage on rowspanned cells
19735  foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19736  if ($trwsp['rowspan'] > 0) {
19737  if (isset($dom[$trid]['endpage'])) {
19738  if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
19739  $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
19740  } elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
19741  $dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
19742  $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
19743  $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
19744  } else {
19745  $dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
19746  }
19747  }
19748  }
19749  }
19750  }
19751  $this->x += ($cellspacingx / 2);
19752  } else {
19753  // opening tag (or self-closing tag)
19754  if (!isset($opentagpos)) {
19755  if ($this->inxobj) {
19756  // we are inside an XObject template
19757  $opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
19758  } elseif (!$this->InFooter) {
19759  if (isset($this->footerlen[$this->page])) {
19760  $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
19761  } else {
19762  $this->footerpos[$this->page] = $this->pagelen[$this->page];
19763  }
19764  $opentagpos = $this->footerpos[$this->page];
19765  }
19766  }
19767  $this->openHTMLTagHandler($dom, $key, $cell);
19768  }
19769  } else { // closing tag
19770  $prev_numpages = $this->numpages;
19771  $old_bordermrk = $this->bordermrk[$this->page];
19772  $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
19773  if ($this->bordermrk[$this->page] > $old_bordermrk) {
19774  $startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
19775  }
19776  if ($prev_numpages > $this->numpages) {
19777  $startlinepage = $this->page;
19778  }
19779  }
19780  } elseif (strlen($dom[$key]['value']) > 0) {
19781  // print list-item
19782  if (!$this->empty_string($this->lispacer) AND ($this->lispacer != '^')) {
19783  $this->SetFont($pfontname, $pfontstyle, $pfontsize);
19784  $this->resetLastH();
19785  $minstartliney = $this->y;
19786  $maxbottomliney = ($startliney + ($this->FontSize * $this->cell_height_ratio));
19787  $this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
19788  $this->SetFont($curfontname, $curfontstyle, $curfontsize);
19789  $this->resetLastH();
19790  if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
19791  $pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
19792  $pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
19793  $this->y += ((($pfontsize - $curfontsize) * $this->cell_height_ratio / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
19794  $minstartliney = min($this->y, $minstartliney);
19795  $maxbottomliney = max(($this->y + (($pfontsize * $this->cell_height_ratio) / $this->k)), $maxbottomliney);
19796  }
19797  }
19798  // text
19799  $this->htmlvspace = 0;
19800  if ((!$this->premode) AND $this->isRTLTextDir()) {
19801  // reverse spaces order
19802  $lsp = ''; // left spaces
19803  $rsp = ''; // right spaces
19804  if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
19805  $lsp = $matches[1];
19806  }
19807  if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
19808  $rsp = $matches[1];
19809  }
19810  $dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
19811  }
19812  if ($newline) {
19813  if (!$this->premode) {
19814  $prelen = strlen($dom[$key]['value']);
19815  if ($this->isRTLTextDir()) {
19816  // right trim except non-breaking space
19817  $dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
19818  } else {
19819  // left trim except non-breaking space
19820  $dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
19821  }
19822  $postlen = strlen($dom[$key]['value']);
19823  if (($postlen == 0) AND ($prelen > 0)) {
19824  $dom[$key]['trimmed_space'] = true;
19825  }
19826  }
19827  $newline = false;
19828  $firstblock = true;
19829  } else {
19830  $firstblock = false;
19831  // replace empty multiple spaces string with a single space
19832  $dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
19833  }
19834  $strrest = '';
19835  if ($this->rtl) {
19836  $this->x -= $this->textindent;
19837  } else {
19838  $this->x += $this->textindent;
19839  }
19840  if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
19841  if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
19842  // HTML <a> Link
19843  $hrefcolor = '';
19844  if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
19845  $hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
19846  }
19847  $hrefstyle = -1;
19848  if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
19849  $hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
19850  }
19851  $strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
19852  } else {
19853  $wadj = 0; // space to leave for block continuity
19854  $adjblks = 0; // number of blocks
19855  // check the next text blocks for continuity
19856  $nkey = ($key + 1);
19857  $write_block = true;
19858  $tmp_fontname = $this->FontFamily;
19859  $tmp_fontstyle = $this->FontStyle;
19860  $tmp_fontsize = $this->FontSizePt;
19861  while ($write_block AND isset($dom[$nkey])) {
19862  if ($dom[$nkey]['tag']) {
19863  if ($dom[$nkey]['block']) {
19864  // end of block
19865  $write_block = false;
19866  }
19867  $tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
19868  $tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
19869  $tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
19870  } else {
19871  $nextstr = preg_split('/'.$this->re_space['p'].'+/'.$this->re_space['m'], $dom[$nkey]['value']);
19872  if (isset($nextstr[0])) {
19873  $wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
19874  ++$adjblks;
19875  }
19876  if (isset($nextstr[1])) {
19877  $write_block = false;
19878  }
19879  }
19880  ++$nkey;
19881  }
19882  // check for reversed text direction
19883  if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl == 'L')) OR (!$this->rtl AND ($this->tmprtl == 'R')))) {
19884  // LTR text on RTL direction or RTL text on LTR direction
19885  $reverse_dir = true;
19886  $this->rtl = !$this->rtl;
19887  $revshift = ($this->GetStringWidth($dom[$key]['value']) + $wadj) + 0.000001; // add little quantity for rounding problems
19888  if ($this->rtl) {
19889  $this->x += $revshift;
19890  } else {
19891  $this->x -= $revshift;
19892  }
19893  $xws = $this->x;
19894  }
19895  // ****** write only until the end of the line and get the rest ******
19896  $strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
19897  // restore default direction
19898  if ($reverse_dir AND ($wadj == 0)) {
19899  $this->x = $xws;
19900  $this->rtl = !$this->rtl;
19901  $reverse_dir = false;
19902  }
19903  }
19904  }
19905  $this->textindent = 0;
19906  if (strlen($strrest) > 0) {
19907  // store the remaining string on the previous $key position
19908  $this->newline = true;
19909  if ($strrest == $dom[$key]['value']) {
19910  // used to avoid infinite loop
19911  ++$loop;
19912  } else {
19913  $loop = 0;
19914  }
19915  $dom[$key]['value'] = $strrest;
19916  if ($cell) {
19917  if ($this->rtl) {
19918  $this->x -= $this->cell_padding['R'];
19919  } else {
19920  $this->x += $this->cell_padding['L'];
19921  }
19922  }
19923  if ($loop < 3) {
19924  --$key;
19925  }
19926  } else {
19927  $loop = 0;
19928  }
19929  }
19930  ++$key;
19931  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')) {
19932  // check if we are on a new page or on a new column
19933  if ((!$undo) AND ($this->y < $this->start_transaction_y)) {
19934  // we are on a new page or on a new column and the total object height is less than the available vertical space.
19935  // restore previous object
19936  $this->rollbackTransaction(true);
19937  // restore previous values
19938  foreach ($this_method_vars as $vkey => $vval) {
19939  $$vkey = $vval;
19940  }
19941  // add a page (or trig AcceptPageBreak() for multicolumn mode)
19942  $pre_y = $this->y;
19943  if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
19944  $startliney = $this->y;
19945  }
19946  $undo = true; // avoid infinite loop
19947  } else {
19948  $undo = false;
19949  }
19950  }
19951  } // end for each $key
19952  // align the last line
19953  if (isset($startlinex)) {
19954  $yshift = $minstartliney - $startliney;
19955  if (($yshift > 0) OR ($this->page > $startlinepage)) {
19956  $yshift = 0;
19957  }
19958  $t_x = 0;
19959  // the last line must be shifted to be aligned as requested
19960  $linew = abs($this->endlinex - $startlinex);
19961  if ($this->inxobj) {
19962  // we are inside an XObject template
19963  $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
19964  if (isset($opentagpos)) {
19965  $midpos = $opentagpos;
19966  } else {
19967  $midpos = 0;
19968  }
19969  if ($midpos > 0) {
19970  $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
19971  $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
19972  } else {
19973  $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
19974  $pend = '';
19975  }
19976  } else {
19977  $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
19978  if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
19979  $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
19980  $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
19981  } elseif (isset($opentagpos)) {
19982  $midpos = $opentagpos;
19983  } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
19984  $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
19985  $midpos = $this->footerpos[$startlinepage];
19986  } else {
19987  $midpos = 0;
19988  }
19989  if ($midpos > 0) {
19990  $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
19991  $pend = substr($this->getPageBuffer($startlinepage), $midpos);
19992  } else {
19993  $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
19994  $pend = '';
19995  }
19996  }
19997  if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
19998  // calculate shifting amount
19999  $tw = $w;
20000  if ($this->lMargin != $prevlMargin) {
20001  $tw += ($prevlMargin - $this->lMargin);
20002  }
20003  if ($this->rMargin != $prevrMargin) {
20004  $tw += ($prevrMargin - $this->rMargin);
20005  }
20006  $one_space_width = $this->GetStringWidth(chr(32));
20007  $no = 0; // number of spaces on a line contained on a single block
20008  if ($this->isRTLTextDir()) { // RTL
20009  // remove left space if exist
20010  $pos1 = $this->revstrpos($pmid, '[(');
20011  if ($pos1 > 0) {
20012  $pos1 = intval($pos1);
20013  if ($this->isUnicodeFont()) {
20014  $pos2 = intval($this->revstrpos($pmid, '[('.chr(0).chr(32)));
20015  $spacelen = 2;
20016  } else {
20017  $pos2 = intval($this->revstrpos($pmid, '[('.chr(32)));
20018  $spacelen = 1;
20019  }
20020  if ($pos1 == $pos2) {
20021  $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
20022  if (substr($pmid, $pos1, 4) == '[()]') {
20023  $linew -= $one_space_width;
20024  } elseif ($pos1 == strpos($pmid, '[(')) {
20025  $no = 1;
20026  }
20027  }
20028  }
20029  } else { // LTR
20030  // remove right space if exist
20031  $pos1 = $this->revstrpos($pmid, ')]');
20032  if ($pos1 > 0) {
20033  $pos1 = intval($pos1);
20034  if ($this->isUnicodeFont()) {
20035  $pos2 = intval($this->revstrpos($pmid, chr(0).chr(32).')]')) + 2;
20036  $spacelen = 2;
20037  } else {
20038  $pos2 = intval($this->revstrpos($pmid, chr(32).')]')) + 1;
20039  $spacelen = 1;
20040  }
20041  if ($pos1 == $pos2) {
20042  $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
20043  $linew -= $one_space_width;
20044  }
20045  }
20046  }
20047  $mdiff = ($tw - $linew);
20048  if ($plalign == 'C') {
20049  if ($this->rtl) {
20050  $t_x = -($mdiff / 2);
20051  } else {
20052  $t_x = ($mdiff / 2);
20053  }
20054  } elseif ($plalign == 'R') {
20055  // right alignment on LTR document
20056  $t_x = $mdiff;
20057  } elseif ($plalign == 'L') {
20058  // left alignment on RTL document
20059  $t_x = -$mdiff;
20060  }
20061  } // end if startlinex
20062  if (($t_x != 0) OR ($yshift < 0)) {
20063  // shift the line
20064  $trx = sprintf('1 0 0 1 %.3F %.3F cm', ($t_x * $this->k), ($yshift * $this->k));
20065  $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
20066  $endlinepos = strlen($pstart);
20067  if ($this->inxobj) {
20068  // we are inside an XObject template
20069  $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
20070  foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
20071  if ($pak >= $pask) {
20072  $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
20073  $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
20074  }
20075  }
20076  } else {
20077  $this->setPageBuffer($startlinepage, $pstart.$pend);
20078  // shift the annotations and links
20079  if (isset($this->PageAnnots[$this->page])) {
20080  foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
20081  if ($pak >= $pask) {
20082  $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
20083  $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
20084  }
20085  }
20086  }
20087  }
20088  $this->y -= $yshift;
20089  }
20090  }
20091  // restore previous values
20092  $this->setGraphicVars($gvars);
20093  if ($this->num_columns > 1) {
20094  $this->selectColumn();
20095  } elseif ($this->page > $prevPage) {
20096  $this->lMargin = $this->pagedim[$this->page]['olm'];
20097  $this->rMargin = $this->pagedim[$this->page]['orm'];
20098  }
20099  // restore previous list state
20100  $this->cell_height_ratio = $prev_cell_height_ratio;
20101  $this->listnum = $prev_listnum;
20102  $this->listordered = $prev_listordered;
20103  $this->listcount = $prev_listcount;
20104  $this->lispacer = $prev_lispacer;
20105  if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
20106  $this->Ln($this->lasth);
20107  if ($this->y < $maxbottomliney) {
20108  $this->y = $maxbottomliney;
20109  }
20110  }
20111  unset($dom);
20112  }
20113 
20121  protected function openHTMLTagHandler(&$dom, $key, $cell) {
20122  $tag = $dom[$key];
20123  $parent = $dom[($dom[$key]['parent'])];
20124  $firsttag = ($key == 1);
20125  // check for text direction attribute
20126  if (isset($tag['dir'])) {
20127  $this->setTempRTL($tag['dir']);
20128  } else {
20129  $this->tmprtl = false;
20130  }
20131  if ($tag['block']) {
20132  $hbz = 0; // distance from y to line bottom
20133  $hb = 0; // vertical space between block tags
20134  // calculate vertical space for block tags
20135  if (isset($this->tagvspaces[$tag['value']][0]['h']) AND ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
20136  $cur_h = $this->tagvspaces[$tag['value']][0]['h'];
20137  } elseif (isset($tag['fontsize'])) {
20138  $cur_h = ($tag['fontsize'] / $this->k) * $this->cell_height_ratio;
20139  } else {
20140  $cur_h = $this->FontSize * $this->cell_height_ratio;
20141  }
20142  if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
20143  $n = $this->tagvspaces[$tag['value']][0]['n'];
20144  } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
20145  $n = 0.6;
20146  } else {
20147  $n = 1;
20148  }
20149  $hb = ($n * $cur_h);
20150  if (($this->htmlvspace <= 0) AND ($n > 0)) {
20151  if (isset($parent['fontsize'])) {
20152  $hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
20153  } else {
20154  $hbz = $this->FontSize * $this->cell_height_ratio;
20155  }
20156  }
20157  }
20158  // Opening tag
20159  switch($tag['value']) {
20160  case 'table': {
20161  $cp = 0;
20162  $cs = 0;
20163  $dom[$key]['rowspans'] = array();
20164  if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
20165  // set table header
20166  if (!$this->empty_string($dom[$key]['thead'])) {
20167  // set table header
20168  $this->thead = $dom[$key]['thead'];
20169  if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
20170  $this->theadMargins = array();
20171  $this->theadMargins['cell_padding'] = $this->cell_padding;
20172  $this->theadMargins['lmargin'] = $this->lMargin;
20173  $this->theadMargins['rmargin'] = $this->rMargin;
20174  $this->theadMargins['page'] = $this->page;
20175  }
20176  }
20177  }
20178  // store current margins and page
20179  $dom[$key]['old_cell_padding'] = $this->cell_padding;
20180  if (isset($tag['attribute']['cellpadding'])) {
20181  $pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
20182  $this->SetCellPadding($pad);
20183  }
20184  if (isset($tag['attribute']['cellspacing'])) {
20185  $cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
20186  }
20187  $prev_y = $this->y;
20188  if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
20189  $this->inthead = true;
20190  // add a page (or trig AcceptPageBreak() for multicolumn mode)
20191  $this->checkPageBreak($this->PageBreakTrigger + 1);
20192  }
20193  break;
20194  }
20195  case 'tr': {
20196  // array of columns positions
20197  $dom[$key]['cellpos'] = array();
20198  break;
20199  }
20200  case 'hr': {
20201  if ((isset($tag['height'])) AND ($tag['height'] != '')) {
20202  $hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
20203  } else {
20204  $hrHeight = $this->GetLineWidth();
20205  }
20206  $this->addHTMLVertSpace($hbz, ($hrHeight / 2), $cell, $firsttag);
20207  $x = $this->GetX();
20208  $y = $this->GetY();
20209  $wtmp = $this->w - $this->lMargin - $this->rMargin;
20210  if ($cell) {
20211  $wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
20212  }
20213  if ((isset($tag['width'])) AND ($tag['width'] != '')) {
20214  $hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
20215  } else {
20216  $hrWidth = $wtmp;
20217  }
20218  $prevlinewidth = $this->GetLineWidth();
20219  $this->SetLineWidth($hrHeight);
20220  $this->Line($x, $y, $x + $hrWidth, $y);
20221  $this->SetLineWidth($prevlinewidth);
20222  $this->addHTMLVertSpace(($hrHeight / 2), 0, $cell, !isset($dom[($key + 1)]));
20223  break;
20224  }
20225  case 'a': {
20226  if (array_key_exists('href', $tag['attribute'])) {
20227  $this->HREF['url'] = $tag['attribute']['href'];
20228  }
20229  break;
20230  }
20231  case 'img': {
20232  if (isset($tag['attribute']['src'])) {
20233  // replace relative path with real server path
20234  if (($tag['attribute']['src'][0] == '/') AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
20235  $findroot = strpos($tag['attribute']['src'], $_SERVER['DOCUMENT_ROOT']);
20236  if (($findroot === false) OR ($findroot > 1)) {
20237  $tag['attribute']['src'] = $_SERVER['DOCUMENT_ROOT'].$tag['attribute']['src'];
20238  }
20239  }
20240  $tag['attribute']['src'] = urldecode($tag['attribute']['src']);
20241  $type = $this->getImageFileType($tag['attribute']['src']);
20242  $testscrtype = @parse_url($tag['attribute']['src']);
20243  if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
20244  // convert URL to server path
20245  $tag['attribute']['src'] = str_replace(K_PATH_URL, K_PATH_MAIN, $tag['attribute']['src']);
20246  }
20247  if (!isset($tag['width'])) {
20248  $tag['width'] = 0;
20249  }
20250  if (!isset($tag['height'])) {
20251  $tag['height'] = 0;
20252  }
20253  //if (!isset($tag['attribute']['align'])) {
20254  // the only alignment supported is "bottom"
20255  // further development is required for other modes.
20256  $tag['attribute']['align'] = 'bottom';
20257  //}
20258  switch($tag['attribute']['align']) {
20259  case 'top': {
20260  $align = 'T';
20261  break;
20262  }
20263  case 'middle': {
20264  $align = 'M';
20265  break;
20266  }
20267  case 'bottom': {
20268  $align = 'B';
20269  break;
20270  }
20271  default: {
20272  $align = 'B';
20273  break;
20274  }
20275  }
20276  $prevy = $this->y;
20277  $xpos = $this->x;
20278  // eliminate marker spaces
20279  if (isset($dom[($key - 1)])) {
20280  if (($dom[($key - 1)]['value'] == ' ') OR (isset($dom[($key - 1)]['trimmed_space']))) {
20281  $xpos -= $this->GetStringWidth(chr(32));
20282  } elseif ($this->rtl AND $dom[($key - 1)]['value'] == ' ') {
20283  $xpos += (2 * $this->GetStringWidth(chr(32)));
20284  }
20285  }
20286  $imglink = '';
20287  if (isset($this->HREF['url']) AND !$this->empty_string($this->HREF['url'])) {
20288  $imglink = $this->HREF['url'];
20289  if ($imglink{0} == '#') {
20290  // convert url to internal link
20291  $lnkdata = explode(',', $imglink);
20292  if (isset($lnkdata[0])) {
20293  $page = intval(substr($lnkdata[0], 1));
20294  if (empty($page) OR ($page <= 0)) {
20295  $page = $this->page;
20296  }
20297  if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
20298  $lnky = floatval($lnkdata[1]);
20299  } else {
20300  $lnky = 0;
20301  }
20302  $imglink = $this->AddLink();
20303  $this->SetLink($imglink, $lnky, $page);
20304  }
20305  }
20306  }
20307  $border = 0;
20308  if (isset($tag['border']) AND !empty($tag['border'])) {
20309  // currently only support 1 (frame) or a combination of 'LTRB'
20310  $border = $tag['border'];
20311  }
20312  $iw = '';
20313  if (isset($tag['width'])) {
20314  $iw = $this->getHTMLUnitToUnits($tag['width'], 1, 'px', false);
20315  }
20316  $ih = '';
20317  if (isset($tag['height'])) {
20318  $ih = $this->getHTMLUnitToUnits($tag['height'], 1, 'px', false);
20319  }
20320  if (($type == 'eps') OR ($type == 'ai')) {
20321  $this->ImageEps($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
20322  } elseif ($type == 'svg') {
20323  $this->ImageSVG($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
20324  } else {
20325  $this->Image($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
20326  }
20327  switch($align) {
20328  case 'T': {
20329  $this->y = $prevy;
20330  break;
20331  }
20332  case 'M': {
20333  $this->y = (($this->img_rb_y + $prevy - ($tag['fontsize'] / $this->k)) / 2) ;
20334  break;
20335  }
20336  case 'B': {
20337  $this->y = $this->img_rb_y - ($tag['fontsize'] / $this->k);
20338  break;
20339  }
20340  }
20341  }
20342  break;
20343  }
20344  case 'dl': {
20345  ++$this->listnum;
20346  if ($this->listnum == 1) {
20347  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
20348  } else {
20349  $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
20350  }
20351  break;
20352  }
20353  case 'dt': {
20354  $this->addHTMLVertSpace($hbz, 0, $cell, $firsttag);
20355  break;
20356  }
20357  case 'dd': {
20358  if ($this->rtl) {
20359  $this->rMargin += $this->listindent;
20360  } else {
20361  $this->lMargin += $this->listindent;
20362  }
20363  ++$this->listindentlevel;
20364  $this->addHTMLVertSpace($hbz, 0, $cell, $firsttag);
20365  break;
20366  }
20367  case 'ul':
20368  case 'ol': {
20369  ++$this->listnum;
20370  if ($tag['value'] == 'ol') {
20371  $this->listordered[$this->listnum] = true;
20372  } else {
20373  $this->listordered[$this->listnum] = false;
20374  }
20375  if (isset($tag['attribute']['start'])) {
20376  $this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
20377  } else {
20378  $this->listcount[$this->listnum] = 0;
20379  }
20380  if ($this->rtl) {
20381  $this->rMargin += $this->listindent;
20382  $this->x -= $this->listindent;
20383  } else {
20384  $this->lMargin += $this->listindent;
20385  $this->x += $this->listindent;
20386  }
20387  ++$this->listindentlevel;
20388  if ($this->listnum == 1) {
20389  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
20390  } else {
20391  $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
20392  }
20393  break;
20394  }
20395  case 'li': {
20396  $this->addHTMLVertSpace($hbz, 0, $cell, $firsttag);
20397  if ($this->listordered[$this->listnum]) {
20398  // ordered item
20399  if (isset($parent['attribute']['type']) AND !$this->empty_string($parent['attribute']['type'])) {
20400  $this->lispacer = $parent['attribute']['type'];
20401  } elseif (isset($parent['listtype']) AND !$this->empty_string($parent['listtype'])) {
20402  $this->lispacer = $parent['listtype'];
20403  } elseif (isset($this->lisymbol) AND !$this->empty_string($this->lisymbol)) {
20404  $this->lispacer = $this->lisymbol;
20405  } else {
20406  $this->lispacer = '#';
20407  }
20408  ++$this->listcount[$this->listnum];
20409  if (isset($tag['attribute']['value'])) {
20410  $this->listcount[$this->listnum] = intval($tag['attribute']['value']);
20411  }
20412  } else {
20413  // unordered item
20414  if (isset($parent['attribute']['type']) AND !$this->empty_string($parent['attribute']['type'])) {
20415  $this->lispacer = $parent['attribute']['type'];
20416  } elseif (isset($parent['listtype']) AND !$this->empty_string($parent['listtype'])) {
20417  $this->lispacer = $parent['listtype'];
20418  } elseif (isset($this->lisymbol) AND !$this->empty_string($this->lisymbol)) {
20419  $this->lispacer = $this->lisymbol;
20420  } else {
20421  $this->lispacer = '!';
20422  }
20423  }
20424  break;
20425  }
20426  case 'blockquote': {
20427  if ($this->rtl) {
20428  $this->rMargin += $this->listindent;
20429  } else {
20430  $this->lMargin += $this->listindent;
20431  }
20432  ++$this->listindentlevel;
20433  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
20434  break;
20435  }
20436  case 'br': {
20437  $this->addHTMLVertSpace($hbz, 0, $cell, $firsttag);
20438  break;
20439  }
20440  case 'div': {
20441  $this->addHTMLVertSpace($hbz, 0, $cell, $firsttag);
20442  break;
20443  }
20444  case 'p': {
20445  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
20446  break;
20447  }
20448  case 'pre': {
20449  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
20450  $this->premode = true;
20451  break;
20452  }
20453  case 'sup': {
20454  $this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
20455  break;
20456  }
20457  case 'sub': {
20458  $this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
20459  break;
20460  }
20461  case 'h1':
20462  case 'h2':
20463  case 'h3':
20464  case 'h4':
20465  case 'h5':
20466  case 'h6': {
20467  $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
20468  break;
20469  }
20470  // Form fields (since 4.8.000 - 2009-09-07)
20471  case 'form': {
20472  if (isset($tag['attribute']['action'])) {
20473  $this->form_action = $tag['attribute']['action'];
20474  } else {
20475  $this->form_action = K_PATH_URL.$_SERVER['SCRIPT_NAME'];
20476  }
20477  if (isset($tag['attribute']['enctype'])) {
20478  $this->form_enctype = $tag['attribute']['enctype'];
20479  } else {
20480  $this->form_enctype = 'application/x-www-form-urlencoded';
20481  }
20482  if (isset($tag['attribute']['method'])) {
20483  $this->form_mode = $tag['attribute']['method'];
20484  } else {
20485  $this->form_mode = 'post';
20486  }
20487  break;
20488  }
20489  case 'input': {
20490  if (isset($tag['attribute']['name']) AND !$this->empty_string($tag['attribute']['name'])) {
20491  $name = $tag['attribute']['name'];
20492  } else {
20493  break;
20494  }
20495  $prop = array();
20496  $opt = array();
20497  if (isset($tag['attribute']['readonly']) AND !$this->empty_string($tag['attribute']['readonly'])) {
20498  $prop['readonly'] = true;
20499  }
20500  if (isset($tag['attribute']['value']) AND !$this->empty_string($tag['attribute']['value'])) {
20501  $value = $tag['attribute']['value'];
20502  }
20503  if (isset($tag['attribute']['maxlength']) AND !$this->empty_string($tag['attribute']['maxlength'])) {
20504  $opt['maxlen'] = intval($tag['attribute']['value']);
20505  }
20506  $h = $this->FontSize * $this->cell_height_ratio;
20507  if (isset($tag['attribute']['size']) AND !$this->empty_string($tag['attribute']['size'])) {
20508  $w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
20509  } else {
20510  $w = $h;
20511  }
20512  if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
20513  $checked = true;
20514  } else {
20515  $checked = false;
20516  }
20517  switch ($tag['attribute']['type']) {
20518  case 'text': {
20519  if (isset($value)) {
20520  $opt['v'] = $value;
20521  }
20522  $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
20523  break;
20524  }
20525  case 'password': {
20526  if (isset($value)) {
20527  $opt['v'] = $value;
20528  }
20529  $prop['password'] = 'true';
20530  $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
20531  break;
20532  }
20533  case 'checkbox': {
20534  $this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
20535  break;
20536  }
20537  case 'radio': {
20538  $this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
20539  break;
20540  }
20541  case 'submit': {
20542  $w = $this->GetStringWidth($value) * 1.5;
20543  $h *= 1.6;
20544  $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
20545  $action = array();
20546  $action['S'] = 'SubmitForm';
20547  $action['F'] = $this->form_action;
20548  if ($this->form_enctype != 'FDF') {
20549  $action['Flags'] = array('ExportFormat');
20550  }
20551  if ($this->form_mode == 'get') {
20552  $action['Flags'] = array('GetMethod');
20553  }
20554  $this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
20555  break;
20556  }
20557  case 'reset': {
20558  $w = $this->GetStringWidth($value) * 1.5;
20559  $h *= 1.6;
20560  $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
20561  $this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
20562  break;
20563  }
20564  case 'file': {
20565  $prop['fileSelect'] = 'true';
20566  $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
20567  if (!isset($value)) {
20568  $value = '*';
20569  }
20570  $w = $this->GetStringWidth($value) * 2;
20571  $h *= 1.2;
20572  $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
20573  $jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
20574  $this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
20575  break;
20576  }
20577  case 'hidden': {
20578  if (isset($value)) {
20579  $opt['v'] = $value;
20580  }
20581  $opt['f'] = array('invisible', 'hidden');
20582  $this->TextField($name, 0, 0, $prop, $opt, '', '', false);
20583  break;
20584  }
20585  case 'image': {
20586  // THIS TYPE MUST BE FIXED
20587  if (isset($tag['attribute']['src']) AND !$this->empty_string($tag['attribute']['src'])) {
20588  $img = $tag['attribute']['src'];
20589  } else {
20590  break;
20591  }
20592  $value = 'img';
20593  //$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
20594  if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
20595  $jsaction = $tag['attribute']['onclick'];
20596  } else {
20597  $jsaction = '';
20598  }
20599  $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
20600  break;
20601  }
20602  case 'button': {
20603  $w = $this->GetStringWidth($value) * 1.5;
20604  $h *= 1.6;
20605  $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
20606  if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
20607  $jsaction = $tag['attribute']['onclick'];
20608  } else {
20609  $jsaction = '';
20610  }
20611  $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
20612  break;
20613  }
20614  }
20615  break;
20616  }
20617  case 'textarea': {
20618  $prop = array();
20619  $opt = array();
20620  if (isset($tag['attribute']['readonly']) AND !$this->empty_string($tag['attribute']['readonly'])) {
20621  $prop['readonly'] = true;
20622  }
20623  if (isset($tag['attribute']['name']) AND !$this->empty_string($tag['attribute']['name'])) {
20624  $name = $tag['attribute']['name'];
20625  } else {
20626  break;
20627  }
20628  if (isset($tag['attribute']['value']) AND !$this->empty_string($tag['attribute']['value'])) {
20629  $opt['v'] = $tag['attribute']['value'];
20630  }
20631  if (isset($tag['attribute']['cols']) AND !$this->empty_string($tag['attribute']['cols'])) {
20632  $w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
20633  } else {
20634  $w = 40;
20635  }
20636  if (isset($tag['attribute']['rows']) AND !$this->empty_string($tag['attribute']['rows'])) {
20637  $h = intval($tag['attribute']['rows']) * $this->FontSize * $this->cell_height_ratio;
20638  } else {
20639  $h = 10;
20640  }
20641  $prop['multiline'] = 'true';
20642  $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
20643  break;
20644  }
20645  case 'select': {
20646  $h = $this->FontSize * $this->cell_height_ratio;
20647  if (isset($tag['attribute']['size']) AND !$this->empty_string($tag['attribute']['size'])) {
20648  $h *= ($tag['attribute']['size'] + 1);
20649  }
20650  $prop = array();
20651  $opt = array();
20652  if (isset($tag['attribute']['name']) AND !$this->empty_string($tag['attribute']['name'])) {
20653  $name = $tag['attribute']['name'];
20654  } else {
20655  break;
20656  }
20657  $w = 0;
20658  if (isset($tag['attribute']['opt']) AND !$this->empty_string($tag['attribute']['opt'])) {
20659  $options = explode('#!NwL!#', $tag['attribute']['opt']);
20660  $values = array();
20661  foreach ($options as $val) {
20662  if (strpos($val, '#!TaB!#') !== false) {
20663  $opts = explode('#!TaB!#', $val);
20664  $values[] = $opts;
20665  $w = max($w, $this->GetStringWidth($opts[1]));
20666  } else {
20667  $values[] = $val;
20668  $w = max($w, $this->GetStringWidth($val));
20669  }
20670  }
20671  } else {
20672  break;
20673  }
20674  $w *= 2;
20675  if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
20676  $prop['multipleSelection'] = 'true';
20677  $this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
20678  } else {
20679  $this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
20680  }
20681  break;
20682  }
20683  case 'tcpdf': {
20684  if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
20685  // Special tag used to call TCPDF methods
20686  if (isset($tag['attribute']['method'])) {
20687  $tcpdf_method = $tag['attribute']['method'];
20688  if (method_exists($this, $tcpdf_method)) {
20689  if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
20690  $params = unserialize(urldecode($tag['attribute']['params']));
20691  call_user_func_array(array($this, $tcpdf_method), $params);
20692  } else {
20693  $this->$tcpdf_method();
20694  }
20695  $this->newline = true;
20696  }
20697  }
20698  }
20699  break;
20700  }
20701  default: {
20702  break;
20703  }
20704  }
20705  // define tags that support borders and background colors
20706  $bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
20707  if (in_array($tag['value'], $bordertags)) {
20708  // set border
20709  $dom[$key]['borderposition'] = $this->getBorderStartPosition();
20710  }
20711  if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
20712  $pba = $dom[$key]['attribute']['pagebreakafter'];
20713  // check for pagebreak
20714  if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
20715  // add a page (or trig AcceptPageBreak() for multicolumn mode)
20716  $this->checkPageBreak($this->PageBreakTrigger + 1);
20717  }
20718  if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
20719  OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
20720  // add a page (or trig AcceptPageBreak() for multicolumn mode)
20721  $this->checkPageBreak($this->PageBreakTrigger + 1);
20722  }
20723  }
20724  }
20725 
20734  protected function closeHTMLTagHandler(&$dom, $key, $cell, $maxbottomliney=0) {
20735  $tag = $dom[$key];
20736  $parent = $dom[($dom[$key]['parent'])];
20737  $lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
20738  $in_table_head = false;
20739  // maximum x position (used to draw borders)
20740  if ($this->rtl) {
20741  $xmax = $this->w;
20742  } else {
20743  $xmax = 0;
20744  }
20745  if ($tag['block']) {
20746  $hbz = 0; // distance from y to line bottom
20747  $hb = 0; // vertical space between block tags
20748  // calculate vertical space for block tags
20749  if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
20750  $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
20751  } elseif (isset($parent['fontsize'])) {
20752  $pre_h = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
20753  } else {
20754  $pre_h = $this->FontSize * $this->cell_height_ratio;
20755  }
20756  if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
20757  $n = $this->tagvspaces[$tag['value']][1]['n'];
20758  } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
20759  $n = 0.6;
20760  } else {
20761  $n = 1;
20762  }
20763  $hb = ($n * $pre_h);
20764  if ($this->y < $maxbottomliney) {
20765  $hbz = ($maxbottomliney - $this->y);
20766  }
20767  }
20768  // Closing tag
20769  switch($tag['value']) {
20770  case 'tr': {
20771  $table_el = $dom[($dom[$key]['parent'])]['parent'];
20772  if (!isset($parent['endy'])) {
20773  $dom[($dom[$key]['parent'])]['endy'] = $this->y;
20774  $parent['endy'] = $this->y;
20775  }
20776  if (!isset($parent['endpage'])) {
20777  $dom[($dom[$key]['parent'])]['endpage'] = $this->page;
20778  $parent['endpage'] = $this->page;
20779  }
20780  if (!isset($parent['endcolumn'])) {
20781  $dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
20782  $parent['endcolumn'] = $this->current_column;
20783  }
20784  // update row-spanned cells
20785  if (isset($dom[$table_el]['rowspans'])) {
20786  foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
20787  $dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
20788  if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
20789  if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
20790  $dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
20791  } elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
20792  $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
20793  $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
20794  $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
20795  }
20796  }
20797  }
20798  // report new endy and endpage to the rowspanned cells
20799  foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
20800  if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
20801  $dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
20802  $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
20803  $dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
20804  $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
20805  $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
20806  $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
20807  }
20808  }
20809  // update remaining rowspanned cells
20810  foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
20811  if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
20812  $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
20813  $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
20814  $dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
20815  }
20816  }
20817  }
20818  $this->setPage($dom[($dom[$key]['parent'])]['endpage']);
20819  if ($this->num_columns > 1) {
20820  $this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
20821  }
20822  $this->y = $dom[($dom[$key]['parent'])]['endy'];
20823  if (isset($dom[$table_el]['attribute']['cellspacing'])) {
20824  $cellspacing = $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
20825  $this->y += $cellspacing;
20826  }
20827  $this->Ln(0, $cell);
20828  if ($this->current_column == $parent['startcolumn']) {
20829  $this->x = $parent['startx'];
20830  }
20831  // account for booklet mode
20832  if ($this->page > $parent['startpage']) {
20833  if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
20834  $this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
20835  } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
20836  $this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
20837  }
20838  }
20839  break;
20840  }
20841  case 'tablehead':
20842  // closing tag used for the thead part
20843  $in_table_head = true;
20844  $this->inthead = false;
20845  case 'table': {
20846  $table_el = $parent;
20847  // set default border
20848  if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
20849  // set default border
20850  $border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
20851  } else {
20852  $border = 0;
20853  }
20854  $default_border = $border;
20855  if (isset($table_el['attribute']['cellspacing'])) {
20856  $cellspacing = $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
20857  } else {
20858  $cellspacing = 0;
20859  }
20860  // fix bottom line alignment of last line before page break
20861  foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
20862  // update row-spanned cells
20863  if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
20864  foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
20865  if ($trwsp['trid'] == $trkey) {
20866  $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
20867  }
20868  if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0)) {
20869  $dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
20870  }
20871  }
20872  }
20873  if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
20874  $pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
20875  $dom[$prevtrkey]['endy'] = $pgendy;
20876  // update row-spanned cells
20877  if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
20878  foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
20879  if (($trwsp['trid'] == $trkey) AND ($trwsp['mrowspan'] > 1) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
20880  $dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
20881  $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
20882  }
20883  }
20884  }
20885  }
20886  $prevtrkey = $trkey;
20887  $table_el = $dom[($dom[$key]['parent'])];
20888  }
20889  // for each row
20890  unset($xmax);
20891  foreach ($table_el['trids'] as $j => $trkey) {
20892  $parent = $dom[$trkey];
20893  if (!isset($xmax)) {
20894  $xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
20895  }
20896  // for each cell on the row
20897  foreach ($parent['cellpos'] as $k => $cellpos) {
20898  if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
20899  $cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
20900  $cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
20901  $endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
20902  $startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
20903  $endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
20904  $startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
20905  $endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
20906  } else {
20907  $endy = $parent['endy'];
20908  $startpage = $parent['startpage'];
20909  $endpage = $parent['endpage'];
20910  $startcolumn = $parent['startcolumn'];
20911  $endcolumn = $parent['endcolumn'];
20912  }
20913  if ($this->num_columns == 0) {
20914  $this->num_columns = 1;
20915  }
20916  if (isset($cellpos['border'])) {
20917  $border = $cellpos['border'];
20918  }
20919  if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
20920  $this->SetFillColorArray($cellpos['bgcolor']);
20921  $fill = true;
20922  } else {
20923  $fill = false;
20924  }
20925  $x = $cellpos['startx'];
20926  $y = $parent['starty'];
20927  $starty = $y;
20928  $w = abs($cellpos['endx'] - $cellpos['startx']);
20929  // get border modes
20930  $border_start = $this->getBorderMode($border, $position='start');
20931  $border_end = $this->getBorderMode($border, $position='end');
20932  $border_middle = $this->getBorderMode($border, $position='middle');
20933  // design borders around HTML cells.
20934  for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
20935  $ccode = '';
20936  $this->setPage($page);
20937  if ($this->num_columns < 2) {
20938  // single-column mode
20939  $this->x = $x;
20940  $this->y = $this->tMargin;
20941  }
20942  // account for margin changes
20943  if ($page > $startpage) {
20944  if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
20945  $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
20946  } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
20947  $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
20948  }
20949  }
20950  if ($startpage == $endpage) { // single page
20951  $deltacol = 0;
20952  $deltath = 0;
20953  for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
20954  $this->selectColumn($column);
20955  if ($startcolumn == $endcolumn) { // single column
20956  $cborder = $border;
20957  $h = $endy - $parent['starty'];
20958  $this->y = $y;
20959  $this->x = $x;
20960  } elseif ($column == $startcolumn) { // first column
20961  $cborder = $border_start;
20962  $this->y = $starty;
20963  $this->x = $x;
20964  $h = $this->h - $this->y - $this->bMargin;
20965  if ($this->rtl) {
20966  $deltacol = $this->x + $this->rMargin - $this->w;
20967  } else {
20968  $deltacol = $this->x - $this->lMargin;
20969  }
20970  } elseif ($column == $endcolumn) { // end column
20971  $cborder = $border_end;
20972  if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
20973  $this->y = $this->columns[$column]['th']['\''.$page.'\''];
20974  }
20975  $this->x += $deltacol;
20976  $h = $endy - $this->y;
20977  } else { // middle column
20978  $cborder = $border_middle;
20979  if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
20980  $this->y = $this->columns[$column]['th']['\''.$page.'\''];
20981  }
20982  $this->x += $deltacol;
20983  $h = $this->h - $this->y - $this->bMargin;
20984  }
20985  $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20986  } // end for each column
20987  } elseif ($page == $startpage) { // first page
20988  $deltacol = 0;
20989  $deltath = 0;
20990  for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
20991  $this->selectColumn($column);
20992  if ($column == $startcolumn) { // first column
20993  $cborder = $border_start;
20994  $this->y = $starty;
20995  $this->x = $x;
20996  $h = $this->h - $this->y - $this->bMargin;
20997  if ($this->rtl) {
20998  $deltacol = $this->x + $this->rMargin - $this->w;
20999  } else {
21000  $deltacol = $this->x - $this->lMargin;
21001  }
21002  } else { // middle column
21003  $cborder = $border_middle;
21004  if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
21005  $this->y = $this->columns[$column]['th']['\''.$page.'\''];
21006  }
21007  $this->x += $deltacol;
21008  $h = $this->h - $this->y - $this->bMargin;
21009  }
21010  $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
21011  } // end for each column
21012  } elseif ($page == $endpage) { // last page
21013  $deltacol = 0;
21014  $deltath = 0;
21015  for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
21016  $this->selectColumn($column);
21017  if ($column == $endcolumn) { // end column
21018  $cborder = $border_end;
21019  if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
21020  $this->y = $this->columns[$column]['th']['\''.$page.'\''];
21021  }
21022  $this->x += $deltacol;
21023  $h = $endy - $this->y;
21024  } else { // middle column
21025  $cborder = $border_middle;
21026  if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
21027  $this->y = $this->columns[$column]['th']['\''.$page.'\''];
21028  }
21029  $this->x += $deltacol;
21030  $h = $this->h - $this->y - $this->bMargin;
21031  }
21032  $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
21033  } // end for each column
21034  } else { // middle page
21035  $deltacol = 0;
21036  $deltath = 0;
21037  for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
21038  $this->selectColumn($column);
21039  $cborder = $border_middle;
21040  if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
21041  $this->y = $this->columns[$column]['th']['\''.$page.'\''];
21042  }
21043  $this->x += $deltacol;
21044  $h = $this->h - $this->y - $this->bMargin;
21045  $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
21046  } // end for each column
21047  }
21048  if ($cborder OR $fill) {
21049  // draw border and fill
21050  if ($this->inxobj) {
21051  // we are inside an XObject template
21052  if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
21053  $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
21054  $pagemark = &$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
21055  } else {
21056  $pagemark = &$this->xobjects[$this->xobjid]['intmrk'];
21057  }
21058  $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
21059  $pstart = substr($pagebuff, 0, $pagemark);
21060  $pend = substr($pagebuff, $pagemark);
21061  $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
21062  $pagemark += strlen($ccode);
21063  } else {
21064  // draw border and fill
21065  if (end($this->transfmrk[$this->page]) !== false) {
21066  $pagemarkkey = key($this->transfmrk[$this->page]);
21067  $pagemark = &$this->transfmrk[$this->page][$pagemarkkey];
21068  } elseif ($this->InFooter) {
21069  $pagemark = &$this->footerpos[$this->page];
21070  } else {
21071  $pagemark = &$this->intmrk[$this->page];
21072  }
21073  $pagebuff = $this->getPageBuffer($this->page);
21074  $pstart = substr($pagebuff, 0, $pagemark);
21075  $pend = substr($pagebuff, $pagemark);
21076  $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
21077  $pagemark += strlen($ccode);
21078  }
21079  }
21080  } // end for each page
21081  // restore default border
21082  $border = $default_border;
21083  } // end for each cell on the row
21084  if (isset($table_el['attribute']['cellspacing'])) {
21085  $cellspacing = $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
21086  $this->y += $cellspacing;
21087  }
21088  $this->Ln(0, $cell);
21089  $this->x = $parent['startx'];
21090  if ($endpage > $startpage) {
21091  if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
21092  $this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
21093  } elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
21094  $this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
21095  }
21096  }
21097  }
21098  if (!$in_table_head) { // we are not inside a thead section
21099  $this->cell_padding = $table_el['old_cell_padding'];
21100  // reset row height
21101  $this->resetLastH();
21102  if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages]) AND ($this->emptypagemrk[$this->numpages] == $this->pagelen[$this->numpages])) {
21103  // remove last blank page
21104  $this->deletePage($this->numpages);
21105  }
21106  if (isset($this->theadMargins['top'])) {
21107  // restore top margin
21108  $this->tMargin = $this->theadMargins['top'];
21109  $this->pagedim[$this->page]['tm'] = $this->tMargin;
21110  }
21111  if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
21112  // reset main table header
21113  $this->thead = '';
21114  $this->theadMargins = array();
21115  }
21116  }
21117  $parent = $table_el;
21118  break;
21119  }
21120  case 'a': {
21121  $this->HREF = '';
21122  break;
21123  }
21124  case 'sup': {
21125  $this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
21126  break;
21127  }
21128  case 'sub': {
21129  $this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize'])/$this->k));
21130  break;
21131  }
21132  case 'div': {
21133  $this->addHTMLVertSpace($hbz, 0, $cell, false, $lasttag);
21134  break;
21135  }
21136  case 'blockquote': {
21137  if ($this->rtl) {
21138  $this->rMargin -= $this->listindent;
21139  } else {
21140  $this->lMargin -= $this->listindent;
21141  }
21142  --$this->listindentlevel;
21143  $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
21144  break;
21145  }
21146  case 'p': {
21147  $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
21148  break;
21149  }
21150  case 'pre': {
21151  $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
21152  $this->premode = false;
21153  break;
21154  }
21155  case 'dl': {
21156  --$this->listnum;
21157  if ($this->listnum <= 0) {
21158  $this->listnum = 0;
21159  $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
21160  } else {
21161  $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
21162  }
21163  $this->resetLastH();
21164  break;
21165  }
21166  case 'dt': {
21167  $this->lispacer = '';
21168  $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
21169  break;
21170  }
21171  case 'dd': {
21172  $this->lispacer = '';
21173  if ($this->rtl) {
21174  $this->rMargin -= $this->listindent;
21175  } else {
21176  $this->lMargin -= $this->listindent;
21177  }
21178  --$this->listindentlevel;
21179  $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
21180  break;
21181  }
21182  case 'ul':
21183  case 'ol': {
21184  --$this->listnum;
21185  $this->lispacer = '';
21186  if ($this->rtl) {
21187  $this->rMargin -= $this->listindent;
21188  } else {
21189  $this->lMargin -= $this->listindent;
21190  }
21191  --$this->listindentlevel;
21192  if ($this->listnum <= 0) {
21193  $this->listnum = 0;
21194  $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
21195  } else {
21196  $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
21197  }
21198  $this->resetLastH();
21199  break;
21200  }
21201  case 'li': {
21202  $this->lispacer = '';
21203  $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
21204  break;
21205  }
21206  case 'h1':
21207  case 'h2':
21208  case 'h3':
21209  case 'h4':
21210  case 'h5':
21211  case 'h6': {
21212  $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
21213  break;
21214  }
21215  // Form fields (since 4.8.000 - 2009-09-07)
21216  case 'form': {
21217  $this->form_action = '';
21218  $this->form_enctype = 'application/x-www-form-urlencoded';
21219  break;
21220  }
21221  default : {
21222  break;
21223  }
21224  }
21225  // draw border and background (if any)
21226  $this->drawHTMLTagBorder($parent, $xmax);
21227  if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
21228  $pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
21229  // check for pagebreak
21230  if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
21231  // add a page (or trig AcceptPageBreak() for multicolumn mode)
21232  $this->checkPageBreak($this->PageBreakTrigger + 1);
21233  }
21234  if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
21235  OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
21236  // add a page (or trig AcceptPageBreak() for multicolumn mode)
21237  $this->checkPageBreak($this->PageBreakTrigger + 1);
21238  }
21239  }
21240  $this->tmprtl = false;
21241  }
21242 
21252  protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
21253  if ($firsttag) {
21254  $this->Ln(0, $cell);
21255  $this->htmlvspace = 0;
21256  return;
21257  }
21258  if ($lasttag) {
21259  $this->Ln($hbz, $cell);
21260  $this->htmlvspace = 0;
21261  return;
21262  }
21263  if ($hb < $this->htmlvspace) {
21264  $hd = 0;
21265  } else {
21266  $hd = $hb - $this->htmlvspace;
21267  $this->htmlvspace = $hb;
21268  }
21269  $this->Ln(($hbz + $hd), $cell);
21270  }
21271 
21278  protected function getBorderStartPosition() {
21279  if ($this->rtl) {
21280  $xmax = $this->lMargin;
21281  } else {
21282  $xmax = $this->w - $this->rMargin;
21283  }
21284  return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
21285  }
21286 
21294  protected function drawHTMLTagBorder($tag, $xmax) {
21295  if (!isset($tag['borderposition'])) {
21296  // nothing to draw
21297  return;
21298  }
21299  $prev_x = $this->x;
21300  $prev_y = $this->y;
21301  $prev_lasth = $this->lasth;
21302  $border = 0;
21303  $fill = false;
21304  $this->lasth = 0;
21305  if (isset($tag['border']) AND !empty($tag['border'])) {
21306  // get border style
21307  $border = $tag['border'];
21308  if (!$this->empty_string($this->thead) AND (!$this->inthead)) {
21309  // border for table header
21310  $border = $this->getBorderMode($border, $position='middle');
21311  }
21312  }
21313  if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
21314  // get background color
21315  $old_bgcolor = $this->bgcolor;
21316  $this->SetFillColorArray($tag['bgcolor']);
21317  $fill = true;
21318  }
21319  if (!$border AND !$fill) {
21320  // nothing to draw
21321  return;
21322  }
21323  if (isset($tag['attribute']['cellspacing'])) {
21324  $cellspacing = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
21325  } else {
21326  $cellspacing = 0;
21327  }
21328  if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
21329  // draw the border externally respect the sqare edge.
21330  $border['mode'] = 'ext';
21331  }
21332  if ($this->rtl) {
21333  if ($xmax >= $tag['borderposition']['x']) {
21334  $xmax = $tag['borderposition']['xmax'];
21335  }
21336  $w = ($tag['borderposition']['x'] - $xmax);
21337  } else {
21338  if ($xmax <= $tag['borderposition']['x']) {
21339  $xmax = $tag['borderposition']['xmax'];
21340  }
21341  $w = ($xmax - $tag['borderposition']['x']);
21342  }
21343  if ($w <= 0) {
21344  return;
21345  }
21346  $w += $cellspacing;
21347  $startpage = $tag['borderposition']['page'];
21348  $startcolumn = $tag['borderposition']['column'];
21349  $x = $tag['borderposition']['x'];
21350  $y = $tag['borderposition']['y'];
21351  $endpage = $this->page;
21352  $starty = $tag['borderposition']['y'] - $cellspacing;
21353  $currentY = $this->y;
21354  $this->x = $x;
21355  // get latest column
21356  $endcolumn = $this->current_column;
21357  if ($this->num_columns == 0) {
21358  $this->num_columns = 1;
21359  }
21360  // get border modes
21361  $border_start = $this->getBorderMode($border, $position='start');
21362  $border_end = $this->getBorderMode($border, $position='end');
21363  $border_middle = $this->getBorderMode($border, $position='middle');
21364  // design borders around HTML cells.
21365  for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
21366  $ccode = '';
21367  $this->setPage($page);
21368  if ($this->num_columns < 2) {
21369  // single-column mode
21370  $this->x = $x;
21371  $this->y = $this->tMargin;
21372  }
21373  // account for margin changes
21374  if ($page > $startpage) {
21375  if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
21376  $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
21377  } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
21378  $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
21379  }
21380  }
21381  if ($startpage == $endpage) {
21382  // single page
21383  for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
21384  $this->selectColumn($column);
21385  if ($startcolumn == $endcolumn) { // single column
21386  $cborder = $border;
21387  $h = ($currentY - $y) + $cellspacing;
21388  $this->y = $starty;
21389  } elseif ($column == $startcolumn) { // first column
21390  $cborder = $border_start;
21391  $this->y = $starty;
21392  $h = $this->h - $this->y - $this->bMargin;
21393  } elseif ($column == $endcolumn) { // end column
21394  $cborder = $border_end;
21395  $h = $currentY - $this->y;
21396  } else { // middle column
21397  $cborder = $border_middle;
21398  $h = $this->h - $this->y - $this->bMargin;
21399  }
21400  $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
21401  } // end for each column
21402  } elseif ($page == $startpage) { // first page
21403  for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
21404  $this->selectColumn($column);
21405  if ($column == $startcolumn) { // first column
21406  $cborder = $border_start;
21407  $this->y = $starty;
21408  $h = $this->h - $this->y - $this->bMargin;
21409  } else { // middle column
21410  $cborder = $border_middle;
21411  $h = $this->h - $this->y - $this->bMargin;
21412  }
21413  $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
21414  } // end for each column
21415  } elseif ($page == $endpage) { // last page
21416  for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
21417  $this->selectColumn($column);
21418  if ($column == $endcolumn) {
21419  // end column
21420  $cborder = $border_end;
21421  $h = $currentY - $this->y;
21422  } else {
21423  // middle column
21424  $cborder = $border_middle;
21425  $h = $this->h - $this->y - $this->bMargin;
21426  }
21427  $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
21428  } // end for each column
21429  } else { // middle page
21430  for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
21431  $this->selectColumn($column);
21432  $cborder = $border_middle;
21433  $h = $this->h - $this->y - $this->bMargin;
21434  $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
21435  } // end for each column
21436  }
21437  if ($cborder OR $fill) {
21438  // draw border and fill
21439  if ($this->inxobj) {
21440  // we are inside an XObject template
21441  if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
21442  $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
21443  $pagemark = &$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
21444  } else {
21445  $pagemark = &$this->xobjects[$this->xobjid]['intmrk'];
21446  }
21447  $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
21448  $pstart = substr($pagebuff, 0, $pagemark);
21449  $pend = substr($pagebuff, $pagemark);
21450  $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
21451  $pagemark += strlen($ccode);
21452  } else {
21453  if (end($this->transfmrk[$this->page]) !== false) {
21454  $pagemarkkey = key($this->transfmrk[$this->page]);
21455  $pagemark = &$this->transfmrk[$this->page][$pagemarkkey];
21456  } elseif ($this->InFooter) {
21457  $pagemark = &$this->footerpos[$this->page];
21458  } else {
21459  $pagemark = &$this->intmrk[$this->page];
21460  }
21461  $pagebuff = $this->getPageBuffer($this->page);
21462  $pstart = substr($pagebuff, 0, $this->bordermrk[$this->page]);
21463  $pend = substr($pagebuff, $this->bordermrk[$this->page]);
21464  $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
21465  $offsetlen = strlen($ccode);
21466  $this->bordermrk[$this->page] += $offsetlen;
21467  $this->cntmrk[$this->page] += $offsetlen;
21468  $pagemark += $offsetlen;
21469  }
21470  }
21471  } // end for each page
21472  if (isset($old_bgcolor)) {
21473  // restore background color
21474  $this->SetFillColorArray($old_bgcolor);
21475  }
21476  // restore pointer position
21477  $this->x = $prev_x;
21478  $this->y = $prev_y;
21479  $this->lasth = $prev_lasth;
21480  }
21481 
21488  public function setLIsymbol($symbol='!') {
21489  $symbol = strtolower($symbol);
21490  switch ($symbol) {
21491  case '!' :
21492  case '#' :
21493  case 'disc' :
21494  case 'circle' :
21495  case 'square' :
21496  case '1':
21497  case 'decimal':
21498  case 'decimal-leading-zero':
21499  case 'i':
21500  case 'lower-roman':
21501  case 'I':
21502  case 'upper-roman':
21503  case 'a':
21504  case 'lower-alpha':
21505  case 'lower-latin':
21506  case 'A':
21507  case 'upper-alpha':
21508  case 'upper-latin':
21509  case 'lower-greek': {
21510  $this->lisymbol = $symbol;
21511  break;
21512  }
21513  default : {
21514  $this->lisymbol = '';
21515  }
21516  }
21517  }
21518 
21527  public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
21528  $this->booklet = $booklet;
21529  if ($inner >= 0) {
21530  $this->lMargin = $inner;
21531  }
21532  if ($outer >= 0) {
21533  $this->rMargin = $outer;
21534  }
21535  }
21536 
21543  protected function swapMargins($reverse=true) {
21544  if ($reverse) {
21545  // swap left and right margins
21546  $mtemp = $this->original_lMargin;
21547  $this->original_lMargin = $this->original_rMargin;
21548  $this->original_rMargin = $mtemp;
21549  $deltam = $this->original_lMargin - $this->original_rMargin;
21550  $this->lMargin += $deltam;
21551  $this->rMargin -= $deltam;
21552  }
21553  }
21554 
21567  public function setHtmlVSpace($tagvs) {
21568  $this->tagvspaces = $tagvs;
21569  }
21570 
21577  public function setListIndentWidth($width) {
21578  return $this->customlistindent = floatval($width);
21579  }
21580 
21587  public function setOpenCell($isopen) {
21588  $this->opencell = $isopen;
21589  }
21590 
21598  public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
21599  $this->htmlLinkColorArray = $color;
21600  $this->htmlLinkFontStyle = $fontstyle;
21601  }
21602 
21613  public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
21614  $supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
21615  $retval = 0;
21616  $value = 0;
21617  $unit = 'px';
21618  $k = $this->k;
21619  if ($points) {
21620  $k = 1;
21621  }
21622  if (in_array($defaultunit, $supportedunits)) {
21623  $unit = $defaultunit;
21624  }
21625  if (is_numeric($htmlval)) {
21626  $value = floatval($htmlval);
21627  } elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
21628  $value = floatval($mnum[1]);
21629  if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
21630  if (in_array($munit[1], $supportedunits)) {
21631  $unit = $munit[1];
21632  }
21633  }
21634  }
21635  switch ($unit) {
21636  // percentage
21637  case '%': {
21638  $retval = (($value * $refsize) / 100);
21639  break;
21640  }
21641  // relative-size
21642  case 'em': {
21643  $retval = ($value * $refsize);
21644  break;
21645  }
21646  // height of lower case 'x' (about half the font-size)
21647  case 'ex': {
21648  $retval = $value * ($refsize / 2);
21649  break;
21650  }
21651  // absolute-size
21652  case 'in': {
21653  $retval = ($value * $this->dpi) / $k;
21654  break;
21655  }
21656  // centimeters
21657  case 'cm': {
21658  $retval = ($value / 2.54 * $this->dpi) / $k;
21659  break;
21660  }
21661  // millimeters
21662  case 'mm': {
21663  $retval = ($value / 25.4 * $this->dpi) / $k;
21664  break;
21665  }
21666  // one pica is 12 points
21667  case 'pc': {
21668  $retval = ($value * 12) / $k;
21669  break;
21670  }
21671  // points
21672  case 'pt': {
21673  $retval = $value / $k;
21674  break;
21675  }
21676  // pixels
21677  case 'px': {
21678  $retval = $this->pixelsToUnits($value);
21679  break;
21680  }
21681  }
21682  return $retval;
21683  }
21684 
21692  public function intToRoman($number) {
21693  $roman = '';
21694  while ($number >= 1000) {
21695  $roman .= 'M';
21696  $number -= 1000;
21697  }
21698  while ($number >= 900) {
21699  $roman .= 'CM';
21700  $number -= 900;
21701  }
21702  while ($number >= 500) {
21703  $roman .= 'D';
21704  $number -= 500;
21705  }
21706  while ($number >= 400) {
21707  $roman .= 'CD';
21708  $number -= 400;
21709  }
21710  while ($number >= 100) {
21711  $roman .= 'C';
21712  $number -= 100;
21713  }
21714  while ($number >= 90) {
21715  $roman .= 'XC';
21716  $number -= 90;
21717  }
21718  while ($number >= 50) {
21719  $roman .= 'L';
21720  $number -= 50;
21721  }
21722  while ($number >= 40) {
21723  $roman .= 'XL';
21724  $number -= 40;
21725  }
21726  while ($number >= 10) {
21727  $roman .= 'X';
21728  $number -= 10;
21729  }
21730  while ($number >= 9) {
21731  $roman .= 'IX';
21732  $number -= 9;
21733  }
21734  while ($number >= 5) {
21735  $roman .= 'V';
21736  $number -= 5;
21737  }
21738  while ($number >= 4) {
21739  $roman .= 'IV';
21740  $number -= 4;
21741  }
21742  while ($number >= 1) {
21743  $roman .= 'I';
21744  --$number;
21745  }
21746  return $roman;
21747  }
21748 
21757  protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
21758  $size /= $this->k;
21759  $fill = '';
21760  $color = $this->fgcolor;
21761  $width = 0;
21762  $textitem = '';
21763  $tmpx = $this->x;
21764  $lspace = $this->GetStringWidth(' ');
21765  if ($listtype == '^') {
21766  // special symbol used for avoid justification of rect bullet
21767  $this->lispacer = '';
21768  return;
21769  } elseif ($listtype == '!') {
21770  // set default list type for unordered list
21771  $deftypes = array('disc', 'circle', 'square');
21772  $listtype = $deftypes[($listdepth - 1) % 3];
21773  } elseif ($listtype == '#') {
21774  // set default list type for ordered list
21775  $listtype = 'decimal';
21776  }
21777  switch ($listtype) {
21778  // unordered types
21779  case 'none': {
21780  break;
21781  }
21782  case 'disc': {
21783  $fill = 'F';
21784  }
21785  case 'circle': {
21786  $fill .= 'D';
21787  $r = $size / 6;
21788  $lspace += (2 * $r);
21789  if ($this->rtl) {
21790  $this->x += $lspace;
21791  } else {
21792  $this->x -= $lspace;
21793  }
21794  $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, $fill, array('color'=>$color), $color, 8);
21795  break;
21796  }
21797  case 'square': {
21798  $l = $size / 3;
21799  $lspace += $l;
21800  if ($this->rtl) {;
21801  $this->x += $lspace;
21802  } else {
21803  $this->x -= $lspace;
21804  }
21805  $this->Rect($this->x, ($this->y + (($this->lasth - $l)/ 2)), $l, $l, 'F', array(), $color);
21806  break;
21807  }
21808  // ordered types
21809  // $this->listcount[$this->listnum];
21810  // $textitem
21811  case '1':
21812  case 'decimal': {
21813  $textitem = $this->listcount[$this->listnum];
21814  break;
21815  }
21816  case 'decimal-leading-zero': {
21817  $textitem = sprintf('%02d', $this->listcount[$this->listnum]);
21818  break;
21819  }
21820  case 'i':
21821  case 'lower-roman': {
21822  $textitem = strtolower($this->intToRoman($this->listcount[$this->listnum]));
21823  break;
21824  }
21825  case 'I':
21826  case 'upper-roman': {
21827  $textitem = $this->intToRoman($this->listcount[$this->listnum]);
21828  break;
21829  }
21830  case 'a':
21831  case 'lower-alpha':
21832  case 'lower-latin': {
21833  $textitem = chr(97 + $this->listcount[$this->listnum] - 1);
21834  break;
21835  }
21836  case 'A':
21837  case 'upper-alpha':
21838  case 'upper-latin': {
21839  $textitem = chr(65 + $this->listcount[$this->listnum] - 1);
21840  break;
21841  }
21842  case 'lower-greek': {
21843  $textitem = $this->unichr(945 + $this->listcount[$this->listnum] - 1);
21844  break;
21845  }
21846  /*
21847  // Types to be implemented (special handling)
21848  case 'hebrew': {
21849  break;
21850  }
21851  case 'armenian': {
21852  break;
21853  }
21854  case 'georgian': {
21855  break;
21856  }
21857  case 'cjk-ideographic': {
21858  break;
21859  }
21860  case 'hiragana': {
21861  break;
21862  }
21863  case 'katakana': {
21864  break;
21865  }
21866  case 'hiragana-iroha': {
21867  break;
21868  }
21869  case 'katakana-iroha': {
21870  break;
21871  }
21872  */
21873  default: {
21874  $textitem = $this->listcount[$this->listnum];
21875  }
21876  }
21877  if (!$this->empty_string($textitem)) {
21878  // print ordered item
21879  if ($this->rtl) {
21880  $textitem = '.'.$textitem;
21881  } else {
21882  $textitem = $textitem.'.';
21883  }
21884  $lspace += $this->GetStringWidth($textitem);
21885  if ($this->rtl) {
21886  $this->x += $lspace;
21887  } else {
21888  $this->x -= $lspace;
21889  }
21890  $this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
21891  }
21892  $this->x = $tmpx;
21893  $this->lispacer = '^';
21894  }
21895 
21902  protected function getGraphicVars() {
21903  $grapvars = array(
21904  'FontFamily' => $this->FontFamily,
21905  'FontStyle' => $this->FontStyle,
21906  'FontSizePt' => $this->FontSizePt,
21907  'rMargin' => $this->rMargin,
21908  'lMargin' => $this->lMargin,
21909  'cell_padding' => $this->cell_padding,
21910  'cell_margin' => $this->cell_margin,
21911  'LineWidth' => $this->LineWidth,
21912  'linestyleWidth' => $this->linestyleWidth,
21913  'linestyleCap' => $this->linestyleCap,
21914  'linestyleJoin' => $this->linestyleJoin,
21915  'linestyleDash' => $this->linestyleDash,
21916  'textrendermode' => $this->textrendermode,
21917  'textstrokewidth' => $this->textstrokewidth,
21918  'DrawColor' => $this->DrawColor,
21919  'FillColor' => $this->FillColor,
21920  'TextColor' => $this->TextColor,
21921  'ColorFlag' => $this->ColorFlag,
21922  'bgcolor' => $this->bgcolor,
21923  'fgcolor' => $this->fgcolor,
21924  'htmlvspace' => $this->htmlvspace,
21925  'listindent' => $this->listindent,
21926  'listindentlevel' => $this->listindentlevel,
21927  'listnum' => $this->listnum,
21928  'listordered' => $this->listordered,
21929  'listcount' => $this->listcount,
21930  'lispacer' => $this->lispacer,
21931  'cell_height_ratio' => $this->cell_height_ratio,
21932  'font_stretching' => $this->font_stretching,
21933  'font_spacing' => $this->font_spacing,
21934  // extended
21935  'lasth' => $this->lasth,
21936  'tMargin' => $this->tMargin,
21937  'bMargin' => $this->bMargin,
21938  'AutoPageBreak' => $this->AutoPageBreak,
21939  'PageBreakTrigger' => $this->PageBreakTrigger,
21940  'x' => $this->x,
21941  'y' => $this->y,
21942  'w' => $this->w,
21943  'h' => $this->h,
21944  'wPt' => $this->wPt,
21945  'hPt' => $this->hPt,
21946  'fwPt' => $this->fwPt,
21947  'fhPt' => $this->fhPt,
21948  'page' => $this->page,
21949  'current_column' => $this->current_column,
21950  'num_columns' => $this->num_columns
21951  );
21952  return $grapvars;
21953  }
21954 
21962  protected function setGraphicVars($gvars, $extended=false) {
21963  $this->FontFamily = $gvars['FontFamily'];
21964  $this->FontStyle = $gvars['FontStyle'];
21965  $this->FontSizePt = $gvars['FontSizePt'];
21966  $this->rMargin = $gvars['rMargin'];
21967  $this->lMargin = $gvars['lMargin'];
21968  $this->cell_padding = $gvars['cell_padding'];
21969  $this->cell_margin = $gvars['cell_margin'];
21970  $this->LineWidth = $gvars['LineWidth'];
21971  $this->linestyleWidth = $gvars['linestyleWidth'];
21972  $this->linestyleCap = $gvars['linestyleCap'];
21973  $this->linestyleJoin = $gvars['linestyleJoin'];
21974  $this->linestyleDash = $gvars['linestyleDash'];
21975  $this->textrendermode = $gvars['textrendermode'];
21976  $this->textstrokewidth = $gvars['textstrokewidth'];
21977  $this->DrawColor = $gvars['DrawColor'];
21978  $this->FillColor = $gvars['FillColor'];
21979  $this->TextColor = $gvars['TextColor'];
21980  $this->ColorFlag = $gvars['ColorFlag'];
21981  $this->bgcolor = $gvars['bgcolor'];
21982  $this->fgcolor = $gvars['fgcolor'];
21983  $this->htmlvspace = $gvars['htmlvspace'];
21984  $this->listindent = $gvars['listindent'];
21985  $this->listindentlevel = $gvars['listindentlevel'];
21986  $this->listnum = $gvars['listnum'];
21987  $this->listordered = $gvars['listordered'];
21988  $this->listcount = $gvars['listcount'];
21989  $this->lispacer = $gvars['lispacer'];
21990  $this->cell_height_ratio = $gvars['cell_height_ratio'];
21991  $this->font_stretching = $gvars['font_stretching'];
21992  $this->font_spacing = $gvars['font_spacing'];
21993  if ($extended) {
21994  // restore extended values
21995  $this->lasth = $gvars['lasth'];
21996  $this->tMargin = $gvars['tMargin'];
21997  $this->bMargin = $gvars['bMargin'];
21998  $this->AutoPageBreak = $gvars['AutoPageBreak'];
21999  $this->PageBreakTrigger = $gvars['PageBreakTrigger'];
22000  $this->x = $gvars['x'];
22001  $this->y = $gvars['y'];
22002  $this->w = $gvars['w'];
22003  $this->h = $gvars['h'];
22004  $this->wPt = $gvars['wPt'];
22005  $this->hPt = $gvars['hPt'];
22006  $this->fwPt = $gvars['fwPt'];
22007  $this->fhPt = $gvars['fhPt'];
22008  $this->page = $gvars['page'];
22009  $this->current_column = $gvars['current_column'];
22010  $this->num_columns = $gvars['num_columns'];
22011  }
22012  $this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
22013  if (!$this->empty_string($this->FontFamily)) {
22014  $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
22015  }
22016  }
22017 
22025  protected function getObjFilename($name) {
22026  return tempnam(K_PATH_CACHE, $name.'_');
22027  }
22028 
22037  protected function writeDiskCache($filename, $data, $append=false) {
22038  if ($append) {
22039  $fmode = 'ab+';
22040  } else {
22041  $fmode = 'wb+';
22042  }
22043  $f = @fopen($filename, $fmode);
22044  if (!$f) {
22045  $this->Error('Unable to write cache file: '.$filename);
22046  } else {
22047  fwrite($f, $data);
22048  fclose($f);
22049  }
22050  // update file length (needed for transactions)
22051  if (!isset($this->cache_file_length['_'.$filename])) {
22052  $this->cache_file_length['_'.$filename] = strlen($data);
22053  } else {
22054  $this->cache_file_length['_'.$filename] += strlen($data);
22055  }
22056  }
22057 
22065  protected function readDiskCache($filename) {
22066  return file_get_contents($filename);
22067  }
22068 
22075  protected function setBuffer($data) {
22076  $this->bufferlen += strlen($data);
22077  if ($this->diskcache) {
22078  if (!isset($this->buffer) OR $this->empty_string($this->buffer)) {
22079  $this->buffer = $this->getObjFilename('buffer');
22080  }
22081  $this->writeDiskCache($this->buffer, $data, true);
22082  } else {
22083  $this->buffer .= $data;
22084  }
22085  }
22086 
22093  protected function replaceBuffer($data) {
22094  $this->bufferlen = strlen($data);
22095  if ($this->diskcache) {
22096  if (!isset($this->buffer) OR $this->empty_string($this->buffer)) {
22097  $this->buffer = $this->getObjFilename('buffer');
22098  }
22099  $this->writeDiskCache($this->buffer, $data, false);
22100  } else {
22101  $this->buffer = $data;
22102  }
22103  }
22104 
22111  protected function getBuffer() {
22112  if ($this->diskcache) {
22113  return $this->readDiskCache($this->buffer);
22114  } else {
22115  return $this->buffer;
22116  }
22117  }
22118 
22127  protected function setPageBuffer($page, $data, $append=false) {
22128  if ($this->diskcache) {
22129  if (!isset($this->pages[$page])) {
22130  $this->pages[$page] = $this->getObjFilename('page'.$page);
22131  }
22132  $this->writeDiskCache($this->pages[$page], $data, $append);
22133  } else {
22134  if ($append) {
22135  $this->pages[$page] .= $data;
22136  } else {
22137  $this->pages[$page] = $data;
22138  }
22139  }
22140  if ($append AND isset($this->pagelen[$page])) {
22141  $this->pagelen[$page] += strlen($data);
22142  } else {
22143  $this->pagelen[$page] = strlen($data);
22144  }
22145  }
22146 
22154  protected function getPageBuffer($page) {
22155  if ($this->diskcache) {
22156  return $this->readDiskCache($this->pages[$page]);
22157  } elseif (isset($this->pages[$page])) {
22158  return $this->pages[$page];
22159  }
22160  return false;
22161  }
22162 
22170  protected function setImageBuffer($image, $data) {
22171  if ($this->diskcache) {
22172  if (!isset($this->images[$image])) {
22173  $this->images[$image] = $this->getObjFilename('image'.$image);
22174  }
22175  $this->writeDiskCache($this->images[$image], serialize($data));
22176  } else {
22177  $this->images[$image] = $data;
22178  }
22179  if (!in_array($image, $this->imagekeys)) {
22180  $this->imagekeys[] = $image;
22181  ++$this->numimages;
22182  }
22183  }
22184 
22193  protected function setImageSubBuffer($image, $key, $data) {
22194  if (!isset($this->images[$image])) {
22195  $this->setImageBuffer($image, array());
22196  }
22197  if ($this->diskcache) {
22198  $tmpimg = $this->getImageBuffer($image);
22199  $tmpimg[$key] = $data;
22200  $this->writeDiskCache($this->images[$image], serialize($tmpimg));
22201  } else {
22202  $this->images[$image][$key] = $data;
22203  }
22204  }
22205 
22213  protected function getImageBuffer($image) {
22214  if ($this->diskcache AND isset($this->images[$image])) {
22215  return unserialize($this->readDiskCache($this->images[$image]));
22216  } elseif (isset($this->images[$image])) {
22217  return $this->images[$image];
22218  }
22219  return false;
22220  }
22221 
22229  protected function setFontBuffer($font, $data) {
22230  if ($this->diskcache) {
22231  if (!isset($this->fonts[$font])) {
22232  $this->fonts[$font] = $this->getObjFilename('font');
22233  }
22234  $this->writeDiskCache($this->fonts[$font], serialize($data));
22235  } else {
22236  $this->fonts[$font] = $data;
22237  }
22238  if (!in_array($font, $this->fontkeys)) {
22239  $this->fontkeys[] = $font;
22240  // store object ID for current font
22241  ++$this->n;
22242  $this->font_obj_ids[$font] = $this->n;
22243  $this->setFontSubBuffer($font, 'n', $this->n);
22244  }
22245  }
22246 
22255  protected function setFontSubBuffer($font, $key, $data) {
22256  if (!isset($this->fonts[$font])) {
22257  $this->setFontBuffer($font, array());
22258  }
22259  if ($this->diskcache) {
22260  $tmpfont = $this->getFontBuffer($font);
22261  $tmpfont[$key] = $data;
22262  $this->writeDiskCache($this->fonts[$font], serialize($tmpfont));
22263  } else {
22264  $this->fonts[$font][$key] = $data;
22265  }
22266  }
22267 
22275  protected function getFontBuffer($font) {
22276  if ($this->diskcache AND isset($this->fonts[$font])) {
22277  return unserialize($this->readDiskCache($this->fonts[$font]));
22278  } elseif (isset($this->fonts[$font])) {
22279  return $this->fonts[$font];
22280  }
22281  return false;
22282  }
22283 
22292  public function movePage($frompage, $topage) {
22293  if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
22294  return false;
22295  }
22296  if ($frompage == $this->page) {
22297  // close the page before moving it
22298  $this->endPage();
22299  }
22300  // move all page-related states
22301  $tmppage = $this->pages[$frompage];
22302  $tmppagedim = $this->pagedim[$frompage];
22303  $tmppagelen = $this->pagelen[$frompage];
22304  $tmpintmrk = $this->intmrk[$frompage];
22305  $tmpbordermrk = $this->bordermrk[$frompage];
22306  $tmpcntmrk = $this->cntmrk[$frompage];
22307  if (isset($this->footerpos[$frompage])) {
22308  $tmpfooterpos = $this->footerpos[$frompage];
22309  }
22310  if (isset($this->footerlen[$frompage])) {
22311  $tmpfooterlen = $this->footerlen[$frompage];
22312  }
22313  if (isset($this->transfmrk[$frompage])) {
22314  $tmptransfmrk = $this->transfmrk[$frompage];
22315  }
22316  if (isset($this->PageAnnots[$frompage])) {
22317  $tmpannots = $this->PageAnnots[$frompage];
22318  }
22319  if (isset($this->newpagegroup[$frompage])) {
22320  $tmpnewpagegroup = $this->newpagegroup[$frompage];
22321  }
22322  for ($i = $frompage; $i > $topage; --$i) {
22323  $j = $i - 1;
22324  // shift pages down
22325  $this->pages[$i] = $this->pages[$j];
22326  $this->pagedim[$i] = $this->pagedim[$j];
22327  $this->pagelen[$i] = $this->pagelen[$j];
22328  $this->intmrk[$i] = $this->intmrk[$j];
22329  $this->bordermrk[$i] = $this->bordermrk[$j];
22330  $this->cntmrk[$i] = $this->cntmrk[$j];
22331  if (isset($this->footerpos[$j])) {
22332  $this->footerpos[$i] = $this->footerpos[$j];
22333  } elseif (isset($this->footerpos[$i])) {
22334  unset($this->footerpos[$i]);
22335  }
22336  if (isset($this->footerlen[$j])) {
22337  $this->footerlen[$i] = $this->footerlen[$j];
22338  } elseif (isset($this->footerlen[$i])) {
22339  unset($this->footerlen[$i]);
22340  }
22341  if (isset($this->transfmrk[$j])) {
22342  $this->transfmrk[$i] = $this->transfmrk[$j];
22343  } elseif (isset($this->transfmrk[$i])) {
22344  unset($this->transfmrk[$i]);
22345  }
22346  if (isset($this->PageAnnots[$j])) {
22347  $this->PageAnnots[$i] = $this->PageAnnots[$j];
22348  } elseif (isset($this->PageAnnots[$i])) {
22349  unset($this->PageAnnots[$i]);
22350  }
22351  if (isset($this->newpagegroup[$j])) {
22352  $this->newpagegroup[$i] = $this->newpagegroup[$j];
22353  } elseif (isset($this->newpagegroup[$i])) {
22354  unset($this->newpagegroup[$i]);
22355  }
22356  }
22357  $this->pages[$topage] = $tmppage;
22358  $this->pagedim[$topage] = $tmppagedim;
22359  $this->pagelen[$topage] = $tmppagelen;
22360  $this->intmrk[$topage] = $tmpintmrk;
22361  $this->bordermrk[$topage] = $tmpbordermrk;
22362  $this->cntmrk[$topage] = $tmpcntmrk;
22363  if (isset($tmpfooterpos)) {
22364  $this->footerpos[$topage] = $tmpfooterpos;
22365  } elseif (isset($this->footerpos[$topage])) {
22366  unset($this->footerpos[$topage]);
22367  }
22368  if (isset($tmpfooterlen)) {
22369  $this->footerlen[$topage] = $tmpfooterlen;
22370  } elseif (isset($this->footerlen[$topage])) {
22371  unset($this->footerlen[$topage]);
22372  }
22373  if (isset($tmptransfmrk)) {
22374  $this->transfmrk[$topage] = $tmptransfmrk;
22375  } elseif (isset($this->transfmrk[$topage])) {
22376  unset($this->transfmrk[$topage]);
22377  }
22378  if (isset($tmpannots)) {
22379  $this->PageAnnots[$topage] = $tmpannots;
22380  } elseif (isset($this->PageAnnots[$topage])) {
22381  unset($this->PageAnnots[$topage]);
22382  }
22383  if (isset($tmpnewpagegroup)) {
22384  $this->newpagegroup[$topage] = $tmpnewpagegroup;
22385  } elseif (isset($this->newpagegroup[$topage])) {
22386  unset($this->newpagegroup[$topage]);
22387  }
22388  // adjust outlines
22389  $tmpoutlines = $this->outlines;
22390  foreach ($tmpoutlines as $key => $outline) {
22391  if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
22392  $this->outlines[$key]['p'] = $outline['p'] + 1;
22393  } elseif ($outline['p'] == $frompage) {
22394  $this->outlines[$key]['p'] = $topage;
22395  }
22396  }
22397  // adjust links
22398  $tmplinks = $this->links;
22399  foreach ($tmplinks as $key => $link) {
22400  if (($link[0] >= $topage) AND ($link[0] < $frompage)) {
22401  $this->links[$key][0] = $link[0] + 1;
22402  } elseif ($link[0] == $frompage) {
22403  $this->links[$key][0] = $topage;
22404  }
22405  }
22406  // adjust javascript
22407  $tmpjavascript = $this->javascript;
22408  global $jfrompage, $jtopage;
22409  $jfrompage = $frompage;
22410  $jtopage = $topage;
22411  $this->javascript = preg_replace_callback('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/',
22412  create_function('$matches', 'global $jfrompage, $jtopage;
22413  $pagenum = intval($matches[3]) + 1;
22414  if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
22415  $newpage = ($pagenum + 1);
22416  } elseif ($pagenum == $jfrompage) {
22417  $newpage = $jtopage;
22418  } else {
22419  $newpage = $pagenum;
22420  }
22421  --$newpage;
22422  return "this.addField(\'".$matches[1]."\',\'".$matches[2]."\',".$newpage."";'), $tmpjavascript);
22423  // return to last page
22424  $this->lastPage(true);
22425  return true;
22426  }
22427 
22435  public function deletePage($page) {
22436  if (($page < 1) OR ($page > $this->numpages)) {
22437  return false;
22438  }
22439  // delete current page
22440  unset($this->pages[$page]);
22441  unset($this->pagedim[$page]);
22442  unset($this->pagelen[$page]);
22443  unset($this->intmrk[$page]);
22444  unset($this->bordermrk[$page]);
22445  unset($this->cntmrk[$page]);
22446  if (isset($this->footerpos[$page])) {
22447  unset($this->footerpos[$page]);
22448  }
22449  if (isset($this->footerlen[$page])) {
22450  unset($this->footerlen[$page]);
22451  }
22452  if (isset($this->transfmrk[$page])) {
22453  unset($this->transfmrk[$page]);
22454  }
22455  if (isset($this->PageAnnots[$page])) {
22456  unset($this->PageAnnots[$page]);
22457  }
22458  if (isset($this->newpagegroup[$page])) {
22459  unset($this->newpagegroup[$page]);
22460  }
22461  if (isset($this->pageopen[$page])) {
22462  unset($this->pageopen[$page]);
22463  }
22464  // update remaining pages
22465  for ($i = $page; $i < $this->numpages; ++$i) {
22466  $j = $i + 1;
22467  // shift pages
22468  $this->pages[$i] = $this->pages[$j];
22469  $this->pagedim[$i] = $this->pagedim[$j];
22470  $this->pagelen[$i] = $this->pagelen[$j];
22471  $this->intmrk[$i] = $this->intmrk[$j];
22472  $this->bordermrk[$i] = $this->bordermrk[$j];
22473  $this->cntmrk[$i] = $this->cntmrk[$j];
22474  if (isset($this->footerpos[$j])) {
22475  $this->footerpos[$i] = $this->footerpos[$j];
22476  } elseif (isset($this->footerpos[$i])) {
22477  unset($this->footerpos[$i]);
22478  }
22479  if (isset($this->footerlen[$j])) {
22480  $this->footerlen[$i] = $this->footerlen[$j];
22481  } elseif (isset($this->footerlen[$i])) {
22482  unset($this->footerlen[$i]);
22483  }
22484  if (isset($this->transfmrk[$j])) {
22485  $this->transfmrk[$i] = $this->transfmrk[$j];
22486  } elseif (isset($this->transfmrk[$i])) {
22487  unset($this->transfmrk[$i]);
22488  }
22489  if (isset($this->PageAnnots[$j])) {
22490  $this->PageAnnots[$i] = $this->PageAnnots[$j];
22491  } elseif (isset($this->PageAnnots[$i])) {
22492  unset($this->PageAnnots[$i]);
22493  }
22494  if (isset($this->newpagegroup[$j])) {
22495  $this->newpagegroup[$i] = $this->newpagegroup[$j];
22496  } elseif (isset($this->newpagegroup[$i])) {
22497  unset($this->newpagegroup[$i]);
22498  }
22499  if (isset($this->pageopen[$j])) {
22500  $this->pageopen[$i] = $this->pageopen[$j];
22501  } elseif (isset($this->pageopen[$i])) {
22502  unset($this->pageopen[$i]);
22503  }
22504  }
22505  // remove last page
22506  unset($this->pages[$this->numpages]);
22507  unset($this->pagedim[$this->numpages]);
22508  unset($this->pagelen[$this->numpages]);
22509  unset($this->intmrk[$this->numpages]);
22510  unset($this->bordermrk[$this->numpages]);
22511  unset($this->cntmrk[$this->numpages]);
22512  if (isset($this->footerpos[$this->numpages])) {
22513  unset($this->footerpos[$this->numpages]);
22514  }
22515  if (isset($this->footerlen[$this->numpages])) {
22516  unset($this->footerlen[$this->numpages]);
22517  }
22518  if (isset($this->transfmrk[$this->numpages])) {
22519  unset($this->transfmrk[$this->numpages]);
22520  }
22521  if (isset($this->PageAnnots[$this->numpages])) {
22522  unset($this->PageAnnots[$this->numpages]);
22523  }
22524  if (isset($this->newpagegroup[$this->numpages])) {
22525  unset($this->newpagegroup[$this->numpages]);
22526  }
22527  if (isset($this->pageopen[$this->numpages])) {
22528  unset($this->pageopen[$this->numpages]);
22529  }
22530  --$this->numpages;
22531  $this->page = $this->numpages;
22532  // adjust outlines
22533  $tmpoutlines = $this->outlines;
22534  foreach ($tmpoutlines as $key => $outline) {
22535  if ($outline['p'] > $page) {
22536  $this->outlines[$key]['p'] = $outline['p'] - 1;
22537  } elseif ($outline['p'] == $page) {
22538  unset($this->outlines[$key]);
22539  }
22540  }
22541  // adjust links
22542  $tmplinks = $this->links;
22543  foreach ($tmplinks as $key => $link) {
22544  if ($link[0] > $page) {
22545  $this->links[$key][0] = $link[0] - 1;
22546  } elseif ($link[0] == $page) {
22547  unset($this->links[$key]);
22548  }
22549  }
22550  // adjust javascript
22551  $tmpjavascript = $this->javascript;
22552  global $jpage;
22553  $jpage = $page;
22554  $this->javascript = preg_replace_callback('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/',
22555  create_function('$matches', 'global $jpage;
22556  $pagenum = intval($matches[3]) + 1;
22557  if ($pagenum >= $jpage) {
22558  $newpage = ($pagenum - 1);
22559  } elseif ($pagenum == $jpage) {
22560  $newpage = 1;
22561  } else {
22562  $newpage = $pagenum;
22563  }
22564  --$newpage;
22565  return "this.addField(\'".$matches[1]."\',\'".$matches[2]."\',".$newpage."";'), $tmpjavascript);
22566  // return to last page
22567  $this->lastPage(true);
22568  return true;
22569  }
22570 
22578  public function copyPage($page=0) {
22579  if ($page == 0) {
22580  // default value
22581  $page = $this->page;
22582  }
22583  if (($page < 1) OR ($page > $this->numpages)) {
22584  return false;
22585  }
22586  if ($page == $this->page) {
22587  // close the page before cloning it
22588  $this->endPage();
22589  }
22590  // copy all page-related states
22591  ++$this->numpages;
22592  $this->page = $this->numpages;
22593  $this->pages[$this->page] = $this->pages[$page];
22594  $this->pagedim[$this->page] = $this->pagedim[$page];
22595  $this->pagelen[$this->page] = $this->pagelen[$page];
22596  $this->intmrk[$this->page] = $this->intmrk[$page];
22597  $this->bordermrk[$this->page] = $this->bordermrk[$page];
22598  $this->cntmrk[$this->page] = $this->cntmrk[$page];
22599  $this->pageopen[$this->page] = false;
22600  if (isset($this->footerpos[$page])) {
22601  $this->footerpos[$this->page] = $this->footerpos[$page];
22602  }
22603  if (isset($this->footerlen[$page])) {
22604  $this->footerlen[$this->page] = $this->footerlen[$page];
22605  }
22606  if (isset($this->transfmrk[$page])) {
22607  $this->transfmrk[$this->page] = $this->transfmrk[$page];
22608  }
22609  if (isset($this->PageAnnots[$page])) {
22610  $this->PageAnnots[$this->page] = $this->PageAnnots[$page];
22611  }
22612  if (isset($this->newpagegroup[$page])) {
22613  $this->newpagegroup[$this->page] = $this->newpagegroup[$page];
22614  }
22615  // copy outlines
22616  $tmpoutlines = $this->outlines;
22617  foreach ($tmpoutlines as $key => $outline) {
22618  if ($outline['p'] == $page) {
22619  $this->outlines[] = array('t' => $outline['t'], 'l' => $outline['l'], 'y' => $outline['y'], 'p' => $this->page);
22620  }
22621  }
22622  // copy links
22623  $tmplinks = $this->links;
22624  foreach ($tmplinks as $key => $link) {
22625  if ($link[0] == $page) {
22626  $this->links[] = array($this->page, $link[1]);
22627  }
22628  }
22629  // return to last page
22630  $this->lastPage(true);
22631  return true;
22632  }
22633 
22648  public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC') {
22649  $fontsize = $this->FontSizePt;
22650  $fontfamily = $this->FontFamily;
22651  $fontstyle = $this->FontStyle;
22652  $w = $this->w - $this->lMargin - $this->rMargin;
22653  $spacer = $this->GetStringWidth(chr(32)) * 4;
22654  $page_first = $this->getPage();
22655  $lmargin = $this->lMargin;
22656  $rmargin = $this->rMargin;
22657  $x_start = $this->GetX();
22658  $current_page = $this->page;
22659  $current_column = $this->current_column;
22660  if ($this->empty_string($numbersfont)) {
22661  $numbersfont = $this->default_monospaced_font;
22662  }
22663  if ($this->empty_string($filler)) {
22664  $filler = ' ';
22665  }
22666  if ($this->empty_string($page)) {
22667  $gap = ' ';
22668  } else {
22669  $gap = '';
22670  if ($page < 1) {
22671  $page = 1;
22672  }
22673  }
22674  foreach ($this->outlines as $key => $outline) {
22675  if ($this->rtl) {
22676  $aligntext = 'R';
22677  $alignnum = 'L';
22678  } else {
22679  $aligntext = 'L';
22680  $alignnum = 'R';
22681  }
22682  if ($outline['l'] == 0) {
22683  $this->SetFont($fontfamily, $fontstyle.'B', $fontsize);
22684  } else {
22685  $this->SetFont($fontfamily, $fontstyle, $fontsize - $outline['l']);
22686  }
22687  // check for page break
22688  $this->checkPageBreak(($this->FontSize * $this->cell_height_ratio));
22689  // set margins and X position
22690  if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
22691  $this->lMargin = $lmargin;
22692  $this->rMargin = $rmargin;
22693  } else {
22694  if ($this->current_column != $current_column) {
22695  if ($this->rtl) {
22696  $x_start = $this->w - $this->columns[$this->current_column]['x'];
22697  } else {
22698  $x_start = $this->columns[$this->current_column]['x'];
22699  }
22700  }
22701  $lmargin = $this->lMargin;
22702  $rmargin = $this->rMargin;
22703  $current_page = $this->page;
22704  $current_column = $this->current_column;
22705  }
22706  $this->SetX($x_start);
22707  $indent = ($spacer * $outline['l']);
22708  if ($this->rtl) {
22709  $this->rMargin += $indent;
22710  $this->x -= $indent;
22711  } else {
22712  $this->lMargin += $indent;
22713  $this->x += $indent;
22714  }
22715  $link = $this->AddLink();
22716  $this->SetLink($link, $outline['y'], $outline['p']);
22717  // write the text
22718  $this->Write(0, $outline['t'], $link, 0, $aligntext, false, 0, false, false, 0);
22719  $this->SetFont($numbersfont, $fontstyle, $fontsize);
22720  if ($this->empty_string($page)) {
22721  $pagenum = $outline['p'];
22722  } else {
22723  // placemark to be replaced with the correct number
22724  $pagenum = '{#'.($outline['p']).'}';
22725  if ($this->isUnicodeFont()) {
22726  $pagenum = '{'.$pagenum.'}';
22727  }
22728  }
22729  $numwidth = $this->GetStringWidth($pagenum);
22730  if ($this->rtl) {
22731  $tw = $this->x - $this->lMargin;
22732  } else {
22733  $tw = $this->w - $this->rMargin - $this->x;
22734  }
22735  $fw = $tw - $numwidth - $this->GetStringWidth(chr(32));
22736  $numfills = floor($fw / $this->GetStringWidth($filler));
22737  if ($numfills > 0) {
22738  $rowfill = str_repeat($filler, $numfills);
22739  } else {
22740  $rowfill = '';
22741  }
22742  if ($this->rtl) {
22743  $pagenum = $pagenum.$gap.$rowfill.' ';
22744  } else {
22745  $pagenum = ' '.$rowfill.$gap.$pagenum;
22746  }
22747  // write the number
22748  $this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
22749  }
22750  $page_last = $this->getPage();
22751  $numpages = $page_last - $page_first + 1;
22752  if (!$this->empty_string($page)) {
22753  for ($p = $page_first; $p <= $page_last; ++$p) {
22754  // get page data
22755  $temppage = $this->getPageBuffer($p);
22756  for ($n = 1; $n <= $this->numpages; ++$n) {
22757  // update page numbers
22758  $k = '{#'.$n.'}';
22759  $ku = '{'.$k.'}';
22760  $alias_a = $this->_escape($k);
22761  $alias_au = $this->_escape($ku);
22762  if ($this->isunicode) {
22763  $alias_b = $this->_escape($this->UTF8ToLatin1($k));
22764  $alias_bu = $this->_escape($this->UTF8ToLatin1($ku));
22765  $alias_c = $this->_escape($this->utf8StrRev($k, false, $this->tmprtl));
22766  $alias_cu = $this->_escape($this->utf8StrRev($ku, false, $this->tmprtl));
22767  }
22768  if ($n >= $page) {
22769  $np = $n + $numpages;
22770  } else {
22771  $np = $n;
22772  }
22773  $ns = $this->formatTOCPageNumber($np);
22774  $nu = $ns;
22775  $sdiff = strlen($k) - strlen($ns) - 1;
22776  $sdiffu = strlen($ku) - strlen($ns) - 1;
22777  $sfill = str_repeat($filler, $sdiff);
22778  $sfillu = str_repeat($filler, $sdiffu);
22779  if ($this->rtl) {
22780  $ns = $ns.' '.$sfill;
22781  $nu = $nu.' '.$sfillu;
22782  } else {
22783  $ns = $sfill.' '.$ns;
22784  $nu = $sfillu.' '.$nu;
22785  }
22786  $nu = $this->UTF8ToUTF16BE($nu, false);
22787  $temppage = str_replace($alias_au, $nu, $temppage);
22788  if ($this->isunicode) {
22789  $temppage = str_replace($alias_bu, $nu, $temppage);
22790  $temppage = str_replace($alias_cu, $nu, $temppage);
22791  $temppage = str_replace($alias_b, $ns, $temppage);
22792  $temppage = str_replace($alias_c, $ns, $temppage);
22793  }
22794  $temppage = str_replace($alias_a, $ns, $temppage);
22795  }
22796  // save changes
22797  $this->setPageBuffer($p, $temppage);
22798  }
22799  // move pages
22800  $this->Bookmark($toc_name, 0, 0, $page_first);
22801  for ($i = 0; $i < $numpages; ++$i) {
22802  $this->movePage($page_last, $page);
22803  }
22804  }
22805  }
22806 
22820  public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true) {
22821  $prev_htmlLinkColorArray = $this->htmlLinkColorArray;
22822  $prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
22823  // set new style for link
22824  $this->htmlLinkColorArray = array();
22825  $this->htmlLinkFontStyle = '';
22826  $page_first = $this->getPage();
22827  // get the font type used for numbers in each template
22828  $current_font = $this->FontFamily;
22829  foreach ($templates as $level => $html) {
22830  $dom = $this->getHtmlDomArray($html);
22831  foreach ($dom as $key => $value) {
22832  if ($value['value'] == '#TOC_PAGE_NUMBER#') {
22833  $this->SetFont($dom[($key - 1)]['fontname']);
22834  $templates['F'.$level] = $this->isUnicodeFont();
22835  }
22836  }
22837  }
22838  $this->SetFont($current_font);
22839  foreach ($this->outlines as $key => $outline) {
22840  // get HTML template
22841  $row = $templates[$outline['l']];
22842  if ($this->empty_string($page)) {
22843  $pagenum = $outline['p'];
22844  } else {
22845  // placemark to be replaced with the correct number
22846  $pagenum = '{#'.($outline['p']).'}';
22847  if ($templates['F'.$outline['l']]) {
22848  $pagenum = '{'.$pagenum.'}';
22849  }
22850  }
22851  // replace templates with current values
22852  $row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
22853  $row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
22854  // add link to page
22855  $row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
22856  // write bookmark entry
22857  $this->writeHTML($row, false, false, true, false, '');
22858  }
22859  // restore link styles
22860  $this->htmlLinkColorArray = $prev_htmlLinkColorArray;
22861  $this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
22862  // move TOC page and replace numbers
22863  $page_last = $this->getPage();
22864  $numpages = $page_last - $page_first + 1;
22865  if (!$this->empty_string($page)) {
22866  for ($p = $page_first; $p <= $page_last; ++$p) {
22867  // get page data
22868  $temppage = $this->getPageBuffer($p);
22869  for ($n = 1; $n <= $this->numpages; ++$n) {
22870  // update page numbers
22871  $k = '{#'.$n.'}';
22872  $ku = '{'.$k.'}';
22873  $alias_a = $this->_escape($k);
22874  $alias_au = $this->_escape('{'.$k.'}');
22875  if ($this->isunicode) {
22876  $alias_b = $this->_escape($this->UTF8ToLatin1($k));
22877  $alias_bu = $this->_escape($this->UTF8ToLatin1($ku));
22878  $alias_c = $this->_escape($this->utf8StrRev($k, false, $this->tmprtl));
22879  $alias_cu = $this->_escape($this->utf8StrRev($ku, false, $this->tmprtl));
22880  }
22881  if ($n >= $page) {
22882  $np = $n + $numpages;
22883  } else {
22884  $np = $n;
22885  }
22886  $ns = $this->formatTOCPageNumber($np);
22887  $nu = $ns;
22888  if ($correct_align) {
22889  $sdiff = strlen($k) - strlen($ns);
22890  $sdiffu = strlen($ku) - strlen($ns);
22891  $sfill = str_repeat(' ', $sdiff);
22892  $sfillu = str_repeat(' ', $sdiffu);
22893  if ($this->rtl) {
22894  $ns = $ns.$sfill;
22895  $nu = $nu.$sfillu;
22896  } else {
22897  $ns = $sfill.$ns;
22898  $nu = $sfillu.$nu;
22899  }
22900  }
22901  $nu = $this->UTF8ToUTF16BE($nu, false);
22902  $temppage = str_replace($alias_au, $nu, $temppage);
22903  if ($this->isunicode) {
22904  $temppage = str_replace($alias_bu, $nu, $temppage);
22905  $temppage = str_replace($alias_cu, $nu, $temppage);
22906  $temppage = str_replace($alias_b, $ns, $temppage);
22907  $temppage = str_replace($alias_c, $ns, $temppage);
22908  }
22909  $temppage = str_replace($alias_a, $ns, $temppage);
22910  }
22911  // save changes
22912  $this->setPageBuffer($p, $temppage);
22913  }
22914  // move pages
22915  $this->Bookmark($toc_name, 0, 0, $page_first);
22916  for ($i = 0; $i < $numpages; ++$i) {
22917  $this->movePage($page_last, $page);
22918  }
22919  }
22920  }
22921 
22927  public function startTransaction() {
22928  if (isset($this->objcopy)) {
22929  // remove previous copy
22930  $this->commitTransaction();
22931  }
22932  // record current page number and Y position
22933  $this->start_transaction_page = $this->page;
22934  $this->start_transaction_y = $this->y;
22935  // clone current object
22936  $this->objcopy = $this->objclone($this);
22937  }
22938 
22944  public function commitTransaction() {
22945  if (isset($this->objcopy)) {
22946  $this->objcopy->_destroy(true, true);
22947  unset($this->objcopy);
22948  }
22949  }
22950 
22958  public function rollbackTransaction($self=false) {
22959  if (isset($this->objcopy)) {
22960  if (isset($this->objcopy->diskcache) AND $this->objcopy->diskcache) {
22961  // truncate files to previous values
22962  foreach ($this->objcopy->cache_file_length as $file => $length) {
22963  $file = substr($file, 1);
22964  $handle = fopen($file, 'r+');
22965  ftruncate($handle, $length);
22966  }
22967  }
22968  $this->_destroy(true, true);
22969  if ($self) {
22970  $objvars = get_object_vars($this->objcopy);
22971  foreach ($objvars as $key => $value) {
22972  $this->$key = $value;
22973  }
22974  }
22975  return $this->objcopy;
22976  }
22977  return $this;
22978  }
22979 
22987  public function objclone($object) {
22988  return @clone($object);
22989  }
22990 
22998  public function empty_string($str) {
22999  return (is_null($str) OR (is_string($str) AND (strlen($str) == 0)));
23000  }
23001 
23011  public function revstrpos($haystack, $needle, $offset = 0) {
23012  $length = strlen($haystack);
23013  $offset = ($offset > 0)?($length - $offset):abs($offset);
23014  $pos = strpos(strrev($haystack), strrev($needle), $offset);
23015  return ($pos === false)?false:($length - $pos - strlen($needle));
23016  }
23017 
23018  // --- MULTI COLUMNS METHODS -----------------------
23019 
23028  public function setEqualColumns($numcols=0, $width=0, $y='') {
23029  $this->columns = array();
23030  if ($numcols < 2) {
23031  $numcols = 0;
23032  $this->columns = array();
23033  } else {
23034  // maximum column width
23035  $maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
23036  if (($width == 0) OR ($width > $maxwidth)) {
23037  $width = $maxwidth;
23038  }
23039  if ($this->empty_string($y)) {
23040  $y = $this->y;
23041  }
23042  // space between columns
23043  $space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
23044  // fill the columns array (with, space, starting Y position)
23045  for ($i = 0; $i < $numcols; ++$i) {
23046  $this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
23047  }
23048  }
23049  $this->num_columns = $numcols;
23050  $this->current_column = 0;
23051  $this->column_start_page = $this->page;
23052  }
23053 
23061  public function setColumnsArray($columns) {
23062  $this->columns = $columns;
23063  $this->num_columns = count($columns);
23064  $this->current_column = 0;
23065  $this->column_start_page = $this->page;
23066  }
23067 
23074  public function selectColumn($col='') {
23075  if (is_string($col)) {
23076  $col = $this->current_column;
23077  } elseif($col >= $this->num_columns) {
23078  $col = 0;
23079  }
23080  $xshift = 0;
23081  $enable_thead = false;
23082  if ($this->num_columns > 1) {
23083  if ($col != $this->current_column) {
23084  // move Y pointer at the top of the column
23085  if ($this->column_start_page == $this->page) {
23086  $this->y = $this->columns[$col]['y'];
23087  } else {
23088  $this->y = $this->tMargin;
23089  }
23090  // Avoid to write table headers more than once
23091  if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
23092  $enable_thead = true;
23093  $this->maxselcol['page'] = $this->page;
23094  $this->maxselcol['column'] = $col;
23095  }
23096  }
23097  $xshift = $this->colxshift;
23098  // set X position of the current column by case
23099  $listindent = ($this->listindentlevel * $this->listindent);
23100  $colpos = ($col * ($this->columns[$col]['w'] + $this->columns[$col]['s']));
23101  if ($this->rtl) {
23102  $x = $this->w - $this->original_rMargin - $colpos;
23103  $this->rMargin = ($this->w - $x + $listindent);
23104  $this->lMargin = ($x - $this->columns[$col]['w']);
23105  $this->x = $x - $listindent;
23106  } else {
23107  $x = $this->original_lMargin + $colpos;
23108  $this->lMargin = ($x + $listindent);
23109  $this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
23110  $this->x = $x + $listindent;
23111  }
23112  $this->columns[$col]['x'] = $x;
23113  }
23114  $this->current_column = $col;
23115  // fix for HTML mode
23116  $this->newline = true;
23117  // print HTML table header (if any)
23118  if ((!$this->empty_string($this->thead)) AND (!$this->inthead)) {
23119  if ($enable_thead) {
23120  // print table header
23121  $this->writeHTML($this->thead, false, false, false, false, '');
23122  $this->y += $xshift['s'];
23123  // store end of header position
23124  if (!isset($this->columns[$col]['th'])) {
23125  $this->columns[$col]['th'] = array();
23126  }
23127  $this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
23128  $this->lasth = 0;
23129  } elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
23130  $this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
23131  }
23132  }
23133  // account for an html table cell over multiple columns
23134  if ($this->rtl) {
23135  $this->rMargin += $xshift['x'];
23136  $this->x -= ($xshift['x'] + $xshift['p']);
23137  } else {
23138  $this->lMargin += $xshift['x'];
23139  $this->x += $xshift['x'] + $xshift['p'];
23140  }
23141  }
23142 
23149  public function getColumn() {
23150  return $this->current_column;
23151  }
23152 
23159  public function getNumberOfColumns() {
23160  return $this->num_columns;
23161  }
23162 
23170  public function serializeTCPDFtagParameters($pararray) {
23171  return urlencode(serialize($pararray));
23172  }
23173 
23182  public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
23183  // Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
23184  // convert text rendering parameters
23185  if ($stroke < 0) {
23186  $stroke = 0;
23187  }
23188  if ($fill === true) {
23189  if ($stroke > 0) {
23190  if ($clip === true) {
23191  // Fill, then stroke text and add to path for clipping
23192  $textrendermode = 6;
23193  } else {
23194  // Fill, then stroke text
23195  $textrendermode = 2;
23196  }
23197  $textstrokewidth = $stroke;
23198  } else {
23199  if ($clip === true) {
23200  // Fill text and add to path for clipping
23201  $textrendermode = 4;
23202  } else {
23203  // Fill text
23204  $textrendermode = 0;
23205  }
23206  }
23207  } else {
23208  if ($stroke > 0) {
23209  if ($clip === true) {
23210  // Stroke text and add to path for clipping
23211  $textrendermode = 5;
23212  } else {
23213  // Stroke text
23214  $textrendermode = 1;
23215  }
23216  $textstrokewidth = $stroke;
23217  } else {
23218  if ($clip === true) {
23219  // Add text to path for clipping
23220  $textrendermode = 7;
23221  } else {
23222  // Neither fill nor stroke text (invisible)
23223  $textrendermode = 3;
23224  }
23225  }
23226  }
23227  $this->textrendermode = $textrendermode;
23228  $this->textstrokewidth = $stroke * $this->k;
23229  }
23230 
23245  protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
23246  $hyphenword = array(); // hyphens positions
23247  $numchars = count($word);
23248  if ($numchars <= $charmin) {
23249  return $word;
23250  }
23251  $word_string = $this->UTF8ArrSubString($word);
23252  // some words will be returned as-is
23253  $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})(\]?)$/';
23254  if (preg_match($pattern, $word_string) > 0) {
23255  // email
23256  return $word;
23257  }
23258  $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})(\]?)$/';
23259  if (preg_match($pattern, $word_string) > 0) {
23260  // URL
23261  return $word;
23262  }
23263  if (isset($dictionary[$word_string])) {
23264  return $this->UTF8StringToArray($dictionary[$word_string]);
23265  }
23266  // suround word with '_' characters
23267  $tmpword = array_merge(array(95), $word, array(95));
23268  $tmpnumchars = $numchars + 2;
23269  $maxpos = $tmpnumchars - $charmin;
23270  for ($pos = 0; $pos < $maxpos; ++$pos) {
23271  $imax = min(($tmpnumchars - $pos), $charmax);
23272  for ($i = $charmin; $i <= $imax; ++$i) {
23273  $subword = strtolower($this->UTF8ArrSubString($tmpword, $pos, $pos + $i));
23274  if (isset($patterns[$subword])) {
23275  $pattern = $this->UTF8StringToArray($patterns[$subword]);
23276  $pattern_length = count($pattern);
23277  $digits = 1;
23278  for ($j = 0; $j < $pattern_length; ++$j) {
23279  // check if $pattern[$j] is a number
23280  if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
23281  if ($j == 0) {
23282  $zero = $pos - 1;
23283  } else {
23284  $zero = $pos + $j - $digits;
23285  }
23286  if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] != $pattern[$j])) {
23287  $hyphenword[$zero] = $this->unichr($pattern[$j]);
23288  }
23289  ++$digits;
23290  }
23291  }
23292  }
23293  }
23294  }
23295  $inserted = 0;
23296  $maxpos = $numchars - $rightmin;
23297  for($i = $leftmin; $i <= $maxpos; ++$i) {
23298  if(isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
23299  // 173 = soft hyphen character
23300  array_splice($word, $i + $inserted, 0, 173);
23301  ++$inserted;
23302  }
23303  }
23304  return $word;
23305  }
23306 
23315  public function getHyphenPatternsFromTEX($file) {
23316  // TEX patterns are available at:
23317  // http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
23318  $data = file_get_contents($file);
23319  $patterns = array();
23320  // remove comments
23321  $data = preg_replace('/\%[^\n]*/', '', $data);
23322  // extract the patterns part
23323  preg_match('/\\\\patterns\{([^\}]*)\}/i', $data, $matches);
23324  $data = trim(substr($matches[0], 10, -1));
23325  // extract each pattern
23326  $patterns_array = preg_split('/[\s]+/', $data);
23327  // create new language array of patterns
23328  $patterns = array();
23329  foreach($patterns_array as $val) {
23330  if (!$this->empty_string($val)) {
23331  $val = trim($val);
23332  $val = str_replace('\'', '\\\'', $val);
23333  $key = preg_replace('/[0-9]+/', '', $val);
23334  $patterns[$key] = $val;
23335  }
23336  }
23337  return $patterns;
23338  }
23339 
23354  public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
23355  $text = $this->unhtmlentities($text);
23356  $word = array(); // last word
23357  $txtarr = array(); // text to be returned
23358  $intag = false; // true if we are inside an HTML tag
23359  if (!is_array($patterns)) {
23360  $patterns = $this->getHyphenPatternsFromTEX($patterns);
23361  }
23362  // get array of characters
23363  $unichars = $this->UTF8StringToArray($text);
23364  // for each char
23365  foreach ($unichars as $char) {
23366  if ((!$intag) AND $this->unicode->uni_type[$char] == 'L') {
23367  // letter character
23368  $word[] = $char;
23369  } else {
23370  // other type of character
23371  if (!$this->empty_string($word)) {
23372  // hypenate the word
23373  $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
23374  $word = array();
23375  }
23376  $txtarr[] = $char;
23377  if (chr($char) == '<') {
23378  // we are inside an HTML tag
23379  $intag = true;
23380  } elseif ($intag AND (chr($char) == '>')) {
23381  // end of HTML tag
23382  $intag = false;
23383  }
23384  }
23385  }
23386  if (!$this->empty_string($word)) {
23387  // hypenate the word
23388  $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
23389  }
23390  // convert char array to string and return
23391  return $this->UTF8ArrSubString($txtarr);
23392  }
23393 
23400  public function setRasterizeVectorImages($mode) {
23401  $this->rasterize_vector_images = $mode;
23402  }
23403 
23426  protected function getPathPaintOperator($style, $default='S') {
23427  $op = '';
23428  switch($style) {
23429  case 'S':
23430  case 'D': {
23431  $op = 'S';
23432  break;
23433  }
23434  case 's':
23435  case 'd': {
23436  $op = 's';
23437  break;
23438  }
23439  case 'f':
23440  case 'F': {
23441  $op = 'f';
23442  break;
23443  }
23444  case 'f*':
23445  case 'F*': {
23446  $op = 'f*';
23447  break;
23448  }
23449  case 'B':
23450  case 'FD':
23451  case 'DF': {
23452  $op = 'B';
23453  break;
23454  }
23455  case 'B*':
23456  case 'F*D':
23457  case 'DF*': {
23458  $op = 'B*';
23459  break;
23460  }
23461  case 'b':
23462  case 'fd':
23463  case 'df': {
23464  $op = 'b';
23465  break;
23466  }
23467  case 'b*':
23468  case 'f*d':
23469  case 'df*': {
23470  $op = 'b*';
23471  break;
23472  }
23473  case 'CNZ': {
23474  $op = 'W n';
23475  break;
23476  }
23477  case 'CEO': {
23478  $op = 'W* n';
23479  break;
23480  }
23481  case 'n': {
23482  $op = 'n';
23483  break;
23484  }
23485  default: {
23486  if (!empty($default)) {
23487  $op = $this->getPathPaintOperator($default, '');
23488  } else {
23489  $op = '';
23490  }
23491  }
23492  }
23493  return $op;
23494  }
23495 
23503  public function setFontSubsetting($enable=true) {
23504  $this->font_subsetting = $enable ? true : false;
23505  }
23506 
23514  public function getFontSubsetting() {
23515  return $this->font_subsetting;
23516  }
23517 
23527  public function stringLeftTrim($str, $replace='') {
23528  return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
23529  }
23530 
23540  public function stringRightTrim($str, $replace='') {
23541  return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
23542  }
23543 
23553  public function stringTrim($str, $replace='') {
23554  $str = $this->stringLeftTrim($str, $replace);
23555  $str = $this->stringRightTrim($str, $replace);
23556  return $str;
23557  }
23558 
23566  public function isUnicodeFont() {
23567  return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
23568  }
23569 
23578  public function getFontFamilyName($fontfamily) {
23579  // remove spaces and symbols
23580  $fontfamily = preg_replace('/[^a-z0-9\,]/', '', strtolower($fontfamily));
23581  // extract all font names
23582  $fontslist = preg_split('/[,]/', $fontfamily);
23583  // find first valid font name
23584  foreach ($fontslist as $font) {
23585  // replace font variations
23586  $font = preg_replace('/italic$/', 'I', $font);
23587  $font = preg_replace('/oblique$/', 'I', $font);
23588  $font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
23589  // replace common family names and core fonts
23590  $pattern = array();
23591  $replacement = array();
23592  $pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
23593  $replacement[] = 'times';
23594  $pattern[] = '/^sansserif/';
23595  $replacement[] = 'helvetica';
23596  $pattern[] = '/^monospace/';
23597  $replacement[] = 'courier';
23598  $font = preg_replace($pattern, $replacement, $font);
23599  if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
23600  return $font;
23601  }
23602  }
23603  // return current font as default
23604  return $this->CurrentFont['fontkey'];
23605  }
23606 
23620  public function startTemplate($w=0, $h=0) {
23621  if ($this->inxobj) {
23622  // we are already inside an XObject template
23623  return false;
23624  }
23625  $this->inxobj = true;
23626  ++$this->n;
23627  // XObject ID
23628  $this->xobjid = 'XT'.$this->n;
23629  // object ID
23630  $this->xobjects[$this->xobjid] = array('n' => $this->n);
23631  // store current graphic state
23632  $this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
23633  // initialize data
23634  $this->xobjects[$this->xobjid]['intmrk'] = 0;
23635  $this->xobjects[$this->xobjid]['transfmrk'] = array();
23636  $this->xobjects[$this->xobjid]['outdata'] = '';
23637  $this->xobjects[$this->xobjid]['xobjects'] = array();
23638  $this->xobjects[$this->xobjid]['images'] = array();
23639  $this->xobjects[$this->xobjid]['fonts'] = array();
23640  $this->xobjects[$this->xobjid]['annotations'] = array();
23641  // set new environment
23642  $this->num_columns = 1;
23643  $this->current_column = 0;
23644  $this->SetAutoPageBreak(false);
23645  if (($w === '') OR ($w <= 0)) {
23646  $w = $this->w - $this->lMargin - $this->rMargin;
23647  }
23648  if (($h === '') OR ($h <= 0)) {
23649  $h = $this->h - $this->tMargin - $this->bMargin;
23650  }
23651  $this->xobjects[$this->xobjid]['x'] = 0;
23652  $this->xobjects[$this->xobjid]['y'] = 0;
23653  $this->xobjects[$this->xobjid]['w'] = $w;
23654  $this->xobjects[$this->xobjid]['h'] = $h;
23655  $this->w = $w;
23656  $this->h = $h;
23657  $this->wPt = $this->w * $this->k;
23658  $this->hPt = $this->h * $this->k;
23659  $this->fwPt = $this->wPt;
23660  $this->fhPt = $this->hPt;
23661  $this->x = 0;
23662  $this->y = 0;
23663  $this->lMargin = 0;
23664  $this->rMargin = 0;
23665  $this->tMargin = 0;
23666  $this->bMargin = 0;
23667  return $this->xobjid;
23668  }
23669 
23680  public function endTemplate() {
23681  if (!$this->inxobj) {
23682  // we are not inside a template
23683  return false;
23684  }
23685  $this->inxobj = false;
23686  // restore previous graphic state
23687  $this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
23688  return $this->xobjid;
23689  }
23690 
23709  public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
23710  if (!isset($this->xobjects[$id])) {
23711  $this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
23712  }
23713  if ($this->inxobj) {
23714  if ($id == $this->xobjid) {
23715  // close current template
23716  $this->endTemplate();
23717  } else {
23718  // use the template as resource for the template currently opened
23719  $this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
23720  }
23721  }
23722  // set default values
23723  if ($x === '') {
23724  $x = $this->x;
23725  }
23726  if ($y === '') {
23727  $y = $this->y;
23728  }
23729  // check page for no-write regions and adapt page margins if necessary
23730  $this->checkPageRegions($h, $x, $y);
23731  $ow = $this->xobjects[$id]['w'];
23732  $oh = $this->xobjects[$id]['h'];
23733  // calculate template width and height on document
23734  if (($w <= 0) AND ($h <= 0)) {
23735  $w = $ow;
23736  $h = $oh;
23737  } elseif ($w <= 0) {
23738  $w = $h * $ow / $oh;
23739  } elseif ($h <= 0) {
23740  $h = $w * $oh / $ow;
23741  }
23742  // fit the template on available space
23743  $this->fitBlock($w, $h, $x, $y, $fitonpage);
23744  // set page alignment
23745  $rb_y = $y + $h;
23746  // set alignment
23747  if ($this->rtl) {
23748  if ($palign == 'L') {
23749  $xt = $this->lMargin;
23750  } elseif ($palign == 'C') {
23751  $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
23752  } elseif ($palign == 'R') {
23753  $xt = $this->w - $this->rMargin - $w;
23754  } else {
23755  $xt = $x - $w;
23756  }
23757  $rb_x = $xt;
23758  } else {
23759  if ($palign == 'L') {
23760  $xt = $this->lMargin;
23761  } elseif ($palign == 'C') {
23762  $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
23763  } elseif ($palign == 'R') {
23764  $xt = $this->w - $this->rMargin - $w;
23765  } else {
23766  $xt = $x;
23767  }
23768  $rb_x = $xt + $w;
23769  }
23770  // print XObject Template + Transformation matrix
23771  $this->StartTransform();
23772  // translate and scale
23773  $sx = ($w / $this->xobjects[$id]['w']);
23774  $sy = ($h / $this->xobjects[$id]['h']);
23775  $tm = array();
23776  $tm[0] = $sx;
23777  $tm[1] = 0;
23778  $tm[2] = 0;
23779  $tm[3] = $sy;
23780  $tm[4] = $xt * $this->k;
23781  $tm[5] = ($this->h - $h - $y) * $this->k;
23782  $this->Transform($tm);
23783  // set object
23784  $this->_out('/'.$id.' Do');
23785  $this->StopTransform();
23786  // add annotations
23787  if (!empty($this->xobjects[$id]['annotations'])) {
23788  foreach ($this->xobjects[$id]['annotations'] as $annot) {
23789  // transform original coordinates
23790  $coordlt = $this->getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
23791  $ax = ($coordlt[4] / $this->k);
23792  $ay = ($this->h - $h - ($coordlt[5] / $this->k));
23793  $coordrb = $this->getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
23794  $aw = ($coordrb[4] / $this->k) - $ax;
23795  $ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
23796  $this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
23797  }
23798  }
23799  // set pointer to align the next text/objects
23800  switch($align) {
23801  case 'T': {
23802  $this->y = $y;
23803  $this->x = $rb_x;
23804  break;
23805  }
23806  case 'M': {
23807  $this->y = $y + round($h/2);
23808  $this->x = $rb_x;
23809  break;
23810  }
23811  case 'B': {
23812  $this->y = $rb_y;
23813  $this->x = $rb_x;
23814  break;
23815  }
23816  case 'N': {
23817  $this->SetY($rb_y);
23818  break;
23819  }
23820  default:{
23821  break;
23822  }
23823  }
23824  }
23825 
23833  public function setFontStretching($perc=100) {
23834  $this->font_stretching = $perc;
23835  }
23836 
23844  public function getFontStretching() {
23845  return $this->font_stretching;
23846  }
23847 
23855  public function setFontSpacing($spacing=0) {
23856  $this->font_spacing = $spacing;
23857  }
23858 
23866  public function getFontSpacing() {
23867  return $this->font_spacing;
23868  }
23869 
23878  public function getPageRegions() {
23879  return $this->page_regions;
23880  }
23881 
23893  public function setPageRegions($regions=array()) {
23894  // empty current regions array
23895  $this->page_regions = array();
23896  // add regions
23897  foreach ($regions as $data) {
23898  $this->addPageRegion($data);
23899  }
23900  }
23901 
23913  public function addPageRegion($region) {
23914  if (!isset($region['page']) OR empty($region['page'])) {
23915  $region['page'] = $this->page;
23916  }
23917  if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
23918  AND isset($region['yt']) AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
23919  AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
23920  $this->page_regions[] = $region;
23921  }
23922  }
23923 
23932  public function removePageRegion($key) {
23933  if (isset($this->page_regions[$key])) {
23934  unset($this->page_regions[$key]);
23935  }
23936  }
23937 
23949  protected function checkPageRegions($h=0, &$x='', &$y='') {
23950  // set default values
23951  if ($x === '') {
23952  $x = &$this->x;
23953  }
23954  if ($y === '') {
23955  $y = &$this->y;
23956  }
23957  if (empty($this->page_regions)) {
23958  // no page regions defined
23959  return;
23960  }
23961  if (empty($h)) {
23962  $h = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
23963  }
23964  if ($this->rtl) {
23965  $this->lMargin = $this->original_lMargin;
23966  } else {
23967  $this->rMargin = $this->original_rMargin;
23968  }
23969  if ($this->AutoPageBreak AND !$this->InFooter AND (($y + $h) > $this->PageBreakTrigger)) {
23970  // the content will be printed on a new page
23971  return;
23972  }
23973  // adjust coordinates and page margins
23974  foreach ($this->page_regions as $regid => $regdata) {
23975  if ($regdata['page'] == $this->page) {
23976  // check region boundaries
23977  if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
23978  // Y is inside the region
23979  $minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
23980  $yt = max($y, $regdata['yt']);
23981  $yb = min(($yt + $h), $regdata['yb']);
23982  $xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
23983  $xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
23984  if ($regdata['side'] == 'L') { // left side
23985  $new_margin = max($xt, $xb);
23986  if ($this->lMargin < $new_margin) {
23987  if ($this->rtl) {
23988  // adjust left page margin
23989  $this->lMargin = $new_margin;
23990  }
23991  if ($x < $new_margin) {
23992  // adjust x position
23993  $x = $new_margin;
23994  }
23995  }
23996  } elseif ($regdata['side'] == 'R') { // right side
23997  $new_margin = min($xt, $xb);
23998  if (($this->w - $this->rMargin) > $new_margin) {
23999  if (!$this->rtl) {
24000  // adjust right page margin
24001  $this->rMargin = ($this->w - $new_margin);
24002  }
24003  if ($x > $new_margin) {
24004  // adjust x position
24005  $x = $new_margin;
24006  }
24007  }
24008  }
24009  }
24010  }
24011  }
24012  }
24013 
24014  // -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
24015  // SVG METHODS
24016  // -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
24017 
24035  public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
24036  if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
24037  // convert SVG to raster image using GD or ImageMagick libraries
24038  return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
24039  }
24040  $this->svgdir = dirname($file);
24041  $svgdata = file_get_contents($file);
24042  if ($svgdata === false) {
24043  $this->Error('SVG file not found: '.$file);
24044  }
24045  if ($x === '') {
24046  $x = $this->x;
24047  }
24048  if ($y === '') {
24049  $y = $this->y;
24050  }
24051  // check page for no-write regions and adapt page margins if necessary
24052  $this->checkPageRegions($x, $y);
24053  $k = $this->k;
24054  $ox = 0;
24055  $oy = 0;
24056  $ow = $w;
24057  $oh = $h;
24058  $aspect_ratio_align = 'xMidYMid';
24059  $aspect_ratio_ms = 'meet';
24060  $regs = array();
24061  // get original image width and height
24062  preg_match('/<svg([^>]*)>/si', $svgdata, $regs);
24063  if (isset($regs[1]) AND !empty($regs[1])) {
24064  $tmp = array();
24065  if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
24066  $ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
24067  }
24068  $tmp = array();
24069  if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
24070  $oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
24071  }
24072  $tmp = array();
24073  if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
24074  $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
24075  }
24076  $tmp = array();
24077  if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
24078  $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
24079  }
24080  $tmp = array();
24081  $view_box = array();
24082  if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
24083  if (count($tmp) == 5) {
24084  array_shift($tmp);
24085  foreach ($tmp as $key => $val) {
24086  $view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24087  }
24088  $ox = $view_box[0];
24089  $oy = $view_box[1];
24090  }
24091  // get aspect ratio
24092  $tmp = array();
24093  if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
24094  $aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
24095  switch (count($aspect_ratio)) {
24096  case 3: {
24097  $aspect_ratio_align = $aspect_ratio[1];
24098  $aspect_ratio_ms = $aspect_ratio[2];
24099  break;
24100  }
24101  case 2: {
24102  $aspect_ratio_align = $aspect_ratio[0];
24103  $aspect_ratio_ms = $aspect_ratio[1];
24104  break;
24105  }
24106  case 1: {
24107  $aspect_ratio_align = $aspect_ratio[0];
24108  $aspect_ratio_ms = 'meet';
24109  break;
24110  }
24111  }
24112  }
24113  }
24114  }
24115  // calculate image width and height on document
24116  if (($w <= 0) AND ($h <= 0)) {
24117  // convert image size to document unit
24118  $w = $ow;
24119  $h = $oh;
24120  } elseif ($w <= 0) {
24121  $w = $h * $ow / $oh;
24122  } elseif ($h <= 0) {
24123  $h = $w * $oh / $ow;
24124  }
24125  // fit the image on available space
24126  $this->fitBlock($w, $h, $x, $y, $fitonpage);
24127  if ($this->rasterize_vector_images) {
24128  // convert SVG to raster image using GD or ImageMagick libraries
24129  return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
24130  }
24131  // set alignment
24132  $this->img_rb_y = $y + $h;
24133  // set alignment
24134  if ($this->rtl) {
24135  if ($palign == 'L') {
24136  $ximg = $this->lMargin;
24137  } elseif ($palign == 'C') {
24138  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
24139  } elseif ($palign == 'R') {
24140  $ximg = $this->w - $this->rMargin - $w;
24141  } else {
24142  $ximg = $x - $w;
24143  }
24144  $this->img_rb_x = $ximg;
24145  } else {
24146  if ($palign == 'L') {
24147  $ximg = $this->lMargin;
24148  } elseif ($palign == 'C') {
24149  $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
24150  } elseif ($palign == 'R') {
24151  $ximg = $this->w - $this->rMargin - $w;
24152  } else {
24153  $ximg = $x;
24154  }
24155  $this->img_rb_x = $ximg + $w;
24156  }
24157  // store current graphic vars
24158  $gvars = $this->getGraphicVars();
24159  // store SVG position and scale factors
24160  $svgoffset_x = ($ximg - $ox) * $this->k;
24161  $svgoffset_y = -($y - $oy) * $this->k;
24162  if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
24163  $ow = $view_box[2];
24164  $oh = $view_box[3];
24165  }
24166  $svgscale_x = $w / $ow;
24167  $svgscale_y = $h / $oh;
24168  // scaling and alignment
24169  if ($aspect_ratio_align != 'none') {
24170  // store current scaling values
24171  $svgscale_old_x = $svgscale_x;
24172  $svgscale_old_y = $svgscale_y;
24173  // force uniform scaling
24174  if ($aspect_ratio_ms == 'slice') {
24175  // the entire viewport is covered by the viewBox
24176  if ($svgscale_x > $svgscale_y) {
24177  $svgscale_y = $svgscale_x;
24178  } elseif ($svgscale_x < $svgscale_y) {
24179  $svgscale_x = $svgscale_y;
24180  }
24181  } else { // meet
24182  // the entire viewBox is visible within the viewport
24183  if ($svgscale_x < $svgscale_y) {
24184  $svgscale_y = $svgscale_x;
24185  } elseif ($svgscale_x > $svgscale_y) {
24186  $svgscale_x = $svgscale_y;
24187  }
24188  }
24189  // correct X alignment
24190  switch (substr($aspect_ratio_align, 1, 3)) {
24191  case 'Min': {
24192  // do nothing
24193  break;
24194  }
24195  case 'Max': {
24196  $svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
24197  break;
24198  }
24199  default:
24200  case 'Mid': {
24201  $svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
24202  break;
24203  }
24204  }
24205  // correct Y alignment
24206  switch (substr($aspect_ratio_align, 5)) {
24207  case 'Min': {
24208  // do nothing
24209  break;
24210  }
24211  case 'Max': {
24212  $svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
24213  break;
24214  }
24215  default:
24216  case 'Mid': {
24217  $svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
24218  break;
24219  }
24220  }
24221  }
24222  // store current page break mode
24223  $page_break_mode = $this->AutoPageBreak;
24224  $page_break_margin = $this->getBreakMargin();
24225  $cell_padding = $this->cell_padding;
24226  $this->SetCellPadding(0);
24227  $this->SetAutoPageBreak(false);
24228  // save the current graphic state
24229  $this->_out('q'.$this->epsmarker);
24230  // set initial clipping mask
24231  $this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
24232  // scale and translate
24233  $e = $ox * $this->k * (1 - $svgscale_x);
24234  $f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
24235  $this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F cm', $svgscale_x, 0, 0, $svgscale_y, $e + $svgoffset_x, $f + $svgoffset_y));
24236  // creates a new XML parser to be used by the other XML functions
24237  $this->parser = xml_parser_create('UTF-8');
24238  // the following function allows to use parser inside object
24239  xml_set_object($this->parser, $this);
24240  // disable case-folding for this XML parser
24241  xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
24242  // sets the element handler functions for the XML parser
24243  xml_set_element_handler($this->parser, 'startSVGElementHandler', 'endSVGElementHandler');
24244  // sets the character data handler function for the XML parser
24245  xml_set_character_data_handler($this->parser, 'segSVGContentHandler');
24246  // start parsing an XML document
24247  if(!xml_parse($this->parser, $svgdata)) {
24248  $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));
24249  $this->Error($error_message);
24250  }
24251  // free this XML parser
24252  xml_parser_free($this->parser);
24253  // restore previous graphic state
24254  $this->_out($this->epsmarker.'Q');
24255  // restore graphic vars
24256  $this->setGraphicVars($gvars);
24257  $this->lasth = $gvars['lasth'];
24258  if (!empty($border)) {
24259  $bx = $this->x;
24260  $by = $this->y;
24261  $this->x = $ximg;
24262  if ($this->rtl) {
24263  $this->x += $w;
24264  }
24265  $this->y = $y;
24266  $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
24267  $this->x = $bx;
24268  $this->y = $by;
24269  }
24270  if ($link) {
24271  $this->Link($ximg, $y, $w, $h, $link, 0);
24272  }
24273  // set pointer to align the next text/objects
24274  switch($align) {
24275  case 'T':{
24276  $this->y = $y;
24277  $this->x = $this->img_rb_x;
24278  break;
24279  }
24280  case 'M':{
24281  $this->y = $y + round($h/2);
24282  $this->x = $this->img_rb_x;
24283  break;
24284  }
24285  case 'B':{
24286  $this->y = $this->img_rb_y;
24287  $this->x = $this->img_rb_x;
24288  break;
24289  }
24290  case 'N':{
24291  $this->SetY($this->img_rb_y);
24292  break;
24293  }
24294  default:{
24295  // restore pointer to starting position
24296  $this->x = $gvars['x'];
24297  $this->y = $gvars['y'];
24298  $this->page = $gvars['page'];
24299  $this->current_column = $gvars['current_column'];
24300  $this->tMargin = $gvars['tMargin'];
24301  $this->bMargin = $gvars['bMargin'];
24302  $this->w = $gvars['w'];
24303  $this->h = $gvars['h'];
24304  $this->wPt = $gvars['wPt'];
24305  $this->hPt = $gvars['hPt'];
24306  $this->fwPt = $gvars['fwPt'];
24307  $this->fhPt = $gvars['fhPt'];
24308  break;
24309  }
24310  }
24311  $this->endlinex = $this->img_rb_x;
24312  // restore page break
24313  $this->SetAutoPageBreak($page_break_mode, $page_break_margin);
24314  $this->cell_padding = $cell_padding;
24315  }
24316 
24325  protected function getSVGTransformMatrix($attribute) {
24326  // identity matrix
24327  $tm = array(1, 0, 0, 1, 0, 0);
24328  $transform = array();
24329  if (preg_match_all('/(matrix|translate|scale|rotate|skewX|skewY)[\s]*\(([^\)]+)\)/si', $attribute, $transform, PREG_SET_ORDER) > 0) {
24330  foreach ($transform as $key => $data) {
24331  if (!empty($data[2])) {
24332  $a = 1;
24333  $b = 0;
24334  $c = 0;
24335  $d = 1;
24336  $e = 0;
24337  $f = 0;
24338  $regs = array();
24339  switch ($data[1]) {
24340  case 'matrix': {
24341  if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
24342  $a = $regs[1];
24343  $b = $regs[2];
24344  $c = $regs[3];
24345  $d = $regs[4];
24346  $e = $regs[5];
24347  $f = $regs[6];
24348  }
24349  break;
24350  }
24351  case 'translate': {
24352  if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
24353  $e = $regs[1];
24354  $f = $regs[2];
24355  } elseif (preg_match('/([a-z0-9\-\.]+)/si', $data[2], $regs)) {
24356  $e = $regs[1];
24357  }
24358  break;
24359  }
24360  case 'scale': {
24361  if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
24362  $a = $regs[1];
24363  $d = $regs[2];
24364  } elseif (preg_match('/([a-z0-9\-\.]+)/si', $data[2], $regs)) {
24365  $a = $regs[1];
24366  $d = $a;
24367  }
24368  break;
24369  }
24370  case 'rotate': {
24371  if (preg_match('/([0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
24372  $ang = deg2rad($regs[1]);
24373  $x = $regs[2];
24374  $y = $regs[3];
24375  $a = cos($ang);
24376  $b = sin($ang);
24377  $c = -$b;
24378  $d = $a;
24379  $e = ($x * (1 - $a)) - ($y * $c);
24380  $f = ($y * (1 - $d)) - ($x * $b);
24381  } elseif (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
24382  $ang = deg2rad($regs[1]);
24383  $a = cos($ang);
24384  $b = sin($ang);
24385  $c = -$b;
24386  $d = $a;
24387  $e = 0;
24388  $f = 0;
24389  }
24390  break;
24391  }
24392  case 'skewX': {
24393  if (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
24394  $c = tan(deg2rad($regs[1]));
24395  }
24396  break;
24397  }
24398  case 'skewY': {
24399  if (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
24400  $b = tan(deg2rad($regs[1]));
24401  }
24402  break;
24403  }
24404  }
24405  $tm = $this->getTransformationMatrixProduct($tm, array($a, $b, $c, $d, $e, $f));
24406  }
24407  }
24408  }
24409  return $tm;
24410  }
24411 
24421  protected function getTransformationMatrixProduct($ta, $tb) {
24422  $tm = array();
24423  $tm[0] = ($ta[0] * $tb[0]) + ($ta[2] * $tb[1]);
24424  $tm[1] = ($ta[1] * $tb[0]) + ($ta[3] * $tb[1]);
24425  $tm[2] = ($ta[0] * $tb[2]) + ($ta[2] * $tb[3]);
24426  $tm[3] = ($ta[1] * $tb[2]) + ($ta[3] * $tb[3]);
24427  $tm[4] = ($ta[0] * $tb[4]) + ($ta[2] * $tb[5]) + $ta[4];
24428  $tm[5] = ($ta[1] * $tb[4]) + ($ta[3] * $tb[5]) + $ta[5];
24429  return $tm;
24430  }
24431 
24439  protected function convertSVGtMatrix($tm) {
24440  $a = $tm[0];
24441  $b = -$tm[1];
24442  $c = -$tm[2];
24443  $d = $tm[3];
24444  $e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
24445  $f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
24446  $x = 0;
24447  $y = $this->h * $this->k;
24448  $e = ($x * (1 - $a)) - ($y * $c) + $e;
24449  $f = ($y * (1 - $d)) - ($x * $b) + $f;
24450  return array($a, $b, $c, $d, $e, $f);
24451  }
24452 
24459  protected function SVGTransform($tm) {
24460  $this->Transform($this->convertSVGtMatrix($tm));
24461  }
24462 
24478  protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
24479  $objstyle = '';
24480  if(!isset($svgstyle['opacity'])) {
24481  return $objstyle;
24482  }
24483  // clip-path
24484  $regs = array();
24485  if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
24486  $clip_path = $this->svgclippaths[$regs[1]];
24487  foreach ($clip_path as $cp) {
24488  $this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
24489  }
24490  }
24491  // opacity
24492  if ($svgstyle['opacity'] != 1) {
24493  $this->SetAlpha($svgstyle['opacity']);
24494  }
24495  // color
24496  $fill_color = $this->convertHTMLColorToDec($svgstyle['color']);
24497  $this->SetFillColorArray($fill_color);
24498  // text color
24499  $text_color = $this->convertHTMLColorToDec($svgstyle['text-color']);
24500  $this->SetTextColorArray($text_color);
24501  // clip
24502  if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
24503  $top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
24504  $right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
24505  $bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
24506  $left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
24507  $cx = $x + $left;
24508  $cy = $y + $top;
24509  $cw = $w - $left - $right;
24510  $ch = $h - $top - $bottom;
24511  if ($svgstyle['clip-rule'] == 'evenodd') {
24512  $clip_rule = 'CNZ';
24513  } else {
24514  $clip_rule = 'CEO';
24515  }
24516  $this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
24517  }
24518  // fill
24519  $regs = array();
24520  if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
24521  // gradient
24522  $gradient = $this->svggradients[$regs[1]];
24523  if (isset($gradient['xref'])) {
24524  // reference to another gradient definition
24525  $newgradient = $this->svggradients[$gradient['xref']];
24526  $newgradient['coords'] = $gradient['coords'];
24527  $newgradient['mode'] = $gradient['mode'];
24528  $newgradient['gradientUnits'] = $gradient['gradientUnits'];
24529  if (isset($gradient['gradientTransform'])) {
24530  $newgradient['gradientTransform'] = $gradient['gradientTransform'];
24531  }
24532  $gradient = $newgradient;
24533  }
24534  //save current Graphic State
24535  $this->_out('q');
24536  //set clipping area
24537  if (!empty($clip_function) AND method_exists($this, $clip_function)) {
24538  $bbox = call_user_func_array(array($this, $clip_function), $clip_params);
24539  if (is_array($bbox) AND (count($bbox) == 4)) {
24540  list($x, $y, $w, $h) = $bbox;
24541  }
24542  }
24543  if ($gradient['mode'] == 'measure') {
24544  if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
24545  $gtm = $gradient['gradientTransform'];
24546  // apply transformation matrix
24547  $xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
24548  $ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
24549  $xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
24550  $yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
24551  if (isset($gradient['coords'][4])) {
24552  $gradient['coords'][4] = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
24553  }
24554  $gradient['coords'][0] = $xa;
24555  $gradient['coords'][1] = $ya;
24556  $gradient['coords'][2] = $xb;
24557  $gradient['coords'][3] = $yb;
24558 
24559  }
24560  // convert SVG coordinates to user units
24561  $gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
24562  $gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
24563  $gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
24564  $gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
24565  if (isset($gradient['coords'][4])) {
24566  $gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
24567  }
24568  // shift units
24569  if ($gradient['gradientUnits'] == 'objectBoundingBox') {
24570  // convert to SVG coordinate system
24571  $gradient['coords'][0] += $x;
24572  $gradient['coords'][1] += $y;
24573  $gradient['coords'][2] += $x;
24574  $gradient['coords'][3] += $y;
24575  }
24576  // calculate percentages
24577  $gradient['coords'][0] = ($gradient['coords'][0] - $x) / $w;
24578  $gradient['coords'][1] = ($gradient['coords'][1] - $y) / $h;
24579  $gradient['coords'][2] = ($gradient['coords'][2] - $x) / $w;
24580  $gradient['coords'][3] = ($gradient['coords'][3] - $y) / $h;
24581  if (isset($gradient['coords'][4])) {
24582  $gradient['coords'][4] /= $w;
24583  }
24584  // fix values
24585  foreach($gradient['coords'] as $key => $val) {
24586  if ($val < 0) {
24587  $gradient['coords'][$key] = 0;
24588  } elseif ($val > 1) {
24589  $gradient['coords'][$key] = 1;
24590  }
24591  }
24592  if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
24593  // single color (no shading)
24594  $gradient['coords'][0] = 1;
24595  $gradient['coords'][1] = 0;
24596  $gradient['coords'][2] = 0.999;
24597  $gradient['coords'][3] = 0;
24598  }
24599  }
24600  // swap Y coordinates
24601  $tmp = $gradient['coords'][1];
24602  $gradient['coords'][1] = $gradient['coords'][3];
24603  $gradient['coords'][3] = $tmp;
24604  // set transformation map for gradient
24605  if (($gradient['type'] == 3) AND ($gradient['mode'] == 'measure')) {
24606  // gradient is always circular
24607  $cy = $this->h - $y - ($gradient['coords'][1] * ($w + $h));
24608  $this->_out(sprintf('%.3F 0 0 %.3F %.3F %.3F cm', $w*$this->k, $w*$this->k, $x*$this->k, $cy*$this->k));
24609  } else {
24610  $this->_out(sprintf('%.3F 0 0 %.3F %.3F %.3F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k));
24611  }
24612  if (count($gradient['stops']) > 1) {
24613  $this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
24614  }
24615  } elseif ($svgstyle['fill'] != 'none') {
24616  $fill_color = $this->convertHTMLColorToDec($svgstyle['fill']);
24617  if ($svgstyle['fill-opacity'] != 1) {
24618  $this->SetAlpha($svgstyle['fill-opacity']);
24619  }
24620  $this->SetFillColorArray($fill_color);
24621  if ($svgstyle['fill-rule'] == 'evenodd') {
24622  $objstyle .= 'F*';
24623  } else {
24624  $objstyle .= 'F';
24625  }
24626  }
24627  // stroke
24628  if ($svgstyle['stroke'] != 'none') {
24629  $stroke_style = array(
24630  'color' => $this->convertHTMLColorToDec($svgstyle['stroke']),
24631  'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
24632  'cap' => $svgstyle['stroke-linecap'],
24633  'join' => $svgstyle['stroke-linejoin']
24634  );
24635  if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
24636  $stroke_style['dash'] = $svgstyle['stroke-dasharray'];
24637  }
24638  $this->SetLineStyle($stroke_style);
24639  $objstyle .= 'D';
24640  }
24641  // font
24642  $regs = array();
24643  if (!empty($svgstyle['font'])) {
24644  if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
24645  $font_family = $this->getFontFamilyName($regs[1]);
24646  } else {
24647  $font_family = $svgstyle['font-family'];
24648  }
24649  if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
24650  $font_size = trim($regs[1]);
24651  } else {
24652  $font_size = $svgstyle['font-size'];
24653  }
24654  if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
24655  $font_style = trim($regs[1]);
24656  } else {
24657  $font_style = $svgstyle['font-style'];
24658  }
24659  if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
24660  $font_weight = trim($regs[1]);
24661  } else {
24662  $font_weight = $svgstyle['font-weight'];
24663  }
24664  if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
24665  $font_stretch = trim($regs[1]);
24666  } else {
24667  $font_stretch = $svgstyle['font-stretch'];
24668  }
24669  if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
24670  $font_spacing = trim($regs[1]);
24671  } else {
24672  $font_spacing = $svgstyle['letter-spacing'];
24673  }
24674  } else {
24675  $font_family = $this->getFontFamilyName($svgstyle['font-family']);
24676  $font_size = $svgstyle['font-size'];
24677  $font_style = $svgstyle['font-style'];
24678  $font_weight = $svgstyle['font-weight'];
24679  $font_stretch = $svgstyle['font-stretch'];
24680  $font_spacing = $svgstyle['letter-spacing'];
24681  }
24682  $font_size = $this->getHTMLUnitToUnits($font_size, $prevsvgstyle['font-size'], $this->svgunit, false) * $this->k;
24683  $font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
24684  $font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
24685  switch ($font_style) {
24686  case 'italic': {
24687  $font_style = 'I';
24688  break;
24689  }
24690  case 'oblique': {
24691  $font_style = 'I';
24692  break;
24693  }
24694  default:
24695  case 'normal': {
24696  $font_style = '';
24697  break;
24698  }
24699  }
24700  switch ($font_weight) {
24701  case 'bold':
24702  case 'bolder': {
24703  $font_style .= 'B';
24704  break;
24705  }
24706  }
24707  switch ($svgstyle['text-decoration']) {
24708  case 'underline': {
24709  $font_style .= 'U';
24710  break;
24711  }
24712  case 'overline': {
24713  $font_style .= 'O';
24714  break;
24715  }
24716  case 'line-through': {
24717  $font_style .= 'D';
24718  break;
24719  }
24720  default:
24721  case 'none': {
24722  break;
24723  }
24724  }
24725  $this->SetFont($font_family, $font_style, $font_size);
24726  $this->setFontStretching($font_stretch);
24727  $this->setFontSpacing($font_spacing);
24728  return $objstyle;
24729  }
24730 
24749  protected function SVGPath($d, $style='') {
24750  // set fill/stroke style
24751  $op = $this->getPathPaintOperator($style, '');
24752  if (empty($op)) {
24753  return;
24754  }
24755  $paths = array();
24756  $d = str_replace('-', ' -', $d);
24757  $d = str_replace('+', ' +', $d);
24758  preg_match_all('/([a-zA-Z])[\s]*([^a-zA-Z\"]*)/si', $d, $paths, PREG_SET_ORDER);
24759  $x = 0;
24760  $y = 0;
24761  $x1 = 0;
24762  $y1 = 0;
24763  $x2 = 0;
24764  $y2 = 0;
24765  $xmin = 2147483647;
24766  $xmax = 0;
24767  $ymin = 2147483647;
24768  $ymax = 0;
24769  $relcoord = false;
24770  // draw curve pieces
24771  foreach ($paths as $key => $val) {
24772  // get curve type
24773  $cmd = trim($val[1]);
24774  if (strtolower($cmd) == $cmd) {
24775  // use relative coordinated instead of absolute
24776  $relcoord = true;
24777  $xoffset = $x;
24778  $yoffset = $y;
24779  } else {
24780  $relcoord = false;
24781  $xoffset = 0;
24782  $yoffset = 0;
24783  }
24784  $params = array();
24785  if (isset($val[2])) {
24786  // get curve parameters
24787  $rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
24788  $params = array();
24789  foreach ($rawparams as $ck => $cp) {
24790  $params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
24791  }
24792  }
24793  switch (strtoupper($cmd)) {
24794  case 'M': { // moveto
24795  foreach ($params as $ck => $cp) {
24796  if (($ck % 2) == 0) {
24797  $x = $cp + $xoffset;
24798  } else {
24799  $y = $cp + $yoffset;
24800  if ($ck == 1) {
24801  $this->_outPoint($x, $y);
24802  } else {
24803  $this->_outLine($x, $y);
24804  }
24805  $xmin = min($xmin, $x);
24806  $ymin = min($ymin, $y);
24807  $xmax = max($xmax, $x);
24808  $ymax = max($ymax, $y);
24809  if ($relcoord) {
24810  $xoffset = $x;
24811  $yoffset = $y;
24812  }
24813  }
24814  }
24815  break;
24816  }
24817  case 'L': { // lineto
24818  foreach ($params as $ck => $cp) {
24819  if (($ck % 2) == 0) {
24820  $x = $cp + $xoffset;
24821  } else {
24822  $y = $cp + $yoffset;
24823  $this->_outLine($x, $y);
24824  $xmin = min($xmin, $x);
24825  $ymin = min($ymin, $y);
24826  $xmax = max($xmax, $x);
24827  $ymax = max($ymax, $y);
24828  if ($relcoord) {
24829  $xoffset = $x;
24830  $yoffset = $y;
24831  }
24832  }
24833  }
24834  break;
24835  }
24836  case 'H': { // horizontal lineto
24837  foreach ($params as $ck => $cp) {
24838  $x = $cp + $xoffset;
24839  $this->_outLine($x, $y);
24840  $xmin = min($xmin, $x);
24841  $xmax = max($xmax, $x);
24842  if ($relcoord) {
24843  $xoffset = $x;
24844  }
24845  }
24846  break;
24847  }
24848  case 'V': { // vertical lineto
24849  foreach ($params as $ck => $cp) {
24850  $y = $cp + $yoffset;
24851  $this->_outLine($x, $y);
24852  $ymin = min($ymin, $y);
24853  $ymax = max($ymax, $y);
24854  if ($relcoord) {
24855  $yoffset = $y;
24856  }
24857  }
24858  break;
24859  }
24860  case 'C': { // curveto
24861  foreach ($params as $ck => $cp) {
24862  $params[$ck] = $cp;
24863  if ((($ck + 1) % 6) == 0) {
24864  $x1 = $params[($ck - 5)] + $xoffset;
24865  $y1 = $params[($ck - 4)] + $yoffset;
24866  $x2 = $params[($ck - 3)] + $xoffset;
24867  $y2 = $params[($ck - 2)] + $yoffset;
24868  $x = $params[($ck - 1)] + $xoffset;
24869  $y = $params[($ck)] + $yoffset;
24870  $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
24871  $xmin = min($xmin, $x, $x1, $x2);
24872  $ymin = min($ymin, $y, $y1, $y2);
24873  $xmax = max($xmax, $x, $x1, $x2);
24874  $ymax = max($ymax, $y, $y1, $y2);
24875  if ($relcoord) {
24876  $xoffset = $x;
24877  $yoffset = $y;
24878  }
24879  }
24880  }
24881  break;
24882  }
24883  case 'S': { // shorthand/smooth curveto
24884  foreach ($params as $ck => $cp) {
24885  $params[$ck] = $cp;
24886  if ((($ck + 1) % 4) == 0) {
24887  if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
24888  $x1 = (2 * $x) - $x2;
24889  $y1 = (2 * $y) - $y2;
24890  } else {
24891  $x1 = $x;
24892  $y1 = $y;
24893  }
24894  $x2 = $params[($ck - 3)] + $xoffset;
24895  $y2 = $params[($ck - 2)] + $yoffset;
24896  $x = $params[($ck - 1)] + $xoffset;
24897  $y = $params[($ck)] + $yoffset;
24898  $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
24899  $xmin = min($xmin, $x, $x1, $x2);
24900  $ymin = min($ymin, $y, $y1, $y2);
24901  $xmax = max($xmax, $x, $x1, $x2);
24902  $ymax = max($ymax, $y, $y1, $y2);
24903  if ($relcoord) {
24904  $xoffset = $x;
24905  $yoffset = $y;
24906  }
24907  }
24908  }
24909  break;
24910  }
24911  case 'Q': { // quadratic Bézier curveto
24912  foreach ($params as $ck => $cp) {
24913  $params[$ck] = $cp;
24914  if ((($ck + 1) % 4) == 0) {
24915  // convert quadratic points to cubic points
24916  $x1 = $params[($ck - 3)] + $xoffset;
24917  $y1 = $params[($ck - 2)] + $yoffset;
24918  $xa = ($x + (2 * $x1)) / 3;
24919  $ya = ($y + (2 * $y1)) / 3;
24920  $x = $params[($ck - 1)] + $xoffset;
24921  $y = $params[($ck)] + $yoffset;
24922  $xb = ($x + (2 * $x1)) / 3;
24923  $yb = ($y + (2 * $y1)) / 3;
24924  $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
24925  $xmin = min($xmin, $x, $xa, $xb);
24926  $ymin = min($ymin, $y, $ya, $yb);
24927  $xmax = max($xmax, $x, $xa, $xb);
24928  $ymax = max($ymax, $y, $ya, $yb);
24929  if ($relcoord) {
24930  $xoffset = $x;
24931  $yoffset = $y;
24932  }
24933  }
24934  }
24935  break;
24936  }
24937  case 'T': { // shorthand/smooth quadratic Bézier curveto
24938  foreach ($params as $ck => $cp) {
24939  $params[$ck] = $cp;
24940  if (($ck % 2) != 0) {
24941  if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
24942  $x1 = (2 * $x) - $x1;
24943  $y1 = (2 * $y) - $y1;
24944  } else {
24945  $x1 = $x;
24946  $y1 = $y;
24947  }
24948  // convert quadratic points to cubic points
24949  $xa = ($x + (2 * $x1)) / 3;
24950  $ya = ($y + (2 * $y1)) / 3;
24951  $x = $params[($ck - 1)] + $xoffset;
24952  $y = $params[($ck)] + $yoffset;
24953  $xb = ($x + (2 * $x1)) / 3;
24954  $yb = ($y + (2 * $y1)) / 3;
24955  $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
24956  $xmin = min($xmin, $x, $x1, $x2);
24957  $ymin = min($ymin, $y, $y1, $y2);
24958  $xmax = max($xmax, $x, $x1, $x2);
24959  $ymax = max($ymax, $y, $y1, $y2);
24960  if ($relcoord) {
24961  $xoffset = $x;
24962  $yoffset = $y;
24963  }
24964  }
24965  }
24966  break;
24967  }
24968  case 'A': { // elliptical arc
24969  foreach ($params as $ck => $cp) {
24970  $params[$ck] = $cp;
24971  if ((($ck + 1) % 7) == 0) {
24972  $x0 = $x;
24973  $y0 = $y;
24974  $rx = abs($params[($ck - 6)]);
24975  $ry = abs($params[($ck - 5)]);
24976  $ang = -$rawparams[($ck - 4)];
24977  $angle = deg2rad($ang);
24978  $fa = $rawparams[($ck - 3)]; // large-arc-flag
24979  $fs = $rawparams[($ck - 2)]; // sweep-flag
24980  $x = $params[($ck - 1)] + $xoffset;
24981  $y = $params[$ck] + $yoffset;
24982  $cos_ang = cos($angle);
24983  $sin_ang = sin($angle);
24984  $a = ($x0 - $x) / 2;
24985  $b = ($y0 - $y) / 2;
24986  $xa = ($a * $cos_ang) - ($b * $sin_ang);
24987  $ya = ($a * $sin_ang) + ($b * $cos_ang);
24988  $rx2 = $rx * $rx;
24989  $ry2 = $ry * $ry;
24990  $xa2 = $xa * $xa;
24991  $ya2 = $ya * $ya;
24992  $delta = ($xa2 / $rx2) + ($ya2 / $ry2);
24993  if ($delta > 1) {
24994  $rx *= sqrt($delta);
24995  $ry *= sqrt($delta);
24996  $rx2 = $rx * $rx;
24997  $ry2 = $ry * $ry;
24998  }
24999  $numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
25000  if ($numerator < 0) {
25001  $root = 0;
25002  } else {
25003  $root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
25004  }
25005  if ($fa == $fs) {
25006  $root *= -1;
25007  }
25008  $cax = $root * (($rx * $ya) / $ry);
25009  $cay = -$root * (($ry * $xa) / $rx);
25010  // coordinates of ellipse center
25011  $cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
25012  $cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
25013  // get angles
25014  $angs = $this->getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
25015  $dang = $this->getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
25016  if (($fs == 0) AND ($dang > 0)) {
25017  $dang -= (2 * M_PI);
25018  } elseif (($fs == 1) AND ($dang < 0)) {
25019  $dang += (2 * M_PI);
25020  }
25021  $angf = $angs - $dang;
25022  if (($fs == 1) AND ($angs > $angf)) {
25023  $tmp = $angs;
25024  $angs = $angf;
25025  $angf = $tmp;
25026  }
25027  $angs = rad2deg($angs);
25028  $angf = rad2deg($angf);
25029  $pie = false;
25030  if ((isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
25031  $pie = true;
25032  }
25033  $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2);
25034  $this->_outPoint($x, $y);
25035  $xmin = min($xmin, $x);
25036  $ymin = min($ymin, $y);
25037  $xmax = max($xmax, $x);
25038  $ymax = max($ymax, $y);
25039  if ($relcoord) {
25040  $xoffset = $x;
25041  $yoffset = $y;
25042  }
25043  }
25044  }
25045  break;
25046  }
25047  case 'Z': {
25048  $this->_out('h');
25049  break;
25050  }
25051  }
25052  } // end foreach
25053  if (!empty($op)) {
25054  $this->_out($op);
25055  }
25056  return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
25057  }
25058 
25069  protected function getVectorsAngle($x1, $y1, $x2, $y2) {
25070  $dprod = ($x1 * $x2) + ($y1 * $y2);
25071  $dist1 = sqrt(($x1 * $x1) + ($y1 * $y1));
25072  $dist2 = sqrt(($x2 * $x2) + ($y2 * $y2));
25073  $angle = acos($dprod / ($dist1 * $dist2));
25074  if (is_nan($angle)) {
25075  $angle = M_PI;
25076  }
25077  if ((($x1 * $y2) - ($x2 * $y1)) < 0) {
25078  $angle *= -1;
25079  }
25080  return $angle;
25081  }
25082 
25093  protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
25094  // check if we are in clip mode
25095  if ($this->svgclipmode) {
25096  $this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
25097  return;
25098  }
25099  if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
25100  if (!isset($attribs['id'])) {
25101  $attribs['id'] = 'DF_'.(count($this->svgdefs) + 1);
25102  }
25103  $this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
25104  return;
25105  }
25106  $clipping = false;
25107  if ($parser == 'clip-path') {
25108  // set clipping mode
25109  $clipping = true;
25110  }
25111  // get styling properties
25112  $prev_svgstyle = $this->svgstyles[(count($this->svgstyles) - 1)]; // previous style
25113  $svgstyle = $this->svgstyles[0]; // set default style
25114  if (isset($attribs['style']) AND !$this->empty_string($attribs['style'])) {
25115  // fix style for regular expression
25116  $attribs['style'] = ';'.$attribs['style'];
25117  }
25118  foreach ($prev_svgstyle as $key => $val) {
25119  if (in_array($key, $this->svginheritprop)) {
25120  // inherit previous value
25121  $svgstyle[$key] = $val;
25122  }
25123  if (isset($attribs[$key]) AND !$this->empty_string($attribs[$key])) {
25124  // specific attribute settings
25125  if ($attribs[$key] == 'inherit') {
25126  $svgstyle[$key] = $val;
25127  } else {
25128  $svgstyle[$key] = $attribs[$key];
25129  }
25130  } elseif (isset($attribs['style']) AND !$this->empty_string($attribs['style'])) {
25131  // CSS style syntax
25132  $attrval = array();
25133  if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
25134  if ($attrval[1] == 'inherit') {
25135  $svgstyle[$key] = $val;
25136  } else {
25137  $svgstyle[$key] = $attrval[1];
25138  }
25139  }
25140  }
25141  }
25142  // transformation matrix
25143  if (!empty($ctm)) {
25144  $tm = $ctm;
25145  } else {
25146  $tm = $this->svgstyles[(count($this->svgstyles) - 1)]['transfmatrix'];
25147  }
25148  if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
25149  $tm = $this->getTransformationMatrixProduct($tm, $this->getSVGTransformMatrix($attribs['transform']));
25150  }
25151  $svgstyle['transfmatrix'] = $tm;
25152  $invisible = false;
25153  if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
25154  // the current graphics element is invisible (nothing is painted)
25155  $invisible = true;
25156  }
25157  // process tag
25158  switch($name) {
25159  case 'defs': {
25160  $this->svgdefsmode = true;
25161  break;
25162  }
25163  // clipPath
25164  case 'clipPath': {
25165  if ($invisible) {
25166  break;
25167  }
25168  $this->svgclipmode = true;
25169  if (!isset($attribs['id'])) {
25170  $attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
25171  }
25172  $this->svgclipid = $attribs['id'];
25173  $this->svgclippaths[$this->svgclipid] = array();
25174  $this->svgcliptm[$this->svgclipid] = $tm;
25175  break;
25176  }
25177  case 'svg': {
25178  // start of SVG object
25179  break;
25180  }
25181  case 'g': {
25182  // group together related graphics elements
25183  array_push($this->svgstyles, $svgstyle);
25184  $this->StartTransform();
25185  $this->setSVGStyles($svgstyle, $prev_svgstyle);
25186  break;
25187  }
25188  case 'linearGradient': {
25189  if (!isset($attribs['id'])) {
25190  $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
25191  }
25192  $this->svggradientid = $attribs['id'];
25193  $this->svggradients[$this->svggradientid] = array();
25194  $this->svggradients[$this->svggradientid]['type'] = 2;
25195  $this->svggradients[$this->svggradientid]['stops'] = array();
25196  if (isset($attribs['gradientUnits'])) {
25197  $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
25198  } else {
25199  $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
25200  }
25201  //$attribs['spreadMethod']
25202  $x1 = (isset($attribs['x1'])?$attribs['x1']:0);
25203  $y1 = (isset($attribs['y1'])?$attribs['y1']:0);
25204  $x2 = (isset($attribs['x2'])?$attribs['x2']:1);
25205  $y2 = (isset($attribs['y2'])?$attribs['y2']:0);
25206  if (isset($attribs['x1']) AND (substr($attribs['x1'], -1) != '%')) {
25207  $this->svggradients[$this->svggradientid]['mode'] = 'measure';
25208  } else {
25209  $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
25210  }
25211  if (isset($attribs['gradientTransform'])) {
25212  $this->svggradients[$this->svggradientid]['gradientTransform'] = $this->getSVGTransformMatrix($attribs['gradientTransform']);
25213  }
25214  $this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
25215  if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
25216  // gradient is defined on another place
25217  $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
25218  }
25219  break;
25220  }
25221  case 'radialGradient': {
25222  if (!isset($attribs['id'])) {
25223  $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
25224  }
25225  $this->svggradientid = $attribs['id'];
25226  $this->svggradients[$this->svggradientid] = array();
25227  $this->svggradients[$this->svggradientid]['type'] = 3;
25228  $this->svggradients[$this->svggradientid]['stops'] = array();
25229  if (isset($attribs['gradientUnits'])) {
25230  $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
25231  } else {
25232  $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
25233  }
25234  //$attribs['spreadMethod']
25235  $cx = (isset($attribs['cx'])?$attribs['cx']:0.5);
25236  $cy = (isset($attribs['cy'])?$attribs['cy']:0.5);
25237  $fx = (isset($attribs['fx'])?$attribs['fx']:$cx);
25238  $fy = (isset($attribs['fy'])?$attribs['fy']:$cy);
25239  $r = (isset($attribs['r'])?$attribs['r']:0.5);
25240  if (isset($attribs['cx']) AND (substr($attribs['cx'], -1) != '%')) {
25241  $this->svggradients[$this->svggradientid]['mode'] = 'measure';
25242  } else {
25243  $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
25244  }
25245  if (isset($attribs['gradientTransform'])) {
25246  $this->svggradients[$this->svggradientid]['gradientTransform'] = $this->getSVGTransformMatrix($attribs['gradientTransform']);
25247  }
25248  $this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
25249  if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
25250  // gradient is defined on another place
25251  $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
25252  }
25253  break;
25254  }
25255  case 'stop': {
25256  // gradient stops
25257  if (substr($attribs['offset'], -1) == '%') {
25258  $offset = floatval(substr($attribs['offset'], -1)) / 100;
25259  } else {
25260  $offset = floatval($attribs['offset']);
25261  if ($offset > 1) {
25262  $offset /= 100;
25263  }
25264  }
25265  $stop_color = isset($svgstyle['stop-color'])?$this->convertHTMLColorToDec($svgstyle['stop-color']):'black';
25266  $opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
25267  $this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
25268  break;
25269  }
25270  // paths
25271  case 'path': {
25272  if ($invisible) {
25273  break;
25274  }
25275  $d = trim($attribs['d']);
25276  if ($clipping) {
25277  $this->SVGTransform($tm);
25278  $this->SVGPath($d, 'CNZ');
25279  } else {
25280  $this->StartTransform();
25281  $this->SVGTransform($tm);
25282  $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, 0, 0, 1, 1, 'SVGPath', array($d, 'CNZ'));
25283  if (!empty($obstyle)) {
25284  $this->SVGPath($d, $obstyle);
25285  }
25286  $this->StopTransform();
25287  }
25288  break;
25289  }
25290  // shapes
25291  case 'rect': {
25292  if ($invisible) {
25293  break;
25294  }
25295  $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
25296  $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
25297  $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
25298  $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
25299  $rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
25300  $ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
25301  if ($clipping) {
25302  $this->SVGTransform($tm);
25303  $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
25304  } else {
25305  $this->StartTransform();
25306  $this->SVGTransform($tm);
25307  $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
25308  if (!empty($obstyle)) {
25309  $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
25310  }
25311  $this->StopTransform();
25312  }
25313  break;
25314  }
25315  case 'circle': {
25316  if ($invisible) {
25317  break;
25318  }
25319  $cx = (isset($attribs['cx'])?$this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false):0);
25320  $cy = (isset($attribs['cy'])?$this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false):0);
25321  $r = (isset($attribs['r'])?$this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false):0);
25322  $x = $cx - $r;
25323  $y = $cy - $r;
25324  $w = 2 * $r;
25325  $h = $w;
25326  if ($clipping) {
25327  $this->SVGTransform($tm);
25328  $this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
25329  } else {
25330  $this->StartTransform();
25331  $this->SVGTransform($tm);
25332  $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
25333  if (!empty($obstyle)) {
25334  $this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
25335  }
25336  $this->StopTransform();
25337  }
25338  break;
25339  }
25340  case 'ellipse': {
25341  if ($invisible) {
25342  break;
25343  }
25344  $cx = (isset($attribs['cx'])?$this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false):0);
25345  $cy = (isset($attribs['cy'])?$this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false):0);
25346  $rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
25347  $ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):0);
25348  $x = $cx - $rx;
25349  $y = $cy - $ry;
25350  $w = 2 * $rx;
25351  $h = 2 * $ry;
25352  if ($clipping) {
25353  $this->SVGTransform($tm);
25354  $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
25355  } else {
25356  $this->StartTransform();
25357  $this->SVGTransform($tm);
25358  $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
25359  if (!empty($obstyle)) {
25360  $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
25361  }
25362  $this->StopTransform();
25363  }
25364  break;
25365  }
25366  case 'line': {
25367  if ($invisible) {
25368  break;
25369  }
25370  $x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
25371  $y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
25372  $x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
25373  $y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
25374  $x = $x1;
25375  $y = $y1;
25376  $w = abs($x2 - $x1);
25377  $h = abs($y2 - $y1);
25378  if (!$clipping) {
25379  $this->StartTransform();
25380  $this->SVGTransform($tm);
25381  $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
25382  $this->Line($x1, $y1, $x2, $y2);
25383  $this->StopTransform();
25384  }
25385  break;
25386  }
25387  case 'polyline':
25388  case 'polygon': {
25389  if ($invisible) {
25390  break;
25391  }
25392  $points = (isset($attribs['points'])?$attribs['points']:'0 0');
25393  $points = trim($points);
25394  // note that point may use a complex syntax not covered here
25395  $points = preg_split('/[\,\s]+/si', $points);
25396  if (count($points) < 4) {
25397  break;
25398  }
25399  $p = array();
25400  $xmin = 2147483647;
25401  $xmax = 0;
25402  $ymin = 2147483647;
25403  $ymax = 0;
25404  foreach ($points as $key => $val) {
25405  $p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
25406  if (($key % 2) == 0) {
25407  // X coordinate
25408  $xmin = min($xmin, $p[$key]);
25409  $xmax = max($xmax, $p[$key]);
25410  } else {
25411  // Y coordinate
25412  $ymin = min($ymin, $p[$key]);
25413  $ymax = max($ymax, $p[$key]);
25414  }
25415  }
25416  $x = $xmin;
25417  $y = $ymin;
25418  $w = ($xmax - $xmin);
25419  $h = ($ymax - $ymin);
25420  if ($name == 'polyline') {
25421  $this->StartTransform();
25422  $this->SVGTransform($tm);
25423  $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
25424  $this->PolyLine($p, 'D', array(), array());
25425  $this->StopTransform();
25426  } else { // polygon
25427  if ($clipping) {
25428  $this->SVGTransform($tm);
25429  $this->Polygon($p, 'CNZ', array(), array(), true);
25430  } else {
25431  $this->StartTransform();
25432  $this->SVGTransform($tm);
25433  $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
25434  if (!empty($obstyle)) {
25435  $this->Polygon($p, $obstyle, array(), array(), true);
25436  }
25437  $this->StopTransform();
25438  }
25439  }
25440  break;
25441  }
25442  // image
25443  case 'image': {
25444  if ($invisible) {
25445  break;
25446  }
25447  if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
25448  break;
25449  }
25450  $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
25451  $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
25452  $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
25453  $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
25454  $img = $attribs['xlink:href'];
25455  if (!$clipping) {
25456  $this->StartTransform();
25457  $this->SVGTransform($tm);
25458  $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
25459  // fix image path
25460  if (!$this->empty_string($this->svgdir) AND (($img{0} == '.') OR (basename($img) == $img))) {
25461  // replace relative path with full server path
25462  $img = $this->svgdir.'/'.$img;
25463  }
25464  if (($img{0} == '/') AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
25465  $findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
25466  if (($findroot === false) OR ($findroot > 1)) {
25467  // replace relative path with full server path
25468  $img = $_SERVER['DOCUMENT_ROOT'].$img;
25469  }
25470  }
25471  $img = urldecode($img);
25472  $testscrtype = @parse_url($img);
25473  if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
25474  // convert URL to server path
25475  $img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
25476  }
25477  $this->Image($img, $x, $y, $w, $h);
25478  $this->StopTransform();
25479  }
25480  break;
25481  }
25482  // text
25483  case 'text':
25484  case 'tspan': {
25485  $this->svgtextmode['invisible'] = $invisible;
25486  if ($invisible) {
25487  break;
25488  }
25489  array_push($this->svgstyles, $svgstyle);
25490  // only basic support - advanced features must be implemented
25491  $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):$this->x);
25492  $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):$this->y);
25493  $svgstyle['text-color'] = $svgstyle['fill'];
25494  $this->svgtext = '';
25495  if (isset($svgstyle['text-anchor'])) {
25496  $this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
25497  } else {
25498  $this->svgtextmode['text-anchor'] = 'start';
25499  }
25500  if (isset($svgstyle['direction'])) {
25501  if ($svgstyle['direction'] == 'rtl') {
25502  $this->svgtextmode['rtl'] = true;
25503  } else {
25504  $this->svgtextmode['rtl'] = false;
25505  }
25506  } else {
25507  $this->svgtextmode['rtl'] = false;
25508  }
25509  if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
25510  $this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
25511  } else {
25512  $this->svgtextmode['stroke'] = false;
25513  }
25514  $this->StartTransform();
25515  $this->SVGTransform($tm);
25516  $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
25517  $this->x = $x;
25518  $this->y = $y;
25519  break;
25520  }
25521  // use
25522  case 'use': {
25523  if (isset($attribs['xlink:href'])) {
25524  $use = $this->svgdefs[substr($attribs['xlink:href'], 1)];
25525  if (isset($attribs['xlink:href'])) {
25526  unset($attribs['xlink:href']);
25527  }
25528  if (isset($attribs['id'])) {
25529  unset($attribs['id']);
25530  }
25531  $attribs = array_merge($use['attribs'], $attribs);
25532  $this->startSVGElementHandler($parser, $use['name'], $use['attribs']);
25533  }
25534  break;
25535  }
25536  default: {
25537  break;
25538  }
25539  } // end of switch
25540  }
25541 
25550  protected function endSVGElementHandler($parser, $name) {
25551  switch($name) {
25552  case 'defs': {
25553  $this->svgdefsmode = false;
25554  break;
25555  }
25556  // clipPath
25557  case 'clipPath': {
25558  $this->svgclipmode = false;
25559  break;
25560  }
25561  case 'g': {
25562  // ungroup: remove last style from array
25563  array_pop($this->svgstyles);
25564  $this->StopTransform();
25565  break;
25566  }
25567  case 'text':
25568  case 'tspan': {
25569  if ($this->svgtextmode['invisible']) {
25570  // This implementation must be fixed to following the rule:
25571  // 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.
25572  break;
25573  }
25574  // print text
25575  $text = $this->stringTrim($this->svgtext);
25576  if ($this->svgtextmode['text-anchor'] != 'start') {
25577  $textlen = $this->GetStringWidth($text);
25578  // check if string is RTL text
25579  if ($this->svgtextmode['text-anchor'] == 'end') {
25580  if ($this->svgtextmode['rtl']) {
25581  $this->x += $textlen;
25582  } else {
25583  $this->x -= $textlen;
25584  }
25585  } elseif ($this->svgtextmode['text-anchor'] == 'middle') {
25586  if ($this->svgtextmode['rtl']) {
25587  $this->x += ($textlen / 2);
25588  } else {
25589  $this->x -= ($textlen / 2);
25590  }
25591  }
25592  }
25593  $textrendermode = $this->textrendermode;
25594  $textstrokewidth = $this->textstrokewidth;
25595  $this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
25596  $this->Cell(0, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
25597  // restore previous rendering mode
25598  $this->textrendermode = $textrendermode;
25599  $this->textstrokewidth = $textstrokewidth;
25600  $this->svgtext = '';
25601  $this->StopTransform();
25602  array_pop($this->svgstyles);
25603  break;
25604  }
25605  default: {
25606  break;
25607  }
25608  }
25609  }
25610 
25619  protected function segSVGContentHandler($parser, $data) {
25620  $this->svgtext .= $data;
25621  }
25622 
25623  // --- END SVG METHODS -----------------------------
25624 
25625 } // END OF TCPDF CLASS
25626 
25627 //============================================================+
25628 // END OF FILE
25629 //============================================================+